Using iParts in Inventor makes it quick and easy to create hundreds of part variants. When working with Inventor’s API, it also simplifies locating the correct part since you only need to know the location of the factory file and search within for the variants, rather than finding the exact part file. However, searching for the correct variant can be slow due to repeated calls to Inventor’s API. To speed up your program, the best approach is to create a local list of variants, eliminating the need to query Inventor each time you wish to place a part, but that in itself can be slow so how can we improve this.
The following examples were written as an external application interfacing with Inventor. Running the same code within Inventor, such as through a plugin or iLogic, will significantly reduce the execution time, but this might not always be an option. Nevertheless, the techniques outlined below should still improve performance.
For a 500-variant iPart, the simplest method to create your own dictionary took around 8.62 seconds.
var tubeLengths = new Dictionary<string, object>();
foreach (iPartTableRow row in tDoc.ComponentDefinition.iPartFactory.TableRows)
{
if (!tubeLengths.ContainsKey(row[3].Value))
{
tubeLengths[row[3].Value] = row;
}
}
This method simply loops through each row in the iPart table and adds the row number and value of column 3 to the dictionary.
To improve performance, reducing the amount of data transferred can provide the largest boost. By switching from looping through each row to looping through each cell in column 3, the time is nearly halved to 4.75 seconds.
var tubeLengths = new Dictionary<string, object>();
foreach (iPartTableCell row in tDoc.ComponentDefinition.iPartFactory.TableColumns[3])
{
if (!tubeLengths.ContainsKey(row.Value))
{
tubeLengths[row.Value] = row.Row;
}
}
Further gains can be achieved by converting the returned data to a list, using LINQ, and implementing custom indexing to retrieve the row number.
var tableColumns = tDoc.ComponentDefinition.iPartFactory.TableColumns[3].OfType<iPartTableCell>().ToList();
var tubeLengths = tableColumns
.Select((cell, index) => new { cell.Value, Row = index })
.GroupBy(item => item.Value)
.ToDictionary(group => group.Key, group => group.First().Row);
Each of these optimizations adds a small performance boost, reducing the time down to 3.42 seconds.
This is as far as I can get it with pure Inventor but that’s not the only option. The data for the iPart factory is stored as an Excel workbook, which allows us to use other libraries to retrieve the data, effectively bypassing the middleman.
Using the Excel interface is the easiest way to interact with the workbook. By simply switching to this method, the time can be reduced to 2.00 seconds.
excel.Worksheet worksheet= tDoc.ComponentDefinition.iPartFactory.ExcelWorkSheet;
excel.Range usedRange = worksheet.UsedRange;
int rowCount = usedRange.Rows.Count;
var tubeLengths = new Dictionary<string, int>();
for (int i = 2; i <= rowCount; i++) // Assuming data starts from row 2
{
string cellValue = (string)(usedRange.Cells[i, 3] as excel.Range).Value;
// If the value is not already in the dictionary, add it with the current row number
if (!tubeLengths.ContainsKey(cellValue))
{
tubeLengths[cellValue] = i - 2; // Row index adjusted to 0-based index
}
}
As with Inventor, we’re still communicating between two different applications, so there’s a delay in data transfer. This means the same techniques used to improve Inventor’s performance apply here as well. By reducing the amount of data passed between the two applications and utilizing LINQ, the time can be further reduced to 1.16 seconds.
excel.Worksheet worksheet = tDoc.ComponentDefinition.iPartFactory.ExcelWorkSheet;
excel.Range columnRange = worksheet.Range["C2", $"C{worksheet.UsedRange.Rows.Count}"];
object[,] columnData = columnRange.Value2;
var tubeLengths = Enumerable.Range(1, columnData.GetLength(0))
.Select(i => new { Value = columnData[i, 1]?.ToString(), Index = i - 1 })
.Where(x => !string.IsNullOrEmpty(x.Value))
.GroupBy(x => x.Value)
.ToDictionary(g => g.Key, g => g.First().Index);
With these optimizations, we’ve reduced the time from 8.62 seconds to 1.16 seconds—an 86% improvement. Using third-party libraries that can interface directly with the Excel workbook, bypassing the application layer, could likely reduce this time even further. However, for my applications, this level of improvement is sufficient.
If anyone discovers additional ways to enhance performance, please leave a comment below to share with others.
Using the code in context
To make use of the improved speed you want to run your preferred code block only once for each iPart you have at the start of the program.
PartDocument tDoc = (PartDocument)_invApp.Documents.Open(tube, false);
excel.Worksheet worksheet = tDoc.ComponentDefinition.iPartFactory.ExcelWorkSheet;
excel.Range columnRange = worksheet.Range["C2", $"C{worksheet.UsedRange.Rows.Count}"];
object[,] columnData = columnRange.Value2;
var tubeLengths = Enumerable.Range(1, columnData.GetLength(0))
.Select(i => new { Value = columnData[i, 1]?.ToString(), Index = i - 1 })
.Where(x => !string.IsNullOrEmpty(x.Value))
.GroupBy(x => x.Value)
.ToDictionary(g => g.Key, g => g.First().Index);
tDoc.Close();
Once tubeLengths
has been created we can then run the following code whenever we want to add any parts to the assembly oDoc
. This method can be used as a drop in replacement for the method to add occurrences seen in Placing objects in Autodesk Inventor using the API
Matrix matrix = oTG.CreateMatrix();
string length = "100 mm";
if (!tubeLengths.ContainsKey(length))
{
Console.WriteLine($"No tube length found for '{length}'");
return;
}
object tRow = tubeLengths[length];
oDoc.ComponentDefinition.Occurrences.AddiPartMember(tube, matrix, tRow);
Leave a Reply