Hello,
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:
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);
This function receives the ads_name and convert it to an AcDbObjectId. Most of selection set functions will still use the ads_name as parameters and on theses cases you don't need to convert it. The ads_name can store several entities or just one. This will depend on how you or the user has performed the selection.
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:
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);
How to use
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:
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)) {
// 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++) {
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.
// 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.
The acedSSName() function get an at the passed index. If we have more than one entity selected this loop will get every single entity into this selection set.
See you next class.
87 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
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.
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.
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.
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
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
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.
please how to select enity on screen by using object id
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.
Hi fernando
Please how to determine wither the entity is selected with grips point (not highlight ),
Regards,
M.S
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.
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
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.
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
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.
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..
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.
I have no words to thank you!!
:-)
Kind regards!
Ioan
Hello Fernando!
I'm using acedSSGet("_CP", in my Application. Unfortunately this function
catches only Objects that are visible on Acad - screen. If the "_CP"-Polygon
is outside the visible area, the function did not work. I do not like to make a window zoom to MIN/MAN rectangle area. Is there any other way in ARX to catch all (not only the visible) objects in a Polygon?
Best regards!
Ioan
Hello Ioan,
This is an AutoCAD normal behavior to speed up selection sets.
If you want to select regions out of visible screen you will need to create your own selection routine which will search inside ModelSpace.
This will be certainly slow and I would recommend you to perform a ZOOM Window around your polygon, select your entities and get back to the last ZOOM window.
Sorry about the bad news.
Regards,
Fernando,
Thanks for all your previous help.
I want to specify an iverse selection. Say I want to select all the entities that are NOT circles. I tried the following:
struct resbuf *entFilter = NULL;
entFilter = acutBuildList(-4, "<"NOT", RTDXF0, "CIRCLE", -4, "NOT>", RTNONE);
acedSSGet(_T("X"), NULL, NULL, entFilter, setName);
But it yields an empty selection. What am I missing?
Dawie
Dawie,
Strange, your code seems to be ok except from an extra " before the first NOT.
One other thing missing is the UNICODE macro _T() before all strings.
So it should work.
The extra " is to get my submition to work with HTLM.
Hi Fernando,
your page is a great help for me.
But, how to select all lines with xdata?
I tried:
acutBuildList(RTDXF0, _T("LINE"), -3, _T("KNOTEN"), _T("ZONE"), RTNONE);
acutBuildList(RTDXF0, _T("LINE"), -3, _T("ZONE"), RTNONE);
My xdata there:
(-3 (EXTEND (1002 . {) (1011 -2.22572e+006 -3.72234e+006 0.0) (1010 0.0 0.0 0.0) (1005 . 109C5D2) (1000 . 0) (1002 . }) ) (ZONE (1002 . {) (1005 . 10998AA) (1000 . 1) (1002 . })) (KNOTEN (1002 . {) (1000 . 1) (1002 . })))
I just getting errors like:
Invalid type in acutBuildList() arg #4
Regards,
Soeren.
Hi Soeren,
You are missing the RTSTR before your strings. Try again with:
acutBuildList(RTDXF0, _T("LINE"), -3, _T("KNOTEN"), RTSTR,_T("ZONE"), RTNONE);
Regards.
Hi Fernando,
I select a polyline via Editor.GetEntity and would like to display all vertexes just like when selecting the polyline by mouseclick.
Is there something like "Displaygrippoints" ?
Hello Mark,
I think you may use two approaches:
-Use Entity Overrule through .NET. Take a look at this article:
Overrule
-Use acedGrDraw() method to draw temporary graphics at your screen emulating the grip's drawing
Regards,
1. Overrule is not an option as I have to be compatible with ACAD 2008.
2. The .NET counterpart of acedGrDraw is Editor.DrawVector.
With this function I can draw a rectangle, but how can I fill it ?.
Mark,
You are correct about 2008.
Well, the acedGrDraw() is very limited so to draw a rectangle you will need to draw 4 lines, etc. Maybe by using acedGrText() you can draw some special ASCII char with a painted square or something like that.
Hhhmmm,
that doesn't sound very good.
I would like to call worldDraw directly because it has handy functions such as geometry().polygon, but I did not find a way to call it.
Mark,
You cannot call worldDraw() yourself from outside a custom entity class tree. It is an internal method called by AutoCAD which you can intercept and do your custom drawing.
Further, it is possible to draw custom graphics by using InputPoint (take a look at the SDK documentation about InputPointManager). It allows you to create the graphics direct into AutoCAD screen during your commands. You can capture the current selected entity, read its data and draw something. The major feature is that this method receives a drawing context and thus you may use the AcGi drawing primitives. It is very useful for creating cursors, custom symbols, etc.
Regards.
Yes,
We can then use DrawContext.Geometry()
Still cumbersome, but better than nothing.
Thanks
Fernando,
How do you get AutoCAD's current selection set? I tried to use acedSSGet() with "P", but it will give me the previous selection set even if there is nothing selected in AutoCAD when I call the function. I even tried acedGetCurrentSelectionSet(), but it gives me the same result.
Johan
Johan,
The acedGetCurrentSelectionSet() method will only return you the current selection set if your custom command adds the ACRX_CMD_USEPICKSET flag.
This flag will avoid AutoCAD to clear preselected entities when your command is fired.
So, with this flag, before you call your custom command you will be able to select the entities and they will be returned by the acedGetCurrentSelectionSet() method from inside your custom command.
Regards,
Hi Fernando,
can you help me to convert an int
to AcDbObjectId. I just succeeded
to convert AcDbObjectId to int
via acutsprintf for storing the Id.
Now I need something for reverse.
Thank you.
Regards Frank
Hello Frank,
Sorry about not getting back before.
You can use the AcDbObjectId methods called:
asOldId() and setFromOldId()
They will allow you to convert from and to a LONG.
Make sure you call isValid() after converting to make sure the ObjectId is valid into current database context.
Regards,
Hi Fernado,
I am using RealDWG library where "acedSSGet" & other selection set functions are not available. Can you please suggest any other way to select entities within a specified boundary ?
Or any approach to implement it ?
Thanks
Sandy
Hello Sandy,
RealDWG does not allow any interface interaction. All acedXXX methods are meant to be used only with AutoCAD as the host runtime environment.
Regards,
hi,
I am new to LISP and ObjectARX programming...
I am getting issues while selecting object in LISP and executing command (which uses object selected in LISP) in C++ as follows -:
LISP code
----------------------
(while (null en)
(setq en (entsel))
);while
(command "MYCOMMAND")
----------------------
CPP code (based on objectARX 2009) of "MYCOMMAND"
----------------------
ads_name mSelectionSet;
const TCHAR* sSelectionMode = _T("_I")
int iStatus = acedSSGet(sSelectionMode, NULL, NULL,NULL, mSelectionSet);
----------------------
(Tried with selection mode = "_I" \ "P") thinking that I have selected object in LISP.
but get iStatus = -5001 & mSelectionSet holds garbage values after execution of "acedSSGet"
So thought that selection set is not created in LISP so changed LISP code as follows -:
LISP code
----------------------
(while (null en)
(setq en (entsel))
);while
(setq ss (ssadd (en) ss ))
(command "MYCOMMAND")
----------------------
In this case "MYCOMMAND" is not executed and saw following error at AutoCAD command prompt
----------------------
Error: bad function: ( (195.785 26.4755 0.0))
----------------------
Is it possible to select object in LISP code & use that in CPP code?
If yes, How? via "Selection Set"? Please reply how to add selected entity in the selection set and how can that be accessed in CPP code?
Is there some other way to access object in CPP code where selection is made using LISP functions?
Any help is highly apprecited!
thanks and regards
ND
Hello ND.
It should be possible but I have never tried this.
Try to use the following function from inside your C++ code:
acedGetCurrentSelectionSet()
Maybe it will work better than acedSSGet()
Regards.
hi,
Thanks a lot for your help!
But sorry to say that using "acedGetCurrentSelectionSet" did not resolve my issue.
However from other sources, I found how to get object ID associated to the entiry selected in LISP. It is as follows -:
In LISP we select entity and assigne some name to that (say "EN" ) as
---
(while (null en)
(setq EN (entsel))
);while
(command "MYCOMMAND")
---
In C++ code for "MYCOMMAND"
---
const ACHAR* sname = L"EN";
struct resbuf* value = NULL;
struct resbuf* Head = NULL;
if( acedGetSym(sname, &value))
{
Head = value;
while(value != NULL)
{
if(value->restype != RTENAME)
{
value = value->rbnext;
continue;
}
AcDbObjectId objectId;
if(acdbGetObjectId
(objectId,
value->resval.rlname )
!= Acad::eOk)
{
break;
}
value = value->rbnext;
}
if(Head)
acutRelRb(Head);
}
---
Thanks a lot for your help.
Your blog is very good for ObjectARX learners like me.
thanks and regards
ND.
Hi ND,
Thank you for sharing the solution.
Regards.
is it possible to select object using ObjectID
If you mean by creating a selection set, it is something like this:
// Suppose you have an ObjectIdArray of existing entities
AcDbObjectIdArray selImport;
// Prepare selection set
ads_name lstSelecionSet;
acedSSAdd(NULL, NULL, lstSelecionSet);
for (int i = 0; i < selImport.length(); i++)
{
// add to selection set
ads_name objEntityName;
acdbGetAdsName(objEntityName, selImport[i]);
acedSSAdd(objEntityName, lstSelecionSet, lstSelecionSet);
}
// After using the selection set you need to release it
acedSSFree(lstSelecionSet);
I want to select an entity using its objectID, i m selecting entity by using it's point3d
OuterObjectPoint3d = New Point3d(tempOuterEntity.GeomExtents.MaxPoint.X, tempOuterEntity.GeomExtents.MaxPoint.Y, tempOuterEntity.GeomExtents.MaxPoint.Z)
if any other entity is exist at that place or intersect that line then i m unable to select that entity so i want to select that entity using it's object id.
i want to create a function that will perform subtraction based on parameters of outerObjectID and innerObjectID
my function work properly if no any adjacent entity exist or no intersection is found but if any intersection found then it give message duplicate object found
please help me.
hi...
How to read polyline?
I have read Line,circle succesfully.
Thanking You!!
Snehal
Sneha,
Polylines are different. To get their vertexes you will need to open the entity itself then traverser its vertexes by using a vertex iterator.
The process is similar for 2D and 3D polylines (I don't know which one you need):
void PolyPoints(AcDbObjectId plineId)
{
AcDb2dPolyline* pPline = NULL;
acdbOpenObject(pPline, plineId, AcDb::kForRead);
AcDbObjectIterator* pVertIter= pPline->vertexIterator();
pPline->close();
for (int vertexNumber = 0; !pVertIter->done(); vertexNumber++, pVertIter->step())
{
AcDbObjectId vertexObjId = pVertIter->objectId();
AcDb2dVertex *pVertex = NULL;
acdbOpenObject(pVertex, vertexObjId, AcDb::kForRead);
AcGePoint3d location = pVertex->position();
pVertex->close();
}
delete pVertIter;
}
This code is just a example for 2D polylines and you should add error checking and also support for 3D polylines if you will.
Cheers.
Hi,
struct resbuf eb1;
TCHAR sbuf1[10];
eb1.restype = 0; // Entity name
_tcscpy(sbuf1, _T("POLYLINE"));
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;
}
but,acedSSGet does not return RTNORM,so how can I get ads_name of polyline entity.And is there any method for rectangle,like AcDbLine for line.
SNEHA,
The "POLYLINE" entity name corresponds to a 3D Polyline. The 2D Polyline is represented by the "LWPOLYLINE" entity name.
Further, the rectangle is not an entity it is just a LWPOLYLINE created by a specific command.
To know the exact entity name create it with the AutoCAD commands and then perform a LIST command over it and look what is written on first line right beside the Layer text.
Regards,
Hi....
Thank you so much!!!
your answer helps me a lot.I have done it successfully!
Hi,
AcDb2dPolyline* pPline = NULL;
acdbOpenObject(pPline, plineId, AcDb::kForRead);
AcDbObjectIterator* pVertIter= pPline->vertexIterator();
pPline->close();
for (int vertexNumber = 0; !pVertIter->done(); vertexNumber++, pVertIter->step())
{
AcDbObjectId vertexObjId = pVertIter->objectId();
AcDb2dVertex *pVertex = NULL;
acdbOpenObject(pVertex, vertexObjId, AcDb::kForRead);
AcGePoint3d location = pVertex->position();
pVertex->close();
}
delete pVertIter;
i have tried this code,but i got exception at
AcDbObjectIterator* pVertIter= pPline->vertexIterator();
this line.
Can u give me any suggestion plz?
smita,
You need to close your polyline only after you delete the iterator. Try to move the following line:
pPline->close();
to the end of your code.
It should do the trick.
Regards,
Hi,
There is no exception at pPline->close() or delete VertIter.
There is exception when i try to use any method of pPline,like pPline->vertexIterator(); or pPline->getEndPoint();
Thank you for reply..........
smita,
Any call to an entity method requires it is on opened state because AutoCAD pointers are dynamic. If you try to call any entity method after calling close() through the same pointer it will crash AutoCAD.
Regards.
hi...
AcDb2dPolyline* pPline = NULL;
acdbOpenObject(pPline, plineId, AcDb::kForRead);
AcDbObjectIterator* pVertIter= pPline->vertexIterator();
there is exception at
AcDbObjectIterator* pVertIter= pPline->vertexIterator();
this line only.we haven't close entity before this line.
plz help me out.
thank you!
smita,
Check the returned status from acdbOpenObject() method. Further, check if the entity wasn't left opened by a previous routine inside your application.
It must work, something seems to be wrong with other portion of your code.
Regards.
Hi Fernando
I'm trying to use an open and not intersecting polyline to select objects using the mode of fence, but acedSSGet function returns an error code -5001 (Some other error.) I used the example code slightly modified by me (below). What is wrong there?
Regards.
badziewiak
resbuf*
ptArrayToResbuf(const AcGePoint3dArray& ptArray)
{
resbuf* ptList = NULL; // overall list
resbuf* lastRb = NULL; // place holder to end of list
resbuf* rb;
int len = ptArray.length();
for (int i=0;iresval.rpoint );
if (ptList == NULL) {
ptList = rb;
lastRb = rb;
}
else {
lastRb->rbnext = rb;
lastRb = rb;
}
}
return ptList;
}
bool fenceSelect( const AcGePoint3dArray& ptArray, ads_name& ss, const resbuf* filter )
{
// NOTE: flags not allowed on this type of selection
resbuf* ptList = ptArrayToResbuf(ptArray);
if (ptList == NULL)
return false;
int result = acedSSGet(_T("_F"), ptList, NULL, filter, ss);
acutRelRb(ptList);
return result == RTNORM;
}
//CUT...
AcGePoint3dArray arPunkty;
for( int i = 0; i < wPlinia->numVerts(); i++ )
{
AcGePoint2d p2d;
ErrorStatus eS = wPlinia->getPointAt( i, p2d );
if( eS != eOk )
{
acdbFail( L"\nGet polylinie point failed!" );
acutPrintf( L" Error code: %d", eS );
eS = wPlinia->close();
return;
} //if( eS != eOk )
arPunkty.append( AcGePoint3d( p2d.x, p2d.y, 0 ) );
} //for( int i = 0; i < wPlinia->numVerts(); i++ )
ads_name ss;
if( !fenceSelect( arPunkty, ss, NULL ) )
{
acedPrompt( L"\nNothing selected!" );
ErrorStatus eS = wPlinia->close();
return;
} //if( !fenceSelect( arPunkty, NULL ) )
acedSSSetFirst( ss, NULL );
acedSSFree( ss );
ErrorStatus eS = wPlinia->close();
return;
Hi Fernando,
Is there a way to create a selection set picking enities from multiple layers using the acedSSGet(_T("X") function?
The goal would be to create a selection set of, let's say, 2 layers. Layer 0 and Layer 1.
Thanks
Robert
Hi robert,
Did you try to use the Layer filter?
eb2.restype = 8; // Layer name filter
Take a look at this page's examples...it should work.
Cheers,
hii fernando,
i wanted to join lines but iam getting error when iam trying to cast it.plz suggest something .heres my code...
Acad::ErrorStatus es;
AcDbObjectId id;
ads_name name;
ads_point pt;
if(acedEntSel(L"\nSelect Entity 1: ", name, pt) != RTNORM)
{
return;
}
acdbGetObjectId(id, name);
AcDbObjectPointer pEntity1(id, AcDb::kForRead);
if(pEntity1.openStatus() != Acad::eOk)
{
return;
}
if(acedEntSel(L"\nSelect Entity 2: ", name, pt) != RTNORM)
{
return;
}
acdbGetObjectId(id, name);
AcDbObjectPointer pEntity2(id, AcDb::kForRead);
if(pEntity2.openStatus() != Acad::eOk)
{
return;
}
AcDbJoinEntityPE* pJoinPE = AcDbJoinEntityPE::cast(pEntity1->queryX(AcDbJoinEntityPE::desc()));
if (pJoinPE == NULL)
{
acutPrintf(L"\nJoin PE not supported");
return;
}
es = pJoinPE->joinEntity(pEntity1.object(), pEntity1.object());
if (es != Acad::eOk)
{
acutPrintf(L"\nJoin failed");
return;
}
Hi karishma,
The correct way of doing the cast is:
AcDbJoinEntityPE* pJoinPE = AcDbJoinEntityPE::cast(pEntity1);
The desc() method returns the class descriptor instead of the object pointer.
It is used to test the class type or derived hierarchy:
if (pEntity1->isKindOf(AcDbJoinEntityPE::desc()))
{
}
if (pEntity1->isDerivedFrom(AcDbJoinEntityPE::desc()))
{
}
Regards,
Hi fernando,
I need to use the constraints. I set the values of circle constraints once like this
acedCommand(RTSTR,_T("_dcradius"),RTSTR, PAUSE, RTSTR, _T("10,10,10"), RTSTR, _T("50"), 0);
But how to I access the parameters of this command, ie. name, expression and value and change them dynamically.
Regards,
Hi Sandeep,
The DCRADIUS command creates a Radius Constraint by doing the following actions:
- It creates an entity of AcDbRadialDimension class which draws the constraint dimension
- This dimension entity has a reactor attached of AcDbAssocDependency class
- The AcDbCircle entity you selected has a AcDbAssocGeomDependency reactor attached
Note that this constraint is a little more complex when compared to Parallel for example. It does involve the creation of a new graphical entity (the radial dimension) to display the constraint.
I believe the information you are looking for (name, expression, etc) is stored into one of these two Reactors.
I would recommend you to create a test routine to open this Reactors and debug their pointers to check what is accessible through them.
Hope it helps.
I guess I was not able to explain my question correctly. I am talking about the Parameters Manager. When we turn on the parameters manger for creating the constraints, we get the following
Name Displays the variable name
Expression Displays the real number or the equation for the expression
Value Displays the value of the expression
Additional Columns
Type Displays the dimensional constraint type or variable value
Description Displays the comments or notes associated with the user variable
I want to access these parameters (name, value etc.) and alter their values. Is it possible through acedGetVar() and acedSetVar() method?
Regards,
Sandeep,
The "Assoc" network is stored into NOD (Named Object Dictionary).
There is a dictionary entry there called "ACAD_ASSOCNETWORK" which holds an object of AcDbAssocNetwork class.
Once you get this object you will be able to navigate through all constraints currently set in your drawing.
This class will provide you methods to access all the information you need.
This API is not simple because it relies on several HardPointer/SoftPointer relationships and Reactors.
You will probably have to open objects to go deep and find the information you want.
Regards.
Hi Fernando,
I need to do some operation on multiple solids at a single time. Is there any way that I can get a single pointer to all the entities and do my operation. I dont want to do union as it will make them as single entity.
For ex:
AcDb3dSolid *ps1, *ps2, *ps3;
Can I get a single pointer to the above solids, say *ps and move them all together? Is it possible through acedssget or any other method?
Hi Rahul,
I didn't quite understand the term "at a single time". Do you mean "at a single command" or "as a single entity"?
You could make the union with a clone of all entities and do the processing in memory scope without adding the clones to ModelSpace. If the resulting solid is satisfactory you then add it to ModelSpace and delete the clones.
Through selection set you can select them all by providing their ObjectId into the selection set, no big deal.
Another option would be to add them to an AcDbGroup object which will make them be selected together.
Anyway, several options but I would need to better understand what exactly you need to suggest the more appropriate approach.
Regards,
I want to get one pointer that with which I can work on all the solids. I want to keep the solids as separate entities.
I dont want to use 100 pointers to move 100 different solids but I want one single pointer that could move all the solids.
Regards,
Well, you can put them into a single block.
Create a AcDbBlockTableRecord named "ALL_BLOCKS", add them to this container and then insert a AcDbBlockReference entity, pointing to this block into ModelSpace.
So all ModelSpace operations like MOVE, ROTATE, etc. will be performed at a Block Reference entity thus carrying on all solids.
In other hand, if you need to change the solids individually you will need to get them inside the Block Table Record to get their ObjectIds and then change the source entities. This would have the same effect like you do with REFEDIT command.
Regards,
Thanks but cant I use acedssget to select these solids and get one pointer through which I can do the needy operation?
Rahul,
No, the pointer is dynamically generated by AutoCAD's database from the entity's ObjectId so it always point to a single entity.
The pointer address is valid only if the entity is still open.
You cannot store a pointer for a later use because it won't point to the same entity's memory area.
If you want to store references to entities you can use:
- AcDbObjectId (valid only during the current AutoCAD session)
- AcDbHandle (valid across sessions but only for that specific DWG)
The suggestion I made was to put the solids into another container and then access it through a single entity (BlockReference).
So I still can't understand why you need a single pointer...what for?
Regards,
The solids are placed over one another and I need to bring one new solid that will cut through all the solids. The boolean Subtract API provides an option to subtract one entity from one entity but I need to cut multiple solids through a single solid.
In simple words how can I create a hole through 100 solids using one solid? Thats why I want to get one pointer to these 100 solids.
I hope you got my question clearly this time
Regards
I believe you can still use the subtraction.
I know it will create a single big solid as a result of your 100 solids being cut by the pipe crossing them but you can extract the disjointed solids by using this AcDb3dSolid method:
virtual Acad::ErrorStatus separateBody(AcArray & newSolids);
This method separates the solid into a list of solids representing the additional disjoint volumes. This solid is reduced to a solid with one volume. The calling application is responsible for the resulting entities (either appending them to a database or deleting them when they are no longer needed). When the calling application closes this AcDb3dSolid, the resulting solid will be committed to the database. So, if the other solids are not appended to the database, you will lose some data.
Returns Acad::eOk if successful; otherwise, returns Acad::eGeneralModelingFailure.
So, basically:
- perform the subtraction
- get the big solid and extract the disjoint bodies
- append the bodies to ModelSpace
- delete the big solid
Does it help?
Thanks fernando.
It will surely help me.
Regards,
Rahul
Hello Fernando,
The article is very useful and the comments too.
I wanted to know whether there is a way to check if the selection set has been modified.
My requirement is that I want to check if there are same or different entities in the Selection Set on the PickFirstModified callback.
It should also consider sub-selection. For example, if I select table cell, is it possible to check if the selection set has been modified without traversing the table cells?
Thank you.
Hello Ashish,
Take a look at the reactors. I think you will need to monitor the selection through command reactors once there isn't a specific selection modified event. There is a AcEditorReactor::pickfirstModified() event you can use to monitor the current selection and check whether it was changed or not.
For the selected Table Cell, take a look at the following function which passes in the Subent path for the provided selected entity's objectId:
Acad::ErrorStatus acedGetFullSubentPathsForCurrentSelection(
const AcDbObjectId& selectedObject,
AcDbFullSubentPathArray& selectedSubentities
);
Each ObjectId can be obtained from the array of selected entities passed in to this method:
Acad::ErrorStatus acedGetCurrentSelectionSet(
AcDbObjectIdArray& sset
);
Hope it helps.
Regards,
HI Fernando,
Is there any way to select an entity closest to certain coordinate (x,y) or point.
Thanks
Hi,
Assuming the entity you are looking for is into the same XY place, I think you could inflate the point turning into a square and use the selection set crossing window to find entities intercepting this square. The size of this square will give you the precision.
Note that it is not guarantee there will be only one entity returned by this selection so you would need to do additional work on selected entities to find what you want. Of course you will do that only if the selection set size is greater than 1.
Hope it helps.
Hi Fernando,
How can I let the user modify a selection set. I have and objectid array. when running my command I want to highlight the entities from the array and let user add/remove from the selection.I tried with acedSSSetFirst and acedSSGet :
modifySelectionSet(AcDbObjectIdArray& selectionSet)
{
ads_name ss, ename;
////create a selection set
acedSSAdd(NULL, NULL, ss);
for (int i = 0; i < selectionSet.length(); i++)
{
//get the ads name from objectID
acdbGetAdsName(ename, selectionSet[i]);
//add the selected entity ads name to selection set
acedSSAdd(ename, ss, ss);
}
////select with grip
acedSSSetFirst(ss, NULL);
acedSSGet(NULL, NULL, NULL, NULL, ss);
//here some custom code to get objectid array from ss and modify the selectionSet //array
acedSSFree(ss);
}
Hi DMA,
I never used these functions together like this.
Have you tried SetImpliedSelection() like shown here:
https://adndevblog.typepad.com/autocad/2015/01/entity-selection-to-modify-properties.html
Hope it helps.
Any ideas as to why acedSSGetFirst returns -5001? We have a handler that detects the pickFirst event (Entity Reactor that implements pickfirstModified). We are using acedSSGetFirst to return the current selection set and that seems to work.
Problem is at some point after multiple successful executions acedSSGetFirst returns -5001. Any ideas as to why this might be happening?
Hi,
Are you calling acedSSFree() at the end of your loop where you call acedSSGetFirst()?
Regards,
Thank you Fernando for the prompt response.
After every call to acedSSGetFirst we release both resbuf using this function
if (P_ss != NULL)
{
if (P_ss->restype == RTPICKS)
{
ads_ssfree(P_ss->resval.rlname);
}
ads_relrb(P_ss);
}
where P_ss is the returned resbuf of acedSSGetFirst call.
Did you set the last resbuf's "rbnext" pointer to NULL?
Something like: pLastRb->rbnext = NULL;
and then release the P_ss chain?
No, high level our code looks like this
#CleanupSelectionSet(P_ss) function has the implementation I shared in previous post
------------------
// Get Selection Sets
acedSSGetFirst(&gripSet, &pickFirstSet);
CleanupSelectionSet(gripSet);
gripSet = NULL;
// Get count of features selected
ads_sslength(pickFirstSet->resval.rlname, &length);
// Process pickFirst selection set
for (int idx = 0; idx < length; idx++)
{
ads_ssname(pickFirstSet->resval.rlname, idx, entityName);
...
}
CleanupSelectionSet(pickFirstSet);
pickFirstSet = NULL;
------------------
Hello,
Sorry about the delayed reply.
I think you will need to isolate this routine and try to confirm it works because other portion of you code may be causing something before it is ever called.
Regards,
Post a Comment