Class 8 - Selection Sets
On this class we will cover the first ways we can interact with user to allow our application to get information from drawing screen You probably will need to use this method inside your application.
Introduction
This is one of the most important ways to interact with user because it will allow you to get information from drawing screen through selected entities. Some times you will request user to select entities individually and sometimes you will select them using a filter.
A selection set is a group of entities which are currently selected by an user or by an application. The most important concept involved when selecting entities from screen is that AutoCAD will return their names through a type called ads_name. This type contains the entity name (which is valid only on the current session) and it can be converted to ObjectId using the acdbGetObjectId() global function:
Acad::ErrorStatus acdbGetObjectId (AcDbObjectId& objId, const ads_name objName);
The selection is made using a function called acedSSGet() which will apply a selection or prompt the user to do that. The function signature is:
int acedSSGet (const ACHAR *str, const void *pt1, const void *pt2,
const struct resbuf *entmask, ads_name ss);
It receives a selection option, two points, a mask and returns the resulting selection set. After use the selection set it needs to be released and this is done through the acedSSFree() function The selection option will instruct AutoCAD interface to do one of the following methods:
Selection Code | Description |
|---|---|
NULL | Single-point selection (if pt1 is specified) |
# | Nongeometric (all, last, previous) |
:$ | Prompts supplied |
. | User pick |
:? | Other callbacks |
A | All |
B | Box |
C | Crossing |
CP | Crossing Polygon |
:D | Duplicates OK |
:E | Everything in aperture |
F | Fence |
G | Groups |
I | Implied |
:K | Keyword callbacks |
L | Last |
M | Multiple |
P | Previous |
:S | Force single object selection only |
W | Window |
WP | Window Polygon |
X | Extended search (search whole database) |
This way we can perform the selection by several ways. Some examples are presented below:
ads_point pt1, pt2;
ads_name ssname;
pt1[X] = pt1[Y] = pt1[Z] = 0.0;
pt2[X] = pt2[Y] = 5.0; pt2[Z] = 0.0;// Get the current PICKFIRST or ask user for a selection
acedSSGet(NULL, NULL, NULL, NULL, ssname);// Get the current PICKFIRST set
acedSSGet(_T("I"), NULL, NULL, NULL, ssname);// Repeat the previous selection set
acedSSGet(_T("P"), NULL, NULL, NULL, ssname);// Selects the last created entity
acedSSGet(_T("L"), NULL, NULL, NULL, ssname);// Selects entity passing through point (5,5)
acedSSGet(NULL, pt2, NULL, NULL, ssname);// Selects entities inside the window from point (0,0) to (5,5)
acedSSGet(_T("W"), pt1, pt2, NULL, ssname);
Using Selection filters
Filters are a powerful way to speed up selection sets and avoid runtime operations to verify entities. You can use single filters or composed filters. Each filter is specified through a structure called resbuf. A resbuf is a linked list which store several types of information and may contains several items. To use a filter we need to construct it and pass it as a parameters of acedSSGet() method. The selection is performed but each selected entity will need to respect the filter. There are a lot of filters we can create and the SDK documentation cover all of them. The most used examples are presented below:
struct resbuf eb1, eb2;
TCHAR sbuf1[10], sbuf2[10];
ads_name ssname1, ssname2;eb1.restype = 0; // Entity name filter
_tcscpy(sbuf1, _T("CIRCLE"));
eb1.resval.rstring = sbuf1;
eb1.rbnext = NULL;
// Retrieve all circles
acedSSGet(_T("X"), NULL, NULL, &eb1, ssname1);eb2.restype = 8; // Layer name filter
_tcscpy(sbuf2, _T("0"));
eb2.resval.rstring = sbuf2;
eb2.rbnext = NULL;
// Retrieve all entities on layer 0
acedSSGet(_T("X"), NULL, NULL, &eb2, ssname2);
Modifying entities through a selection set
To modify entities inside a selection set we need to walk through selection items, get each one, convert the ads_name to an ObjectId, open the entity for write, modify it and then close it. This operation can also be done using a transaction which is, in long operations, much better.
To show you how to walk through a selection set I will present a short code to select all CIRCLE entities inside the drawing and then change its color to red. The operation is pretty simple and is done this way:
// Construct the filter
struct resbuf eb1;
TCHAR sbuf1[10];
eb1.restype = 0; // Entity name
_tcscpy(sbuf1, _T("CIRCLE"));
eb1.resval.rstring = sbuf1;
eb1.rbnext = NULL;
// Select All Circles
ads_name ss;
if (acedSSGet(_T("X"), NULL, NULL, &eb1, ss) != RTNORM){
acutRelRb(&eb1);
return;
// Free the resbuf
acutRelRb(&eb1);
// Get the length (how many entities were selected)
long length = 0;
if ((acedSSLength( ss, &length ) != RTNORM) (length == 0)) {
acedSSFree( ss );
return;
ads_name ent;
AcDbObjectId id = AcDbObjectId::kNull;
// Walk through the selection set and open each entity
for (long i = 0; i < length; i++) {
if (acedSSName(ss,i,ent) != RTNORM) continue;
if (acdbGetObjectId(id,ent) != Acad::eOk) continue;
AcDbEntity* pEnt = NULL;
if (acdbOpenAcDbEntity(pEnt,id,AcDb::kForWrite) != Acad::eOk)
continue;
// Change color
pEnt->setColorIndex(1);
pEnt->close();
// Free selection
acedSSFree( ss );
I have used some new functions (like acdbOpenAcDbEntity) that are also part of ObjectARX SDK. Pay attention to the memory releases regarding to selection set and resbuf types. Note that I have used also a function called acedSSLength() to get the length of selection set.
See you next class.




18 Comments:
Hi Fernando,
There is a logical operator missing where reads:
// Get the length (how many entities were selected)
long length = 0;
if ((acedSSLength( ss, &length ) != RTNORM) (length == 0)) {
acedSSFree( ss );
return;
}
It has to be something like:
if ((acedSSLength( ss, &length ) != RTNORM) || (length == 0)) {
acedSSFree( ss );
return;
}
Regards,
Luis.
BTW, do you happen to have some sample about on how to compare let say "POINT"s and being able to delete them if they are duplicated?
Also, what do I need to do in order to use, this:
if (acedSSLength(ss, &length) != RTNORM)
return BAD;
I get an error if I use "BAD" on the return statement... I was just trying to use the samples on the ARX Dev help
By
Anonymous, at 8:11 PM
Hi Luis,
Thank you. I have updated the sample. Sometimes Blog HTML editor eats some chars...
Regarding to point comparison, be careful on doing this. Each point coodinate is a double which does not have a exact value. The 0(zero) can be sometimes 0.0 or 0.00000001, etc. This will depend on your code implementation and compiler settings. To avoid problems you should use AcGe library point comparison functions that allows you to pass a tolerance to compare point's coordinates. Another approach is to create your own comparison function. It is up to you!
Regarding to the BAD symbol, it is not defined in ObjectARX samples or SDK. You just need to define it or return your own error code. From SDK documentation:
"Note The ObjectARX library doesn't define the values GOOD and BAD, which appear as return values in the code samples throughout this guide (especially in error-handling code). You can define them if you want, or substitute a convention that you prefer."
Regards,
Fernando.
By
Fernando Malard, at 8:50 PM
Hi Fernando,
How do I get the block name of a block selected by the user. I am using the acedSSGet() function to allow the user to select a block. Also, if the user selects a multi line text, then I want to know the string value of the text.
regards,
Jatin.
By
Anonymous, at 6:21 AM
Hi Jatin,
Block entities are reflex of their parent BlockTableRecord. So the block name is the name of the entity's parent BTR. To get it you need to first get its ObjectId which is stored inside the block entity (AcDbBlockReference).
Call the blockId() method over the block entity and open it (for read) as an AcDbBlockTableRecord. Then use the method:
Acad::ErrorStatus getName( char*& pName) const;
It will return the name of your block.
Regarding to the multiline text, it is an AcDbMText entity. There is a method called:
char* contents() const;
For further information, take a look at these entity's classes.
Regards,
Fernando.
By
Fernando Malard, at 8:53 AM
Hi Fernando,
I am a newbie and so difficult to understand your explanation. Can I send you part of my code on your yahoo Id. Maybe you can add a couple of lines/comments to that so that it will make sense to me. My id is kulkarni.jatin@rediffmail.com
Regards,
Jatin
By
Anonymous, at 5:49 PM
Hi
I have a problem with selection set using acedSSGet.
What I'm trying to do is check whether there is 1 or more entities in
certain area. I've read documentation and figuread out that it would be
best to choose either "W" or "C" parameter. I don't know if I'm getting
it right but I think that when I use "W" the whole entity must be
selected [like in AutoCAD2007 selecting the right direction] and in "C"
anything that passes through selection area will be selected [like
selecting the left direction in autocad].
That's the theory.
I wrote something like this:
pt1[X] = 1.0; pt1[Y] = 131.0; pt1[Z] = 171.0; //temp2
pt2[X] = -1.0; pt2[Y] = 19.0; pt2[Z] = 169.0; //temp2
if (acedSSGet(_T("C"), pt1, pt2, NULL, ssname)!= RTNORM){...} //temp2
in order to select anything that passes through point 0,130,170 or
0,20,170
[the idea is that at the beginning there is one entity, which is then
cloned and mirrored - I want to check if there is one: then I can do
the operations or if there are 2 entities then I should do nothing]
I want to check point 0, 130,170 [which should be crossed by one
entity] and 0,20,170 [the cloned one].
But the interesting part is that when I count selected entities:
if ((acedSSLength( nazwa, &length ) != RTNORM) || (length == 0)) {...}
I get 3 [sic!] of them.
I have no idea where the other 2 came from and what they are.
When I was trying to do it using point which should belong to both
original and cloned one:
pt1[X] = 30.0; pt1[Y] = 75.0; pt1[Z] = 225.0;
if (acedSSGet(NULL, pt1, NULL, NULL, ssname)!= RTNORM){ ...}
it found 1 entity, but when I copied in autocad using _copy it didn't
find anything.
Could you tell me if I'm doing it the wrong way? Or how could I do
it?
Thanks in advance
Marek
By
Anonymous, at 7:59 AM
Hi Marek,
Your code fragment looks fine but your error maybe is coming from another part of your application.
Could you build a separate sample with only this tasks and check if the error is still there?
Generally this selection set problem is caused by a wrong filter and due that you receive 2 extra objects that are Layout1 and Layout2 database objects.
Regards,
Fernando.
By
Fernando Malard, at 8:31 AM
please how to select enity on screen by using object id
By
Anonymous, at 8:43 AM
Hi,
What do you mean with "select entity on screen" ?
Would you like to highlight the entity or just open the entity and get/set information?
Regards.
By
Fernando Malard, at 8:59 AM
Hi fernando
Please how to determine wither the entity is selected with grips point (not highlight ),
Regards,
M.S
By
Anonymous, at 4:21 AM
Hi M.S,
You need to use a Windows Hook funcion to do that. Something like:
BOOL GripHook (MSG *pMsg)
{
static bool signal = false;
if (!signal)
{
signal = true;
struct resbuf *grip = NULL, *ents = NULL;
if acedSSGetFirst(&grip, &ents) == RTNORM)
{
// Use resbuf info here
}
signal = false;
}
return (FALSE) ;
}
To make the Hook to work, at your entrypoint, on AcRx::kInitAppMsg handler:
acedRegisterFilterWinMsg (GripHook);
To remove the Hook, inside your AcRx::kUnloadAppMsg handler:
acedRemoveFilterWinMsg (GripHook);
I don't know exactly how the resbuf information about grips are organized but it should be somewhere inside AutoCAD LISP or ObjectARX documentation.
Hope this help.
Fernando.
By
Fernando Malard, at 7:26 PM
Hello
I need to select some entities but i want to show a customized dialog like:
"Select lines or [All/Single width/Double width]: "
so that the command prompt accepts keywords or a selection set.
(I tried acedSSGet, and acedEntSel but none of them works as i'd like)
Regards, Alonso
By
Anonymous, at 7:05 AM
Hello Alonso,
Keyword prompts are handled by two methods used together: acedInitGet() and acedGetKword(). Below is a simple example of a Yes/No prompt using keywords.
TCHAR yesNo[8]={_T("")};
acedInitGet(0, _T("Yes No"));
acedGetKword(_T("\nProceed? Yes/No [Yes]: "), yesNo);
if (!_tcsicmp(yesNo, _T("")) || !_tcsnicmp(yesNo,_T("Yes"),_tcslen(yesNo)))
YourMethod1();
else
YourMethod2();
More information at ObjectARX documentation of these methods.
Regards.
By
Fernando Malard, at 8:36 AM
Thx for your answer, but thats not what i wanted to know.
If i use acedSSGet(..) how can i change the default promt from
"Select objects:" to
"Select lines [All/Single width/Double width]: "
?
Regards, Alonso
By
Anonymous, at 8:50 AM
Hi Alonso,
I'm afraid this is not possible. The standard "Select Objects" prompt can not be changed as far as I know.
You will need to create your own command or use a commandWillStart() to handle something before the command loop starts.
Sorry about that.
By
Fernando Malard, at 9:10 AM
Hello!
What is wrong with this code???:
ads_name entName;
AcDbObjectId BlockId= AcDbObjectId::kNull;
//if (acedSSGet(_T("I"), NULL, NULL, NULL, entName) != RTNORM)
if (acedSSGet(NULL, NULL, NULL, NULL, entName) != RTNORM)
{
acutPrintf(_T("\nNothing selected"));
return;
}
Acad::ErrorStatus es;
AcDbEntity* pEnt=NULL;
//AcDbEntity* pBlockEnt;
es = acdbGetObjectId(BlockId, entName);
es = acdbOpenAcDbEntity(pEnt, BlockId, AcDb::kForWrite);
if(es != Acad::eOk)
{
AfxMessageBox(_T("Error selection! Please restart the command!"), MB_OK);
return;
}
I allways get an
"
Unhandled exception at 0x620.... in acad.exe: 0xC0054...: Access violation reading location 0x0030....
"
After selecting the entity, entName, BlockId are all OK, but
acdbGetObjectId give me the error..
Got crazy..
By
Bordei, at 5:57 PM
Hello Bordei,
The problem is that ads_name when used with acedSSGet() method represents a selection set and it may contain several entities even when you select just one.
As a selection set it need to be traversed following its length and for each postion you need to use the acedSSName() method to get the entity name to get its AcDbObjectId through acdbGetObjectId().
See this page's example showing how to get the selection set length and how to traverse all its entities.
Best Regards.
By
Fernando Malard, at 8:53 PM
I have no words to thank you!!
:-)
Kind regards!
Ioan
By
Bordei, at 7:01 AM
Post a Comment
<< Home