Hi community!
I have a C#-WPF-Application. In this application there is a list of blocks in my drawing. When I click one of these blocks in the listbox I have several button-functions.
One of them:
Select the in the list selected block in the drawing.
Another:
Explode the in the list selected block.
I found several topics with Selection, but none of them worked for me. I'm looking for a selection via ObjectId or blockname of a single block, in the most cases the selectionfilter needs an array(?).
And I'm absolutly helpless with block-explosion.
I'm really new to Autocad and C#-Addin-programming.
Thank you in advance!
Greets
Klaus
Solved! Go to Solution.
Solved by chiefbraincloud. Go to Solution.
This blog post explains the selection filter mechanism
http://through-the-interface.typepad.com/through_the_interface/2008/07/conditional-sel.html
What you basically need for the selection filter (by blockname) is:
TypedValue[] SSfilter = { new TypedValue(0, "INSERT"), new TypedValue(2, "BLOCKNAME") };
If you have objectid(s) and want them to be selected in the editor, you use editor.SetImpliedSelection(objectid())
Assuming you are using the managed API, there are two ways to explode a BlockReference, one is the Explode method inherited from entity. You pass in a DbObjectCollection, and AutoCAD fills the collection with the entities generated from the explode. You are then responsible for either adding them to the database, or disposing of them when you are done, depending on your needs.
The other is BlockReference.ExplodeToOwnerSpace(). That one automatically appends the generated entities to the parent of the block reference. This could be the Model space, or a paper space layout, or even another block if the block was nested. The Block must be uniformly scaled in both methods.
Okay, first problem: Selection:
I tried the Selection like this:
public void selectBlock(string blockName) { BlockTableRecord btr = null; Editor ed = doc.Editor; using (tr = db.TransactionManager.StartTransaction()) { BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForWrite); foreach (ObjectId objId in bt) { btr = (BlockTableRecord)tr.GetObject(objId, OpenMode.ForWrite); if (blockName == btr.Name) { TypedValue[] tvs = new TypedValue[] { new TypedValue( (int)DxfCode.Operator, "<or" ), new TypedValue ( (int)DxfCode.BlockName, btr.Name ), new TypedValue( (int)DxfCode.Operator, "or>" ) }; SelectionFilter sf = new SelectionFilter(tvs); PromptSelectionResult psr = ed.SelectAll(sf); ed.WriteMessage("\nFound {0} entit{1}.", psr.Value.Count, (psr.Value.Count == 1 ? "y" : "ies")); } } } }
I know, I could simply give the blockName to the DxfCode.BlockName-variable without foreach and so on, but I also tried it with objectId. Hmm, theeditor-message shows up that AutoCAD finds one entity. But this entity isn't selected!?
But Kean Walmsley also writes in his blog: " This simply tells you how many entities met the selection criteria - it doesn't leave them selected for use by further commands.".
Okay, now... How do I leave it selected for use by further commands?
I've also tried the selection like you posted it with :
new TypedValue(0, "INSERT"), new TypedValue(2, blockName)
If this is, what you've meant, it doesn't work for me either.
And I didn't get your point with the objectId-selection, my editor wants an array of ObjectIds. Should I create an Array of ObjectIds with the objectid of the block on the first position?
But if I get it working with the blockname, I do not need the selection by objectid. I simply want the easiest way to select the block.
__________________________________
Second problem: Explosion
My block is a BlockTableRecord. How do I get a BlockReference to work with my BlockTableRecord?
Many thanks!
Klaus
Well, I've already done all this once, then when I went to post it, the discussion group kicked me out, so here goes again.
First, I think you need to do a little research on the difference between a BlockTableRecord, and a BlockReference. It has been covered on this discussion group so I'm not going to get into here, especially because I have no idea what your experience level with AutoCAD is.
Kean's comment "This simply tells you how many entities met the selection criteria - it doesn't leave them selected for use by further commands." Means that the entities are not left selected in the editor, there is a selection set created (psr.value) that can be used by code to do other things with the selection, they just don't appear to be selected to the user after function ends.
I have worked up these two quick examples of how to get the objects you want and leave them selected in the editor after your command completes. One piece that is not shown here is the the CommandMethod that is called to use this code must have the "CommandFlags.Redraw" set.
public void selectBlock(string blockName) { Database db = HostApplicationServices.WorkingDatabase; Document doc = Application.DocumentManager.MdiActiveDocument; Editor ed = doc.Editor; using (Transaction trans = db.TransactionManager.StartTransaction()) { try { BlockTable bt = (BlockTable)trans.GetObject(db.BlockTableId, OpenMode.ForRead, false, true); if (bt.Has(blockName)) { ObjectId btrid = bt[blockName]; if (!btrid.IsEffectivelyErased) { BlockTableRecord btr = (BlockTableRecord)trans.GetObject(btrid, OpenMode.ForRead, false, true); ObjectIdCollection brefIDs = btr.GetBlockReferenceIds(true, false); ObjectId[] oids = new ObjectId[brefIDs.Count]; brefIDs.CopyTo(oids, 0); ed.SetImpliedSelection(oids); } } trans.Commit(); } catch (System.Exception ex) { // } } } public void selectBlock2(string blockName) { Database db = HostApplicationServices.WorkingDatabase; Document doc = Application.DocumentManager.MdiActiveDocument; Editor ed = doc.Editor; TypedValue[] tvs = new TypedValue[] { new TypedValue(0, "INSERT"), new TypedValue(2, blockName) }; SelectionFilter sf = new SelectionFilter(tvs); PromptSelectionResult psr = ed.SelectAll(sf); if (psr.Status == PromptStatus.OK) { ed.SetImpliedSelection(psr.Value.GetObjectIds()); } }
I would use the second method, as it does not require a transaction, but the first method does demonstrate something that may be useful for your Explode problem.
You can not call Explode on a BlockTableRecord. You also can not "Select" them using selection methods or filters. There is no graphical representation of a BlockTableRecord.
So you say you have a BlockTableRecord. If all you want is to find out what is in that BlockTableRecord, you can iterate through it's contents with a For Each of DbObject. I am assuming that is not what you want, and that what you want is to Explode the BlockReferences that point to that BlockTableRecord. The first example above shows how to get the ObjectIds of all BlockReferences that point to a particular BlockTableRecord. You can then use those ObjectIds to open each BlockReference and call .ExplodeToOwnerSpace if you want to duplicate the behavior of the AutoCAD Explode command (I'm not sure if you have to erase the original block, or if the API does it for you, but the API does add each of the resultant entities to the owner of the BlockReference), or you can call .Explode and the use the results of that explode to do whatever it is you need to do (In which case you must add the objects to the database or dispose of them, and if you want the original block to be erased you have to do that yourself).
Give that a shot. I'll see if I can find a good article describing the difference between a BlockTableRecord and a BlockReference.
@chiefbraincloud wrote:It has been covered on this discussion group so I'm not going to get into here, especially because I have no idea what your experience level with AutoCAD is.
Hmm, well... My AutoCAD-experiences belong to an half-an-hour introduction of a friend of mine.
It is a small project for my school leaving examination. I have to make a little plugin for a company, it isn't really bad for them if it is only a prototype.
Many thanks, the Selection works like a charm!
I tried the explosion like this:
public void explodeBlock(string blockName) { Editor ed = doc.Editor; using (tr = db.TransactionManager.StartTransaction()) { BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead, false, true); if (bt.Has(blockName)) { ObjectId btrid = bt[blockName]; if (!btrid.IsEffectivelyErased) { BlockTableRecord btr = (BlockTableRecord)tr.GetObject(btrid, OpenMode.ForRead, false, true); ObjectIdCollection brefIds = btr.GetBlockReferenceIds(true, false); ObjectId[] oids = new ObjectId[brefIds.Count]; brefIds.CopyTo(oids, 0); foreach (ObjectId objid in brefIds) { BlockReference br = (BlockReference)tr.GetObject(objid, OpenMode.ForWrite); br.ExplodeToOwnerSpace(); } } } } }
I do not really realize if something has changed, can I check this anywhere?
Greets
Klaus
first: these two lines are useless in your function
ObjectId[] oids = new ObjectId[brefIds.Count];
brefIds.CopyTo(oids, 0);
Second, I would definitely put the ExplodeToOwnerSpace() method inside a Try/Catch, because if the block has a non-uniform scale (ie. the X, Y, and Z scales are not the same) which is not that uncommon, the ExplodeToOwnerSpace method will fail.
To see the change, try using your selection tool to select all references of a block. Look at the drawing window, and you should see some grips (little colored squares), by default there should be one grip per block (but there is a user setting that would cause more grips to show up).
then zoom in to where you can see one or more of the blocks.
Run your explode tool on the same blockName. Then you can perform two tests. one, try using the mouse and selecting a part of the block you have in your view. If it is exploded, you will now have individual entities (lines, arcs, circles, etc...) instead of multiple entities grouped into a single block, and you will see more than one grip, and the whole block will not be selected, but just the entity or entities you pick.
test two, try running your select tool again on the same block. If the original blocks were erased when they were exploded, you will get zero selected objects. If not you will have to add a line of code in your for each to erase them manually.
Basically not being able to see a difference should be a good thing (if you aren't getting any errors) but the help docs are not clear about whether the original block is erased by ExplodeToOwnerSpace, so you could have a bunch of new entities created right on top of the old block, but what you really want is a bunch of new entities, and no more old block.
Hmm for me it does nothing. Not even if i call
br.Erase()
I tried it with the drawing I've got from alfred.neswadba (http://forums.autodesk.com/t5/NET/Edit-a-Block-via-C/m-p/3366541#M27591), you find it attached. Seems not to work.
Oh, the last function I have to implement should copy a block and paste it. For paste there should be a cursor to set the insertpoint for the block (like the usual insert in AutoCAD). How complex would this be?
Greets
Klaus
See if this is working for you,
tested on A2010 only
Public Sub ApplyAttributes(db As Database, tr As Transaction, bref As BlockReference) Dim btrec As BlockTableRecord = TryCast(tr.GetObject(bref.BlockTableRecord, OpenMode.ForRead), BlockTableRecord) If btrec.HasAttributeDefinitions Then Dim atcoll As Autodesk.AutoCAD.DatabaseServices.AttributeCollection = bref.AttributeCollection For Each subid As ObjectId In btrec Dim ent As Entity = DirectCast(subid.GetObject(OpenMode.ForRead), Entity) Dim attDef As AttributeDefinition = TryCast(ent, AttributeDefinition) If attDef IsNot Nothing Then Dim attRef As New AttributeReference() attRef.SetDatabaseDefaults() attRef.SetAttributeFromBlock(attDef, bref.BlockTransform) attRef.Position = attDef.Position.TransformBy(bref.BlockTransform) attRef.Tag = attDef.Tag attRef.TextString = attDef.TextString attRef.AdjustAlignment(db) atcoll.AppendAttribute(attRef) tr.AddNewlyCreatedDBObject(attRef, True) End If Next End If End Sub Public Sub TestInsert() Dim blkname As String = "VT_VZ_VZ52_10a_ATT" ''<-- as per as on your drawing Dim doc As Document = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument Dim ed As Editor = doc.Editor Dim db As Database = doc.Database Try Using docloc As DocumentLock = doc.LockDocument Using tr As Transaction = db.TransactionManager.StartTransaction Dim bt As BlockTable = tr.GetObject(db.BlockTableId, OpenMode.ForRead) If Not bt.Has(blkname) Then MsgBox("Block does not exists") Return End If Dim pto As PromptPointOptions = New PromptPointOptions(vbLf + "Pick a block insertion point: ") Dim ptres As PromptPointResult = ed.GetPoint(pto) Dim ipt As Point3d If ptres.Status <> PromptStatus.Cancel Then ipt = ptres.Value End If Dim btr As BlockTableRecord = DirectCast(tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite, False), BlockTableRecord) Dim blk As BlockTableRecord = DirectCast(tr.GetObject(bt(blkname), OpenMode.ForRead, False), BlockTableRecord) Dim bref As New BlockReference(ipt, blk.ObjectId) bref.BlockUnit = UnitsValue.Millimeters bref.Rotation = 0 bref.ScaleFactors = New Scale3d(1000.0) ''<-- as per as on your drawing btr.AppendEntity(bref) tr.AddNewlyCreatedDBObject(bref, True) ApplyAttributes(db, tr, bref) ed.Regen() tr.Commit() End Using End Using Catch ex As System.Exception MsgBox(ex.Message) End Try End Sub
~'J'~
Okay, Explosion is working!
Just add an tr.Commit() to my function....
To the insert:
I'm not very common with VB, so i translated it with < Convert C# to VB.NET >. What I got is (in your TestInsert()-method):
PromptPointOptions pto = new PromptPointOptions(Constants.vbLf + "Pick a block insertion point: ");
Hmm Constants."anything" doesn't tell me anything, so i've deleted "Constants.", but vbLf is nowhere declared in this method. How do you use it?
The second thing:
BlockTableRecord blk = (BlockTableRecord)tr.GetObject(bt(blkname), OpenMode.ForRead, false);
'bt' is a variable but used like a method, can you explain this to me?
And many thanks to chiefbraincloud for your help at Selection & Explosion!
Greets
Klaus
Oops. I didn't even look to see if you had the .commit in there.
vbLf is a constant for a line feed character (in Visual Basic), I don't know if C# has a similar set of constants, but I know you can use chr(13).
2nd prob should be square braces instead of parenthesis:
BlockTableRecord blk = (BlockTableRecord)tr.GetObject(bt[blkname], OpenMode.ForRead, false);
I didn't look at Hallex's code other than what you posted as conversion problems.
Sorry, try converted on C# instead
public static void ApplyAttributes(Database db, Transaction tr, BlockReference bref) { BlockTableRecord btrec = tr.GetObject(bref.BlockTableRecord, OpenMode.ForRead) as BlockTableRecord; if (btrec.HasAttributeDefinitions) { Autodesk.AutoCAD.DatabaseServices.AttributeCollection atcoll = bref.AttributeCollection; foreach (ObjectId subid in btrec) { Entity ent = (Entity)subid.GetObject(OpenMode.ForRead); AttributeDefinition attDef = ent as AttributeDefinition; if (attDef != null) { AttributeReference attRef = new AttributeReference(); attRef.SetDatabaseDefaults(); attRef.SetAttributeFromBlock(attDef, bref.BlockTransform); attRef.Position = attDef.Position.TransformBy(bref.BlockTransform); attRef.Tag = attDef.Tag; attRef.TextString = attDef.TextString; attRef.AdjustAlignment(db); atcoll.AppendAttribute(attRef); tr.AddNewlyCreatedDBObject(attRef, true); } } } } [CommandMethod("insBlock", CommandFlags.Modal | CommandFlags.UsePickSet)] public void TestInsert() { string blkname = "VT_VZ_VZ52_10a_ATT"; //'<-- as per as on your drawing Document doc = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument; Editor ed = doc.Editor; Database db = doc.Database; try { using (DocumentLock docloc = doc.LockDocument()) { using (Transaction tr = db.TransactionManager.StartTransaction()) { BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); if (!bt.Has(blkname)) { ed.WriteMessage("\nBlock does not exists"); return; } PromptPointOptions pto = new PromptPointOptions("\nPick a block insertion point: "); PromptPointResult ptres = ed.GetPoint(pto); Point3d ipt = default(Point3d); if (ptres.Status != PromptStatus.Cancel) { ipt = ptres.Value; } BlockTableRecord btr = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite, false); BlockTableRecord blk = (BlockTableRecord)tr.GetObject(bt[blkname], OpenMode.ForRead, false); BlockReference bref = new BlockReference(ipt, blk.ObjectId); bref.BlockUnit = UnitsValue.Millimeters; bref.Rotation = 0; bref.ScaleFactors = new Scale3d(1000.0); //'<-- as per as on your drawing btr.AppendEntity(bref); tr.AddNewlyCreatedDBObject(bref, true); ApplyAttributes(db, tr, bref); ed.Regen(); tr.Commit(); } } } catch (System.Exception ex) { ed.WriteMessage(ex.Message); } }
~'J'~
I forgot all about "\n".
Shame on me. My lisp programming skills are getting too rusty.
But you know many other stuffs,
Regards,
Oleg
What about Copy-Paste, just a quick code almost not tested
[CommandMethod("copypaste", CommandFlags.Modal | CommandFlags.UsePickSet)] public void CopyPaste() { Document doc = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument; Database db = doc.Database; Editor ed = doc.Editor; Matrix3d ucs = ed.CurrentUserCoordinateSystem; try { using (Transaction tr = db.TransactionManager.StartTransaction()) { PromptEntityOptions peo = new PromptEntityOptions("\nSelect a block >>"); peo.SetRejectMessage("\nSelect block only >>"); peo.AddAllowedClass(typeof(BlockReference), false); PromptEntityResult res; res = ed.GetEntity(peo); if (res.Status != PromptStatus.OK) return; Entity ent = (Entity)tr.GetObject(res.ObjectId, OpenMode.ForRead); if (ent == null) return; ObjectIdCollection ids = new ObjectIdCollection(); ids.Add(ent.ObjectId); ObjectId chkId = ent.ObjectId; BlockReference bref = ent as BlockReference; Point3d pt = Point3d.Origin; BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); BlockTableRecord btr = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite); PromptPointOptions pto = new PromptPointOptions("\nPick a block insertion point: "); PromptPointResult ptres = ed.GetPoint(pto); Point3d ipt=Point3d.Origin; if (ptres.Status != PromptStatus.Cancel) { ipt = ptres.Value; } using (IdMapping map = new IdMapping()) { db.DeepCloneObjects(ids, bref.BlockId, map, false); ObjectId clid = map[bref.ObjectId].Value; BlockReference brefclone = (BlockReference)tr.GetObject(clid, OpenMode.ForWrite); brefclone.Position =ipt; } tr.Commit(); } } catch (System.Exception ex) { ed.WriteMessage(ex.Message); } }
~'J'~
EDIT: Wow, wow, wow, I was reading the first page and didn't see the second page
That solved the second problem!
How should I use chr(13)?
PromptPointOptions pto = new PromptPointOptions(chr(13) + "Pick a block insertion point: ");
gives the same problem
Greets
Klaus
Well, I am surprised the chr function is not automatically in c#, but anyway, Oleg's code reminded me of the appropriate way to send a newline in c# (and lisp) use "\nPick a block insertion point: "
That's great!
The block is pasted dreamlike.
BUT: When I refresh my blocklist, I havn't got a new entry for the pasted block. That means, in my list is only the old entry.
public void setBlockList() { BlockList = new List<String>(); using (tr = db.TransactionManager.StartTransaction()) { BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); foreach (ObjectId objId in bt) { BlockTableRecord btr = (BlockTableRecord)tr.GetObject(objId, OpenMode.ForRead); if (isAttDef(btr)) { BlockList.Add(btr.Name); } } } }
That is my Blocklist-Function.
Do you know why it fails?
Greets
Klaus
I didn't see nothing wrong with your code
Perhaps you might be want to refresh BlockList first
Something like:
public void setBlockList() { List<String> BlockList = new List<String>(); Document doc = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument; Database db = doc.Database; Editor ed = doc.Editor; using (Transaction tr = db.TransactionManager.StartTransaction()) { BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); foreach (ObjectId objId in bt) { BlockTableRecord btr = (BlockTableRecord)tr.GetObject(objId, OpenMode.ForRead); if (btr.HasAttributeDefinitions) // if (isAttDef(btr)) { BlockList.Add(btr.Name); } } } foreach (string bname in BlockList) { ed.WriteMessage("\n{0}", bname); } }
~'J'~
I call my setBlockList()-Function direkt after the Commit. No Copy-Blockname.....
In the commandline (
ed.WriteMessage("\n{0}", bname);
) is only one blockname too, the blockname of blk (using the function you posted).
Greets
Klaus
I don't think it is failing.
I think you still don't quite understand the difference between a BlockReference and a BlockTableRecord.
You are listing BlockTableRecords in your Block List. When you copy/paste, you are creating a new BlockReference pointing to the same BlockTableRecord as the original copied BlockReference. This does not create a new BlockTableRecord.
There can only be one BlockTableRecord with a given name, but there can (and usually will be) many BlockReferences that point to same BlockTableRecord. That is really the point of Blocks, that you store the definition of the block (BlockTableRecord) once in the BlockTable, and then you create multiple instances of the block (BlockReference) as many times as needed in the Model or Paper spaces. This saves space in the database and keeps the drawing size smaller, because the BlockReferences just contain a pointer to the BlockTableRecord, instead of containing all of the information for the lines, polylines and whatever other geometry they might contain.
I'm still looking for the old post of mine that explains the difference in greater detail...
Can't find what you're looking for? Ask the community or share your knowledge.