is it possible to speed op writing to excel ?

is it possible to speed op writing to excel ?

Darkforce_the_ilogic_guy
Advisor Advisor
2,675 Views
16 Replies
Message 1 of 17

is it possible to speed op writing to excel ?

Darkforce_the_ilogic_guy
Advisor
Advisor

I have an complete code that writt a lot of data to an Excel fil ... is there a way to speed this up . ? .... alt this time one of my large assambly take 8 min to write and save all the data

0 Likes
2,676 Views
16 Replies
Replies (16)
Message 2 of 17

A.Acheson
Mentor
Mentor

@Darkforce_the_ilogic_guy 

Is this for bom extraction line by line? What contact method to excel. Go Excel or object Excel Application?  What are inventor version.

 

 I found a solution to quicker excel functionality in ipart and content center.

Inventor 2020. I added the variable  LibXL. Worked great but all my go excel code stopped working, not good. So I  removed LibXL and just tried timing the excel application opening outside inventor, it was taking up to 10 secs.
So I went to the XL start folder path. This path loads files like templates, personal macro.  I looked at what could be removed in this case I had a bunch of templates not being used. Old, I assume crash files and so on. After cleaning the Time to open excel application reduce to 4-5 secs.

 

Not sure if this would help the code run quicker, but it could help the initial connection. 

 

If this solved a problem, please click (accept) as solution.‌‌‌‌
Or if this helped you, please, click (like)‌‌
Regards
Alan
0 Likes
Message 3 of 17

dg2405
Advocate
Advocate

Using OleDb should speed things up!

Here is an code-example which we use automatically to clean up custom iprops on save. We have an Excel-table with all Props who should be deleted or created. It taks 0.3s to do this! Here is the code:

 

AddReference "Microsoft.Office.Interop.Excel" 'To use excel
Imports Microsoft.Office.Interop.Excel        'To use excel
AddReference "System.Data"
AddReference "System.Core"
AddReference "System.Xml"
Imports System.IO
Imports System.Data.OleDb
Imports System.Data
Sub Main()

Dim oApp As Inventor.Application = ThisApplication
Dim oDoc As Document = ThisDoc.Document
Dim PropertyName As String

'lokalen Pfad definieren
Dim LocalFileName As String = "C:\Vault-Workspace\Preferences\Excel\CustomiProperties.xlsx"

Dim DT As System.Data.DataTable
DT = xls2Datatable(LocalFileName, "CustomiProperties")
For Each dr As DataRow In DT.Rows
	'ipt_erzeugen/löschen
	If TypeOf oDoc Is PartDocument Then
		If Not TypeOf dr("ipt_erzeugen") Is DBNull Then
			PropertyName = dr("ipt_erzeugen")
			CreateProp(oDoc, PropertyName)
		End If
		If Not TypeOf dr("ipt_löschen") Is DBNull Then
			PropertyName = dr("ipt_löschen")
			DelProp(oDoc, PropertyName)
		End If
	End If
	'iam_erzeugen/löschen
	If TypeOf oDoc Is AssemblyDocument Then
		If Not TypeOf dr("iam_erzeugen") Is DBNull Then
			PropertyName = dr("iam_erzeugen")
			CreateProp(oDoc, PropertyName)
		End If
		If Not TypeOf dr("iam_löschen") Is DBNull Then
			PropertyName = dr("iam_löschen")
			DelProp(oDoc, PropertyName)
		End If
	End If
	'idw_erzeugen/löschen
	If TypeOf oDoc Is DrawingDocument Then
		If Not TypeOf dr("idw_erzeugen") Is DBNull Then
			PropertyName = dr("idw_erzeugen")
			CreateProp(oDoc, PropertyName)
		End If
		If Not TypeOf dr("idw_löschen") Is DBNull Then
			PropertyName = dr("idw_löschen")
			DelProp(oDoc, PropertyName)
		End If
	End If
Next

'Properties löschen
Dim oDesPropSet As PropertySet = oDoc.PropertySets("Design Tracking Properties")
If oDesPropSet("Designer").Value <> "" Then oDesPropSet("Designer").Value = ""
If oDesPropSet("Engineer").Value <> "" Then oDesPropSet("Engineer").Value = ""
Dim oDocSumInfPropSet As PropertySet = oDoc.PropertySets.Item("Document Summary Information")
If oDocSumInfPropSet("Manager").Value = "XX" Then oDocSumInfPropSet("Manager").Value = ""

'Bezeichnung in Zeichnung gleichsetzen mit 3D-Dokument-Bezeichnung
If TypeOf oDoc Is DrawingDocument Then
	Dim o3DDoc As Document
	Try 'Wenn keine Ansichten auf Blatt würde sonst Fehler kommen, oder Wenn Referenz zu 3D-Dokument fehlt
		o3DDoc = ThisDoc.ModelDocument
		Dim Bezeichnung_3D As String = o3DDoc.PropertySets.Item("Design Tracking Properties")("Description").Value
		Dim Bezeichnung_2D As String = oDesPropSet("Description").Value
		If Bezeichnung_3D <> Bezeichnung_2D Then oDesPropSet("Description").Value = Bezeichnung_3D
		If oDoc.PropertySets("Inventor Summary Information")("Title").Value <> "" Then oDoc.PropertySets("Inventor Summary Information")("Title").Value = ""
	Catch
		GoTo AfterSetBezeichnung3D
	End Try
End If
AfterSetBezeichnung3D:

'Ansicht beim Speichern auf ISO stellen
If oDoc.ThumbnailSaveOption <> ThumbnailSaveOptionEnum.kActiveComponentIsoViewOnSave Then
	oDoc.SetThumbnailSaveOption(ThumbnailSaveOptionEnum.kActiveComponentIsoViewOnSave)
End If

'Aktuelle Inventor Version in iProperties für Migration
Dim oCusPropSet As PropertySet = oDoc.PropertySets("Inventor User Defined Properties")
Dim sInvVersion As String = oApp.SoftwareVersion.DisplayVersion & "V3"
If oCusPropSet("Inv-Version").Value <> sInvVersion Then oCusPropSet("Inv-Version").Value = sInvVersion

'Original stutzen, da bei altem DS der komplette Pfad reingeschrieben wurde
Dim sOriginal As String = RToBackSlash(oCusPropSet("Original").Value)
If oCusPropSet("Original").Value <> sOriginal Then oCusPropSet("Original").Value = sOriginal

'Anzahl der lokalen Trigger in CustomProperty
Dim oEventPropSet As PropertySet
Dim LocalTriggerCount As String
Try 'Muss über Try gehen, da wenn noch nie ein Trigger im Dokument erstellt wurde, das PropertySet nicht existiert
	oEventPropSet = oDoc.PropertySets("_iLogicEventsRules")
	LocalTriggerCount = CStr(oEventPropSet.Count)
Catch
	LocalTriggerCount = CStr(0)
End Try
Dim SubAssyDocs As Integer = 0
For Each oRefDoc In oDoc.ReferencedDocuments
	If TypeOf oRefDoc Is AssemblyDocument Then SubAssyDocs = SubAssyDocs +1
Next
Dim LocalTriggerStr As String = "AllRefDocs" & oDoc.AllReferencedDocuments.Count & "|" &  "RefDocs" & oDoc.ReferencedDocuments.Count & "|" &  "SubAssyDocs" & SubAssyDocs & "|" & "LocalTrigger" &LocalTriggerCount
If oCusPropSet("Local-Trigger").Value <> LocalTriggerStr Then oCusPropSet("Local-Trigger").Value = LocalTriggerStr

End Sub

Function xls2Datatable(ByVal Dateiname As String, ByVal Blattname As String) As System.Data.DataTable
	If System.IO.File.Exists(Dateiname) = False Then Return Nothing

	Dim FileInfo As FileInfo = New FileInfo(Dateiname)
	Dim DT As New System.Data.DataTable

	Dim oConn As OleDbConnection = New OleDbConnection
	If Dateiname.Contains(".xlsx") Then
	oConn.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;" & _
	  "Data Source=" & Dateiname & ";" & _
	  "Extended Properties=""Excel 12.0 Xml;HDR=YES"";"
	Else
	oConn.ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;" & _
	  "Data Source=" & Dateiname & ";" & _
	  "Extended Properties=""Excel 8.0;HDR=Yes;IMEX=1"";"
	End If
	oConn.Open()

	Dim cmd As OleDbCommand
	cmd = New OleDbCommand("SELECT * FROM [" & Blattname & "$]", oConn)

	Dim reader As OleDbDataReader = cmd.ExecuteReader( _
	CommandBehavior.KeyInfo Or CommandBehavior.CloseConnection)
	Dim Schema As System.Data.DataTable = reader.GetSchemaTable()
	Dim columns(Schema.Rows.Count - 1) As DataColumn
	Dim column As DataColumn

	' Build the schema for the table that will contain the data.
	For i As Integer = 0 To columns.GetUpperBound(0) Step 1
	column = New DataColumn
	With column
	  .AllowDBNull = CBool(Schema.Rows(i)("AllowDBNull"))
	  .AutoIncrement = CBool(Schema.Rows(i)("IsAutoIncrement"))
	  .ColumnName = CStr(Schema.Rows(i)("ColumnName"))
	  .DataType = CType(Schema.Rows(i)("DataType"), Type)

	  If .DataType Is GetType(String) Then
	    .MaxLength = CInt(Schema.Rows(i)("ColumnSize"))
	  End If

	  .ReadOnly = CBool(Schema.Rows(i)("IsReadOnly"))
	  .Unique = CBool(Schema.Rows(i)("IsUnique"))
	End With
	columns(i) = column
	Next i

	Dim Data As New System.Data.DataTable
	Dim row As DataRow

	Data.Columns.AddRange(columns)

	' Get the data itself.
	While reader.Read()
	row = Data.NewRow()

	For i As Integer = 0 To columns.GetUpperBound(0)
	  row(i) = reader(i)
	Next i

	Data.Rows.Add(row)
	End While

	reader.Close()
	oConn.Close()

	Return Data
End Function

Function CreateProp(ByVal oDoc As Document, ByVal PropertyName As String)
	Dim oCustomPropSet As PropertySet = oDoc.PropertySets("Inventor User Defined Properties")
	Try
		oProp = oCustomPropSet(PropertyName)
	Catch
		'Wenn Error dann Property anlegen
		oCustomPropSet.Add("", PropertyName)
	End Try
End Function

Function DelProp(ByVal oDoc As Document, ByVal PropertyName As String)
	Dim oCustomPropSet As PropertySet = oDoc.PropertySets("Inventor User Defined Properties")
	Try
		oCustomPropSet(PropertyName).Delete
	Catch
	End Try
End Function

Function RToBackSlash(ByVal strText As String) As String
    RToBackSlash = Right(strText, Len(strText) - InStrRev(strText, "\"))
End Function
Message 4 of 17

WCrihfield
Mentor
Mentor

Interesting approach @dg2405.

  I have seen other similar techniques too that result in increased processing time when not working directly with Excel each time data is retrieved.  It seems that, when you read all the needed Excel data into a type of memory based storage (variables, arrays, DataTables, or other), then do all interacting with that data via that memory storage, instead of directly working with Excel each time, it results in an increase in processing speed.  This greatly reduces the direct communications with the Excel application to one data retrieve step (and if needed, one data write step), instead of many throughout the code.  It seems to always be faster to work with data already stored in memory, than to interact with documents saved on a hard drive.  Multi-dimensional arrays and arrays of arrays are also sometimes used to store table type data, for certain purposes, but they can be odd to work with.

 

If this solved your problem, or answered your question, please click ACCEPT SOLUTION.
Or, if this helped you, please click (LIKE or KUDOS) 👍.

Wesley Crihfield

EESignature

(Not an Autodesk Employee)

0 Likes
Message 5 of 17

WCrihfield
Mentor
Mentor

@Darkforce_the_ilogic_guy 

Here is a very simple / basic example using iLogic (vb.net) that will read the values from all cell in a range on an Excel worksheet to a 2-dimensional array of Object.  I used Object, because you may have different types of data across your range.  You can change the array's type or just convert data as needed to suit your needs.

 

I created a new Excel document to test with, and left the default sheet name of "Sheet1".   I simply filled the first 3 values in column A with a,b,c.  Then column B with 1,2,3.  So cell "A1" = "a" and cell "B3" = 3.  Then saved it to my C:\Temp\ folder as "Test.xlsx".

 

I usually choose to bypass using iLogic's GoExcel stuff, and access Excel directly, so I have more control.

I included a lot of comments so that everyone know the set-up and can follow what is going on.  But it should be simple enough for most folks to follow, even without the comments.

I'm using Inventor Pro 2021.1 and Excel 2019, on Windows 10, and it all works for me.

Here's the iLogic (vb.net) code:

 

AddReference "Microsoft.Office.Interop.Excel.dll"
Imports Microsoft.Office.Interop.Excel
Sub Main
	Dim oXLFile As String = "C:\Temp\Test.xlsx"
	'Excel spreadsheet is set-up like this:
	'Cell A1 = a, Cell A2 = b, Cell A3 = c
	'Cell B1 = 1, Cell B2 = 2, Cell B3 = 3

	'start a new instance of Excel
	Dim oExcel As New Microsoft.Office.Interop.Excel.ApplicationClass
	oExcel.DisplayAlerts = False
	oExcel.Visible = False
	'open the Excel document
	Dim oWB As Workbook = oExcel.Workbooks.Open(oXLFile)
	'specify the sheet you want to work with
	Dim oWS As Worksheet = oWB.Sheets.Item("Sheet1")
	'specify the range of cells in that sheet you want to read into memory
	Dim oRange As Range = oWS.Range("A1:B3")
	'create 2-dimensional array of Object, and set the range's values into it
	Dim oArray(, ) As Object = oRange.Value

	'read from an array by specifying row, then column, both starting with 1
	'example: oArray(1,1) gets data at first row, first column
	'example: oArray(3,2) gets data at 3rd row, and 2nd column
	Dim oVal As String = CStr(oArray(3, 2)) 'should return "3" from Cell B3
	
	MsgBox("oVal = " & oVal, , "")
	
	'close the workbook (Excel document)
	oWB.Close
	'close this instance of Excel
	oExcel.Quit
End Sub

 

If this solved your problem, or answered your question, please click ACCEPT SOLUTION.
Or, if this helped you, please click (LIKE or KUDOS) 👍.

If you have time, please... Vote For My IDEAS 💡or you can Explore My CONTRIBUTIONS

Inventor 2021 Help | Inventor Forum | Inventor Customization Forum | Inventor Ideas Forum

 

Wesley Crihfield

EESignature

(Not an Autodesk Employee)

0 Likes
Message 6 of 17

WCrihfield
Mentor
Mentor

@Darkforce_the_ilogic_guy 

Here is an article I wrote about reading Excel data into 2D arrays and writing 2D arrays to Excel.  The code in my last post was a variation of one of the codes posted there.  But since you are inquiring about writing to Excel, instead of reading from it, the other code in this article may be of more interest to you.

 

If this solved your problem, or answered your question, please click ACCEPT SOLUTION.
Or, if this helped you, please click (LIKE or KUDOS) 👍.

Wesley Crihfield

EESignature

(Not an Autodesk Employee)

0 Likes
Message 7 of 17

liminma8458
Collaborator
Collaborator

Hi, I wonder whether it is possible to leverage LibXL in iLogic or VB.NET code, so we don't need to launch Excel.exe if we only need to read data from Excel?

Thanks
Limin
Inventor pro 2026 64 bit; Windows 11 pro 64 bit version 24H2; Office 2016 64 bit

Check into My Apps:
iCable : Create Cables, Hoses, Tubes and Pipes Easily.
iCable Professional : Create and Edit Them, Quick, Easy and Much More.
iCable Electrical : Auto Router to Create Massive Cables Automatically for Electrical Cabinet.
0 Likes
Message 8 of 17

yan.gauthier
Advocate
Advocate

If you can work with external libraries, then DocumentFormat.OpenXml would be a good way to manipulate excel files without needing excel app. I use this for my C# addin.

 

Otherwise, here's my experience about excel:

 

each time you access a cell/range, it takes up time. See it as a transaction. each time you interact with the content of the excel file, you do a transaction. Reduce this to a minimum. Need to write a bunch of data in several cells ? make a variant array, same size as the range you are writing to (array need to be 2D and 1 based : arr(1 to x, 1 to 1) as variant.

 

You can also read a bunch of cells by assigning a range value to a variant Array.

 

Of course, there is always the excel starting time that is long.

 

Regards,

0 Likes
Message 9 of 17

WCrihfield
Mentor
Mentor

Sure.  You can just use the GoExcel interface object defined within iLogic.  It provides sort of like shortcut access to a bunch of properties & methods that are defined within the iLogic add-in for working with spreadsheet documents.  Do you have Excel installed?  If so, you don't have to do anything special to use these, but if you don't have Excel installed, you may need to set the Windows environment variable called "UseLibXl" to 1.  I believe it is an internal spreadsheet reader application within Windows, and when you set this variable to 1 (basically 1 = on/yes ; 0 = off/no), that instructs it to try to use that resource first, if possible/appropriate, which sometimes can improve performance.  If you're not familiar with how to set Windows environment variables, here are a couple of links to related topics.

https://www.alphr.com/environment-variables-windows-10/ 

https://forums.autodesk.com/t5/inventor-forum/inventor-2018-1-uselibxl/td-p/7335936 

https://phoenixnap.com/kb/windows-set-environment-variable

 

If this solved your problem, or answered your question, please click ACCEPT SOLUTION.
Or, if this helped you, please click (LIKE or KUDOS) 👍.

If you want and have time, I would appreciate your Vote(s) for My IDEAS 💡or you can Explore My CONTRIBUTIONS

Wesley Crihfield

EESignature

(Not an Autodesk Employee)

0 Likes
Message 10 of 17

liminma8458
Collaborator
Collaborator

Yes. I do the environment setup for iLogic. But normally, I read data from VB.NET, is it possible to leverage the power of LibXL so I don't need to launch Excel.exe in the VB.NET code?

Thanks
Limin
Inventor pro 2026 64 bit; Windows 11 pro 64 bit version 24H2; Office 2016 64 bit

Check into My Apps:
iCable : Create Cables, Hoses, Tubes and Pipes Easily.
iCable Professional : Create and Edit Them, Quick, Easy and Much More.
iCable Electrical : Auto Router to Create Massive Cables Automatically for Electrical Cabinet.
0 Likes
Message 11 of 17

WCrihfield
Mentor
Mentor

Have you looked into this option?  I don't currently do much with Inventor add-ons due to corporate restrictions (no employees have administrator rights to their own computers), so I haven't tried it out yet myself.  That site points out that it can be used with many platforms (C, C++, C# (which is also a .net), Delphi, and others), but doesn't specifically mention vb.net.  It seems to me that you should be able to add a reference to, or somehow point to that LibXL resource, but since I have always had Excel available, and haven't had any performance issues with what I've been doing, I've haven't dived down that 'rabbit hole' to explore it yet.

Wesley Crihfield

EESignature

(Not an Autodesk Employee)

0 Likes
Message 12 of 17

yan.gauthier
Advocate
Advocate

Microsoft has a SDK to read/wrtie to excel files by accessing the xml behind:

 

https://docs.microsoft.com/en-us/office/open-xml/how-to-parse-and-read-a-large-spreadsheet

 

if by VB.NET, you mean an addin or an external application, I could share the code I use to read and write to an excel document (it's in C#, but it is simple enough that translating to VB.net shouldn't be too hard)

0 Likes
Message 13 of 17

liminma8458
Collaborator
Collaborator

It is addin (.dll) from VB.NET. Yes, if you can share some code snippet on how to read cell value from Excel without launching Excel.exe in VB.NET or C#, it would be very appreciated!

Thanks
Limin
Inventor pro 2026 64 bit; Windows 11 pro 64 bit version 24H2; Office 2016 64 bit

Check into My Apps:
iCable : Create Cables, Hoses, Tubes and Pipes Easily.
iCable Professional : Create and Edit Them, Quick, Easy and Much More.
iCable Electrical : Auto Router to Create Massive Cables Automatically for Electrical Cabinet.
0 Likes
Message 14 of 17

yan.gauthier
Advocate
Advocate

Here's the content of the class I use to write an excel file based on content from a SQlite DB we use to store electrical schematik related information. Then I read the data from an IO list file made the the electrical department. I compare with balloons in the DrawingSheet in order to import related data into a custom table in the same Sheet.  

 

 

using Schematik;
using Inventor;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.Linq;
using System.Linq;
using System.Windows.Forms;
using GenikAddin.DataControl;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
using System.IO;
using System.Globalization;

namespace GenikAddin
{

    public static class ExcelExtension
    {


        public static string OpenExcelFile(string documentPath)
        {
            string basePath = "";

            basePath = SchematikMethod.GetProjectESFolder(SchematikMethod.GetProjectFolder(documentPath));

            string filePath = "";

            using (OpenFileDialog openFileDialog = new OpenFileDialog())
            {
                openFileDialog.Filter = "excel files (*.xlsx)|*.xlsx;*.xls|All files (*.*)|*.*";
                openFileDialog.FilterIndex = 1;
                openFileDialog.InitialDirectory = basePath;
                openFileDialog.RestoreDirectory = true;

                if (openFileDialog.ShowDialog(new Form() { TopMost = true }) == DialogResult.OK)
                {
                    filePath = openFileDialog.FileName;
                }

            }

            return filePath;

        }

        public static string SaveExcelFile(string documentPath)
        {

            //document path is used as a base for default folder open option

            string basePath = "";

            string projectPath = SchematikMethod.GetProjectFolder(documentPath);

            basePath = SchematikMethod.GetProjectESFolder(projectPath);

            //if basePath was returned equal to projectPath, do not search for Exportation.

            if (!(projectPath.Equals(basePath, StringComparison.CurrentCultureIgnoreCase)))
            {
                //add Exportation folder to path
                basePath = System.IO.Path.Combine(basePath, "Exportation");

                if (!Directory.Exists(basePath))
                {
                    //if folder does not exists, add it
                    Directory.CreateDirectory(basePath);

                    //Also add a _OLD subfolder
                    Directory.CreateDirectory(System.IO.Path.Combine(basePath, "_OLD"));
                }
            }


            string filePath = "";

            using (SaveFileDialog saveFileDialog = new SaveFileDialog())
            {

                saveFileDialog.Filter = "excel files (*.xlsx)|*.xlsx;*.xls|All files (*.*)|*.*";
                saveFileDialog.FilterIndex = 1;
                saveFileDialog.InitialDirectory = basePath;
                saveFileDialog.RestoreDirectory = true;
                saveFileDialog.FileName = SchematikMethod.GetProjectNumber(documentPath) + "_EXPORT LISTE ES_" + DateTime.Now.ToString("yyyy-MM-dd hh'h'mm");

                if (saveFileDialog.ShowDialog(new Form() { TopMost = true }) == DialogResult.OK)
                {
                    filePath = saveFileDialog.FileName;
                }

            }

            return filePath;

        }

        public static void GenerateSchematikExcelFile2(List<ESComponent> components, string excelFilePath)
        {
            // Create a spreadsheet document by supplying the filepath.
            // By default, AutoSave = true, Editable = true, and Type = xlsx.
            using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Create(excelFilePath, SpreadsheetDocumentType.Workbook))
            {
                // Add a WorkbookPart to the document.
                WorkbookPart workbookpart = spreadsheetDocument.AddWorkbookPart();
                workbookpart.Workbook = new Workbook();

                // Add a WorksheetPart to the WorkbookPart.
                WorksheetPart worksheetPart = workbookpart.AddNewPart<WorksheetPart>();
                SheetData sheetData = new SheetData();
                worksheetPart.Worksheet = new Worksheet(sheetData);

                // Add Sheets to the Workbook.
                DocumentFormat.OpenXml.Spreadsheet.Sheets sheets = spreadsheetDocument.WorkbookPart.Workbook.AppendChild(new DocumentFormat.OpenXml.Spreadsheet.Sheets());

                // Append a new worksheet and associate it with the workbook.
                DocumentFormat.OpenXml.Spreadsheet.Sheet sheet = new DocumentFormat.OpenXml.Spreadsheet.Sheet()
                {
                    Id = spreadsheetDocument.WorkbookPart.
                    GetIdOfPart(worksheetPart),
                    SheetId = 1,
                    Name = "Schematik"
                };
                sheets.Append(sheet);

                // Get the SharedStringTablePart. If it does not exist, create a new one.
                SharedStringTablePart shareStringPart;
                if (spreadsheetDocument.WorkbookPart.GetPartsOfType<SharedStringTablePart>().Any())
                {
                    shareStringPart = spreadsheetDocument.WorkbookPart.GetPartsOfType<SharedStringTablePart>().First();
                }
                else
                {
                    shareStringPart = spreadsheetDocument.WorkbookPart.AddNewPart<SharedStringTablePart>();
                }


                //Start inserting values to spreadsheet
                DocumentFormat.OpenXml.Spreadsheet.Row row;
                uint rowIndex = 1;

                //Generating headers
                row = new DocumentFormat.OpenXml.Spreadsheet.Row() { RowIndex = rowIndex };
                sheetData.Append(row);

                AddCellToSchematik("TagMec", "B", 1, row, shareStringPart);
                AddCellToSchematik("Module", "C", 1, row, shareStringPart);
                AddCellToSchematik("Section", "D", 1, row, shareStringPart);
                AddCellToSchematik("Marque", "E", 1, row, shareStringPart);
                AddCellToSchematik("Modèle", "F", 1, row, shareStringPart);
                AddCellToSchematik("Description", "G", 1, row, shareStringPart);
                AddCellToSchematik("Caractéristique du câble", "L", 1, row, shareStringPart);
                AddCellToSchematik("Orientation du câble", "M", 1, row, shareStringPart);
                AddCellToSchematik("Caractéristique du connecteur", "N", 1, row, shareStringPart);
                //AddCellToSchematik("Position", "V", 1, row, shareStringPart);
                AddCellToSchematik("Type D'I/O", "X", 1, row, shareStringPart);
                AddCellToSchematik("Status (0 = inchangé, 1 = ajouté, 2 = modifié, 3 = Retiré)", "Z", 1, row, shareStringPart);

                foreach (ESComponent component in components)
                {

                    //Reorder list based on position value
                    List<InOut> inOuts = new List<InOut>(component.InOuts.OrderBy(x => x.Position));

                    foreach (InOut inOut in inOuts)
                    {
                        if (inOut.IOType != "AUCUN" && !string.IsNullOrWhiteSpace(inOut.IOType))
                        {
                            rowIndex++;
                            row = new DocumentFormat.OpenXml.Spreadsheet.Row() { RowIndex = rowIndex };
                            sheetData.Append(row);

                            AddCellToSchematik(component.TagMec + "-" + component.Id, "B", rowIndex, row, shareStringPart);
                            AddCellToSchematik(component.Module, "C", rowIndex, row, shareStringPart);
                            AddCellToSchematik(component.Location, "D", rowIndex, row, shareStringPart);
                            AddCellToSchematik(component.Mfr, "E", rowIndex, row, shareStringPart);
                            AddCellToSchematik(component.PartNumber, "F", rowIndex, row, shareStringPart);
                            AddCellToSchematik(inOut.Description, "G", rowIndex, row, shareStringPart);
                            AddCellToSchematik(component.CableInfo, "L", rowIndex, row, shareStringPart);
                            AddCellToSchematik(component.Orientation ? "90" : "0", "M", rowIndex, row, shareStringPart);
                            AddCellToSchematik(component.ConnectorInfo, "N", rowIndex, row, shareStringPart);                          
                            //AddCellToSchematik(inOut.Position.ToString(), "V", rowIndex, row, shareStringPart);
                            AddCellToSchematik(inOut.IOType, "X", rowIndex, row, shareStringPart); //Change to Y when IO list change
                            AddCellToSchematik(component.Status.ToString(), "Z", rowIndex, row, shareStringPart);
                        }

                    }

                    // reset component status after export
                    if (component.Status != 3)
                    {
                        component.Status = 0;
                    }

                }

                workbookpart.Workbook.Save();

            }

        }

        private static void AddCellToSchematik(string text, string column, uint rowIndex, DocumentFormat.OpenXml.Spreadsheet.Row row, SharedStringTablePart shareStringPart)
        {     
            // Insert the text into the SharedStringTablePart.
            int index = InsertSharedStringItem(text, shareStringPart);

            DocumentFormat.OpenXml.Spreadsheet.Cell newCell = new DocumentFormat.OpenXml.Spreadsheet.Cell() { CellReference = column + rowIndex };
            row.AppendChild(newCell);
            newCell.CellValue = new CellValue(index.ToString());
            newCell.DataType = new EnumValue<CellValues>(CellValues.SharedString);
        }
 
        // Given text and a SharedStringTablePart, creates a SharedStringItem with the specified text 
        // and inserts it into the SharedStringTablePart. If the item already exists, returns its index.
        private static int InsertSharedStringItem(string text, SharedStringTablePart shareStringPart)
        {
            // If the part does not contain a SharedStringTable, create one.
            if (shareStringPart.SharedStringTable == null)
            {
                shareStringPart.SharedStringTable = new SharedStringTable();
            }

            int i = 0;

            // Iterate through all the items in the SharedStringTable. If the text already exists, return its index.
            foreach (SharedStringItem item in shareStringPart.SharedStringTable.Elements<SharedStringItem>())
            {
                if (item.InnerText == text)
                {
                    return i;
                }

                i++;
            }

            // The text does not exist in the part. Create the SharedStringItem and return its index.
            shareStringPart.SharedStringTable.AppendChild(new SharedStringItem(new DocumentFormat.OpenXml.Spreadsheet.Text(text)));
            shareStringPart.SharedStringTable.Save();

            return i;
        }



        public static void GenerateSchematikExcelFile(List<ESComponent> components, string excelFilePath = null)
        {
            try
            {
                if (components == null || components.Count == 0)
                    throw new Exception("GenerateExcelFile: Null or empty input table!\n");

                // load excel, and create a new workbook
                var excelApp = new Excel.Application();
                excelApp.Workbooks.Add();

                // single worksheet
                Excel._Worksheet workSheet = excelApp.ActiveSheet;
                workSheet.Name = "Schematik";

                //File Headers
                workSheet.Cells[1, 1] = "TagMec";
                workSheet.Cells[1, 2] = "Module";
                workSheet.Cells[1, 3] = "Localisation";
                workSheet.Cells[1, 4] = "Mfr";
                workSheet.Cells[1, 5] = "PartNo";
                workSheet.Cells[1, 6] = "Description";
                workSheet.Cells[1, 7] = "InfoCable";
                workSheet.Cells[1, 8] = "InfoConnecteur";
                workSheet.Cells[1, 9] = "IOType";
                workSheet.Cells[1, 10] = "Position";


                // rows
                int pos = 1;
                foreach (ESComponent component in components)
                {
                    
                    //Reorder list based on position value
                    List<InOut> inOuts = new List<InOut>(component.InOuts.OrderBy(x => x.Position));

                    foreach (InOut inOut in inOuts)
                    {
                        if(inOut.IOType != "AUCUN" && !string.IsNullOrWhiteSpace(inOut.IOType))
                        {
                            pos++;
                            workSheet.Cells[pos, 1] = component.TagMec + "-" + component.Id;
                            workSheet.Cells[pos, 2] = component.Module;
                            workSheet.Cells[pos, 3] = component.Location;
                            workSheet.Cells[pos, 4] = component.Mfr;
                            workSheet.Cells[pos, 5] = component.PartNumber;
                            workSheet.Cells[pos, 6] = inOut.Description;
                            workSheet.Cells[pos, 7] = component.CableInfo;
                            workSheet.Cells[pos, 8] = component.ConnectorInfo;
                            workSheet.Cells[pos, 9] = inOut.IOType;
                            workSheet.Cells[pos, 10] = inOut.Position;
                        }
                        
                    }
                    
                }

                // check file path
                if (!string.IsNullOrEmpty(excelFilePath))
                {
                    try
                    {
                        workSheet.SaveAs(excelFilePath);
                        excelApp.Quit();
                        MessageBox.Show(new Form() { TopMost = true }, "Excel file saved!");
                    }
                    catch (Exception ex)
                    {
                        throw new Exception("GenerateExcelFile: Excel file could not be saved! Check filepath.\n"
                                            + ex.Message);
                    }
                }
                else
                { // no file path is given
                    excelApp.Visible = true;
                }
            }
            catch (Exception ex)
            {
                throw new Exception("GenerateExcelFile: \n" + ex.Message);
            }
        }

        
        public static void ReadIOFromExcel(string excelFilePath, Inventor.Sheet sheet)
        {

            const string TagMec = "B";
            const string Description1 = "G";
            const string Description2 = "H";
            const string Description3 = "I";
            const string Description4 = "J";
            const string TagEle = "R"; //Change to S when IO list change


            //fetch balloon values from sheet
            List<string> tagMecList = new List<string>();

            foreach(Balloon balloon in sheet.Balloons)
            {
                tagMecList.Add(balloon.BalloonValueSets[1].OverrideValue);
            }

            if (tagMecList.Count == 0)
            {
                throw new Exception("La feuille active ne contient pas de ballons !");
            }
            //make sure values are unique
            tagMecList = tagMecList.Distinct().ToList();


            //look for table in drawing
            CustomTables tables = sheet.CustomTables;
            Inventor.Point2d tablePos = null;
            if (sheet.Border is Inventor.Border border)
            {
                tablePos = border.RangeBox.MaxPoint;
            }
            else
            {
                tablePos = AddInServer.InvApp.TransientGeometry.CreatePoint2d(sheet.Width, sheet.Height);
            }

            //Delete table if already exists
            foreach(Inventor.CustomTable table in tables)
            {
                if (table.Title.Equals("Schematik", StringComparison.CurrentCultureIgnoreCase))
                {
                    table.Delete();
                }
            }

            string[] columnTitles = { "ID", "TagMec","TagEle", "Description" };

            CustomTable newTable = sheet.CustomTables.Add("Schematik", AddInServer.InvApp.TransientGeometry.CreatePoint2d(0, 0), 4, 1, columnTitles);

            List<SchematikTableRow> schematikTableRows = new List<SchematikTableRow>();
            SchematikTableRow schematikTableRow = null;

            //open file in readonly fashion
            using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Open(excelFilePath, false))
            {
                WorkbookPart workbookPart = spreadsheetDocument.WorkbookPart;

                //Get worksheet id named "Table IO"
                var sheetId = workbookPart.Workbook.Descendants<DocumentFormat.OpenXml.Spreadsheet.Sheet>().FirstOrDefault(x => x.Name.Equals("Table IO"))?.Id;

                
                WorksheetPart worksheetPart = sheetId != null ? (WorksheetPart)workbookPart.GetPartById(sheetId) : workbookPart.WorksheetParts.First();
                

                OpenXmlReader reader = OpenXmlReader.Create(worksheetPart); //generate the reader
                string text;
                string value = null;
                int actualRow = 1;

                while (reader.Read())
                {
                    if (reader.ElementType == typeof(DocumentFormat.OpenXml.Spreadsheet.Cell))
                    {
                        if ((DocumentFormat.OpenXml.Spreadsheet.Cell)reader.LoadCurrentElement() is DocumentFormat.OpenXml.Spreadsheet.Cell cell && cell.DataType != null)
                        {
                            if (!string.IsNullOrWhiteSpace(cell.InnerText))
                            {
                                string column = "";
                                int row = 0;
                                
                                //use cellReference to know column and row position
                                if (cell.CellReference != null && cell.CellReference.HasValue)
                                {
                                    column = string.Concat(cell.CellReference.InnerText.Where(x => !char.IsDigit(x)).Select(x => x));

                                    //parsing is guaranteed since only digits are fetched from the string
                                    row = int.Parse(string.Concat(cell.CellReference.InnerText.Where(x => char.IsDigit(x)).Select(x => x)));
                                }
                                
                                //IO list template values starts at 4
                                if(row > 3 && (column == TagMec || column == Description1 || column == Description2 || column == Description3 || column == Description4 || column == TagEle))
                                {

                                    //Create New Inventor TalbeRow only when pointer change to a new row
                                    if (row > actualRow + 2) //offset values 
                                    {
                                        schematikTableRow = new SchematikTableRow
                                        {
                                            Id = actualRow + 3
                                        };
                                        schematikTableRows.Add(schematikTableRow);
                                        actualRow++;
                                    }


                                    value = cell.InnerText;

                                    switch (cell.DataType.Value)
                                    {
                                        //fetch actual cellValue if it comes from the shared string table
                                        case CellValues.SharedString:
                                            var stringTable = workbookPart.GetPartsOfType<SharedStringTablePart>().FirstOrDefault();

                                            if (stringTable != null)
                                            {
                                                value = stringTable.SharedStringTable.ElementAt(int.Parse(value)).InnerText;
                                            }
                                            break;
                                        case CellValues.Boolean:
                                            switch (value)
                                            {
                                                case "0":
                                                    value = "FALSE";
                                                    break;
                                                default:
                                                    value = "TRUE";
                                                    break;
                                            }
                                            break;
                                    }
 
                                    switch (column)
                                    {
                                        case TagMec:
                                            schematikTableRow.TagMec = value;
                                            break;
                                        case Description1:
                                            schematikTableRow.Description1 = value;
                                            break;
                                        case Description2:
                                            schematikTableRow.Description2 = value;
                                            break;
                                        case Description3:
                                            schematikTableRow.Description3 = value;
                                            break;
                                        case Description4:
                                            schematikTableRow.Description4 = value;
                                            break;
                                        case TagEle:
                                            schematikTableRow.TagEle = value;
                                            break;

                                    }
                                }

                                
                            }
                            
                        }
                        text = value;
                    }
                }

                reader.Dispose();
            }

            Inventor.Row tableRow = null;
            //generate table
            int descColWidth = 1;
            int idColWidth = 1;
            int tagMecColWidth = 1;
            int tagEleColWidth = 1;

            //Filter list to contain only one occurrence of any TagELE in case of valve manifold which can contain 32 lines with only one TagELE
            schematikTableRows = new List<SchematikTableRow>(schematikTableRows.GroupBy(x => x.TagEle).Select(x => x.FirstOrDefault()).ToList().OrderBy(x => x.Id));

            foreach (SchematikTableRow row in schematikTableRows)
            {
                if (tagMecList.Where(x => x.Equals(row.TagMec, StringComparison.CurrentCultureIgnoreCase)).Any())
                {
                    string desc = string.Join(" ", new string[] { row.Description1, row.Description2, row.Description3, row.Description4 });
                    tableRow = newTable.Rows.Add();
                    tableRow[1].Value = row.Id.ToString();
                    tableRow[2].Value = row.TagMec;
                    tableRow[3].Value = row.TagEle;
                    tableRow[4].Value = desc;

                    //set columns width values
                    idColWidth = idColWidth < row.Id.ToString().Length ? row.Id.ToString().Length : idColWidth;
                    tagMecColWidth = tagMecColWidth < row.TagMec.Length ? row.TagMec.Length : tagMecColWidth;
                    tagEleColWidth = tagEleColWidth < row.TagEle.Length ? row.TagEle.Length : tagMecColWidth;
                    descColWidth = descColWidth < desc.Length ? desc.Length : descColWidth;
      
                }
            }

            //Adjust columns width
            newTable.Columns[1].Width = idColWidth * 0.3;
            newTable.Columns[2].Width = tagMecColWidth * 0.3;
            newTable.Columns[3].Width = tagEleColWidth * 0.3;
            newTable.Columns[4].Width = descColWidth * 0.25;


            //Delete first row if empty
            if (string.IsNullOrWhiteSpace(newTable.Rows[1][1].Value))
            {
                newTable.Rows[1].Delete();
            }

            tablePos.X = tablePos.X - newTable.RangeBox.MaxPoint.X - 0.3175;
            tablePos.Y = tablePos.Y - newTable.RangeBox.MaxPoint.Y - 0.3175;

            newTable.Position = tablePos;
        }

        private class SchematikTableRow
        {
            public int Id { get; set;}
            public string TagMec { get; set; }
            public string TagEle { get; set; }
            public string Description1 { get; set; }
            public string Description2 { get; set; }
            public string Description3 { get; set; }
            public string Description4 { get; set; }

        }

    }
}

 

 

0 Likes
Message 15 of 17

liminma8458
Collaborator
Collaborator

Betreff:

Thank you very much for the code. I will try it.

 

What do you think whether we can do something like this:

 

Imports LibXL

.....

'use the functions in LibXL namespace to extract data from Excel

.....

 

Where is LibXL library? can we use it as reference in VB.NET as pointed to?

Thanks
Limin
Inventor pro 2026 64 bit; Windows 11 pro 64 bit version 24H2; Office 2016 64 bit

Check into My Apps:
iCable : Create Cables, Hoses, Tubes and Pipes Easily.
iCable Professional : Create and Edit Them, Quick, Easy and Much More.
iCable Electrical : Auto Router to Create Massive Cables Automatically for Electrical Cabinet.
0 Likes
Message 16 of 17

liminma8458
Collaborator
Collaborator

sorry. It should go to yan.gauthier

Thanks
Limin
Inventor pro 2026 64 bit; Windows 11 pro 64 bit version 24H2; Office 2016 64 bit

Check into My Apps:
iCable : Create Cables, Hoses, Tubes and Pipes Easily.
iCable Professional : Create and Edit Them, Quick, Easy and Much More.
iCable Electrical : Auto Router to Create Massive Cables Automatically for Electrical Cabinet.
0 Likes
Message 17 of 17

yan.gauthier
Advocate
Advocate

I don't know anything about LibXl.... OpenXml is supported by Microsoft. So it should be the way to go to do it. 

 

Have a look at the link I posted about openXML to see for yourself.

0 Likes