Visual LISP, AutoLISP and General Customization
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

The best method for Attribute Extraction and/or Block replacement?

31 REPLIES 31
Reply
Message 1 of 32
AlexFielder
2271 Views, 31 Replies

The best method for Attribute Extraction and/or Block replacement?

Hi all,

 

I have a little project to take care of that requires a batch of drawings to have their drawing frame blocks updated & attributes amended/truncated to a specific version and I'd rather not reinvent the wheel by resorting to programming anything using .NET.

 

I've searched the internet/forums and stumbled across a couple of threads/documents:

 

http://forums.autodesk.com/t5/NET/How-to-programmatically-extract-the-attributes-from-a-CAD-file/td-...

 

http://forums.autodesk.com/t5/AutoCAD-2013-2014-DWG-Format/Block-Replace-in-AutoCAD-2014-does-not-wo...

 

http://www.widom-assoc.com/AU-CP12-3L.pdf

 

None of which do exactly what I need.

 

What is the "from the horses mouth" workflow/method I should take? Data Extraction Tool/ATTOUT/Another approach I haven't thought of?

 

Thanks,

 

Alex.

31 REPLIES 31
Message 21 of 32
AlexFielder
in reply to: hmsilva

Hi Henrique,

 

That's exactly the same realisation I just came to, except I simply put false where you have nil.

 

Thanks again one and all. Most of these past couple of posts are classic "should-have-talked-to-the-bear" moments.

 

Doh!

 

No doubt I'll be back with more questions later. 🙂

 

BTW here's my routine that calls the bns_attin command:

 

(defun c:atin (/ dn pa padn ss)
(vl-load-com)
(load "attout")
(setq dn (vl-filename-base (getvar "dwgname")))
(setq pa (getvar "dwgprefix"))
(setq padn (strcat pa dn ".txt"))
;(setq ss (ssget "_X" (list (cons 0 "INSERT")'(66.1))))
(bns_attin padn false)
)

 

Message 22 of 32
AlexFielder
in reply to: AlexFielder

Okay, for my next question I need to call a .NET LispFunction I borrowed from here:

 

http://www.theswamp.org/index.php?topic=42890.0

 

(specifically the code posted by ChillMe1):

 

Imports System
Imports System.Text
Imports System.IO
Imports System.Data
Imports System.Linq
 
Imports Autodesk.AutoCAD.Runtime
Imports Autodesk.AutoCAD.DatabaseServices
Imports Autodesk.AutoCAD.EditorInput
Imports Autodesk.AutoCAD.Geometry
Imports Autodesk.AutoCAD.ApplicationServices
Imports Autodesk.AutoCAD.Interop
Imports Autodesk.AutoCAD
Imports Autodesk.AutoCAD.ApplicationServices.Application
Imports Autodesk.AutoCAD.LayerManager
Imports Autodesk.AutoCAD.Windows
 
' This line is not mandatory, but improves loading performances
<Assembly: CommandClass(GetType(TransferAttributes.MyCommands))> 
 
Namespace TransferAttributes
 
    ' This class is instantiated by AutoCAD for each document when
    ' a command is called by the user the first time in the context
    ' of a given document. In other words, non static data in this class
    ' is implicitly per-document!
    Public Class MyCommands
 
        <CommandMethod("UpdateAtts")> _
        Public Sub UpdateAtts()
            Dim myDoc As Document = Application.DocumentManager.MdiActiveDocument
            Dim myEd As Editor = myDoc.Editor
            Dim selEntID As ObjectId = myEd.GetEntity("Select Old Block:").ObjectId
            Dim selEntID2 As ObjectId '= myEd.GetEntity("Select New block:").ObjectId
            Dim myXML As New Xml.XmlDocument()
            myXML.Load("c:\i-cad\UpdateAtts_A.xml")
            Using myTrans As Transaction = myDoc.Database.TransactionManager.StartTransaction
                Dim myBrefA As BlockReference = selEntID.GetObject(OpenMode.ForWrite)
                Dim myBrefB As BlockReference '= selEntID2.GetObject(OpenMode.ForWrite)
                Dim myAttsA As AttributeCollection = myBrefA.AttributeCollection
                Dim myAttsB As AttributeCollection '= myBrefB.AttributeCollection
                Dim blockNameA As String = ""
                Dim blockNameB As String = ""
                If myBrefA.Name.StartsWith("*") Then
                    Dim myBTR As BlockTableRecord = myBrefA.DynamicBlockTableRecord.GetObject(OpenMode.ForRead)
                    blockNameA = myBTR.Name
                Else
                    blockNameA = myBrefA.Name
                End If
 
                Dim oldNode As Xml.XmlNode = myXML.SelectSingleNode("//oldblock[@name='" & blockNameA.ToUpper & "']")
                Dim myBT As BlockTable = myDoc.Database.BlockTableId.GetObject(OpenMode.ForWrite)
                If myBT.Has(oldNode.Attributes("newname").Value) = False Then
                    'insert DWG file as a Block
                    Dim myDWG As New IO.FileInfo(oldNode.Attributes("path").Value)
                    If myDWG.Exists = False Then
                        MsgBox("The file " & myDWG.FullName & " does not exist.")
                        Exit Sub
                    End If
                    'Create a blank Database
                    Dim dwgDB As New Database(False, True)
                    'Read a DWG file into the blank database
                    dwgDB.ReadDwgFile(myDWG.FullName, FileOpenMode.OpenForReadAndAllShare, True, "")
                    'insert the dwg file into the current file's block table
                    myDoc.Database.Insert(oldNode.Attributes("newname").Value.ToUpper, dwgDB, True)
                    'close/dispose of the previously blank database.
                    dwgDB.Dispose()
                End If
                selEntID2 = InsertBlock(myDoc.Database, myBrefA.BlockName, myBrefA.Position, oldNode.Attributes("newname").Value, myBrefA.ScaleFactors.X, myBrefA.ScaleFactors.Y, myBrefA.ScaleFactors.Z)
                myBrefB = selEntID2.GetObject(OpenMode.ForWrite)
                myAttsB = myBrefB.AttributeCollection
                For Each myNode As Xml.XmlNode In oldNode.SelectNodes("attribute")
                    For Each myAttID As ObjectId In myAttsA
                        Dim myAtt As AttributeReference = myAttID.GetObject(OpenMode.ForRead)
                        If myAtt.Tag.ToUpper = myNode.Attributes("name").Value.ToUpper Then
                            For Each myAttBID As ObjectId In myAttsB
                                Dim myAttB As AttributeReference = myAttBID.GetObject(OpenMode.ForWrite)
                                If myAttB.Tag.ToUpper = myNode.Attributes("newname").Value.ToUpper Then
                                    myAttB.TextString = myAtt.TextString
                                End If
                            Next
                        End If
                    Next
                Next
                myBrefA.Erase()
                myTrans.Commit()
            End Using
 
        End Sub
        Public Function InsertBlock(ByVal DatabaseIn As Database, _
                        ByVal BTRToAddTo As String, _
                        ByVal InsPt As Geometry.Point3d, _
                        ByVal BlockName As String, _
                        ByVal XScale As Double, _
                        ByVal YScale As Double, _
                        ByVal ZScale As Double) As DatabaseServices.ObjectId
            Using myTrans As Transaction = DatabaseIn.TransactionManager.StartTransaction
                Dim myBlockTable As BlockTable = DatabaseIn.BlockTableId.GetObject(OpenMode.ForRead)
                'If the suppplied Block Name is not 
                'in the specified Database, get out gracefully.
                If myBlockTable.Has(BlockName) = False Then
                    Return Nothing
                End If
                'If the specified BlockTableRecord does not exist,
                'get out gracefully
                If myBlockTable.Has(BTRToAddTo) = False Then
                    Return Nothing
                End If
                Dim myBlockDef As BlockTableRecord = _
                    myBlockTable(BlockName).GetObject(OpenMode.ForRead)
                Dim myBlockTableRecord As BlockTableRecord = _
                    myBlockTable(BTRToAddTo).GetObject(OpenMode.ForWrite)
                'Create a new BlockReference
                Dim myBlockRef As New BlockReference(InsPt, myBlockDef.Id)
                'Set the scale factors
                myBlockRef.ScaleFactors = New Geometry.Scale3d(XScale, YScale, ZScale)
                'Add the new BlockReference to the specified BlockTableRecord
                myBlockTableRecord.AppendEntity(myBlockRef)
                'Add the BlockReference to the BlockTableRecord.
                myTrans.AddNewlyCreatedDBObject(myBlockRef, True)
                Dim myAttColl As DatabaseServices.AttributeCollection = _
                    myBlockRef.AttributeCollection
                'Find Attributes and add them to the AttributeCollection
                'of the BlockReference
                For Each myEntID As ObjectId In myBlockDef
                    Dim myEnt As Entity = myEntID.GetObject(OpenMode.ForRead)
                    If TypeOf myEnt Is DatabaseServices.AttributeDefinition Then
                        Dim myAttDef As DatabaseServices.AttributeDefinition = myEnt
                        Dim myAttRef As New DatabaseServices.AttributeReference
                        myAttRef.SetAttributeFromBlock(myAttDef, myBlockRef.BlockTransform)
                        myAttColl.AppendAttribute(myAttRef)
                        myTrans.AddNewlyCreatedDBObject(myAttRef, True)
                    End If
                Next
                myTrans.Commit()
                Return myBlockRef.Id
            End Using
        End Function
 

 I have modified this using an example posted by Tony Tanzillo so that it is now a LispFunction that accepts a lisp selecionset:

 

'<CommandMethod("UpdateAtts")> _
        <LispFunction("UpdateAtts")> _
        Public Sub UpdateAtts(args As ResultBuffer)
            If args Is Nothing Then
                Throw New ArgumentException("Requires one argument")
            End If
            Dim values As TypedValue() = args.AsArray
            If values.Length <> 1 Then
                Throw New ArgumentException("Wrong number of arguments")
            End If
            If values(0).TypeCode <> CInt(LispDataType.SelectionSet) Then
                Throw New ArgumentException("Bad argument type - requires a selection set")
            End If
            Dim ss As SelectionSet = DirectCast(values(0).Value, SelectionSet)
            Dim myDoc As Document = Application.DocumentManager.MdiActiveDocument
            Dim myEd As Editor = myDoc.Editor
            Dim selEntIDs As ObjectId() = ss.GetObjectIds()
            Dim selEntID As ObjectId = selEntIDs(0)
            'Dim selEntID As ObjectId = myEd.GetEntity("Select Old Block:").ObjectId
            'Everything after this point is unchanged from the above - ergo no point in duplicating the code twice in the same post!

 My Lisp routine that *should* call this .net lispfunction is:

 

(defun c:uatts (/ss)
(command "netload" C:\Users\Alex\documents\visual studio 2012\Projects\BlockReplace\BlockReplace\bin\Debug\BlockReplace.dll )
(setq ss (ssget "_X" (list (cons 0 "INSERT")'(66.1))))
(updateatts ss)
(princ)
)

 but all I see at the command line when I run it is:

; error: too few arguments

 Any ideas what I'm missing?

Message 23 of 32
hmsilva
in reply to: AlexFielder

(command "netload" "C:\\Users\\Alex\\documents\\visual studio 2012\\Projects\\BlockReplace\\BlockReplace\\bin\\Debug\\BlockReplace.dll" )

or

(command "netload" "C:/Users/Alex/documents/visual studio 2012/Projects/BlockReplace/BlockReplace/bin/Debug/BlockReplace.dll" )

 

HTH

Henrique

 

 

EESignature

Message 24 of 32
AlexFielder
in reply to: hmsilva

Thanks Henrique, all useful information.

 

Perhaps I should have been clearer though; If I've already netloaded the .dll file prior to running the uatts command, what is the correct syntax for loading the LispFunction with the ss selectionset passed to it?

Message 25 of 32
AlexFielder
in reply to: AlexFielder

Turns out I've answered my own question. The problem had to do with the LISP itself.

 

This is the code that now fires my LispFunction:

 

(defun c:uatts (/ ss)
(command "netload" "C:\\Users\\Alex\\documents\\visual studio 2012\\Projects\\BlockReplace\\BlockReplace\\bin\\Debug\\BlockReplace.dll" )
(setq ss (ssget "_X" (list (cons 0 "INSERT")'(66 . 1))))
(updateatts ss)

 The crucial step was the lack of a space in the first line:

 

(defun c:uatts (/<space was required here>ss)

 

 coupled with I think the same lack of a space in this line:

 

(setq ss (ssget "_X" (list (cons 0 "INSERT")'(66<space-required-here>.<space-required-here>1))))

 Doh!

 

Time for another coffee.

Message 26 of 32
hmsilva
in reply to: AlexFielder

I'm glad you got it!

Henrique

EESignature

Message 27 of 32
Lee_Mac
in reply to: AlexFielder

Just a heads-up: copying the contents of the Express Tools programs and posting them publicly violates the EULA

 

Lee

Message 28 of 32
AlexFielder
in reply to: Lee_Mac

Thanks for the information Lee; as I suspected the time window to edit the offending post has passed.

 

If it needs to be removed someone else will have to do it.

Message 29 of 32
AlexFielder
in reply to: AlexFielder

Here's my latest set of scripts for this thread:

 

::Commented out the below as we need it to work on non-superseded files
::could use the following instead of blindly running AutoCAD "C:\Program Files (x86)\Autodesk\ScriptPro 2.0\bin\ScriptPro.exe"
::
::
::
:: Need to make sure that blockreplace tool is installed and loaded in AutoCAD 2014!
::
::
::
::
::

::FOR /R .\1 %%f in (*.dwg) do start /wait "C:\Program Files\Autodesk\AutoCAD 2014\acad.exe" "%%f" /b .\atout.scr 
REM write the header to the Scriptpro.bpl file
type scriptpro_header.txt > scriptpro_test.bpl
REM go to the sub-sub-sub-folder
pushd .\1\HR4C\1-1
REM find .dwgs and list them in dwglist.txt
REM Need to create this file after having moved files in the root of the drawing folder to \CURRENT
for /d %%f in (.\*) do (IF NOT EXIST "%%f"\SUPERCEDED (MKDIR "%%f"\SUPERCEDED) && IF NOT EXIST "%%f"\CURRENT (MKDIR "%%f"\CURRENT) && IF NOT EXIST "%%f"\WIP (MKDIR "%%f"\WIP))
for /d %%f in (.\*) do (
pushd %%f 
REM Move anything in the root to \CURRENT
MOVE .\*.* .\CURRENT\
pushd .\WIP\
REM Move anything in WIP to \CURRENT
MOVE .\*.* ..\CURRENT\
popd
pushd .\CURRENT\
REM Copy .dwg to \SUPERCEDED
COPY .\*.dwg ..\SUPERCEDED\
popd
popd
)
popd
REM Create our drawing script
pushd .\1
dir /s /b *.dwg >..\dwglist.txt
popd
REM filter out case-independent "superceded" and append to the scriptpro.bpl file.
findstr /i /v superceded dwglist.txt >>scriptpro_test.bpl
REM write the footer to the Scriptpro.bpl file
type scriptpro_footer.txt >> scriptpro_test.bpl
REM store the scriptpro_test.bpl file as a variable
REM run the scriptpro.bpl file
CHOICE /M "Do you want to continue dwg processing with AutoCAD?"
IF ERRORLEVEL 1 GOTO :Process
IF ERRORLEVEL 2 GOTO :Skip
:Process
FOR /R %%f in (*_test.bpl) do "C:\Program Files (x86)\Autodesk\ScriptPro 2.0\bin\ScriptPro.exe" "%%~f" "run" "exit"
:Skip
REM write the masterlist header to masterlist for each variation of the border(s) we're expecting.
type 5.2masterlist_header.txt > 5.2masterlist.txt
type 5.2ILmasterlist_header.txt > 5.2ILmasterlist.txt
type 5.1masterlist_header.txt > 5.1masterlist.txt
type 6.1masterlist_header.txt > 6.1masterlist.txt
REM append the resultant .txt files to the masterlist.txt file ready for opening/processing in Excel.
REM delete the tmplist.txt file
del tmplist.txt
REM turn the cmd /f switch off
cmd /f:OFF
REM cycle through the directory tree looking for .txt files, then cycle through each file preserving tabs as we go.
for /r .\1 %%f in (*.txt) do for /f "tokens=* delims=	" %%i in ("%%f") do type "%%i" >>tmplist.txt
REM Turn the cmd /f switch back on
cmd /F:ON
REM filter and append the contents of list.txt to the newly created masterlist.txt removing rows containing HANDLE as we go.
REM findstr /i /v "HANDLE 5.2 6.1" tmplist.txt >>5.1masterlist.txt REM gets 5.1 version frames
REM findstr /i /v "HANDLE 5.1 6.1" tmplist.txt >>5.2masterlist.txt REM gets 5.2 version frames
REM findstr /i /v "HANDLE 5.1 5.2" tmplist.txt >>6.1masterlist.txt REM gets 6.1 version frames
REM gets 5.1 frames
findstr /i /r /c:".*5.1(block).*" tmplist.txt >>5.1masterlist.txt
REM gets 5.2 IL frames
findstr /i /r /c:".*SB-IL_996-5.2(block).*" tmplist.txt >>5.2ILmasterlist.txt
REM gets 5.2 frames
findstr /i /v "HANDLE 5.1 IL 6.1" tmplist.txt >>5.2masterlist.txt
REM gets 6.1 frames
findstr /i /r /c:".*6.1(block).*" tmplist.txt >>6.1masterlist.txt
REM Assumes Office 2010
for /r %%f in (*masterlist.txt) do "C:\Program Files (x86)\Microsoft Office\Office14\EXCEL.EXE" "%%f"

 The above creates and runs a scriptpro.bpl format file with contents similar to the following:

 

[General_Start]
Version*3
Product*2014
Script*C:\temp\attout.scr
TimeOut*30
RestartCount*30
IniScript*
LogFileName*C:\Temp\
AutoCADPath*
Sleep*0
RunwithoutOpen*True
[General_End]
[DWGList_Start]
C:\temp\1\123456\Current\123456.dwg
C:\temp\1\789012\Current\789012.dwg
[DWGList_End]

 

 

 This script file separately calls the attout.scr file which has the line:

 

(load "CompanyNameCommands")
atout

 The file "CompanyNameCommands" lisp file contains the following:

 

(defun c:atout ( / dn pa padn ss)
(vl-load-com)
(load "attout")
(setq dn (vl-filename-base (getvar "dwgname")))
(setq pa (getvar "dwgprefix"))
(setq padn (strcat pa dn ".txt"))
(setq ss (ssget "_X" (list (cons 0 "INSERT")'(66 . 1))))
(bns_attout padn ss)
);atout
(defun c:atin (/ dn pa padn)
(vl-load-com)
(load "attout")
(setq dn (vl-filename-base (getvar "dwgname")))
(setq pa (getvar "dwgprefix"))
(setq padn (strcat pa dn ".txt"))
(bns_attin padn false)
);atin
(defun c:uatts (/ ss)
(setq ss (ssget "_X" (list (cons 0 "INSERT")'(66 . 1))))
(updateatts ss)
);uatts
;;;(defun c:client ( dwgnum sht rev / newdn )
(defun client ( dwgnum sht rev / newdn )
(if (not (= (type dwgnum) 'STR))
    (setq dwgnum "DWGNUM")
  );if
  (if (not (= (type sht) 'STR))
    (setq sht "001")
  );if
  (if (not (= (type rev) 'STR))
    (setq rev "REV")
  );if
  (setq old-echo (getvar "CMDECHO"))
  (setvar "cmdecho" 1)
  (setvar "draworderctl" 1)
;;;Step 1. A quick save of the current drawing. (works)
  (command "qsave")
;;;Step 2. A save as to a drawing with file name "######_sht_001_iss-###.dwg" in the same directory as the current drawing.
(setq newdn (strcat dwgnum "_sht_" sht "_iss-" rev ".dwg"))
(setq DwgName (strcat (getvar "dwgprefix") newdn))
(if (findfile DwgName);Check file exists
(command "_.saveas" "" DwgName "Y");If file exits then Overwrite
  (command "_.saveas" "" DwgName)
  );if
  (setvar "CMDECHO" old-echo)
   (setvar "DRAWORDERCTL" 3)
)

 the last step I have to piece together for this is a file saveas using the (client "dwgnum" "sht" "rev") command.

 

I've looked into running some of this with the accoreconsole.exe available as part of AutoCAD 2014 but it doesn't seem to like the "bns_attout" command from the attout.lsp file -< that file seems to load just fine if you run accoreconsole.exe at a command prompt. The (bns_attout padn ss) command fired from the above atout command just never does anything.

 

???

Message 30 of 32
bhull1985
in reply to: AlexFielder

With express tools loaded?

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Please use code tags and credit where credit is due. Accept as solution, if solved. Let's keep it trim people!
Message 31 of 32
AlexFielder
in reply to: bhull1985

I hadn't thought of that- is (load "attout") which normally loads the attout.lsp file not enough then? How would you load Express Tools otherwise from the accoreconsole.exe Command prompt?
Message 32 of 32
bhull1985
in reply to: Lee_Mac

Fair use?

 

http://en.wikipedia.org/wiki/Fair_use

 

Fair use is a limitation and exception to the exclusive right granted by copyright law to the author of a creative work. In United States copyright law, fair use is a doctrine that permits limited use of copyrighted material without acquiring permission from the rights holders. Examples of fair use include commentary, search engines, criticism, parody, news reporting, research, teaching, library archiving and scholarship. It provides for the legal, unlicensed citation or incorporation of copyrighted material in another author's work under a four-factor balancing test.

 

I never understand why people have qualms with this.

Criticism and parody are allowed, and although some of my attempts at re-writing express code surely falls under the parody catagorey, i believe most of what people who are taking express routines and legitimately having issues with them in the course of their daily work are indeed allowed to come and post their problems (with excerpts) onto these support forums.

If this was hackers anonymous, i wouldn't be saying this. But it's autodesk forums, support at that. Commentary, teaching, library archiving.

Perhaps i'm missing something, but...credit where credit is due, and i've never seen anyone try to claim an express routine as their own.

That last line, too:

It provides for the legal, unlicensed citation or incorporation of copyrighted material in another aurthor's work under a four-factor balancing test (which i have no idea what is. Will look up after submitting this post).

I feel obligated to post this because OP clearly stated the routine belonged to "express" as such this seems fair use, but perhaps not? 🙂

Goodmorning, too, by the way.

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Please use code tags and credit where credit is due. Accept as solution, if solved. Let's keep it trim people!

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Autodesk Design & Make Report

”Boost