December 2012

12/31/2012

When you implement my custom entity, the function intersectWith is called when the user enters the TRIM command. Although you implement custom entity such that it can handle arcs and circles, you will find that the intersectWith function of the custom entity is passed an entity for which isKindOf( AcDbArc::desc() ) and isKindOf(AcDbCircle::desc()) are both False when the arcs and circles are selected. So how can you provide special handling for circles and arcs?

For arcs and circles, the entity passed is actually an AcDbEllipse, which allows functions that handle all elliptical objects generically. If you can handle circles and arcs in a specific way ( not as general ellipses ) then extract their defining data ( such as center, normal, start and end points ) and implement the intersections.

You might wonder, when an entity is hatched, is there is an associative entity created that is the hatch (an AcDbHatch)? And how do I locate this object?

The way the hatch responds to changes in the entity is through notification. The hatch is actually made a persistent reactor of the entity in the hatch. You can then obtain the hatch entity in this way. The following code sample demonstrates this:

To find the entities that intersect a selected polyline (LWPOLYLINE in this case), use a selection set containing the entities that cross the polyline using the acedSSGet() function coupled with the 'F' (Fence) options. (See code below.)

In the application listed below, the user is asked to select a polyline (LWPOLYLINE) entity. Having selected the polyline entity and verified that the selection is an AcDbPolyline entity, proceed and retrieve the entities bounding box.

I will explain the purpose of these later and possible alternatives to getting the bounding box of the entity. Next we iterate through the polyline and place all the vertices of the polyline in an AcGePoint3dArray. In order to use the function acedSSGet() with the 'F' option, we need to pass in as the second argument to acedSSGet() a resbuf linked list that contains all the points that make up our "Fence" point which is essentially the vertices that make up our selected LWPOLYLINE entity. Using a 'for' loop we step through our AcGePoint3dArray and build a linked list of result buffers (of type RT3DPOINT). The variable 'pPointList' is that list of result buffers. This list of point is passed to the acedSSGet() function as the second argument. The resulting selection set is returned in 'interSS'.

In this case the selection set 'interSS' will contain one entity which is our selected AcDbPolyline entity. After all it is part of the 'Fence'. In the final 'for' loop I walk through the selection set and get the objectId of each entity and compare it to the objectId of the current entity to that of our selected LWPOLYLINE, if they are the same I simply 'continue' at that point. Here I just inform the user how many entities intersect our AcDbPolyline and print out to the command prompt the kinds of entity found.

Here is the code for the first part, I will explain the bounding box next:

// Here are your new AutoCAD functions. Add your code.

//////////////////////////////////////////////////////

void fencepoly ()

{

AcDbObject *pObj;

AcDbPolyline *pPolyEnt;

AcDbObjectId polyId, objId;

AcGePoint3dArray arPolyPoints;

AcGePoint3d polyPoint;

AcGeBoundBlock2d polyBox;

AcGePoint2d minPolyBox, maxPolyBox;

struct resbuf *pPointList, *pTemp;

ads_name ename;

ads_name interSS;

ads_point pickpt, curPoint;

int rc; long len; long count;

// Select the Window entity to get its handle

rc = acedEntSel(L"\nSelect LWPOLYLINE entity ", ename, pickpt);

if(rc != RTNORM)

{

acutPrintf(L"\nError selecting LWPOLYLINE entity ");

return;

}

// Get the entity object ID

acdbGetObjectId(polyId, ename);

// Open the entity for a read operation

acdbOpenObject(pObj, polyId, AcDb::kForRead);

// Is pObj an AcDbPolyline object ?

pPolyEnt = AcDbPolyline::cast(pObj);

if(!pPolyEnt)

{

acutPrintf(L"\nEntity selected is not a LWPOLYLINE ");

pObj->close();

return;

}

polyBox = getPolylineBoundBox(pPolyEnt);

polyBox.getMinMaxPoints(minPolyBox, maxPolyBox);

acedCommand(RTSTR, L"_ZOOM", RTSTR, L"_W",

RTPOINT, asDblArray(minPolyBox),

RTPOINT, asDblArray(maxPolyBox), RTNONE);

for(count = 0; count < pPolyEnt->numVerts(); count++)

{

pPolyEnt->getPointAt(count, polyPoint);

arPolyPoints.append(polyPoint);

}

pPolyEnt->close();

for(count = 0; count < arPolyPoints.length(); count++)

{

polyPoint = arPolyPoints.at(count);

acdbPointSet(asDblArray(polyPoint), curPoint);

if(count == 0)

{

pPointList = acutNewRb(RT3DPOINT);

acdbPointSet(curPoint, pPointList->resval.rpoint);

pTemp = pPointList;

}

else

{

pTemp->rbnext = acutNewRb(RT3DPOINT);

pTemp = pTemp->rbnext;

acdbPointSet(curPoint, pTemp->resval.rpoint);

}

}

// pPointList is now a linked list of resbuf

// containing points

rc = acedSSGet(L"_F", pPointList, NULL, NULL, interSS);

acutRelRb(pPointList);

acedSSLength(interSS, &len);

acutPrintf(

L"\nThere are %d entities crossing your selected LWPOLYLINE entity.",

len - 1);

for(count = 0; count < len; count++)

{

acedSSName(interSS, count, ename);

acdbGetObjectId(objId, ename);

acdbOpenObject(pObj, objId, AcDb::kForRead);

if(objId == polyId)

{

pObj->close();

continue;

}

const TCHAR *cstr = pObj->isA()->name();

acutPrintf(

L"\nThe entity crossing your LWPOLYLINE is a %s.",

cstr);

pObj->close();

}

}

Let's move on and discuss the user-defined function 'getPolylineBoundBox()', here is how its called from 'fencepoly':

polyBox = getPolylineBoundBox(pPolyEnt);

Refer to the code below. This function takes as a parameter the selected AcDbPolyline. You then step through all of the segments of the polyline in a 'for' loop and test to see if the segments are 'arc' or 'line' based. These are all added to an AcGeVoidPointerArray. If you are dealing with a single segment polyline, cast the first element of 'geCurves' to an AcGeCurve2d geometric entity, otherwise, create a single AcGeCompositeCurve2d, which is a single curve made up of a collection of sub curves. The 'orthoBoundBlock()' will give you the bounding box of the curve that is parallel to the coordinate axis. Having returned the bounding box, get the lower left and upper right point for the box. These are the points to which you execute 'acedCommand()' with the 'ZOOM' window option. Here is the 'getPolylineBoundBox()' function:

AcGeBoundBlock2d getPolylineBoundBox(AcDbPolyline* pPoly) {

AcGeVector3d normal;

int nSegs;

AcGeLineSeg2d *pLine;

AcGeLineSeg2d line;

AcGeCircArc2d *pArc;

AcGeCircArc2d arc;

AcGeVoidPointerArray geCurves;

AcGeCurve2d *pPolyGeCurve;

AcGeBoundBlock2d polyBoundBox;

nSegs = pPoly->numVerts() - 1;

for(int i = 0; i < nSegs; i++)

{

if(pPoly->segType(i) == AcDbPolyline::kLine)

{

pPoly->getLineSegAt(i, line);

pLine = new AcGeLineSeg2d(line);

geCurves.append(pLine);

}

elseif(pPoly->segType(i) == AcDbPolyline::kArc)

{

pPoly->getArcSegAt(i, arc);

pArc = new AcGeCircArc2d(arc);

geCurves.append( pArc );

}

}// for

if(geCurves.length() == 1)

pPolyGeCurve = (AcGeCurve2d*)(geCurves[0]);

else

pPolyGeCurve = new AcGeCompositeCurve2d(geCurves);

polyBoundBox = pPolyGeCurve->orthoBoundBlock();

return polyBoundBox;

}

As an alternative to getting the bounding box of the polyline, you can simply execute a zoom extents. The reason a zoom extents or zoom to the extents of the polyline is required is that if you are zoomed in on the polyline and some entities cross the polyline but are not in the view screen (display screen), they will not be reported even though all the vertices of the polyline for the point list as the second argument of acedSSGet() can be extracted.

Issue After successfully following the the CAcUiDockControlBar sample, I place a combo box or a CAcUiXXXComboBox at the bottom portion the dockbar. If I expand the combobox so the items are expanded, I can select an entry, but when I move the mouse cursor over the expanded portion, it disappears. The text size in the color combobox is larger than the tree control in the docked bar.

Solution If you use CAcUiXXXComboBox(s), you'll need to make sure the following combobox styles are set:

When the expanded portion disappears, it is by design. When you move the mouse cursor outside a docked bar, the cursor needs to be changed to be an Acad cursor type. Therefore, if you have a combo box that expands over any Acad's properties, Acad will get its cursor back. At the time when CAcUiDockControlBar is created, this issue existed.

There is a undocumented function CanFrameworkTakeFocus() of the CAcUiDockControlBar (comes with the SDK), which controls the behavior. To change it, override it and return false.

class CSampDialogBar : public CAcUiDockControlBar

//class CSampDialogBar : public CAcUiDialogBar

{

DECLARE_DYNAMIC(CSampDialogBar);

// Construction

public:

CSampDialogBar(); // standard constructor

CTreeCtrl m_tree;

CComboBox m_MyCombo;

CAcUiColorComboBox m_combo;

void PaintControlBar(CDC* pDC);

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CSampDialogBar)

public:

virtual BOOL Create(CWnd*pParent, LPCTSTR lpszTitle);

//}}AFX_VIRTUAL

private:

// this override is to make the any control

// that extends beyond the dockbar to be

// selectable. Otherwise, the default

// is to restore Acad cursor so the portion

// is repainted immediately.

bool CanFrameworkTakeFocus() { returnfalse;}

// Implementation

protected:

// Generated message map functions

//{{AFX_MSG(CSampDialogBar)

afx_msg int OnCreate (LPCREATESTRUCT lpCreateStruct);

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

virtualvoid SizeChanged (CRect *lpRect, BOOL bFloating, int flags);

};

Now you should be able to select the expanded items out of the combobox.

3. In regards to combobox's text size, it is too big because it will be whatever default is out there since itself doesn't have a font size set. To overcome it, just get a font you want from something and set to it, for example, in your OnCreate().

The dimension text cannot be read directly it is because the information is contained into an AdDbMtext entity owned by a block table record. From the AcDbDimension (or derived classes), you can get the block table record ID using the method dimBlockId(). Then you can iterate throught the AcDbBlockTableRecord object for the AcDbMText entity, and call the contents() method. See the following code:

This is actually a question of Windows, but may help for some developers. There are several approaches for showing a tooltip for Windows' control in AutoCAD. The code you write depends on the level of complexity.

The other blog post introduces add tooltip for CAcUiBitmapButton. Those methods are implemented by CAcUiBitmapButton.

The setting for controlling the startup dialog is stored in a profile in the system registry. The preferred way to have application-specific settings that do not apply to "normal" use of AutoCAD would be to set up a profile for your application. When this profile is active, you can then change the system variables to the values you require, and then set the active profile back to the default one. You can then make a special shortcut for your application, which calls AutoCAD with the /p switch, specifying your application's profile.

The ActiveX API is the easiest way to achieve this. The following sample code is for a Visual Basic application (which might be called from your installation script):

Dim Acad AsObject

Dim strDefaultActiveProfile AsString

Set Acad = CreateObject("AutoCAD.Application")

'Get active profile name

strDefaultActiveProfile = Acad.Preferences.Profiles.ActiveProfile

'Set active profile name to ours, then change settings

Acad.Preferences.Profiles.ActiveProfile = "My Application Profile"

Acad.Preferences.System.EnableStartupDialog = False

'Restore the previous active profile

Acad.Preferences.Profiles.ActiveProfile = strDefaultActiveProfile

Acad.Quit

This starts AutoCAD, sets a new profile named "My Application Profile", sets the Start Up dialog to not display, and then resets the profile to the default and exits AutoCAD. Please note that this should be used from your installation program only to create your profile and its settings. Subsequent "real" usage of your application should start AutoCAD with /p "My Application Profile".