Sunday, March 27, 2005

Class 8 - Selection Sets

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:

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:
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:

Selection Code

Description

NULL

Single-point selection (if pt1 is specified)
or user selection (if pt1 is also NULL)

#

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.
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 :

Anonymous said...

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

Fernando Malard said...

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.

Anonymous said...

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.

Fernando Malard said...

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.

Anonymous said...

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

Anonymous said...

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

Fernando Malard said...

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.

Anonymous said...

please how to select enity on screen by using object id

Fernando Malard said...

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.

Anonymous said...

Hi fernando
Please how to determine wither the entity is selected with grips point (not highlight ),

Regards,
M.S

Fernando Malard said...

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.

Anonymous said...

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

Fernando Malard said...

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.

Anonymous said...

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

Fernando Malard said...

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.

Ioan said...

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..

Fernando Malard said...

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.

Ioan said...

I have no words to thank you!!
:-)

Kind regards!
Ioan

Anonymous said...

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

Fernando Malard said...

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,

Anonymous said...

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

Fernando Malard said...

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.

Anonymous said...

So it should work.

The extra " is to get my submition to work with HTLM.

Anonymous said...

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.

Fernando Malard said...

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.

Mark said...

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" ?

Fernando Malard said...

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,

Mark said...

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 ?.

Fernando Malard said...

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.

Mark said...

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.

Fernando Malard said...

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.

Mark said...

Yes,

We can then use DrawContext.Geometry()
Still cumbersome, but better than nothing.

Thanks

Anonymous said...

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

Fernando Malard said...

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,

Anonymous said...

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

Fernando Malard said...

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,

netSurfer said...

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

Fernando Malard said...

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,

Anonymous said...

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

Fernando Malard said...

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.

Anonymous said...

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.

Fernando Malard said...

Hi ND,

Thank you for sharing the solution.

Regards.

Anonymous said...

is it possible to select object using ObjectID

Fernando Malard said...

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);

SPSharma said...

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.

SNEHA said...

hi...
How to read polyline?
I have read Line,circle succesfully.
Thanking You!!
Snehal

Fernando Malard said...

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.

SNEHA said...

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.

Fernando Malard said...

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,

SNEHA said...

Hi....
Thank you so much!!!
your answer helps me a lot.I have done it successfully!

Anonymous said...

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?

Fernando Malard said...

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,

Anonymous said...

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..........

Fernando Malard said...

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.

Anonymous said...

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!

Fernando Malard said...

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.

Anonymous said...

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;

Fernando Malard said...
This comment has been removed by the author.
robert f said...

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

Fernando Malard said...

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,

Unknown said...

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;
}

Fernando Malard said...

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,

Sandeep said...

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,

Fernando Malard said...

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.

Sandeep said...

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,

Fernando Malard said...

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.

Rahul said...

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?

Fernando Malard said...

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,

Rahul said...

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,

Fernando Malard said...

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,

Rahul said...

Thanks but cant I use acedssget to select these solids and get one pointer through which I can do the needy operation?

Fernando Malard said...

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,

Rahul said...

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

Fernando Malard said...

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?

Rahul said...

Thanks fernando.

It will surely help me.

Regards,
Rahul

Ashish said...

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.

Fernando Malard said...

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,

Anonymous said...

HI Fernando,

Is there any way to select an entity closest to certain coordinate (x,y) or point.

Thanks

Fernando Malard said...

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.

DMA said...

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);
}

Fernando Malard said...

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.

Anonymous said...

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?

Fernando Malard said...

Hi,

Are you calling acedSSFree() at the end of your loop where you call acedSSGetFirst()?

Regards,

Anonymous said...

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.

Fernando Malard said...

Did you set the last resbuf's "rbnext" pointer to NULL?

Something like: pLastRb->rbnext = NULL;

and then release the P_ss chain?

Anonymous said...

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;
------------------

Fernando Malard said...

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,