View Full Version : Help with deleting multiple copies of blocks (not anon) with selection set
cmccartney
2012-10-18, 10:27 PM
As part of a larger routine ive inserted multiple copies of four different blocks (all named, none anon), so now im trying to delete them all at once when im done with them. so i figured create a selection set, the erase. im i going about it all wrong? heres the code for the eraser so far:
Private Sub eraser()
Dim doc As Document = Application.DocumentManager.MdiActiveDocument
Dim db As Database = doc.Database
Dim ed As Editor = doc.Editor
Using tr As Transaction = db.TransactionManager.StartTransaction()
Dim mytvs(3) As TypedValue
mytvs(3) = New TypedValue(2, "Standard")
mytvs(3) = New TypedValue(2, "Compact")
mytvs(3) = New TypedValue(2, "Handicap")
mytvs(3) = New TypedValue(2, "Van")
Dim myfilter As New SelectionFilter(mytvs)
Dim mypsr As PromptSelectionResult = ed.SelectAll(myfilter)
If mypsr.Status = PromptStatus.OK Then
Dim sset As SelectionSet = mypsr.Value
Dim bt As BlockTable = tr.GetObject(db.BlockTableId, OpenMode.ForRead)
Dim btr As BlockTableRecord = bt(sset.GetObjectIds.ToString).GetObject(OpenMode.ForRead)
For Each obid As ObjectId In btr.GetBlockReferenceIds(True, True)
Dim ent As Entity = obid.GetObject(OpenMode.ForWrite)
ent.Erase()
Next
End If
tr.Commit()
End Using
End Sub
i think the highlited portion is the problem.
BlackBox
2012-10-19, 12:41 AM
Rather than attempting to create a selection set of the blocks added to the Database during your Transaction's Using statement (because you only need one Transaction), testing that the selection set is valid, iterating the selection set, and deleting each ObjectId... Perhaps it would be simpler to instead just store the Block's ObjectId to an IEnumerable, public static List<ObjectId>, or public static List<Entity> Property Object at the time the Block's are added to the Database, that can then be used to 'clean up' after the fact?
BlackBox
2012-10-19, 12:49 AM
In the event the C# jargon doesn't make sense:
Pseudo-code (C#):
public static List<ObjectId> myBlocks { get; set; }
Pseudo-code (VB.NET, converted using developerFusion (http://www.developerfusion.com/tools/convert/csharp-to-vb/)):
Public Shared Property myBlocks() As List(Of ObjectId)
Get
Return m_myBlocks
End Get
Set
m_myBlocks = Value
End Set
End Property
Private Shared m_myBlocks As List(Of ObjectId)
_gile
2012-10-19, 08:54 AM
Hi,
I agree with RenderMan, the best way is to store the BlockReference ObjectIdS in a collection (ObjectIdCollection) when they're added to the Database. But there's no need to use a class property as the ObjectIdCollection won't be got from outside of the class, a private field would do the trick (and don't set the field static/Shared which means per session rather than per document).
If you want to keep on the SelectionSet route, change your selection filter building to :
Dim mytvs(1) As TypedValue
mytvs(0) = New TypedValue(0, "INSERT")
mytvs(1) = New TypedValue(2, "Standard,Compact,Handicap,Van")
Dim myfilter As New SelectionFilter(mytvs)
And don't use the BlockTableRecord.GetBlockReferenceIds() method, just iterate the Selection set :
Dim sset As SelectionSet = mypsr.Value
For Each obid As ObjectId In sset.GetObjectId()
Dim ent As Entity = obid.GetObject(OpenMode.ForWrite)
ent.Erase()
Next
If you want to go on the BlockTableRecord.GetBlockReferenceIds() method, you do not need a selection set, iterate the BlockTable looking for BlockTableRecord which Name matches the ones you're looking for, the get the references ObjectId, open them and erase them.
cmccartney
2012-10-19, 03:20 PM
I'm definetly open to not using a selection set. hrmm so your saying create an objectidcollection within my block insertion sub, then pass that collection to the eraser sub? Insertion and deletion are performed with seperate button click events if that matters.
BlackBox
2012-10-19, 04:20 PM
But there's no need to use a class property as the ObjectIdCollection won't be got from outside of the class, a private field would do the trick (and don't set the field static/Shared which means per session rather than per document).
Perhaps my logic is flawed (I am still only learning)... How is using a non-static (per-Document) Field better, more efficient, etc. than using a single static List<ObjectId>, which in theory *should* invoke the Add() Method at the time the BlockReference is added to the Database, and invokes the Remove() Method when done (perhaps in a Finally block?)...??? :confused:
I'm definetly open to not using a selection set. hrmm so your saying create an objectidcollection within my block insertion sub, then pass that collection to the eraser sub? Insertion and deletion are performed with seperate button click events if that matters.
So long as the ObjectId Collection remains in scope (which it *should*, if you're passing it as a parameter)... Yes, that is correct (AFAIK).
_gile
2012-10-19, 04:20 PM
If your insertion sub and your eraser sub are within the same class (as they should), within this class you can define a private field in this class (out of any method). This field can be considered as global variable within the class and can be accessed by both subs.
Public Class Dialog
Private blocks As ObjectIdCollection
Private Sub btnInsert_Click(sender As Object, e As EventArgs) Handles btnInsert.Click
' ...
Dim id As ObjectId = modelSpace.AppendEntity(blockRef)
blocks.Add(id)
' ...
End Sub
Private Sub btnErase_Click(sender As Object, e As EventArgs) Handles btnErase.Click
' ...
For Each id As ObjectId In blocks
Dim br As BlockReference = id.GetObject(OpenMode.ForWrite)
br.Erase()
Next
blocks.Clear()
' ...
End Sub
' ...
End Class
_gile
2012-10-19, 05:17 PM
Perhaps my logic is flawed (I am still only learning)... How is using a non-static (per-Document) Field better, more efficient, etc. than using a single static List<ObjectId>, which in theory *should* invoke the Add() Method at the time the BlockReference is added to the Database, and invokes the Remove() Method when done (perhaps in a Finally block?)...???
I'll try to explain my point.
Firstly, I think it's not necessary to define a property as long as the data won't be used from outside of the class. A class field is sufficient. This field may be any type which suits the purpose: a List<ObjectId> (List(Of ObjectId) in VB) or an ObjectIdCollection.
Secondly I think it is not a good practice to use the static (Shared in VB) modifier when there's no need too (whatever the modifier applies to: field or property). Mostly, in this case where dealing with ObjectId which are document resident.
Try this little snippet, in a drawing run TEST1 and TEST2, in the same session, if you open a new drawing and run TEST2 (whatever you've run TEST1 or not in this session) an exception will occur.
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
namespace StaticFieldTest
{
public class Commands
{
private static ObjectIdCollection staticIds = new ObjectIdCollection();
[CommandMethod("Test1")]
public void Test1()
{
Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
PromptEntityResult per = ed.GetEntity("\nSelect an entity: ");
if (per.Status == PromptStatus.OK)
staticIds.Add(per.ObjectId);
}
[CommandMethod("Test2")]
public void Test2()
{
Database db = HostApplicationServices.WorkingDatabase;
if (staticIds == null)
Application.ShowAlertDialog("Null");
else
foreach (ObjectId id in staticIds)
using (Entity ent = (Entity)id.Open(OpenMode.ForWrite))
{
ent.Highlight();
}
}
}
}
BlackBox
2012-10-19, 05:22 PM
I'll try to explain my point.
Thanks for clarifying, Gile... Great job (as always). :beer:
cmccartney
2012-10-19, 10:19 PM
Hrmm i think i missing something, i was unable to get this to work using either way. although i am thinking selection set my be the way to go afterall as i want to be able to delete these blocks even if they were already in teh drawing at the start of teh acad session (if i started placing, but then interrupted and had to come back later). so heres the insertion code as it stood before i attempted your suggestions. thoughts? and thanks again for the help, i am definetly learning things from this :)
Imports System
Imports Autodesk.AutoCAD.Runtime
Imports Autodesk.AutoCAD.ApplicationServices
Imports Autodesk.AutoCAD.DatabaseServices
Imports Autodesk.AutoCAD.Geometry
Imports Autodesk.AutoCAD.EditorInput
Public Class StallCounterInterface
Private Sub StallCounterInterface_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Counter()
End Sub
Public Sub blocktest(ByVal BlockName As String)
Dim doc As Document = Application.DocumentManager.MdiActiveDocument
Dim db As Database = doc.Database
Dim ed As Editor = doc.Editor
'test if block exists
Using tr As Transaction = db.TransactionManager.StartTransaction()
Dim bt As BlockTable = tr.GetObject(db.BlockTableId, OpenMode.ForRead)
'insert source file if block not found
If Not bt.Has(BlockName) Then
Dim origin As New Point3d(0, 0, 0)
Dim filename As String = "C:\mkacadd_Hybrid\Civil3D\CAD data\Support\Blocks\StallCount.dwg"
ed.WriteMessage(vbLf & BlockName & " block does not exist, now inserting source file!")
Using dbdwg As New Database(False, True)
dbdwg.ReadDwgFile(filename, IO.FileShare.Read, True, "")
Dim blkid As ObjectId
blkid = db.Insert(filename, dbdwg, True)
Dim obr As New BlockReference(origin, blkid)
Dim obtr As BlockTableRecord = tr.GetObject(bt(BlockTableRecord.ModelSpace), OpenMode.ForWrite)
obtr.AppendEntity(obr)
tr.AddNewlyCreatedDBObject(obr, True)
'erases source file
obr.Erase(True)
tr.Commit()
End Using
End If
End Using
'initiate loop for cotninous block placement unitl user cancels
Dim LoopControl As Boolean = True
Do While LoopControl
doc.LockDocument()
Me.Hide()
Using tr As Transaction = db.TransactionManager.StartTransaction()
Dim bt As BlockTable = tr.GetObject(db.BlockTableId, OpenMode.ForRead)
Dim id As ObjectId = bt(BlockName)
' Get insertion point
Dim ppo As New PromptPointOptions(vbLf & "Specify insertion point, press [ESC] to return:")
Dim ppr As PromptPointResult = ed.GetPoint(ppo)
' Exit if the user presses ESC or cancels the command
If ppr.Status = PromptStatus.Cancel Then Exit Do
If ppr.Status = PromptStatus.OK Then
Dim pts As Point3d = ppr.Value
Dim ptstr As New Point2d(ppr.Value.X, ppr.Value.Y)
Dim ppor As New PromptPointOptions(vbLf & "Specify Rotation point:")
'rubber band to show rotation
ppor.UseBasePoint = True
ppor.BasePoint = pts
Dim pprr As PromptPointResult = ed.GetPoint(ppor)
Dim ptend As New Point2d(pprr.Value.X, pprr.Value.Y)
' Create a block reference
Dim br As New BlockReference(pts, id)
' Get Model space
Dim btr As BlockTableRecord = tr.GetObject(bt(BlockTableRecord.ModelSpace), OpenMode.ForWrite)
' Add the block reference to Model space
btr.AppendEntity(br)
tr.AddNewlyCreatedDBObject(br, True)
'Rotate block based on insertion and rotation points
Dim curucsmat As Matrix3d = ed.CurrentUserCoordinateSystem
Dim curUCS As CoordinateSystem3d = curucsmat.CoordinateSystem3d
Dim ang = ptstr.GetVectorTo(ptend).Angle.ToString()
Dim rot = Math.Truncate(ang * 10000) / 10000
br.TransformBy(Matrix3d.Rotation(rot, curUCS.Zaxis, New Point3d(ppr.Value.X, ppr.Value.Y, 0)))
tr.Commit()
End If
End Using
Loop
Counter()
Me.Show()
End Sub
Private Sub Counter()
Dim doc As Document = Application.DocumentManager.MdiActiveDocument
Dim ed As Editor = doc.Editor
Dim numS As Integer = 0
Dim numC As Integer = 0
Dim numH As Integer = 0
Dim numV As Integer = 0
Dim numT As Integer = 0
Using tr As Transaction = doc.TransactionManager.StartTransaction()
Dim mytvs(0) As TypedValue
mytvs(0) = New TypedValue(0, "Insert")
Dim myfilter As New SelectionFilter(mytvs)
Dim mypsr As PromptSelectionResult = ed.SelectAll(myfilter)
If mypsr.Status = PromptStatus.OK Then
Dim sset As SelectionSet = mypsr.Value
For Each id As ObjectId In mypsr.Value.GetObjectIds
Dim br As BlockReference = id.GetObject(OpenMode.ForRead)
Select Case br.Name
Case "Standard"
numS = numS + 1
Case "Compact"
numC = numC + 1
Case "Handicap"
numH = numH + 1
Case "Van"
numV = numV + 1
Case Else
End Select
numT = numS + numC + numH + numV
Next
End If
tr.Commit()
End Using
StdCnt.Text = numS
ComCnt.Text = numC
HanCnt.Text = numH
VanCnt.Text = numV
TtlCnt.Text = numT
End Sub
Private Sub eraser()
End Sub
Private Sub StdBtn_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles StdBtn.Click
Dim BlockName As String = "Standard"
blocktest(BlockName)
End Sub
Private Sub CompBtn_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles CompBtn.Click
Dim BlockName As String = "Compact"
blocktest(BlockName)
End Sub
Private Sub VanBtn_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles VanBtn.Click
Dim BlockName As String = "Van"
blocktest(BlockName)
End Sub
Private Sub HandBtn_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles HandBtn.Click
Dim BlockName As String = "Handicap"
blocktest(BlockName)
End Sub
Private Sub ExitBtn_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ExitBtn.Click
Me.Close()
'set current layer to layer before app was run.
Application.SetSystemVariable("clayer", Laycurrent.laycur)
End Sub
Private Sub Erasebtn_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Erasebtn.Click
eraser()
End Sub
End Class
_gile
2012-10-20, 03:27 PM
Hi,
Here's what could be your 'Eraser' method. I also made some changes (removed unusefull statements) to the BlockTest one.
It seems to work (VB is not at all my favorite language).
Private Sub Eraser()
Dim doc As Document = Application.DocumentManager.MdiActiveDocument
Dim db As Database = doc.Database
Dim ed As Editor = doc.Editor
Dim filter As TypedValue() = {New TypedValue(0, "INSERT"), New TypedValue(2, "Standard,Compact,Handicap,Van")}
Dim psr As PromptSelectionResult = ed.SelectAll(New SelectionFilter(filter))
If psr.Status <> PromptStatus.OK Then
Return
End If
Using doc.LockDocument()
Using tr As Transaction = db.TransactionManager.StartTransaction()
For Each id As ObjectId In psr.Value.GetObjectIds()
Dim br As BlockReference = DirectCast(tr.GetObject(id, OpenMode.ForWrite), BlockReference)
br.[Erase]()
Next
tr.Commit()
End Using
End Using
End Sub
Private Sub BlockTest(blockName As String)
Dim doc As Document = Application.DocumentManager.MdiActiveDocument
Dim db As Database = doc.Database
Dim ed As Editor = doc.Editor
Using doc.LockDocument()
Using tr As Transaction = db.TransactionManager.StartTransaction()
Dim blockTable As BlockTable = DirectCast(tr.GetObject(db.BlockTableId, OpenMode.ForRead), BlockTable)
If Not blockTable.Has(blockName) Then
ed.WriteMessage(vbLf & "{0} block does not exist, now inserting source file!", blockName)
Dim filename As String = "C:\mkacadd_Hybrid\Civil3D\CAD data\Support\Blocks\StallCount.dwg"
Dim blkId As ObjectId
Using dbdwg As New Database(False, True)
dbdwg.ReadDwgFile(filename, FileShare.Read, True, "")
blkId = db.Insert(Path.GetFileNameWithoutExtension(filename), dbdwg, True)
End Using
' Purge StallCount block
Dim btr As BlockTableRecord = DirectCast(tr.GetObject(blkId, OpenMode.ForWrite), BlockTableRecord)
btr.[Erase]()
End If
Me.Hide()
Dim id As ObjectId = blockTable(blockName)
Dim modelSpace As BlockTableRecord = _
DirectCast(tr.GetObject(blockTable(BlockTableRecord.ModelSpace), OpenMode.ForWrite), BlockTableRecord)
Dim ppo As New PromptPointOptions(vbLf & "Specify insertion point, (press ESC, SHIFT or ENTER to return): ")
ppo.AllowNone = True
Dim pao As New PromptAngleOptions(vbLf & "Specifiy the rotation: ")
pao.UseBasePoint = True
pao.DefaultValue = 0.0
pao.UseDefaultValue = True
While True
Dim ppr As PromptPointResult = ed.GetPoint(ppo)
If ppr.Status <> PromptStatus.OK Then
Exit While
End If
Dim pts As Point3d = ppr.Value
pao.BasePoint = pts
Dim pdr As PromptDoubleResult = ed.GetAngle(pao)
If pdr.Status = PromptStatus.Cancel Then
Exit While
End If
Dim angle As Double = pdr.Value
Dim br As New BlockReference(pts, id)
br.Rotation = angle
br.TransformBy(ed.CurrentUserCoordinateSystem)
modelSpace.AppendEntity(br)
tr.AddNewlyCreatedDBObject(br, True)
db.TransactionManager.QueueForGraphicsFlush()
End While
tr.Commit()
End Using
End Using
Counter()
Me.Show()
End Sub
cmccartney
2012-10-23, 03:29 PM
ahhh. i think i forgot the "doc.lockdocument" part when i was trying earlier. so got it to work now. One oddity, i assume this is happening because its being initiated from a windows form, but it does not actually erase anything until after the form is closed. Which is fine since erasing everything should be the last thing you are doing anyways.
Private Sub eraser()
Dim doc As Document = Application.DocumentManager.MdiActiveDocument
Dim db As Database = doc.Database
Dim ed As Editor = doc.Editor
Dim filter As TypedValue() = {New TypedValue(0, "INSERT"), New TypedValue(2, "Standard,Compact,Handicap,Van")}
Dim psr As PromptSelectionResult = ed.SelectAll(New SelectionFilter(filter))
If psr.Status <> PromptStatus.OK Then
Return
End If
doc.LockDocument()
Using tr As Transaction = db.TransactionManager.StartTransaction()
For Each id As ObjectId In psr.Value.GetObjectIds()
Dim br As BlockReference = DirectCast(tr.GetObject(id, OpenMode.ForWrite), BlockReference)
br.Erase()
Next
tr.Commit()
End Using
Me.Close()
Application.SetSystemVariable("clayer", Laycurrent.laycur)
End Sub
Thanks again for the help guys.
_gile
2012-10-23, 06:37 PM
You should explicitly call Dispose() on the doc.LockDocument()
Using doc.LockDocument()
'...
End Using
Powered by vBulletin® Version 4.2.5 Copyright © 2025 vBulletin Solutions Inc. All rights reserved.