How to read ETABS (*.e2k) file using Excel VBA
Extract data from ETABS (*.e2k) file
How to read ETABS (*.e2k) file using Excel VBA
Overview
- In this tutorial, I’ll show you how to extract data from an ETABS *.e2k file using Excel VBA.
- Why?
- I am assuming that:
- You have basic knowledge of VBA and know how to add modules and create new subs.
- You know how to generate an ETABS E2K (*.e2k) file and are familiar with its layout.
Setup
- You’ll need an e2k file for testing.
- For this setup, I am putting the e2k file in the same folder as the Excel file, with the name “Model.e2k”.
- Use a macro-enabled Excel file with:
- Sheet Name
StoryData - Sheet Name
SectionData - Create a new module and add the ExtractData method:
1 2 3
Sub ExtractData() 'Add your code here End Sub
I am using ETABS version 22.5 to generate my e2k file. The e2k file structure doesn’t change much between versions, but if that happens, adjust your code as per your e2k file structure.
- Sheet Name
Extract Data from e2k file
- We can divide this task into multiple sub-tasks:
- Reading data from the e2k file and storing it for further processing.
- Finding the line number for a specific section as per our requirement.
- Extracting data from lines that contain the required data.
Load Data from e2k file
- The e2k file is just a text file with a custom file extension.
- So, we can read it like a text file using VBA.
- Here, we are storing data in a string collection, line by line, for easier access to each line.
- For the file path, we are going to use
ThisWorkbook.Path & "\Model.e2k".
1
2
3
4
5
Sub ExtractData()
'Read E2K file into a collection
Dim data As Collection
Set data = ReadE2K(ThisWorkbook.Path & "\Model.e2k")
End Sub
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Function ReadE2K(filePath As String) As Collection
'Create new collection to store lines from the E2K file
Dim data As Collection
Set data = New Collection
'Using File system utility to work with files and folders
Dim fso As Object
Set fso = CreateObject("Scripting.FileSystemObject")
'Read ANL file line by line and load it to data collection
Dim textStream As Object
Dim line As String
If fso.FileExists(filePath) Then
Set textStream = fso.OpenTextFile(filePath, 1) ' ForReading = 1
Do While Not textStream.AtEndOfStream
line = textStream.ReadLine
data.Add line
Loop
textStream.Close
End If
Set ReadE2K = data
End Function
Find e2k file from Folder
- This part is only required if you don’t use a fixed name for your ETABS model.
- If you don’t know the e2k file name in advance, you have to write additional code to automatically find the e2k file in the folder.
1
2
3
4
5
6
7
8
9
10
11
12
13
Sub ExtractData()
Dim filePath As String
filePath = GetE2KFilePath()
If filePath = "" Then
MsgBox "No .E2K file found in the folder.", vbExclamation
Exit Sub
End If
'Read E2K file into a collection
Dim data As Collection
Set data = ReadE2K(filePath)
End Sub
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Public Function GetE2KFilePath() As String
'Using File system utility to work with files and folders
Dim fso As Object
Set fso = CreateObject("Scripting.FileSystemObject")
'Get the folder containing the workbook
Dim folder As Object
Set folder = fso.GetFolder(ThisWorkbook.Path)
'Loop through all files in folder and find .ANL file
Dim file As Object
For Each file In folder.Files
If LCase(fso.GetExtensionName(file.Name)) = "e2k" Then
GetE2KFilePath = file.Path
Exit Function
End If
Next
GetE2KFilePath = ""
End Function
Find row with specific section
- You have to adjust this part depending on which data you need to extract from the e2k file.
- I’ve added sample code for extracting Story data and Section Data.
- Here, we’re just looping through all lines and finding the section that matches our section name.
- After finding the specific section, a do while loop is added to loop until the end of the section.
- We’re writing this data to the Excel file on the “StoryData” sheet, so make sure this sheet exists.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Sub ExtractData()
Dim filePath As String
filePath = GetE2KFilePath()
If filePath = "" Then
MsgBox "No .E2K file found in the folder.", vbExclamation
Exit Sub
End If
'Read E2K file into a collection
Dim data As Collection
Set data = ReadE2K(filePath)
'Extract Story Data
ExtractStoryData data
End Sub
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Public Sub ExtractStoryData(data As Collection)
Dim ws As Worksheet,rowId As Long, colId As Long
Set ws = ThisWorkbook.Sheets("StoryData")
ws.Cells.Clear
rowId = 1
colId = 1
'Find Story Data section
Dim i As Long, j as Long, line As String
For i = 1 To data.Count
If InStr(1, data(i), "$ STORIES", vbTextCompare) > 0 Then
'Extract Story Data
j = i + 1
Do While j <= data.Count And data(j) <> ""
line = data(j)
ws.Cells(rowId, colId).Value = line
rowId = rowId + 1
j = j + 1
Loop
Exit For
End If
Next i
End Sub
Extract Data from line
- Once we find a line that contains the required results, we need code to extract specific results from that string.
- For this, we have 4 common functions which will extract a number, decimal, string, or quoted string from a given string.
- Here, the numberIndex variable is used to specify which value to extract from the string:
- numberIndex=1 means extract the first value from the string
- numberIndex=2 means extract the second value from the string
- numberIndex=3 means extract the third value from the string
- We can use these functions in combination with our find matching row code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Public Sub ExtractStoryData(data As Collection)
Dim ws As Worksheet,rowId As Long, colId As Long
Set ws = ThisWorkbook.Sheets("StoryData")
ws.Cells.Clear
rowId = 1
colId = 1
ws.Cells(rowId, colId).Value = "Story Name"
ws.Cells(rowId, colId+1).Value = "Height"
rowId = rowId + 1
'Find Story Data section
Dim i As Long, j as Long, line As String
For i = 1 To data.Count
If InStr(1, data(i), "$ STORIES", vbTextCompare) > 0 Then
'Extract Story Data
j = i + 1
Do While j <= data.Count And data(j) <> ""
line = data(j)
ws.Cells(rowId, colId).Value = GetQuotedString(line, 1) 'Story Name
ws.Cells(rowId, colId+1).Value = GetDecimal(line, 1) 'Height
rowId = rowId + 1
j = j + 1
Loop
Exit For
End If
Next i
End Sub
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Public Sub ExtractSectionData(data As Collection)
Dim ws As Worksheet,rowId As Long, colId As Long
Set ws = ThisWorkbook.Sheets("SectionData")
ws.Cells.Clear
rowId = 1
colId = 1
ws.Cells(rowId, colId).Value = "Section Name"
ws.Cells(rowId, colId+1).Value = "Mateirial"
ws.Cells(rowId, colId+2).Value = "Width"
ws.Cells(rowId, colId+3).Value = "Depth"
rowId = rowId + 1
'Find Section Data section
Dim i As Long, j as Long, line As String,shpapeType As String
For i = 1 To data.Count
If InStr(1, data(i), "$ FRAME SECTIONS", vbTextCompare) > 0 Then
'Extract Section Data
j = i + 1
Do While j <= data.Count And data(j) <> ""
line = data(j)
shpapeType = GetQuotedString(line, 3)
If shpapeType = "Concrete Rectangular" Then
ws.Cells(rowId, colId).Value = GetQuotedString(line, 1) 'Section Name
ws.Cells(rowId, colId+1).Value = GetQuotedString(line, 2) 'Material
ws.Cells(rowId, colId+2).Value = GetDecimal(line, 2) 'Width
ws.Cells(rowId, colId+3).Value = GetDecimal(line, 1) 'Depth
rowId = rowId + 1
End If
j = j + 1
Loop
Exit For
End If
Next i
End Sub
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Function GetNumber(line As String, numberIndex As Long) As Long
Dim parts() As String
parts = Split(line, " ")
'Loop through all parts and return the first number
Dim i As Long
Dim matchCount As Integer
For i = LBound(parts) To UBound(parts)
If IsNumeric(parts(i)) Then
matchCount = matchCount + 1
If matchCount = numberIndex Then
GetNumber = CLng(parts(i))
Exit Function
End If
End If
Next i
GetNumber = 0
End Function
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Function GetDecimal(line As String, numberIndex As Long) As Double
Dim parts() As String
parts = Split(line, " ")
'Loop through all parts and return the first number
Dim i As Long
Dim matchCount As Integer
For i = LBound(parts) To UBound(parts)
If IsNumeric(parts(i)) Then
matchCount = matchCount + 1
If matchCount = numberIndex Then
GetDecimal = CDbl(parts(i))
Exit Function
End If
End If
Next i
GetDecimal = 0
End Function
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Function GetString(line As String, numberIndex As Long) As String
Dim parts() As String
parts = Split(line, " ")
'Loop through all parts and return the first string
Dim i As Long
Dim matchCount As Integer
For i = LBound(parts) To UBound(parts)
If Len(Trim(parts(i))) > 0 And Not IsNumeric(parts(i)) Then
matchCount = matchCount + 1
If matchCount = numberIndex Then
GetString = CStr(parts(i))
Exit Function
End If
End If
Next i
GetString = ""
End Function
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Function GetQuotedString(line As String, quoteIndex As Long) As String
Dim parts() As String
parts = Split(line, """")
'Loop through all parts and return the quoted string
Dim i As Long
Dim matchCount As Integer
For i = LBound(parts) To UBound(parts)
If Len(Trim(parts(i))) > 0 Then
matchCount = matchCount + 1
If matchCount = quoteIndex * 2 Then 'Quoted strings are in even positions
GetQuotedString = CStr(parts(i))
Exit Function
End If
End If
Next i
GetQuotedString = ""
End Function
Final Version
- This is the final version of the code.
- This is just sample code to get you started; you still have to write separate code for each section.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
Sub ExtractData()
Dim filePath As String
filePath = GetE2KFilePath()
If filePath = "" Then
MsgBox "No .E2K file found in the folder.", vbExclamation
Exit Sub
End If
'Read E2K file into a collection
Dim data As Collection
Set data = ReadE2K(filePath)
'Extract Story Data
ExtractStoryData data
'Extract Section Data
ExtractSectionData data
End Sub
Public Function GetE2KFilePath() As String
'Using File system utility to work with files and folders
Dim fso As Object
Set fso = CreateObject("Scripting.FileSystemObject")
'Get the folder containing the workbook
Dim folder As Object
Set folder = fso.GetFolder(ThisWorkbook.Path)
'Loop through all files in folder and find .ANL file
Dim file As Object
For Each file In folder.Files
If LCase(fso.GetExtensionName(file.Name)) = "e2k" Then
GetE2KFilePath = file.Path
Exit Function
End If
Next
GetE2KFilePath = ""
End Function
Function ReadE2K(filePath As String) As Collection
'Create new collection to store lines from the E2K file
Dim data As Collection
Set data = New Collection
'Using File system utility to work with files and folders
Dim fso As Object
Set fso = CreateObject("Scripting.FileSystemObject")
'Read ANL file line by line and load it to data collection
Dim textStream As Object
Dim line As String
If fso.FileExists(filePath) Then
Set textStream = fso.OpenTextFile(filePath, 1) ' ForReading = 1
Do While Not textStream.AtEndOfStream
line = textStream.ReadLine
data.Add line
Loop
textStream.Close
End If
Set ReadE2K = data
End Function
Public Sub ExtractStoryData(data As Collection)
Dim ws As Worksheet,rowId As Long, colId As Long
Set ws = ThisWorkbook.Sheets("StoryData")
ws.Cells.Clear
rowId = 1
colId = 1
ws.Cells(rowId, colId).Value = "Story Name"
ws.Cells(rowId, colId+1).Value = "Height"
rowId = rowId + 1
'Find Story Data section
Dim i As Long, j as Long, line As String
For i = 1 To data.Count
If InStr(1, data(i), "$ STORIES", vbTextCompare) > 0 Then
'Extract Story Data
j = i + 1
Do While j <= data.Count And data(j) <> ""
line = data(j)
ws.Cells(rowId, colId).Value = GetQuotedString(line, 1) 'Story Name
ws.Cells(rowId, colId+1).Value = GetDecimal(line, 1) 'Height
rowId = rowId + 1
j = j + 1
Loop
Exit For
End If
Next i
End Sub
Public Sub ExtractSectionData(data As Collection)
Dim ws As Worksheet,rowId As Long, colId As Long
Set ws = ThisWorkbook.Sheets("SectionData")
ws.Cells.Clear
rowId = 1
colId = 1
ws.Cells(rowId, colId).Value = "Section Name"
ws.Cells(rowId, colId+1).Value = "Mateirial"
ws.Cells(rowId, colId+2).Value = "Width"
ws.Cells(rowId, colId+3).Value = "Depth"
rowId = rowId + 1
'Find Section Data section
Dim i As Long, j as Long, line As String,shpapeType As String
For i = 1 To data.Count
If InStr(1, data(i), "$ FRAME SECTIONS", vbTextCompare) > 0 Then
'Extract Section Data
j = i + 1
Do While j <= data.Count And data(j) <> ""
line = data(j)
shpapeType = GetQuotedString(line, 3)
If shpapeType = "Concrete Rectangular" Then
ws.Cells(rowId, colId).Value = GetQuotedString(line, 1) 'Section Name
ws.Cells(rowId, colId+1).Value = GetQuotedString(line, 2) 'Material
ws.Cells(rowId, colId+2).Value = GetDecimal(line, 2) 'Width
ws.Cells(rowId, colId+3).Value = GetDecimal(line, 1) 'Depth
rowId = rowId + 1
End If
j = j + 1
Loop
Exit For
End If
Next i
End Sub
Function GetNumber(line As String, numberIndex As Long) As Long
Dim parts() As String
parts = Split(line, " ")
'Loop through all parts and return the first number
Dim i As Long
Dim matchCount As Integer
For i = LBound(parts) To UBound(parts)
If IsNumeric(parts(i)) Then
matchCount = matchCount + 1
If matchCount = numberIndex Then
GetNumber = CLng(parts(i))
Exit Function
End If
End If
Next i
GetNumber = 0
End Function
Function GetDecimal(line As String, numberIndex As Long) As Double
Dim parts() As String
parts = Split(line, " ")
'Loop through all parts and return the first number
Dim i As Long
Dim matchCount As Integer
For i = LBound(parts) To UBound(parts)
If IsNumeric(parts(i)) Then
matchCount = matchCount + 1
If matchCount = numberIndex Then
GetDecimal = CDbl(parts(i))
Exit Function
End If
End If
Next i
GetDecimal = 0
End Function
Function GetString(line As String, numberIndex As Long) As String
Dim parts() As String
parts = Split(line, " ")
'Loop through all parts and return the first string
Dim i As Long
Dim matchCount As Integer
For i = LBound(parts) To UBound(parts)
If Len(Trim(parts(i))) > 0 And Not IsNumeric(parts(i)) Then
matchCount = matchCount + 1
If matchCount = numberIndex Then
GetString = CStr(parts(i))
Exit Function
End If
End If
Next i
GetString = ""
End Function
Function GetQuotedString(line As String, quoteIndex As Long) As String
Dim parts() As String
parts = Split(line, """")
'Loop through all parts and return the quoted string
Dim i As Long
Dim matchCount As Integer
For i = LBound(parts) To UBound(parts)
If Len(Trim(parts(i))) > 0 Then
matchCount = matchCount + 1
If matchCount = quoteIndex * 2 Then 'Quoted strings are in even positions
GetQuotedString = CStr(parts(i))
Exit Function
End If
End If
Next i
GetQuotedString = ""
End Function
Conclusion
- Reading data from an e2k file is one of the best ways to extract model info from ETABS, since you don’t have to work with the ETABS API.
- This is just sample code to get you started; you still have to adjust your code as per each section’s data pattern.
- You can also scan the model unit from the
$ CONTROLSsection to avoid any unit-related errors. - The current version of the code is written to be beginner-friendly and is not optimized for large models.
- You might need to do some optimization if you want to reduce execution time for large models.
This post is licensed under CC BY-NC-ND 4.0 by the author.
