PDA

View Full Version : Importing Title Block Info from Excel



stusic
2012-03-21, 04:12 PM
Hey All,

It seems like there should be a simple way, but apparently not. I'd like to populate our title blocks with project info that I have stored in an Excel file. By using a field or a lisp variable, I want to have it search a column of the xls file looking for a matching project number. When it finds a match, it fills in the title block from cells in that projects row.

I've been looking around the internets, and have found a little bit of info, but not quite what I'm looking for. The main problem I'm having is how to search the project number column of the excel file for an entry that matches the field that's automatically populated on our sheet. If it's any easier, our drawing filenames are prefixed with our project number, I'd just have to figure out how to isolate it.

Does anyone have any ideas on searching an excel file with VBA? I'm nearly illiterate in the language, but I'm learning quickly.

Thanks :)

stusic
2012-03-21, 04:19 PM
This is what I have so far (snagged from pefi @ cadtutor.net):



Option Explicit
Dim excelApp As Excel.Application 'points to excel application
Dim wbkObj As Workbook 'points to excel workbook
Dim shtObj As Worksheet 'points ot excel worksheet

Public Sub UpdateTitleblock()
Dim ent As Object
Dim attArr, att As Variant
On Error Resume Next
'Open excel sheet
Err.Clear
Set excelApp = GetObject(, "Excel.Application")
If Err <> 0 Then
Err.Clear
Set excelApp = CreateObject("excel.application")
If Err <> 0 Then
MsgBox "Could not start Excel", vbExclamation
End
End If
End If

Set wbkObj = excelApp.Workbooks.Open(FileName:=ThisDrawing.Path & "\TitleBlock.xls")
If wbkObj.FullName = "" Then MsgBox "Cannot find TitleBlock.xls": excelApp.Quit: End:
Set shtObj = excelApp.Worksheets(1)
'End of open excel sheet
'Update DWG
For Each ent In ThisDrawing.ModelSpace
If ent.Name = "TITLE_BLOCK" Then
If ent.HasAttributes = True Then
attArr = ent.GetAttributes
For Each att In attArr
If att.TagString = "ATTRIBUTE1" Then
att.TextString = shtObj.Cells(1, 2)
End If
If att.TagString = "ATTRIBUTE2" Then
att.TextString = shtObj.Cells(2, 2)
End If
Next
End If
End If
Next
'End of Update DWG
'Close excel sheet
Err.Clear
Set excelApp = GetObject(, "Excel.Application")
If Err <> 0 Then
Err.Clear
MsgBox "No Excel session running.", vbExclamation
Else
excelApp.Quit
excelApp.Visible = True
End If
End Sub


I need to have it search through the TitleBlock.xls for "ATTRIBUTE1" (which I can make a field if necessary).

Ed Jobe
2012-03-21, 08:23 PM
One issue you're going to have is that not all ents have a Name property. You need to first filter for blocks.
There are 2 ways to do this that I would suggest. One is the way you are trying, i.e. directly edit the attributes of a specific block. The other would be to use fields pointing to the documents properties and then update those properties. There are built in props like Title, Author that you can use or you can create custom props. This post (http://forums.augi.com/showpost.php?p=940550&postcount=3) has some useful code that shows you how to find a block and use the AcadSummaryInfo object (the doc properties).

stusic
2012-03-22, 07:51 PM
Is there a preference to using one method or the other (aside form the latter being a bit over my head)? All of our title blocks have the same name.

Ed Jobe
2012-03-23, 03:00 PM
Which one you use depends upon your requirements. Using fields pointing to file props is a little more 'generic' and offers flexibility with other apps, but requires that you update the fields. The former is instantaneous, but only works inside acad. For example, I use the field method with Word and Excel docs because our dms can extract title block info from the file, but neither of those use title blocks. They do however, support fields. Either way you choose, there's enough code in that post to get you started. To find the info in xl that you need, consider using a lookup function in xl, rather than iterating in acad. If you are using a single xls for all your projects then the way you are going is fine. If you are using a separate file for each project, you could just read in a single line csv or xml file.

stusic
2012-03-23, 03:35 PM
I think the way I'm trying is the way to go; I don't need to extract from acad, just import in. Well, that's not exactly true - I do need vba to read either part of the filename or a field in the title block to get the project number as a base for the search in xl.

I have a script in our project management application that spits out all the information for every project in a single xl file, so there's only one file to work with. I'd like to have the user work solely in acad, without opening xl at all. Also, I'm not sure how to define a lookup function (or call one from xl) in vba.

I don't see where it's searching through the xl doc in the code you provided.In case I sound like an idiot, 90% of what's in the link you gave is over my head. I'm trying to stumble through this, with the beginning idea that I'd be able to find something very similar to hack. Not so.

Lemme see what I can glean from what I've got so far...

Ed Jobe
2012-03-23, 07:38 PM
If you have the data in a single xls, then its location doesn't change and you don't need to search for it. You can do as your code shows above and specify the exact cell. You just need to clean up the part that searches for you tb.

stusic
2012-03-26, 06:46 PM
I think I wasn't being clear - it's not the xl file I'm wanting to search for (it'll always be in the same place with the same name), but a cell in Column1 of that file that matches an Autocad field value (the project number). That column has a list of every project number; I need to search that column for a matching project number, then fill in the rest of the title block with information from that particular row. I don't know how to search within an xl document.

Ed Jobe
2012-03-27, 02:33 PM
Its not you. I was getting sick and just didn't read it correctly. I looked at my response and I didn't even correctly state what I meant. What I was trying to get at was whether there was a single file with all the projects or many xls's with one project each (as in, each project has its own folder with its own xls, in which case you wouldn't need to search within the xls). Sorry bout dat.

stusic
2012-03-27, 02:34 PM
Okay, my novice self has learned of the (fairly obvious) FIND function. As such:



Sub Find_Item_No()
Dim FindString As String
Dim Rng As Range
FindString = CLng(Date)
With Sheets("Sheet1").Range("A:A")
Set Rng = .Find(What:=FindString, _
After:=.Cells(.Cells.Count), _
LookIn:=xlFormulas, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False)
If Not Rng Is Nothing Then
Application.Goto Rng, True
Else
MsgBox "Nothing found"
End If
End With
End Sub

Two problems:
1. I don't know how to change the find string (currently "CLng(Date)") to whatever the active autocad drawing filename is.

2. I don't know how to modify that filename in the find string. The xl file spits out something like "165022", where the acad drawing would be named something like "0165022-PD". The leading "0" is always there, but the suffixed letters can change (Piping Diagram, Wiring Diagram, etc.)

Do the gurus have some advice?

Thank you so much :beer:

Ed Jobe
2012-03-27, 02:50 PM
First, the logic: a sub called FindItem shouldn't be trying to fix a filename. Leave that to the calling sub. Now change the sub's signature to accept an argument.

Sub FindItem( filename As String)

Then get rid of the Dim and assignment for FindString.

As for the value to search for, couldn't you just use the filename of the dwg that's hosting this code?

stusic
2012-03-27, 04:03 PM
First, the logic: a sub called FindItem shouldn't be trying to fix a filename. Leave that to the calling sub. Now change the sub's signature to accept an argument.

Sub FindItem( filename As String)

Easy enough...


Then get rid of the Dim and assignment for FindString.

Think I got that too...


As for the value to search for, couldn't you just use the filename of the dwg that's hosting this code?

Sounds right. Is the FindItem sub acting as a variable (i.e., that I can then modify by removing the leading zero and the trailing characters to get the Item Number)?

What do I use to retrieve the filename of the active drawing?

The things management asks of us, and the things I ask of you... Thanks, Ed.

stusic
2012-03-27, 04:06 PM
Current rendition of my code:


' S:\Everyone\ENGR_REQUESTS\AutoREV\Data\GateWay1.xls

'Main functions

Option Explicit
Dim excelApp As Excel.Application 'points to excel application
Dim wbkObj As Workbook 'points to excel workbook
Dim shtObj As Worksheet 'points ot excel worksheet

Public Sub UpdateTitleblock()
Dim ent As Object
Dim attArr, att As Variant
On Error Resume Next
'Open excel sheet
Err.Clear
Set excelApp = GetObject(, "Excel.Application")
If Err <> 0 Then
Err.Clear
Set excelApp = CreateObject("excel.application")
If Err <> 0 Then
MsgBox "Could not start Excel", vbExclamation
End
End If
End If

Set wbkObj = excelApp.Workbooks.Open(FileName:="S:\Everyone\ENGR_REQUESTS\AutoREV\Data\GateWay1.xls")
If wbkObj.FullName = "" Then MsgBox "Cannot find GateWay1.xls": excelApp.Quit: End:
Set shtObj = excelApp.Worksheets(1)
'End of open excel sheet
'Update DWG
For Each ent In ThisDrawing.PaperSpace
If ent.Name = "BORDER*" Then
If ent.HasAttributes = True Then
attArr = ent.GetAttributes
For Each att In attArr
If att.TagString = "SALES_ORDER" Then
att.TextString = shtObj.Cells(1, 2)
End If
Each att In attArr
If att.TagString = "CUSTOMER" Then
att.TextString = shtObj.Cells(1, 2)
End If
Each att In attArr
If att.TagString = "STORE_NAME" Then
att.TextString = shtObj.Cells(1, 2)
End If
If att.TagString = "LOCATION" Then
att.TextString = shtObj.Cells(2, 2)
End If
Next
End If
End If
Next
'End of Update DWG
'Close excel sheet
Err.Clear
Set excelApp = GetObject(, "Excel.Application")
If Err <> 0 Then
Err.Clear
MsgBox "No Excel session running.", vbExclamation
Else
excelApp.Quit
excelApp.Visible = True
End If
End Sub


'Excel Search Function

Sub FindItem(filename As String)
Dim Rng As Range
With Sheets("GateWay1").Range("A:A")
Set Rng = .Find(What:=FindString, _
After:=.Cells(.Cells.Count), _
LookIn:=xlFormulas, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False)
If Not Rng Is Nothing Then
Application.Goto Rng, True
Else
MsgBox "Nothing found"
End If
End With
End Sub

fixo
2012-03-27, 04:45 PM
Just at the first glance:

excelApp.Quit
then after:

excelApp.Visible = True
What do you think ?

stusic
2012-03-27, 06:00 PM
Just at the first glance:

excelApp.Quitthen after:

excelApp.Visible = TrueWhat do you think ?

What's the purpose of these functions? I assume the first is to close out the proggy after I'm done and the second is to make excel visible when the routine runs. Is the first (excelApp.Quit) for maintenance purposes? Also, I'd like to avoid having excel actually open when this runs. Is it possible to extract data from excel even when the file isn't open?

fixo
2012-03-27, 07:43 PM
See if this is working on your end
I'm using scripting dictionary to store pairs tag-value
then close Excel and do separately from Excel the rest part of the work in AutoCAD
hope it make a sense
what about my note above: if you're quit excel you can't to
rich at any properties of this application after that,
so this line:

excelApp.Visible = True
is nonsense :)


Option Explicit
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
'requires reference to: Microsoft Scripting Runtime
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
Dim excelApp As Excel.Application 'points to excel application
Dim wbkObj As Workbook 'points to excel workbook
Dim shtObj As Worksheet 'points ot excel worksheet
Dim rngObj As Range 'points ot excel range
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
Public Sub UpdateTitleblock()

On Error Resume Next
'Open excel sheet
Err.Clear
Set excelApp = GetObject(, "Excel.Application")
If Err <> 0 Then
Err.Clear
MsgBox "No oen session of Excel", vbExclamation
Set excelApp = CreateObject("excel.application")
If Err <> 0 Then
Err.Clear
MsgBox "Could not start Excel", vbExclamation
End
End If
End If



Set wbkObj = excelApp.Workbooks.Open(filename:="S:\Everyone\ENGR_REQUESTS\AutoREV\Data\GateWay1.xls")
'Set wbkObj = excelApp.Workbooks.Open(filename:="C:\Test\BATM\Brad\GateWay1.xls") 'my dummy test file
If wbkObj Is Nothing Then MsgBox "Cannot find GateWay1.xls": GoTo Exit_Excel: End:
excelApp.Visible = True
wbkObj.Activate
Set shtObj = excelApp.Worksheets(1)
shtObj.Activate
Set rngObj = shtObj.UsedRange

' define the Scripting dictionary object to store
' attribute tags as keys, attribute values as items
Dim oDict As New Dictionary
Dim row As Long
For row = 1 To rngObj.Rows.Count
If Not oDict.Exists(CStr(rngObj.Cells(row, 1).Value)) Then
'' assume the tags were stored in column A, and values in column B:
oDict.Add CStr(rngObj.Cells(row, 1).Value), CStr(rngObj.Cells(row, 2).Value)

End If

Next

Exit_Excel:

'' clean up and over
Set rngObj = Nothing
Set shtObj = Nothing
wbkObj.Close
Set wbkObj = Nothing
excelApp.Quit
Set excelApp = Nothing
DoEvents

Resume Exit_Excel

'-------------------End of work with excel-------------------'

'Update DWG

Dim ent As AcadEntity
Dim blkRef As AcadBlockReference
Dim attArr As Variant
Dim att As AcadAttributeReference


For Each ent In ThisDrawing.PaperSpace
If TypeOf ent Is AcadBlockReference Then
Set blkRef = ent
If UCase(blkRef.EffectiveName) Like "BORDER*" Then

If blkRef.HasAttributes = True Then

attArr = blkRef.GetAttributes

Dim n

For n = LBound(attArr) To UBound(attArr)
Set att = attArr(n)

Dim atTag As String

atTag = att.TagString

Select Case Trim(UCase(atTag))

Case "SALES_ORDER"
att.TextString = oDict("SALES_ORDER")

Case "CUSTOMER"
att.TextString = oDict("CUSTOMER")

Case "STORE_NAME"
att.TextString = oDict("STORE_NAME")

Case "LOCATION"
att.TextString = oDict("LOCATION")
''ETC
Case Else
'do nothing
End Select

Next


End If

End If

End If

Next


Set oDict = Nothing
'End of Update DWG

End Sub

~'J'~

Ed Jobe
2012-03-28, 04:18 PM
1. I don't know how to change the find string (currently "CLng(Date)") to whatever the active autocad drawing filename is.
What happens if more that one file is created on the same day? Don't you need a more specific way of finding the file you want?

stusic
2012-03-28, 04:47 PM
What happens if more that one file is created on the same day? Don't you need a more specific way of finding the file you want?

The project management software we use has a script that produces a big excel file listing every project we have. There's only one file; it gets overwritten as new projects come in. Each project is on its own row; that row has all the project data. The first column has the item numbers, the second is the project number, third is the customer, fourth is the city, fifth is the state, etc.

The drawings we create have the item number within the filename. In the attached example, we may have a drawing named "0164319-PD". For the title block, I'd need to fill in the customer, city and state (along with other info, but this would get me started).
I don't have any flexibility on the output of the excel file, and excel chops off the leading zero, so I've got to be able to extract these 6 digits ("164319") from "0164319-PD". So this vba routine would need to search through this excel file for "164319" in the first column and look over (offset) to retrieve the project info (211076, LOCAL GROCER, SMITHVILLE, ALASKA) and fill in the title block attributes of the current drawing.

Attached is an example of the excel file.

Thanks for working with me on this, my understanding is *slowly* getting better...

Ed Jobe
2012-03-28, 07:53 PM
Give me the title of each of the columns and a sample dwg with the titleblock in it.

stusic
2012-03-28, 08:37 PM
Happily!

The excel file doesn't have column titles, but here's a run-down:

Column:


B - SALES_ORDER
D - CUSTOMER
E - CITY
F - STATE
M - STORE_NAME

Thanks for letting me take so much of your time, Ed! You're helping me out a bunch in a pinch. :beer:

Ed Jobe
2012-03-29, 08:06 PM
Its been awhile since I contributed some code here, so I created a dvb. Its all tested with your docs. You just have to make sure that the titleblock is always inserted with the name "TitleBlock". Or, you could change the code to your needs. I'm also inserting the code here so the search engine can find it.

You just need to run the public sub UpdateTitleBlock() Your titleblock had more attributes than the xls you supplied. If there are more, just add them to the Select..Case statement in UpdateTitleBlock().

Option Explicit

' S:\Everyone\ENGR_REQUESTS\AutoREV\Data\GateWay1.xls

'Global vars
Private excelApp As Excel.Application 'points to excel application
Private wbkObj As Workbook 'points to excel workbook
Private rSearch As Range 'Range where the search is performed
Private rFound As Range 'Range where the data is found
Private dwginfo As Collection 'holds the "found" info


Public Function AddSelectionSet(SetName As String) As AcadSelectionSet
' This routine does the error trapping neccessary for when you want to create a
' selectin set. It takes the proposed name and either adds it to the selectionsets
' collection or sets it.
On Error Resume Next
Set AddSelectionSet = ThisDrawing.SelectionSets.Add(SetName)
If Err.Number <> 0 Then
Set AddSelectionSet = ThisDrawing.SelectionSets.Item(SetName)
AddSelectionSet.Clear
End If
End Function

Public Sub GetTitleBlockInfo(PrjNo As String)

On Error GoTo Err_Control

Set dwginfo = New Collection

With rSearch
Set rFound = .Find(What:=PrjNo, _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False)
If Not rFound Is Nothing Then
dwginfo.Add rFound.Offset(, 1).Value, "SALES_ORDER"
dwginfo.Add rFound.Offset(, 3).Value, "CUSTOMER"
dwginfo.Add rFound.Offset(, 4).Value, "CITY"
dwginfo.Add rFound.Offset(, 5).Value, "STATE"
dwginfo.Add rFound.Offset(, 12).Value, "STORE_NAME"
Else
Err.Raise vbObjectError + 101
End If
End With

Exit_Here:
Exit Sub
Err_Control:
Select Case Err.Number
Case Is = 101
'Search Item not found.
'Pass them up to calling sub.
Err.Raise vbObjectError + 101, "Module1.GetTitleBlockInfo", "Search Item not found."
Resume Exit_Here
Case Else
'Handle unforseen errors.
'Pass them up to calling sub.
Err.Raise vbObjectError + 100, "Module1.GetTitleBlockInfo"
Resume Exit_Here
End Select

End Sub

Public Function GetExcel() As Excel.Application

On Error GoTo Err_Control

Dim m_app As Excel.Application
Set m_app = GetObject(, "Excel.Application")

Return_App:
Set GetExcel = m_app

Exit_Here:
Exit Function
Err_Control:
Select Case Err.Number
Case Is = 429
'Excel is not running. Start it.
Set m_app = CreateObject("Excel.Application")
Resume Return_App
Case Else
'Handle unforseen errors.
MsgBox Err.Number & ", " & Err.Description, , "GetExcel"
Err.Clear
Resume Exit_Here
End Select

End Function

Public Function GetSS_BlockName(BlockName As String) As AcadSelectionSet
'creates a ss of blocks with the name supplied in the argument
Dim s2 As AcadSelectionSet

Set s2 = AddSelectionSet("ssBlocks") ' create ss with a name
s2.Clear ' clear the set
Dim intFtyp(3) As Integer ' setup for the filter
Dim varFval(3) As Variant
Dim varFilter1, varFilter2 As Variant
intFtyp(0) = -4: varFval(0) = "<AND"
intFtyp(1) = 0: varFval(1) = "INSERT" ' get only blocks
intFtyp(2) = 2: varFval(2) = BlockName ' whose name is specified in argument
intFtyp(3) = -4: varFval(3) = "AND>"
varFilter1 = intFtyp: varFilter2 = varFval
s2.Select acSelectionSetAll, , , varFilter1, varFilter2 ' do it
Set GetSS_BlockName = s2

End Function

Public Sub UpdateTitleblock()
Dim ent As Object

On Error GoTo Err_Control
'Open excel
Set excelApp = GetExcel()

Set wbkObj = excelApp.Workbooks.Open("S:\Everyone\ENGR_REQUESTS\AutoREV\Data\GateWay1.xls")
Set rSearch = wbkObj.Worksheets(1).Range("A:A")
GetTitleBlockInfo CLng(Left(ThisDrawing.Name, 7))

'Update DWG
Dim ss As AcadSelectionSet
Dim blk As AcadBlockReference
Set ss = GetSS_BlockName("TitleBlock")
Set blk = ss(0)

If blk.HasAttributes = True Then
Dim x As Long
Dim attArr As Variant
Dim att As AcadAttributeReference
attArr = blk.GetAttributes
For x = 0 To UBound(attArr)
Set att = attArr(x)
Select Case att.TagString
Case Is = "SALES_ORDER"
att.TextString = dwginfo("SALES_ORDER")
Case Is = "CUSTOMER"
att.TextString = dwginfo("CUSTOMER")
Case Is = "CITY"
att.TextString = dwginfo("CITY")
Case Is = "STATE"
att.TextString = dwginfo("STATE")
Case Is = "STORE_NAME"
att.TextString = dwginfo("STORE_NAME")
End Select
Next
End If

Cleanup:
'Cleanup out-of-process object, in reverse order of creation.
excelApp.Quit
Set rFound = Nothing
Set rSearch = Nothing
Set wbkObj = Nothing
Set excelApp = Nothing

Exit_Here:
Exit Sub
Err_Control:
Select Case Err.Number
Case Is = 1004
'File not found.
MsgBox "File not found." & vbCrLf & Err.Number & ", " & Err.Description, , Err.Source
Err.Clear
Resume Cleanup
Case Is = vbObjectError + 100
'Unhandled error in GetTitleBlockInfo
MsgBox "Unhandled Error in GetTitleBlockInfo(): " & Err.Number & ", " & Err.Description, , Err.Source
Err.Clear
Resume Cleanup
Case Is = vbObjectError + 101
'File not found.
MsgBox "Project Number was not found in Excel spreadsheet.", , Err.Source
Err.Clear
Resume Cleanup
Case Else
'Handle unforseen errors.
MsgBox Err.Number & ", " & Err.Description, , "UpdateTitleblock"
Err.Clear
Resume Cleanup
End Select

End Sub

stusic
2012-03-30, 12:06 PM
Hrm, I get an error: "6, Overflow"

Seems like a simple problem. Do you think it has anything to do with the fact that there's about a thousand more entries in my excel file than the example I sent you?

EDIT: It seems that this error is caused by numbers being too big for a variable. I wonder if this could be the issue.

Ed Jobe
2012-03-30, 03:08 PM
You're on the right track. The only place I thought that might be a problem is in the line that gets the ProjNo from the dwg title. I cast the 7 digits as Long. That should be good for numbers up to 2,147,483,647. Now I see that I missed the iterator in the For..Next loop. Change i from Integer to Long.

When dealing with errors, its critical that you know where they are coming from. Then you can start to figure out why. That's why if its getting caught by my error trapping, the dialog tells you what procedure the error is from. To find the exact spot, go to your vba ide Tools>Options, General tab and check Break on All Errors. Then step through till you get the error.

stusic
2012-03-30, 05:57 PM
I hate to keep pestering you, but any ideas?



The named selection set exists


In:


Set AddSelectionSet = ThisDrawing.SelectionSets.Add(SetName)


I got an error before too, but it's different now...

Ed Jobe
2012-03-30, 06:28 PM
If you still have Break on All Errors turned on, yes you will get an error. Turn it off after you're done testing. BTW, I uploaded a fixed dvb.

stusic
2012-04-04, 02:42 PM
Ed, thanks a heap, this works great!

I've sure learned a lot about VBA. Maybe I should thank my management also for mishandling implementation of new hardware :)

Ed Jobe
2012-04-04, 03:50 PM
Glad it worked for you. Its stuff like that that's the reason I got started programming.

stusic
2012-04-04, 03:54 PM
Haha, I hear ya brother.

One (slightly related) question: I seem to be missing soem references (are you using acad 2012 and/or Excel 2010?). Is there a quick fix to make it work with 2011/Excel 2007?

Thanks again

Ed Jobe
2012-04-04, 04:45 PM
I used Map 2010 and xl 2010. Just go to Tools>References and uncheck the ones that start with "MISSING:" and then find the appropriate library.