I've made some code to find all blocks "vbredak" in a Drawing.
Version 1 "zbl" is the slowest version.
Version 2 "zbl2" is much faster. But in this version I can't find blocks in another drawing (f.i. an xref)
When I change in version 1
Dim mydb As Database = myDWG.Database Dim mydb As DatabaseServices.Database"c:\test.dwg", IO.FileShare.Read, True, "")
by
mydb.ReadDwgFile(
I can search in other drawings.
How can I adapt the code in version 2 so I can find blocks in ohter DWG's ?
Or
How can I speed up version 1 ?
Here is my code
<CommandMethod("zbl")> _ Public Sub zbl() Dim starttime As Double Dim endtime As Double starttime = DateAndTime.Timer Dim myTransMan As DatabaseServices.TransactionManager Dim myTrans As DatabaseServices.Transaction Dim myBT As DatabaseServices.BlockTable Dim myBTR As DatabaseServices.BlockTableRecord Dim myBlockDef As DatabaseServices.BlockTableRecord Dim mySTE As DatabaseServices.SymbolTableEnumerator Dim myBTE As DatabaseServices.BlockTableRecordEnumerator Dim myDWG As ApplicationServices.Document = ApplicationServices.Application.DocumentManager.MdiActiveDocument Dim mydb As Database = myDWG.Database Dim myEd As EditorInput.Editor = myDWG.Editor myTransMan = mydb.TransactionManager myTrans = myTransMan.StartTransaction myBT = myDB.BlockTableId.GetObject(DatabaseServices.OpenMode.ForRead) mySTE = myBT.GetEnumerator While mySTE.MoveNext myBTR = mySTE.Current.GetObject(DatabaseServices.OpenMode.ForRead) If myBTR.IsLayout = True Then myBTE = myBTR.GetEnumerator Dim myEnt As DatabaseServices.Entity While myBTE.MoveNext myEnt = myBTE.Current.GetObject(DatabaseServices.OpenMode.ForRead) If TypeOf myEnt Is DatabaseServices.BlockReference Then Dim MyBlockRef As DatabaseServices.BlockReference MyBlockRef = myEnt If MyBlockRef.BlockName = "vbredak" Then myEd.WriteMessage("vbredak found") If MyBlockRef.IsDynamicBlock = True Then myBlockDef = MyBlockRef.DynamicBlockTableRecord.GetObject(DatabaseServices.OpenMode.ForRead) Else myBlockDef = MyBlockRef.BlockTableRecord.GetObject(DatabaseServices.OpenMode.ForRead) If myBlockDef.Name = "vbredak" Then myEd.WriteMessage("vbredak found") End If End If End While End If End While myTrans.Dispose() myTransMan.Dispose() myDB.Dispose() endtime = DateAndTime.Timer MsgBox(endtime - starttime & "seconds") End Sub <CommandMethod("zbl2")> _ Public Sub zbl2() Dim starttime As Double Dim endtime As Double starttime = DateAndTime.Timer Dim myDWG As ApplicationServices.Document Dim myEd As EditorInput.Editor Dim myPSR As EditorInput.PromptSelectionResult Dim mySS As EditorInput.SelectionSet Dim myTransMan As DatabaseServices.TransactionManager Dim myTrans As DatabaseServices.Transaction Dim myObjIds As DatabaseServices.ObjectIdCollection Dim myObjId As DatabaseServices.ObjectId Dim myBlock As DatabaseServices.BlockReference Dim myfilter(0) As DatabaseServices.TypedValue Dim mySF As New EditorInput.SelectionFilter(myfilter) myDWG = ApplicationServices.Application.DocumentManager.MdiActiveDocument myfilter(0) = New DatabaseServices.TypedValue(DatabaseServices.DxfCode.BlockName, "vbredak") myEd = myDWG.Editor myPSR = myEd.SelectAll(mySF) mySS = myPSR.Value If Not (mySS Is Nothing) Then myTransMan = myDWG.TransactionManager myTrans = myTransMan.StartTransaction myObjIds = New DatabaseServices.ObjectIdCollection(mySS.GetObjectIds) For Each myObjId In myObjIds myBlock = myObjId.GetObject(DatabaseServices.OpenMode.ForRead) If myBlock.Name = "vbredak" Then myEd.WriteMessage("vbredak found") Next myTrans.Dispose() myTransMan.Dispose() End If endtime = DateAndTime.Timer MsgBox(endtime - starttime & "seconds") End Sub
Hi,
Try the BlockTableRecord.GetBlockReferenceIds() route.
Here's a C# sample:
private ObjectIdCollection GetAllReferences(Database db) { ObjectIdCollection result = new ObjectIdCollection(); using (Transaction tr = db.TransactionManager.StartTransaction()) { BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); foreach (ObjectId btrId in bt) { BlockTableRecord btr = (BlockTableRecord)tr.GetObject(btrId, OpenMode.ForRead); if (!btr.IsLayout && !btr.IsFromExternalReference) foreach (ObjectId refId in btr.GetBlockReferenceIds(true, false)) { BlockReference br = (BlockReference)tr.GetObject(refId, OpenMode.ForRead); BlockTableRecord owner = (BlockTableRecord)tr.GetObject(br.OwnerId, OpenMode.ForRead); if (owner.IsLayout) result.Add(br.ObjectId); } } tr.Commit(); } return result; }
Sorry, I misunderstood the request.
The following sample returns all block reference ObjectIds which name is 'bName' inserted in the 'db' database spaces.
private ObjectIdCollection GetReferences(Database db, string bName) { ObjectIdCollection result = new ObjectIdCollection(); using (Transaction tr = db.TransactionManager.StartTransaction()) { BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); if (bt.Has(bName)) { BlockTableRecord btr = (BlockTableRecord)tr.GetObject(bt[bName], OpenMode.ForRead); foreach (ObjectId refId in btr.GetBlockReferenceIds(true, false)) { BlockReference br = (BlockReference)tr.GetObject(refId, OpenMode.ForRead); BlockTableRecord owner = (BlockTableRecord)tr.GetObject(br.OwnerId, OpenMode.ForRead); if (owner.IsLayout) result.Add(br.ObjectId); } } tr.Commit(); } return result; }
You might want to play with data extracton as well
Add reference to AcDx.dll
Tested on A2009 only
Follow link to get more explanation
Imports System Imports System.Text Imports System.IO Imports System.Data Imports System.Reflection Imports System.Collections.Generic Imports System.Runtime.InteropServices Imports Autodesk.AutoCAD.ApplicationServices Imports Autodesk.AutoCAD.DatabaseServices Imports Autodesk.AutoCAD.Geometry Imports Autodesk.AutoCAD.Colors Imports Autodesk.AutoCAD.Runtime Imports acApp = Autodesk.AutoCAD.ApplicationServices.Application Imports Autodesk.AutoCAD.EditorInput Imports Autodesk.AutoCAD.DataExtraction Imports Autodesk.AutoCAD.Windows 'Imports System.Windows.Forms <Assembly: CommandClass(GetType(TesterVB.ExtractData))> Namespace TesterVB Public Class ExtractData #Region "Data Extraction Example" '' based on Kean Walmsley's example from there: '' http://through-the-interface.typepad.com/through_the_interface/2008/04/extracting-data.html <CommandMethod("zb3")> _ Public Sub zbllk() Dim starttime As Double Dim endtime As Double Dim sb As StringBuilder = New StringBuilder '' Dim fname As String = "C:\Test\bcount.dwg"''<--debug only Dim dialog As New System.Windows.Forms.OpenFileDialog With dialog .CheckPathExists = True .CheckPathExists = True .DefaultExt = "dxf" .DereferenceLinks = True .Multiselect = False .Filter = "Drawing(*.dwg)|*.dwg|All files (*.*)|*.*" .Title = "Select drawing" .FilterIndex = 1 End With If dialog.ShowDialog() <> System.Windows.Forms.DialogResult.OK Then Return End If Dim fname As String = dialog.FileName starttime = DateAndTime.Timer Try Dim dataTable As System.Data.DataTable = extractNames(fname) Dim bname As String = "vbredak" Dim distinctTable As System.Data.DataTable = ExtractBlocksByName(dataTable, bname) ''<--block datatable endtime = DateAndTime.Timer For Each row As System.Data.DataRow In distinctTable.Rows Dim items() As Object = row.ItemArray Dim msg As String = vbTab For i As Integer = 0 To items.Length - 1 msg = msg & distinctTable.Columns(i).ColumnName & " = " & items(i).ToString & vbCr Next sb.Append(msg) Next MsgBox("Time: " & endtime - starttime & " seconds;" & vbCr & "Found " & dataTable.Rows.Count & " blocks", MsgBoxStyle.OkOnly, "DataEXtraction Method") Catch ex As System.Exception MsgBox(vbCr & ex.ToString & vbCr & ex.StackTrace) End Try MsgBox(sb.ToString) End Sub ''' <summary> ''' ''' </summary> ''' <param name="source"></param> ''' <param name="bname"></param> ''' <returns></returns> ''' <remarks></remarks> Public Function ExtractBlocksByName(ByVal source As System.Data.DataTable, ByVal bname As String) As System.Data.DataTable Dim result As New System.Data.DataTable() Dim filter As String = String.Format("{0}='{1}'", "AcDxObjectTypeGlobalName", "BlockReferenceTypeDescriptor." + bname) Dim selrows As System.Data.DataRow() = source.Select(filter) Dim first As System.Data.DataRow = DirectCast(selrows(0), System.Data.DataRow) result = source.Clone() For Each dr As System.Data.DataRow In selrows result.ImportRow(dr) Next Dim tags As New List(Of String)() Dim isattrib As String = "BlockReferenceAttribute." For Each column As System.Data.DataColumn In first.Table.Columns Dim capture As String = column.ColumnName If first(capture).ToString() <> String.Empty Then If capture.StartsWith(isattrib, StringComparison.InvariantCultureIgnoreCase) Then Dim found As String = capture.Replace(isattrib, String.Empty) tags.Add(found) End If End If Next Dim dcolumns As New List(Of String)() From { _ "AcDxObjectTypeName", _ "AcDxHandleData", _ "Layer", _ "Position.X", _ "Position.Y", _ "Position.Z", _ "Rotation", _ "ScaleFactors.X", _ "ScaleFactors.Y", _ "ScaleFactors.Z" _ } If tags.Count > 0 Then For n As Integer = 0 To tags.Count - 1 dcolumns.Add("BlockReferenceAttribute." + tags(n)) Next End If Dim cnt As Integer = dcolumns.Count Dim columns As String() = New String(cnt - 1) {} dcolumns.CopyTo(columns) Dim dvw As DataView = result.DefaultView result = dvw.ToTable(False, columns) Dim xmlcolumns As New List(Of String)() From { _ "BlockName", _ "Handle", _ "Layer", _ "Insertion_X", _ "Insertion_Y", _ "Insertion_Z", _ "Rotation", _ "Scale_X", _ "Scale_Y", _ "Scale_Z" _ } If tags.Count > 0 Then For n As Integer = 0 To tags.Count - 1 xmlcolumns.Add(tags(n)) Next End If For n As Integer = 0 To xmlcolumns.Count - 1 result.Columns(n).ColumnName = xmlcolumns(n) Next Return result End Function ''' <summary> ''' ''' </summary> ''' <param name="fname"></param> ''' <remarks></remarks> Public Function extractNames(ByVal fname As String) As System.Data.DataTable Dim dataTable As New System.Data.DataTable() Dim blkTable As New System.Data.DataTable() Dim doc As Document = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument Dim ed As Editor = doc.Editor Dim tags As New List(Of String)() If Not System.IO.File.Exists(fname) Then ed.WriteMessage(vbLf & "Drawing file does not exist.") Return Nothing Exit Function End If Dim es As IDxExtractionSettings = New DxExtractionSettings() Dim de As IDxDrawingDataExtractor = es.DrawingDataExtractor de.Settings.ExtractFlags = ExtractFlags.None Or ExtractFlags.ModelSpaceOnly de.Settings.ExtractFlags = ExtractFlags.ExtractBlockOnly Dim fr As IDxFileReference = New DxFileReference(Path.GetDirectoryName(fname), fname) de.Settings.DrawingList.AddFile(fr) ' Scan the drawing for object types & their properties de.DiscoverTypesAndProperties(Path.GetDirectoryName(fname)) Dim types As List(Of IDxTypeDescriptor) = de.DiscoveredTypesAndProperties ' Select all the types and properties for extraction ' by adding them one-by-one to these two lists Dim selTypes As New List(Of String)() Dim selProps As New List(Of String)() For Each type As IDxTypeDescriptor In types selTypes.Add(type.GlobalName) For Each pr As IDxPropertyDescriptor In type.Properties If Not selProps.Contains(pr.GlobalName) Then selProps.Add(pr.GlobalName) End If Next Next de.Settings.SetSelectedTypesAndProperties(types, selTypes, selProps) ' Now perform the extraction itself de.ExtractData(Path.GetDirectoryName(fname)) ' Get the results of the extraction dataTable = de.ExtractedData If dataTable.Rows.Count > 0 Then dataTable.TableName = "Block" Dim selrows As DataRow() = dataTable.Select("AcDxObjectTypeGlobalName Like 'BlockReferenceTypeDescriptor.%'") blkTable = dataTable.Clone() For Each dr As DataRow In selrows blkTable.ImportRow(dr) Next '' commented lines is just for populating form listbox control 'Dim columns As String() = New String() {"AcDxObjectTypeName"} 'Dim dvw As DataView = blkTable.DefaultView 'distinctTable = dvw.ToTable(False, columns) 'Dim bnames As New List(Of String)() 'For Each dr As System.Data.DataRow In distinctTable.Rows ' Dim bname As String = dr(0).ToString() ' If Not bnames.Contains(bname) Then ' bnames.Add(bname) ' End If 'Next 'Me.lst.DataSource = bnames'' <-- to fill ListBox only 'lst.SelectedIndex = -1'' <-- set selected items to nothing End If Return blkTable End Function End Class #End Region
~'J'~
Gille, your code works really good and FAST !
I've tried to search also for anonymous blocks. changing
For Each refId As ObjectId In btr.GetBlockReferenceIds(True, False)
in
For Each refId As ObjectId In btr.GetAnonymousBlockIds()
didn't work. Any ideas ?
And second question: can you also use the code to find polylines ?
GetAnonymousBlockIds() returns the anonymous block table record ObjectId collection not the block reference one.
If you want to get all block references for a specific bloc name (named and anonymous), you can go this way:
private ObjectIdCollection GetReferences(Database db, string bName)
{
ObjectIdCollection result = new ObjectIdCollection();
using (Transaction tr = db.TransactionManager.StartTransaction())
{
BlockTable bt =
(BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
if (bt.Has(bName))
{
BlockTableRecord btr =
(BlockTableRecord)tr.GetObject(bt[bName], OpenMode.ForRead);
foreach (ObjectId refId in btr.GetBlockReferenceIds(true, false))
{
BlockReference br =
(BlockReference)tr.GetObject(refId, OpenMode.ForRead);
BlockTableRecord owner =
(BlockTableRecord)tr.GetObject(br.OwnerId, OpenMode.ForRead);
if (owner.IsLayout)
result.Add(br.ObjectId);
}
foreach (ObjectId id in btr.GetAnonymousBlockIds())
{
BlockTableRecord anon =
(BlockTableRecord)tr.GetObject(id, OpenMode.ForRead);
foreach (ObjectId refId in anon.GetBlockReferenceIds(true, false))
{
BlockReference br =
(BlockReference)tr.GetObject(refId, OpenMode.ForRead);
BlockTableRecord owner =
(BlockTableRecord)tr.GetObject(br.OwnerId, OpenMode.ForRead);
if (owner.IsLayout)
result.Add(br.ObjectId);
}
}
}
tr.Commit();
}
return result;
}
If you take look at this link
http://www.theswamp.org/index.php?topic=37089.0
For polylines
Looking at the link you can see other options, but the basic idea is to check a property of the ObjectId to see if it is the type object you want then open the object.
That way you skip a bunch of useless and sometimes expensive cycles opening and closing objects you do not need to.
Here this uses ObjectId.ObjectClass.Name which ObjectClass gets the RxClass of the object and checks to see if the name is "AcDbPolyline" first then opens it and prints the length
<CommandMethod("GetPolyLinesFast")> _ Public Sub GetPolyLinesFast() Dim doc As Document = Application.DocumentManager.MdiActiveDocument Dim db As Database = doc.Database Dim ed As Editor = doc.Editor Using trx As Transaction = db.TransactionManager.StartTransaction() Dim mdlSpaceBtr As BlockTableRecord = SymbolUtilityServices.GetBlockModelSpaceId(db).GetObject(OpenMode.ForRead) For Each objId As ObjectId In mdlSpaceBtr If objId.ObjectClass.Name = "AcDbPolyline" Then Dim plyLne As Polyline = trx.GetObject(objId, OpenMode.ForRead) ed.WriteMessage(String.Format("{0} The PolyLine Length's is {1}", vbCrLf, plyLne.Length)) End If Next trx.Commit() End Using End Sub
Can't find what you're looking for? Ask the community or share your knowledge.