odds and ends of the autodesk® inventor®...

25
Odds and Ends of the Autodesk® Inventor® API Brian Ekins Autodesk, Inc. Code - CP3547 Description This class covers several smaller topics about the Autodesk Inventor programming interface that are often overlooked or misunderstood. Learning Objectives At the end of this class, you will be able to: Make your programs faster by finding and fixing performance problems Enhance your assembly programs using proxies Use reference keys in your programs when appropriate Make use of cameras for defining views, creating animations, generating images, and creating drawing views About the Speaker Brian has worked for Autodesk for 14 years and began working in the CAD industry in the mid 80's. He has worked in various positions, including CAD administrator, applications engineer, instructor, CAD API designer, and consultant. Previous to working for Autodesk, Brian designed the Solid Edge programming interface. He continues to work on the Inventor programming interface and other Inventor platform related projects. [email protected]

Upload: duongliem

Post on 19-Feb-2018

220 views

Category:

Documents


3 download

TRANSCRIPT

Odds and Ends of the Autodesk® Inventor® API Brian Ekins – Autodesk, Inc.

Code - CP3547

Description This class covers several smaller topics about the Autodesk Inventor programming interface that are often overlooked or misunderstood.

Learning Objectives At the end of this class, you will be able to:

Make your programs faster by finding and fixing performance problems

Enhance your assembly programs using proxies

Use reference keys in your programs when appropriate

Make use of cameras for defining views, creating animations, generating images, and creating drawing views

About the Speaker

Brian has worked for Autodesk for 14 years and began working in the CAD industry in the mid 80's. He

has worked in various positions, including CAD administrator, applications engineer, instructor, CAD API

designer, and consultant. Previous to working for Autodesk, Brian designed the Solid Edge programming

interface. He continues to work on the Inventor programming interface and other Inventor platform

related projects.

[email protected]

Odds and Ends of the Autodesk® Inventor® API

2

Optimizing Performance Very few programs run as fast as we would like them to. The fact is that it takes processing time to do the required work and in many cases there’s not much we can do to shorten that time. When your program asks Inventor to perform a compute intensive task, i.e. create a complex feature, it’s going to take a certain amount of time for Inventor to compute and create the feature. However, there are cases where you can significantly speed up performance by using some general programming principles and taking advantage of some often neglected capabilities in the API.

Timing Your Program The first thing to consider when looking at your program’s performance is measuring the current performance. You want to be able to determine possible areas of improvement and if your changes help the performance. If you’re using .Net you can use the StopWatch class from System.Diagnostics. Below is an example of it in use.

' Use the .Net StopWatch class to time an action. Dim stopwatch As New System.Diagnostics.Stopwatch stopwatch.Start()

' Do some things.

MessageBox.Show("Elapsed time: " & stopwatch.ElapsedMilliseconds / 1000 & " seconds")

For timing in VBA I used to use some built-in VBA functions but I noticed some problems with the results and later read about some accuracy issues. I now use the class below. To use this, copy and paste the code below into a new class module. I name the module “clsTimer”.

' clsTimer Definition. Private Declare Function QueryPerformanceFrequency Lib "kernel32" ( _ lpFrequency As Currency) As Long Private Declare Function QueryPerformanceCounter Lib "kernel32" ( _ lpPerformanceCount As Currency) As Long Private ConversionFactor As Currency Private CurrentStartTime As Currency Public Sub Start() Dim iReturn As Long iReturn = QueryPerformanceCounter(CurrentStartTime) End Sub Public Function GetTime() As Double Dim NewTime As Currency Dim iReturn As Long iReturn = QueryPerformanceCounter(NewTime) Dim TotalTime As Currency TotalTime = NewTime - CurrentStartTime GetTime = TotalTime / ConversionFactor End Function Private Sub Class_Initialize() Dim iReturn As Long iReturn = QueryPerformanceFrequency(ConversionFactor) End Sub

Odds and Ends of the Autodesk® Inventor® API

3

You can then add the code below to your code.

' Use the timer class to time an action. Dim timer As New clsTimer timer.Start ' Do some things. MsgBox "Elapsed time: " & Format(timer.GetTime, "0.00000") & " seconds"

Running In-Process vs. Out-of-Process One thing that significantly impacts performance is if your program is running inside Inventor or not. When your program is running within Inventor’s process it’s referred to as in-process. When your program is running in another process it referred to as out-of-process. Add-ins and 32-bit VBA macros run in-process. 64-bit VBA macros, exe’s, and programs that run from within other products (i.e. Excel VBA macros) all run in a process outside of Inventor. When running out-of-process Windows has to package the calls for inter-process communication which adds additional overhead for every API call.

Below is a test program that makes a simple API call. This call was chosen because it doesn’t take any processing on Inventor’s side to respond since it’s simply returning a known value. It makes 100,000 calls of this property and without any optimizations took 74.8 seconds to run out-of-process. The same program running in-process took 0.24 seconds or 312 times faster.

Public Sub CallCost() Dim timer As New clsTimer timer.Start Dim i As Long For i = 1 To 100000 Dim code As Long code = ThisApplication.Locale Next Debug.Print "Total time: " & Format(timer.GetTime, "0.00000") End Sub

Don’t expect your programs to run 312 times faster by changing them to run in-process. This test was designed to test the overhead of making out-of-process calls and doesn’t represent real use of the API. Remember that it’s making 100,000 calls so even with the slow time in the out-of- process test it was still making over 5400 calls per second. The reason you won’t see big time improvements is that typical programs are making calls that require Inventor to do some work. The time that it takes for Inventor to do the work is the same regardless of in-process or out-of-process. The difference is in the overhead of packaging to make the call land return the results. However, in most cases there will be a noticeable difference between running in-process and out-of-process, so ideally you want to run in-process.

You don’t always have the option to run in-process though. Sometimes your program might be some type of batch processor that needs to start Inventor and then perform some action. Or it might be an Excel VBA macro that uses values from Excel to perform some action in Inventor. When you’re forced to run out-of-process there are some things you can do to speed up the processing.

Odds and Ends of the Autodesk® Inventor® API

4

Besides the overhead of packaging the API calls for inter-process communication, another reason for the performance difference between in-process and out-of-process is that Inventor is servicing the out-of-process calls while it’s also servicing the user-interface. It gives priority to the user-interface and then out-of-process API calls slip in when they can. For in-process programs, Inventor dedicates all of its processing to the program. For out-of-process programs, you can get this dedicated processing by using the UserInteractionDisabled property available on the UserInterfaceManager object. Setting the property to True will lock the user-interface. This is typically what you want anyway because you don’t want the user doing things in Inventor while your program is running. It also has the side effect of allowing Inventor to focus on handling API calls instead of watching the user-interface. The code below shows the test program with this optimization and drops the execution time from 74.8 seconds to 61.32 seconds. I’ve seen much bigger differences in other programs. Public Sub CallCost() Dim timer As New clsTimer timer.Start app.UserInterfaceManager.UserInteractionDisabled = True Dim i As Long For i = 1 To 100000 Dim code As Long code = ThisApplication.Locale Next app.UserInterfaceManager.UserInteractionDisabled = False Debug.Print "Total time: " & Format(timer.GetTime, "0.00000") End Sub

When using this property you want make sure to add error handling to your program so that if an error does occur and your program stops, you’ll handle the error so you can turn interaction back on. Otherwise Inventor will remain frozen and you won’t be able to work with it.

Optimizing Inventor VBA Macros Here’s an optimization that’s limited to Inventor’s VBA. It does apply to both in-process (32-bit) and out-of-process (64-bit) VBA macros.

In Inventor’s VBA you access the Inventor Application object using the global ThisApplication property. There is some cost to using this property. The code below calls the Locale property of the Application object 100,000 times and took 74.8 seconds when running out-of-process.

Public Sub CallCost() Dim timer As New clsTimer timer.Start Dim i As Long For i = 1 To 100000 Dim code As Long code = ThisApplication.Locale Next Debug.Print "Total time: " & Format(timer.GetTime, "0.00000") End Sub

Odds and Ends of the Autodesk® Inventor® API

5

Making the small change highlighted below reduced the run time to 18.8 seconds, which is about 4 times faster. You can see the change gets the Application object, assigns it to a variable, and then uses that reference for the rest of the program. It’s a big improvement for a small change.

Public Sub CallCost() Dim timer As New clsTimer timer.Start Dim app As Inventor.Application Set app = ThisApplication Dim i As Long For i = 1 To 100000 Dim code As Long code = app.Locale Next Debug.Print "Total time: " & Format(timer.GetTime, "0.00000") End Sub

Use References Instead of Inline Calls An optimization similar to the previous one is to get and save any objects that you use multiple times. The program below Iterates through all of the occurrences in an assembly and gets the name and filename of each one. Calling this function 10 times takes 22.5 seconds in an assembly of 500 occurrences.

Public Sub GetOccurrences1() Dim asmDoc As AssemblyDocument Set asmDoc = ThisApplication.ActiveDocument Dim i As Integer For i = 1 To asmDoc.ComponentDefinition.Occurrences.Count Dim Name As String Name = asmDoc.ComponentDefinition.Occurrences.Item(i).Name Dim filename As String filename = asmDoc.ComponentDefinition.Occurrences.Item(i). _ ReferencedDocumentDescriptor.ReferencedFileDescriptor.FullFileName Next End Sub

Odds and Ends of the Autodesk® Inventor® API

6

Here’s a slightly different version of the previous program with the differences highlighted. Calling this program 10 times takes less than half the time at 9.7 seconds. The difference is that this gets the ComponentOccurrences object and the ComponentOccurrence objects and assigns them to variables that it uses later. This significantly reduces the number of API calls. A single line of code like, asmDoc.ComponentDefinition.Occurrences.Item(i).Name, results in several API calls and each call takes some time. If you can make those calls once and save and re-use the result you can improve the performance of your programs.

Public Sub GetOccurrences2() Dim asmDoc As AssemblyDocument Set asmDoc = ThisApplication.ActiveDocument Dim occs As ComponentOccurrences Set occs = asmDoc.ComponentDefinition.Occurrences Dim i As Integer For i = 1 To occs.Count Dim Occ As ComponentOccurrence Set Occ = occs.Item(i) Dim Name As String Name = Occ.Name Dim filename As String filename = Occ.ReferencedDocumentDescriptor. _ ReferencedFileDescriptor.FullFileName Next End Sub

Use For Each instead of Count and Item Another optimization is to use the For Each statement when iterating over the contents of a collection. Below is a version of the same test program that uses For Each. Besides being more efficient it also makes the code easier to read. The time for this function to run 10 times was 9.4 seconds. Not a big difference from the previous sample, but the time saved will vary depending on which collection is being iterated through.

Public Sub GetOccurrences3() Dim asmDoc As AssemblyDocument Set asmDoc = ThisApplication.ActiveDocument Dim occs As ComponentOccurrences Set occs = asmDoc.ComponentDefinition.Occurrences Dim Occ As ComponentOccurrence For Each Occ In occs Dim Name As String Name = Occ.Name Dim filename As String filename = Occ.ReferencedDocumentDescriptor. _ ReferencedFileDescriptor.FullFileName Next End Sub

Odds and Ends of the Autodesk® Inventor® API

7

The reason using a For Each statement is faster is because Inventor knows you’ll be iterating over the collection so it can do some internal optimization to improve the performance. When using the Item property Inventor doesn’t know if you just want a single item or will be iterating over multiple items. Some collections are better optimized to return single items, like the ComponentOccurrences collection so you won’t see big improvements, but others will be significantly faster using a For Each statement

Get Items By Name Another optimization associated with collections is to use the capability of many of the collections to get an object in the collection by name instead of iterating through the collection looking for an object with a specific name. The Item property of many of the collections will take either an index or the name of the object within the collection you want. In a few cases there is an additional Item property that is specifically for the name. In either case using these is much more efficient that iterating over the entire collection looking for an object with a specific name.

Below is a code example straight from the Inventor Customization newsgroup that is looking for the DXF translator add-in.

Dim dxfAddIn As TranslatorAddIn For i = 1 To addIns.Count If addIns(i).AddInType = kTranslationApplicationAddIn Then If addIns(i).Description = "Autodesk Internal DXF Translator" Then Set dxfAddIn = addIns.Item(i) Exit For End If End If Next

Here’s the equivalent code using the ItemById property of the ApplicationAddIns object. As you can see the code is much simpler but it also much more efficient because Inventor is doing the look-up instead and is able to it in a much more optimized way than looking through the objects one-by-one.

Dim dxfAddIn As TranslatorAddIn Set dxfAddIn = addIns.ItemById("{C24E3AC4-122E-11D5-8E91-0010B541CD80}")

Don’t Use Constraints and Defer Updates When Possible When working with assemblies, Inventor solves the constraints in order to determine the occurrences positions and orientations. This constraint solving takes time and the more constraints there are the longer it will take to solve. Often, when constructing an assembly with the API, you position the occurrences exactly so you don’t need constraints to position the occurrences.

The first optimization you can do when working with assemblies is to not use constraints at all. If your program positions everything precisely and the assembly won’t be edited by the user, you can place the occurrences and leave out the constraints. You can optionally ground the occurrences so the user doesn’t accidentally reposition them. A program that runs in-process and places 500 occurrences of the same part without any constraints takes 5.32 seconds to run. Compare that to the same final assembly with constraints that takes 699 seconds (11 m 39 s). There are additional API calls to create the constraints but the time Inventor takes to solve the assembly with each new constraint is the big difference.

There is something you can do to so you can still use constraints but get reasonable performance. You can defer assembly updates. You’re essentially turning off the compute until

Odds and Ends of the Autodesk® Inventor® API

8

you either turn it back on or explicitly update the assembly. This setting is available in the user-interface as an application option, as shown below.

This setting is also available through the API as the Application.AssemblyOptions.DeferUpdate property. After setting this property to True and placing the 500 occurrences with constraints the total processing time dropped down to 54 seconds. It was trimmed down to 49 seconds by also using the Application.ScreenUpdating property. This freezes the screen so Inventor doesn’t try to update the graphics while the program is running.

Use Defer When Creating Sketches Sketches also solve as you add additional sketch entities and constraints. When working with sketches you can’t choose to not use constraints because it’s constraints that connect the entities. If the sketch won’t be edited by the user you can minimize the number of constraints used but you can never eliminate them.

A sample that that runs in-process and creates 500 lines with the minimum amount of constraints took 18.5 seconds to complete. Sketches also support the ability to defer compute through the Sketch.DeferUpdates property. Setting this to True for the program that creates the 500 lines reduced the run time to 1.6 seconds.

Minimize API Calls An obvious way to improve performance is to minimize the number of API calls made. Usually this isn’t possible because there are a fixed number of calls you need to make to accomplish a specific objective. However, there are a few cases where you might be using the API as a general programming library, rather than creating or querying things in Inventor.

An example of this is the Facet Enabler add-in I wrote a few years ago. It allows you to read in STL files and supports sectioning of the data. Initially I was using the Inventor API Point object for all of the coordinates and the Matrix object to do transformations. There often are hundreds of thousands of points in an STL file so there were a lot of API calls being made and the program was quite slow. I changed this to use my own data and functions within my program to do the point manipulation and the program sped up several times. This is a fringe case but in cases where you are making many thousands of calls you should look at whether there are alternatives to do some of the processing outside of Inventor to avoid the API call overhead.

Odds and Ends of the Autodesk® Inventor® API

9

Global Transactions An overhead in Inventor any time you create or edit anything is transactions. A transaction is how Inventor wraps actions so they can be undone. Anything you do that is undoable is wrapped within a transaction. It takes some time for Inventor to do the work to support transactions. You can’t perform work in Inventor without it being wrapped in a transaction, but it is possible to minimize the number of transactions that occur. Trying to minimize the transaction overhead will only help in cases where you are doing a lot of creation or edit. Query operations aren’t impacted at all because they don’t participate in undo and a few transactions aren’t going to have enough impact to matter.

The Inventor API supports the ability for you to wrap multiple actions into a single transaction, which means it will result in a single undo. This makes is nice for the user if they want to undo the action of your program. The simple way to do this is shown below. You start a transaction, do whatever your command does, and then end the transaction. Now the user will be able to undo everything the command did in a single undo.

Public Sub SampleCommand() Dim partDoc As PartDocument Set partDoc = ThisApplication.ActiveDocument ' Start a transaction. Dim trans As Transaction Set trans = ThisApplication.TransactionManager.StartTransaction(partDoc, _ "My Command") ' ** Do stuff that creates or edits the model. ' End the transaction. trans.End End Sub

The above is convenient for the user but it doesn’t improve performance because there are just as many transactions. This creates a custom transaction that wraps all of the transactions that are created between the time it was started and ends. There is another type of transaction that will block the individual transactions from being created and the commands will rely on the outer custom transaction. This capability is accessed by using the hidden StartGlobalTransaction method. This is used in exactly the same as the standard StartTransaction and is shown below. The downside of using this, and the reason it’s hidden, is that you need to be very careful to make sure you always end a global transaction or you can leave Inventor in a bad state that will most likely result in a crash as you or the user continues to do work. You’ll want to wrap the code that’s doing the work in an error handler so that if an error occurs you can either end, or more typically, abort the transaction.

' Start a transaction. Dim trans As Transaction Set trans = ThisApplication.TransactionManager.StartGlobalTransaction(partDoc, _ "My Command") ' ** Do stuff that creates or edits the model. ' End the transaction. trans.End

The assembly test described previously that creates 500 occurrences with constraints was taking about 50 seconds. By wrapping that code in a global transaction it’s down to about 40 seconds. The other example that creates 500 sketch lines goes from 1.6 seconds to 1 second.

Odds and Ends of the Autodesk® Inventor® API

10

Proxies What are Proxies? In general terms, a proxy is something that stands in or acts as something else. In Inventor proxies are used extensively in assemblies. The idea of a proxy can be illustrated quite simply by first considering that in Inventor a part defines geometry. Below is a simple part with one of the faces highlighted.

Assemblies don’t contain geometry but instead contain references to parts and other assemblies. Each visible instance of these references is an occurrence. The picture below shows two occurrences of the previous part. The important concept to understand is that the geometry is not copied to the assembly but still only exists in the referenced part. Another way to visualize assemblies is to think of what you see in an assembly as an advanced hologram. It looks like the real object and you can even touch it and use it like the real object but it is just a display of the real object.

Odds and Ends of the Autodesk® Inventor® API

11

In the previous picture, the same face that was highlighted in the part model is highlighted on both occurrences. Here are a couple of interesting questions:

If the highlighted face only exists in the part, then how can you select it in the assembly?

If there is only the one face in the part how can there be two different faces in the assembly?

Proxies provide the technology that answers both of these questions. The geometry in the assembly isn’t actually in the assembly but exists in the part and is represented in the assembly by a proxy for that geometry. The proxy isn’t a copy of the geometry but is an API object that knows the real face it represents in the part and which occurrence in the assembly it is associated with. With that information, it is able to return any information about that face as if it actually exists in the assembly.

Query Operations Using Proxies For programs that just perform query operations on the model, there are often not any changes required to allow it to work correctly in both parts and assemblies. Proxies make it transparent to the program that it isn’t working with the real object. Proxy objects are derived from the object that they represent. This means that they support everything the real object supports. For example, the FaceProxy object has all of the properties and methods that the Face object has. In addition, all proxy objects also support two other properties. The first is the NativeObject property, which returns the object the proxy represents. In this case the FaceProxy object in the assembly will return the Face object in the part. Second, is the ContainingOccurrence property, which returns the occurrence in the assembly that the proxy is associated with.

When the user selects geometry in an assembly, they are always selecting proxies and the API will return proxy objects for the selection. A program that analyzes a face and displays normal vectors along the face can be written to take a standard Face object as input. This allows the program to function as intended in a part. However, the program will also function correctly in an assembly without any changes because variables typed as Face can also handle FaceProxy objects since they are derived from the Face object. The FaceProxy object will correctly handle the various query calls and correctly return the information for the FaceProxy even though it’s in a different position than the real face in the part.

Creating Proxies Working with proxies isn’t always transparent though. In cases where you’re working in an assembly and need to provide geometry to an API call you’ll need to provide a proxy for that geometry. If we look at the earlier example of the two occurrences of the same part, you can see why this is needed. If you want to create a mate constraint between those two highlighted faces, you need to uniquely identify each face. The real face in the part can’t be used because it doesn’t uniquely identify each instance of that face in the assembly. You need proxies to do that.

The creation of constraints is probably the most common reason you’ll need to create proxies. A program that automates the creation of an assembly will typically tag the geometry in the part in some way so it can find it later to create constraints. Attributes are a common way of naming an entity to find it later and there are other ways too but a discussion of this is outside the scope of this paper. The important thing to remember is that for any of the methods for naming a face you’ll need to retrieve the real face in the context of the part where it was named. Once you have the real face then you can create a proxy for it in the assembly.

Odds and Ends of the Autodesk® Inventor® API

12

Proxies are created using the ComponentOccurrence.CreateGeometryProxy method. The occurrence that you call this method on is the occurrence that the proxy will be with respect to. In the previous example, once you’ve gotten the real face in the part you would call the CreateGometryProxy method on one of the occurrences to create the face proxy for that occurrence. Then you would call the method on the other occurrence to create the other face proxy. Now you have proxies that represent to two difference faces in the assembly and can use those to create a constraint.

Below is some sample code that demonstrates this process. It assumes that an attribute set named “MateFace” has been added to the face in the part.

Public Sub ProxySample() ' Create a new assembly document. Dim asmDoc As AssemblyDocument Set asmDoc = ThisApplication.Documents.Add(kAssemblyDocumentObject, _ ThisApplication.FileManager.GetTemplateFile(kAssemblyDocumentObject)) Dim asmDef As AssemblyComponentDefinition Set asmDef = asmDoc.ComponentDefinition ' Place two instances of the part into the assembly. Dim trans As Matrix Set trans = ThisApplication.TransientGeometry.CreateMatrix Dim occ1 As ComponentOccurrence Dim occ2 As ComponentOccurrence Set occ1 = asmDef.Occurrences.Add("C:\Temp\MatePart.ipt", trans) Set occ2 = asmDef.Occurrences.Add("C:\Temp\MatePart.ipt", trans) ' Find the face in the part using the attribute. Dim partDoc As PartDocument Set partDoc = occ1.Definition.Document Dim attribSets As AttributeSetsEnumerator Set attribSets = partDoc.AttributeManager.FindAttributeSets("MateFace") Dim partFace As Face Set partFace = attribSets.Item(1).Parent.Parent ' Get a proxy of the face for the first occurrence. Dim face1 As FaceProxy Call occ1.CreateGeometryProxy(partFace, face1) ' Get a proxy of the face for the second occurrence. Dim face2 As FaceProxy Call occ2.CreateGeometryProxy(partFace, face2) ' Create a mate constraint between the faces. Call asmDef.Constraints.AddMateConstraint(face1, face2, 0) End Sub

Odds and Ends of the Autodesk® Inventor® API

13

Document

ReferenceKeyManager

Reference Keys Reference keys is a topic that not many people know about or understand and is an area I’ve seen people struggle with and misuse. Reference keys are not something you’ll use often in your programs but can be very handy in certain cases.

What is a Reference Key? A reference key is the ID of an object within Inventor. You use the GetReferenceKey method of an object to get its ID, or “key”. You save the ID and at a later time use it to get the same object back. A reference key doesn’t have any time limits, which means you can get the reference key from an object, and the document can go through many changes and versions, and you can still use the reference key to find the same object, even though the object may have changed from when you originally got its reference key. Of course, this assumes the object still exists and hasn’t been deleted or removed through some edit operation. Below is some sample code that gets the reference key from a selected assembly occurrence. By changing the selection filter the code below will work with any object that supports reference keys except for B-Rep objects (SurfaceBody, FaceShell, Face, Edge, Vertex, EdgeLoop, and EdgeUse) which are discussed later.

Public Sub GetRefKey() ' Get the active document. Dim doc As Document Set doc = ThisApplication.ActiveDocument ' Have an object selected. Dim selection As Object Set selection = ThisApplication.CommandManager.Pick(kAssemblyOccurrenceFilter, _ "Select an occurrence.") ' Get a reference key from the selected entity. ' This will fail if the selected object doesn't support reference keys. Dim refKey() As Byte Call selection.GetReferenceKey(refKey) ' Convert the reference key to a string to make saving it easier. Dim strKey As String strKey = doc.ReferenceKeyManager.KeyToString(refKey) ' Print out the reference key string. Debug.Print "RefKey: """ & strKey & """" End Sub

There are a couple of interesting things to note in the above sample. First, a reference key is an array of bytes. A byte is a number between 0 and 255 so a reference key is just a list of small numbers. Second, Inventor creates the reference key and returns it but it’s up to you to save it somewhere so you can use it later. If you’ll be writing out the reference key to a file it’s easier to deal with a String instead of an array of numbers. The KeyToString method does this for you. This sample just prints the result to the immediate window, which in my test resulted in: “AgEBAAQAAAABAAAA”.

General reference key functionality is exposed through the ReferenceKeyManager object which is obtained from a Document object. Each object that supports reference keys has the GetReferenceKey method.

Odds and Ends of the Autodesk® Inventor® API

14

Now that you have a reference key you can use it at any later time, with the same document, to get back the same object. The sample below demonstrates this.

Public Sub BindRefKey() ' Get the active document. Dim doc As Document Set doc = ThisApplication.ActiveDocument ' Convert the string back into an array of bytes. Dim refKey() As Byte Call doc.ReferenceKeyManager.StringToKey("AgEBAAQAAAABAAAA", refKey) ' Bind the key back to the object. Dim obj As Object Set obj = doc.ReferenceKeyManager.BindKeyToObject(refKey) ' Select the object to be able to see the object found. doc.SelectSet.Clear Call doc.SelectSet.Select(obj) End Sub

Basic Reference Key Workflow The concept of a reference key is fairly simple. The steps are:

1. Get a reference key from an object. 2. Save the reference key. 3. Use the reference key at a later time to get back the same object.

Common Problems There are a few gotchas to be aware of that I’ve seen people struggle with before.

A reference key is only good with the same document you originally got it from.

You should ignore the actual value of a reference key. You shouldn’t compare reference keys themselves to check if two reference keys came for the same object. It’s possible that reference keys with different values will bind back to the same object. Because of this you should always bind both reference keys back to their objects and then compare the objects to see if they’re the same.

You must use a reference key context when working with B-Rep entities. This is discussed below. It’s not needed with other types of objects.

B-Rep Entities and Reference Keys When using reference keys with B-Rep entities the concepts shown above still apply but there is some additional work that you need to do. B-Rep entities don’t have a simple internal identifier like other objects. It’s much more complicated for Inventor to be able to find a specific B-Rep entity because they can go through significant changes as modeling operations are performed. A way to visualize how Inventor can find a specific B-Rep entity is to think of the reference key as a set of instructions that identify a particular entity. For example a specific edge might be identified as the intersection of two faces and the faces might be identified by which sketch line was used to create the face as part of an extrusion. Because of the large amount of information that could be in these instructions, a reference key to a B-Rep entity can be quite large. In order to minimize the size of B-Rep reference keys and also improve performance, the idea of a reference key context was introduced. You can think of the context as a table where the instructions are saved. Several reference keys might use one or more of the same instructions. By having this information in a table allows individual instructions to be re-used. A B-Rep

Odds and Ends of the Autodesk® Inventor® API

15

reference key isn’t the full set of instructions but has references into this table. This significantly reduces the size of each reference key.

Here’s the reference key process for a B-Rep entity where the new steps are in bold.

1. Create a reference key context. 2. Ask an object for its reference key, providing the key context. 3. Continue to get reference keys from other objects, using the same key context. 4. Save the reference key. 5. Save the key context. 6. Use the reference key and the key context at a later time to get back the same object.

The process is essentially the same except for whenever you ask an object for a reference key or bind back a reference key to an object you also need to provide the key context. The program below is the same as the previous sample except it works for B-Rep entities.

Public Sub GetBRepRefKey() ' Get the active document. Dim doc As Document Set doc = ThisApplication.ActiveDocument

' Have a face and edge selected. Dim selFace As Face Set selFace = ThisApplication.CommandManager.Pick(kPartFaceFilter, "Select face.") Dim selEdge As Edge Set selEdge = ThisApplication.CommandManager.Pick(kPartEdgeFilter, "Select edge.")

' Set a reference to the ReferenceKeyManager object. Dim refKeyMgr As ReferenceKeyManager Set refKeyMgr = doc.ReferenceKeyManager

' Create a key context. Dim keyContext As Long keyContext = refKeyMgr.CreateKeyContext

' Get reference keys from the selected entities. Dim faceRefKey() As Byte Call selFace.GetReferenceKey(faceRefKey, keyContext) Dim edgeRefKey() As Byte Call selEdge.GetReferenceKey(edgeRefKey, keyContext)

' Get the key context as an array of bytes and convert it to a string. Dim contextArray() As Byte Call refKeyMgr.SaveContextToArray(keyContext, contextArray) Dim strContext As String strContext = refKeyMgr.KeyToString(contextArray)

' Convert the reference keys to strings to make saving it easier. Dim strFaceKey As String strFaceKey = doc.ReferenceKeyManager.KeyToString(faceRefKey) Dim strEdgeKey As String strEdgeKey = doc.ReferenceKeyManager.KeyToString(edgeRefKey)

' Save the results to a file. Open "C:\Temp\RefKeys.txt" For Output As #1 Print #1, strContext Print #1, strEdgeKey Print #1, strFaceKey Close #1 End Sub

Odds and Ends of the Autodesk® Inventor® API

16

You may have noticed in the last sample that the variable used for the key context is typed as a Long. Of course, this value isn’t the key context table but is a pointer to the context. Whenever a key context is needed you pass the pointer to the context and when you create a context a pointer is returned for you to use. When you save the key context along with the reference keys you need to save the full table, not the pointer. The SaveContextToArray method returns an array of bytes that is the full table. This is the data that you need to save, along with any reference keys that were created. The code below binds back to the B-Rep entities.

Public Sub BindBRepRefKey() ' Get the active document. Dim doc As Document Set doc = ThisApplication.ActiveDocument ' Get a reference to the reference key manager. Dim refKeyMgr As ReferenceKeyManager Set refKeyMgr = doc.ReferenceKeyManager ' Read the reference key strings from the file. Dim context As String Dim edgeKey As String Dim faceKey As String Open "C:\Temp\RefKeys.txt" For Input As #1 Line Input #1, context Line Input #1, edgeKey Line Input #1, faceKey Close #1 ' Convert the string to byte arrays. Dim edgeRefKey() As Byte Dim faceRefKey() As Byte Dim contextArray() As Byte Call refKeyMgr.StringToKey(edgeKey, edgeRefKey) Call refKeyMgr.StringToKey(faceKey, faceRefKey) Call refKeyMgr.StringToKey(context, contextArray) ' Create the context by loading the data. Dim refKeyContext As Long refKeyContext = refKeyMgr.LoadContextFromArray(contextArray) ' Bind back the face and edge. Dim foundFace As Face Dim foundEdge As Edge Set foundFace = refKeyMgr.BindKeyToObject(faceRefKey, refKeyContext) Set foundEdge = refKeyMgr.BindKeyToObject(edgeRefKey, refKeyContext) ' Highlight the found entities. doc.SelectSet.Clear doc.SelectSet.Select foundFace doc.SelectSet.Select foundEdge End Sub

Odds and Ends of the Autodesk® Inventor® API

17

Differences with B-Rep Entities when Binding Back There’s a difference when binding back B-Rep entities from other types of entities. The BindKeyToObject method can return an ObjectCollection in the case where the original B-Rep entity has been split into two or more pieces. The pictures below show the progression of a model and the changes that can take place to a face. In the last step face 2 is split into two pieces by an extrude feature. When binding back, both faces will be returned in an ObjectCollection.

Below is some modified code from the sample above that handles this case. When multiples are returned the first one in the collection is considered the “best” match with no priority for the rest of the entities.

' Bind back the face and edge. Dim foundFaces As Object Dim foundEdges As Object Set foundFaces = refKeyMgr.BindKeyToObject(faceRefKey, refKeyContext) Set foundEdges = refKeyMgr.BindKeyToObject(edgeRefKey, refKeyContext) ' Highlight the found entities, special casing for the case where ' more than one might have been returned doc.SelectSet.Clear If TypeOf foundFaces Is ObjectCollection Then Dim obj As Object For Each obj In foundFaces doc.SelectSet.Select obj Next Else doc.SelectSet.Select foundFaces End If

Odds and Ends of the Autodesk® Inventor® API

18

Important Points Here are a few points that I want to emphasize when using reference keys with B-Rep entities.

A single context can be used, and is intended to be used, for multiple reference keys. That’s the reason that the concept of the reference key context was created; so that multiple keys can share the same set of instructions for finding specific entities.

A reference key context and reference keys will only work with the same document they were originally obtained from.

Reference keys will only work with the same reference key context they were originally created with.

When working with B-Rep entities, write code that can handle the return of either a single object or an ObjectCollection.

The BindKeyToObject method can fail if the entity is not available. The CanBindKeyToObject method can be used to first check to see if the key can bind back.

Why Use Reference Keys? An expected question at this point might be why would anyone want to use reference keys? They’re a generic tool that can be used in many ways but here are some examples.

Support associativity in an external read-only application This is one of the more common uses of reference keys. Applications are able to use Apprentice to read B-Rep data and display the model in their application. When the user selects geometry in their applications they’re able to get the corresponding geometry in Apprentice and get the reference key. For example, the geometry might used for a tool path or to position a load or constraint or analysis. Getting reference keys from objects does not require write access to the model. When the model is edited in Inventor they can read in the new geometry and use the reference keys to find the specific geometry that they need to remain associative to.

Maintaining a reference over a recompute This is one of the more common general uses of the functionality. An example of this is if you want to write a program that finds all of the circular faces in a model and places a hole feature at the center of each one. The first part of your programs finds all of the circular faces. Next it begins placing holes on each one. The problem is that any references to B-Rep entities are no longer valid after any change has been made to the model. Creating the hole invalidated all of the references you have to the circular faces. You don’t want to do the analysis again to find circular faces because you’ve potentially added new circular faces by placing the new feature.

Reference keys can help you solve this. Instead of maintaining a reference to a Face object you can get the reference key of each circular face. Reference keys are still valid after a compute, so after you create the first hole you can find the next circular face by using the saved reference key.

Attributes Attributes can be used in some cases to solve the same issues that reference keys do by providing a way of adding information to an object to find it later. Attributes have the advantage that they’re self-contained and you don’t have to save any information. They also have the advantage of supporting a more powerful search mechanism. They have the disadvantage that they require write access to the file and will require the document to be saved. They also have the disadvantage that they don’t return multiple objects in the case where B-Rep entities have been split.

Odds and Ends of the Autodesk® Inventor® API

19

What are Transient Keys? There’s one more thing that I want to quickly touch on; transient keys. Transient key functionality is only available with B-Rep entities. Each of the B-Rep entities supports the TransientKey property which returns a Long value. This is a simple identifier for that specific B-Rep entity. You can bind back to the B-Rep entity with the transient key using the BindTransientKeyToObject method on the SurfaceBody object. This is much simpler than reference keys because it’s just a single Long and you don’t need a key context. However, there’s a BIG limitation with transient keys. They’re only good as long as the model has not been modified. They’re similar to a live reference to a B-Rep entity in that it doesn’t survive through a model compute.

The purpose of transient keys is to provide the ability to match up B-Rep entities when copying bodies. For example, the AlternateBody property of the SurfaceBody returns a copy of the body it was called on. The new body may even be slightly different than the original body. The new body has transient keys that you can get from each B-Rep entity. You can use the BindTransientKeyToObject method to find the equivalent B-Rep entity on the original body. It’s also possible to write out a body as SAT where the transient keys are also output as attributes on the SAT data. This again allows you to match up the B-Rep entities from this output model to the original Inventor model, as long as the model has not been edited since the copy was created.

Odds and Ends of the Autodesk® Inventor® API

20

Cameras When you view a part, assembly, or drawing in Inventor it’s actually like the window you’re looking at is a small monitor and you’re viewing the resulting image of a camera pointed at the model. The model always remains fixed in model space but as you rotate, pan, and zoom the view you’re just changing moving the camera around the model. The pictures below illustrate this. The picture on the left shows the model in model space and a camera. The picture on the right shows what the camera is seeing and what is displayed in the graphics window. By moving the camera around the model, the picture in the graphics window changes.

In the API, a View object represents a graphics window in the Inventor user-interface. The view can be sized, collapsed, maximized, and deleted. Additional views can also be created. The Camera API object is an API only concept that’s not directly seen in the user-interface since all of the user-interaction to manipulate the camera is done through the view. The camera controls what is displayed in the view.

There are two types of cameras; perspective and orthographic (parallel). I’ll begin by discussing perspective cameras because they’re easier to understand since they behave in much the same way as a real-world camera. There are three basic properties that apply to all cameras; eye point, target point, and up vector. The eye point is the position of the camera in model space. With just the eye point defined the camera is fixed at a specific location but can be pointed anywhere and rotated to any angle. The target point is the position in space that the camera is pointed. With the eye and target point defined the camera can still be rotated to get a landscape, portrait, or anything in between orientation. The up vector defines a direction in model space that defines the direction the top of camera is pointed. With these three properties defined the camera is fully positioned.

Odds and Ends of the Autodesk® Inventor® API

21

For a perspective camera the other property you can define is the perspective angle. A camera sees a certain area in front of it, which is referred to as its field of view. The field of view of a perspective camera is represented by a view pyramid and the angle of the pyramid that controls this field of view is called the perspective angle. Changing the perspective angle of a camera is the same as using the zoom feature of a real camera. By narrowing the field of view you see less of the model, effectively zooming in on a smaller area.

With a perspective camera there’s also another way to zoom in on the part. Just like with a real camera you can just move closer to the object, as illustrated below. As you zoom in and out in Inventor with a perspective camera, this is what Inventor is doing; moving the camera closer or further away from the model.

Another behavior of a perspective camera is objects that are further away from the camera appear smaller, just as they do in the real world. You can see why that is because something that is further away takes up less of the field of view, since the size of the field of view gets bigger the further away from the camera it is. Things closer fill up more of the field of view and appear larger.

Odds and Ends of the Autodesk® Inventor® API

22

Orthographic or parallel cameras don’t follow any of these natural behaviors that we’re used to. Instead of a view pyramid, parallel cameras have a rectangular box. They see everything in the box. One of the effects of this is that everything appears the same size regardless of how close it is to the camera. This is because everything takes up the same area in the field of view regardless of how close it is to the camera. The second big difference is that moving the camera closer to the object doesn’t zoom in. This is illustrated below. The model still takes up the same amount of area in the field of view no matter how close the camera is to the object.

To zoom in with a parallel camera the size of the viewing box is changed, reducing the field of view.

Odds and Ends of the Autodesk® Inventor® API

23

When working with cameras associated with a graphics view you use the Camera property of the View object. Typically you’ll get the View object using the Application.ActiveView property. The behavior of the View.Camera property is that every time you call it, it returns a new camera. This behavior makes it easy to run into a very common problem when working with cameras. The code below illustrates the problem. It calls the Camera property of the View object and then calls the ViewOrientationType property of the Camera object, setting the camera position to be a standard iso view. Once it does this it calls the camera’s Apply method. Nothing happens.

invApp.ActiveView.Camera.ViewOrientationType = kIsoTopLeftViewOrientation invApp.ActiveView.Camera.Apply

The reason that nothing happens is that in the first line the Camera property is called and a camera is created and returned. On this camera the iso view is specified. On the next line the Camera property is called again and another camera is created and returned. On this camera the Apply method is called, but this camera hasn’t had the view orientation set but is the same as the current view so you don’t see any visible change. The code below can be used instead and does create the desired result. In this case the Camera property is called and the camera is returned. The orientation for that camera is defined as an iso view and the Apply method on the same camera is called. The view the updates to reflect the new camera position.

Dim cam As Camera = invApp.ActiveView.Camera cam.ViewOrientationType = kIsoTopLeftViewOrientation cam.Apply

Setting a property on a camera doesn’t have an immediate effect. This is because typically you’ll want to make several changes to the camera before you force the view to update. For example you might want to change both the eye and target points and then update the camera. There are two methods that cause changes to the camera take effect. They are Apply and ApplyWithoutTransition. The Apply method uses the same transition technique that inventor uses when you change to a new view orientation; it smoothly transitions from one position to another. The ApplyWithoutTransition immediately shows the new camera position without any transition. The ApplyWithoutTransition is used when you’re doing your own camera animation because you’ll be computing all of the camera positions yourself and don’t want the delay as Inventor transitions from one camera position to another.

In addition to the Eye, Target, and UpVector properties that let you get and set those attributes of the camera, there is also the Perspective property that lets you toggle the camera from perspective to parallel. When the camera is a perspective camera you can use the PerspectiveAngle property to control the field of view. When it’s a parallel camera you can use the GetExtents and SetExtents methods to get and set the field of view. In addition to these functions that let you get and set the underlying properties of a camera there are also some convenience functions that make it easier to manipulate the camera. For example, the ViewOrientationType lets you specify standard view positions, (i.e. top, front, top-left iso, etc.) and Inventor updates the camera properties to match. The Fit method takes into account the size of the model and modifies the camera so the entire model is visible. There is also a function that takes mouse input and modifies the camera to reflect the mouse movement or scrolling.

Odds and Ends of the Autodesk® Inventor® API

24

Below is some code that will fly the camera around the model. To simplify the code, the target is hard coded at the origin and the up vector is the positive Z.

Public Sub RotateCamera() ' Get the active camera. Dim cam As Camera Set cam = ThisApplication.ActiveView.Camera Dim tg As TransientGeometry Set tg = ThisApplication.TransientGeometry ' Define the number of steps in the animation. Dim steps As Integer steps = 360 ' Define the distance between the eye and target. Dim eyeDistance As Double eyeDistance = 15 ' Calculate pi. Dim pi As Double pi = Atn(1) * 4 ' Iterate the specified number of steps. Dim i As Integer For i = 1 To steps ' Calculate the x and y coordinates of the eye. Dim x As Double Dim y As Double x = eyeDistance * Cos(i / steps * (2 * pi)) y = eyeDistance * Sin(i / steps * (2 * pi)) ' Set the eye with a hard coded z value. cam.Eye = tg.CreatePoint(x, y, 3) ' Define the up vector as positive z. cam.UpVector = tg.CreateUnitVector(0, 0, 1) ' Apply the current camera definition to the view. cam.ApplyWithoutTransition Next End Sub

There are several reasons you might want to use the Camera object. The first is that you have a need to control the view within Inventor. For example, you can create a custom fly-trough of an assembly or write some utilities that allow you to quickly go to certain views that are helpful with the types of models you create.

Odds and Ends of the Autodesk® Inventor® API

25

Besides controlling the graphics window, a camera is also used to create non-standard drawing views. Using a camera you can create any view orientation you want. The following code illustrates creating a custom drawing view that is a perspective iso view using a camera. ' Create a camera to be used to define the view orientation. Dim cam As Camera Set cam = ThisApplication.TransientObjects.CreateCamera ' Set up the camera. cam.Perspective = True cam.ViewOrientationType = kIsoTopRightViewOrientation ' Create a drawing view using the camera. Dim viewPnt As Point2d Set viewPnt = ThisApplication.TransientGeometry.CreatePoint2d(12, 12) Dim drawView As DrawingView Set drawView = drawSheet.DrawingViews.AddBaseView(doc, viewPnt, 1, _ kArbitraryViewOrientation, _ kHiddenLineRemovedDrawingViewStyle, , _ oCamera)

Finally, a camera can be used in Apprentice to create images of a part, assembly, or drawing. The VB.Net code below gets an image file from a part. Private Sub CreateImage(Apprentice As Inventor.ApprenticeServerComponent, _ PartOrAssembly As Inventor.ApprenticeServerDocument, _ Filename As String) ' Create a new camera. Dim cam As Inventor.Camera = Apprentice.TransientObjects.CreateCamera ' Associate the document with the camera. cam.SceneObject = PartOrAssembly.ComponentDefinition ' Define the camera. cam.ViewOrientationType = Inventor.ViewOrientationTypeEnum.kIsoTopRightViewOrientation cam.Fit() cam.ApplyWithoutTransition() ' Save the result. cam.SaveAsBitmap(Filename, 800, 600, _ Apprentice.TransientObjects.CreateColor(255, 255, 0), _ Apprentice.TransientObjects.CreateColor(15, 15, 15)) End Sub

Here is the resulting image: