Friday, January 12, 2007

Exercise2 - Step2

Creating a Custom Entity – Exercise 2 – Step 2

Now after you have created a basic custom entity you will learn how to add custom graphics to it. The custom entity’s graphics is generated primarily by the worldDraw() method and optionally by the viewportDraw() method.
As this entity’s base class already generates a polyline graphic we will add some nice extra graphics to it. Each vertex will receive an index number and an arrow indicating the polyline construction direction (Figure 14).
We will use different colors for each graphic type. The index number will use the 256 color code which is the ByLayer color and the arrows will use a fixed 1 color code which is red. If you change the color of this entity’s layer it will change except on the arrows that will stay red. Further, all arrows will be filled.


Figure 14 – Custom Entity with vertex numbers and arrows


To create these graphics you will need to use some trigonometric methods. The following code demonstrates what you can reach this:


Adesk::Boolean AuPolyline::worldDraw (AcGiWorldDraw *mode)
{
assertReadEnabled();
// Call base class first
AcDbPolyline::worldDraw(mode);
double szRef = 5.0;
// ================================================================
// DIRECTION AND VERTEX NUMBERING
int signal = 1;
double ht2 = szRef/4.0;
for(int i=0; i<numVerts(); i++)
{
AcGePoint3d pti;
this->getPointAt(i,pti);
// Draw vertex text
CString strNum;
strNum.Format(_T("%d"),i);
AcGePoint3d ptTxt = pti + (AcGeVector3d::kXAxis*ht2) + (AcGeVector3d::kYAxis*ht2);
mode->subEntityTraits().setColor(256); // ByLayer
mode->geometry().text(ptTxt, AcGeVector3d::kZAxis, AcGeVector3d::kXAxis, ht2, 1.0, 0.0, strNum);
/
/ Arrow direction
AcGePoint3d ptj;
this->getPointAt(i<(numVerts()-1) ? (i+1) : 0, ptj);
A
cGeVector3d dir = (ptj - pti).normalize();
// Side perpendicular vectors
AcGeVector3d perp = dir;
perp.rotateBy(3.141592/2.0,AcGeVector3d::kZAxis);
AcGePoint3d pt1 = ptj - (dir*ht2) + (perp*(ht2/4.0));
AcGePoint3d pt2 = ptj - (dir*ht2) - (perp*(ht2/4.0));
AcGePoint3d pts[3];
pts[0] = ptj;
pts[1] = pt1;
p
ts[2] = pt2;
// Draw arrow polygon
mode->subEntityTraits().setFillType(kAcGiFillAlways);
mode->subEntityTraits().setColor(1); // red
mode->geometry().polygon(3,pts);
mode->subEntityTraits().setFillType(kAcGiFillNever);
}
//------ Returning Adesk::kFalse here will force viewportDraw() call
return (Adesk::kTrue) ;
}

On line 03 we call the proper assert method which inform what type of access we are executing on this method. As we are not changing any data we need only to READ the entity so we call assertReadEnabled() method. On line 05 we forward up the call to its base class method which will draw the polyline curves. On lines 06-10 we initialize some local variables.
Next, from line 11-39 we perform a loop on each polyline vertex to draw our custom graphics. On lines 13-14 we get the current vertex point. On line range 16-20 we draw the vertex text using the text() geometry primitive at a point with a small displacement related to the vertex point. On lines 22-24 we calculate the end point (ptj) of each polyline segment and its unitary direction vector (dir). Next, on lines 26-33 we calculate the 3 points which will build an arrow head graphic. The perp vector will allow us to draw each side of the arrow head according to the Figure 15. On lines 35-38 we draw the filled arrow head using the polygon() primitive with color red and with the fill type always.

Finally, on line 41, we return Adesk::kTrue to avoid the graphics to be made by the viewportDraw() method.
These classes we have used, with AcGe prefix, are part of the AcGe library which contains several utility classes and methods to help us to deal with geometric calculations. This really helps a lot once most of these calculations are a little bit complex.


Figure 15 – AuPolyline side graphics.

Exercise2 - Step1

Hello,

I will post a sequence of 6 steps of my Exercise 2 provided at last year's AU. It will show you how to create a custom entity inside AutoCAD 2007 using ObjectARX 2007 and Visual Studio 2005.

Hope you enjoy these posts!

Creating a Custom Entity – Exercise 2 – Step 1

You will learn in this exercise how to create a simple custom entity. I will keep it simple as much as I can to reinforce the basic concepts involved. On the next steps we will improve this custom entity by adding great features step by step.
The first step is to name our entity and choose from which base class it will derive. Remember we have to split our application into two modules, an ARX (user interfaces) and a DBX (the custom entity class itself). These two projects will be placed into the same Visual Studio Solution and the ARX module will depend on DBX module. The names will be:





  • Solution: Exercise2 (Visual Studio will create a Exercise2.sln file);

  • DBX Project: AuCustomObjects (Visual Studio will create a AuCustomObjects.vcproj file);

  • ARX Project: AuUserInterface (Visual Studio will create a AuUserInterface.vcproj file);

Open Visual Studio, go to the menu File > New Project… Open “Other Project Types” node, click on “Visual Studio Solutions” item. It will show a template called “Blank Solution”. Choose this template and name it as Exercise2 (the location can be any folder you want). Click OK to proceed (Figure 8).
Next, right click the solution icon; select Add and then New Project…, (Figure 9). The dialog, presented on Exercise1 will appear. Select on the list the ObjectARX template and create both AuUserInterface and AuCustomObjects modules. Remember to choose ARX or DBX project type accordingly.





Figure 8 – Blank Solution project

Each project location will be created inside the existing solution by default. Don’t change this location.
Enable MFC option on both projects (don’t need to enable AutoCAD MFC extensions) Enable _DEBUG symbol too. Don’t enable any COM or .NET feature in both projects.
Make AuUserInterface project depend on AuCustomObjects. To do that, right click project AuUserInterface and select Dependencies… Then mark AuCustomObjects project on the list. Click OK.


Figure 9 – Creating solution’s projects

Right click project AuUserInterface again and select “Set as Startup Project” (it will turn to bold). Now test your solution building it: go to menu Build > Build Solution. You should get 2 Builds with 0 errors and (1+3) warnings that are safe to ignore.
The final test at this stage is to load the application inside AutoCAD. Remember our project AuUserInterface depends on AuCustomObjects so the DBX module needs to be loaded first than the ARX module. The unload process must be done in reverse order, AuUserInterface first and then AuCustomObjects. You should get everything working this way.
Note: Depending on which type of build you made (Debug or Release) it is recommended to load both projects with the same compilation type.
Now we have our DBX and ARX module it is time to add our custom entity’s class. In this example, our entity will be derived from AcDbPolyline which represents the AutoCAD POLYLINE entity. The reason for this option is that our custom entity will behave almost exactly like a polyline but it will add some extra features like vertex numbers, direction symbols, hatch, etc. To add this custom class we will use the Autodesk Class Explorer tool which is located at ARXWizard’s toolbar. It is the second button like the Firgure 10 shows.


Figure 10 – ARXWizard toolbar

Once you click on this button a dialog bar will appear allowing you to explore all existing classes into your projects. At this time, there are no custom entity classes available. Select our DBX module and then right click on it. A pop-up menu will be displayed. Select the “Add an ObjectDBX Custom Object” option (Figure 11).


Figure 11 – Autodesk Class Explorer

This wizard has 3 steps. The first step, called Names (Figure 12), allows you to specify all basic custom entity’s features. First, name it as “AuPolyline”. Choose as base class the AcDbPolyline class (note you have several other classes available to derive from). Remaining fields will be filled automatically. Press Next.
Once you select the polyline as your base class you are saying that your custom entity will behave like a polyline except where you redefine it. This is done through the virtual methods I have mentioned before.


Figure 12 – Custom Object Wizard - Names

The Custom Object Wizard also will help you to implement basic class features. This is done on the second step of this Wizard, called Protocols. Enabling these options will instruct the wizard to add the related virtual methods simplifying the class creation process for you.

Inside this dialog you can specify if your entity participate on DWG/DWF protocols, if your entity implements OSNAP points, GRIP points and viewport dependent graphics and, at last, if it implements curve protocols. In this example we will enable only DWG protocol and all 3 AcDbEntity protocols which will allow us to implement some basic features (Figure 13).
The third step, called Advanced, will allow you to add notification and cloning features to your custom entity. In this example we will not use these features. There are many other features you can redefine through a huge set of virtual methods but to keep this example simple we will only implement these ones.
Click Finish to create your Custom Entity’s Class. Remember, it will be placed at you DBX module.


Figure 13 – Custom Object Wizard - Protocols

The Custom Entity Wizard will create default virtual methods but, in this particular case, our base class AcDbPolyline does not support all of these methods signatures. If you compile the solution you will get 4 error messages exactly due that. For now, we will replace the explicit AcDbPolyline:: prefix on these 4 return calls by AcDbCurve:: prefix which is the AcDbPolyline base class. Now you should get no errors.
If you inspect your projects Solution Explorer you will note two new files: AuPolyline.h and AuPolyline.cpp. These files, inside your DBX module, are responsible to declare and implement your custom entity. Open these two files and walk through the generated code.
Now you need to add a new command, inside your ARX module, to create an instance of your new custom entity. To do that, on the Solution Explorer, right click the AuUserInterface project and click “Set as Startup Project” (project name will turn to bold). Now, click on the first icon of ARXWizard toolbar (a icon with “a>” text). This button will open the “ObjectARX Commands” dialog box. It has two command lists. Right click the first list and select New. A new line will be added to this list with a default command called “MyCommand1”. Click OK.
If you open the acrxEntryPoint.cpp file of AuUserInterface project you will find, inside class CAuUserInterfaceApp, the AuUserInterface_MyCommand1() empty method which will be called when you fire MYCOMMAND1 command inside AutoCAD. Inside this method you will create an instance of your AuPolyline entity.
Remember that your custom entity class implementation is located into the DBX project. Due that you will need to add (through #include compiler instruction) a reference to the AuPolyline class declaration file. To do that, add the following compiler instruction at the beginning of acrxEntryPoint.cpp file of your ARX module right after #include "resource.h" line:

#include "..\AuCustomObjects\AuPolyline.h"
The “..\” part of this include path goes up one folder level (getting back to the Solution’s root folder) and then goes into the DBX project folder. Now you can compile your project and shouldn’t get any errors. Next, open AutoCAD, load first the DBX module and then the ARX module (there will be two text messages at AutoCAD command prompt confirming that the both load processes were succeeded. Type your project’s command “MYCOMMAND1” and run a ZOOM EXTENTS command to see the generated entity. If you also run the LIST command and select this entity you will see its detailed information.
The following code will create a 10 sided polyline (closed) and will add it to the Model Space Block Table Record.

// - AuUserInterface._MyCommand1 command (do not rename)
static void AuUserInterface_MyCommand1(void)
{
// AuPolyline entity
AuPolyline* pL = new AuPolyline();
int nSides = 10;
double incAngle = 2*3.141592 / nSides;
// Add vertex list
for (int i=0; i<nSides; i++)
pL->addVertexAt(i,AcGePoint2d(10*cos(i*incAngle),10*sin(i*incAngle)));
// Set Polyline as closed
pL->setClosed(Adesk::kTrue);
// open the proper entity container
AcDbBlockTable* pBT = NULL;
AcDbDatabase* pDB = acdbHostApplicationServices()->workingDatabase();
pDB->getSymbolTable(pBT,AcDb::kForRead);
AcDbBlockTableRecord* pBTR = NULL;
pBT->getAt(ACDB_MODEL_SPACE, pBTR, AcDb::kForWrite);
pBT->close();
// now, add the entity to container
AcDbObjectId Id;
pBTR->appendAcDbEntity(Id, pL);
pBTR->close();
pL->close();
}

On lines 05-07 we instantiate the entity and initialize some local variables. Next, on lines 9-10 we add a list of vertexes to the entity. Line 12 set the polyline as closed. On lines 14-19 we open the AutoCAD database, open the Block Table container and then get the Model Space container. On lines 21-24 we add our entity do Model Space and then close it and the entity itself.
Note: You cannot delete the entity’s pointer once it is added to AutoCAD database. In this case its management is delegated to AutoCAD and you only need to call the close() method. If you delete this pointer you will cause AutoCAD to crash.

Friday, December 15, 2006

Hello,

I'm sure you probably have faced a situation where you perform some heavy calculations inside AutoCAD with or without a dialog running. In these cases AutoCAD may look frozen and this may look strange to the user because it may think AutoCAD has really frozen and then the user may try to close it.

Even if you use a progress bar like AutoCAD status bar if you change the current window and get back to AutoCAD it may look frozen in black/white colors.

This occurs because the processor focus all its resources into your heavy loop and does not save enough time to both AutoCAD and your dialog process its own messages. These messages will affect several things like, for instance, the dialog graphics update.

The below function will let AutoCAD do this and you may call it from inside your loop (maybe a FOR or WHILE heavy statements). This way, on each interaction, this function will let AutoCAD process its messages.

void PumpAcadMessages()
{
// Get AutoCAD application
CWinApp* app = acedGetAcadWinApp();
// Get Main window
CWnd* wnd = app->GetMainWnd();
// dispatch incoming sent messages

MSG msg;
while (::PeekMessage (&msg, wnd->m_hWnd, 0, 0, PM_NOREMOVE))
{
// if can't pump, break
if (!app->PumpMessage())
{
::PostQuitMessage(0);
break;
}
}
LONG lIdle = 0;
// Now it's time to MFC do the work
while (app->OnIdle(lIdle++));
}

If you also would like to process your dialog messages you just need to change the first two lines and use your dialog's CWnd pointer instead of AutoCAD window's pointer.

Cheers!

Thursday, December 14, 2006

AU2006 was fantastic!

Hello,

Au2006 was amazing. There were almost 7500 people this year and several classes were provided.
If you would like to check those classes visit AU Online at: http://www.autodesk.com/auonline

I will post some content of my class but I need first to reorganize its topics and pictures.
Stay tuned.

Cheers!

Monday, August 07, 2006

AU 2006

Hello,

Autodesk University 2006 registration is now open!

AU 2006

I'm glad to inform you that this year I have been accepted as Speaker. My 1,5 hour class will be about Custom Entities. Class code is CP33-3 and more information can be found here:

The Power of ObjectARX(R) - CP33-3

Hope to meet you in Las Vegas!
Regards,
Fernando.

Friday, August 04, 2006

Chat Sessions

Hello,

I have recently added a new button at my Blog to allow chat conversations.
The idea is to schedule some chat sessions with users to discuss specific subjects.
You may start to send me your subject suggestions by e-mail so I can start to create a schedule for the chat sessions.

As I always did all information at this Blog is free as any other provided tools. The idea is to keep this site alive with Google ads and now with "Make Donation" button.

Cheers!
Fernando.

Thursday, July 06, 2006

New Blog about AutoCAD programming

Hello,

Finally we have now an Autodesk Blog about ObjectARX/.NET programming!

Kean Walmsley, Senior Manager of our worldwide DevTech team, has started a blog dedicated to programmers working with Autodesk technologies

Congratulations to Kean Walmsley.

http://through-the-interface.typepad.com

Fernando.

Wednesday, May 17, 2006

Adding your own Status Bar Button

Introduction

Hello,

Sometimes you need to have a quick access to your command mainly when this one is about enable/disable something in your software. Using ObjectARX you can easily add a new button to existing AutoCAD Status Bar.

To add this new button to AutoCAD Status Bar we need to derive a new class from AcPane and add our desired appearence and behavior. In this example I will show you how to create a simple ON/OFF button.

How to Begin

Create a new ARX project using ARXWizard. Add a new empty file called MyPane.h and use the follwoing code:

#pragma once
#include "AcStatusBar.h"
class MyPane : public AcPane
{
public:
// ----------------------
MyPane(void)
{
this->SetToolTipText(_T("This is MyPane Button"));
this->SetText(_T(" MyPane [OFF] "));
this->SetStyle(ACSB_POPOUT);
enabled = false;
}
// ----------------------
virtual ~MyPane(void) {;}
// ----------------------
virtual void OnLButtonDown(UINT nFlags, CPoint point)
{
AcPane::OnLButtonDown(nFlags,point);
enabled = !enabled;
CString strLabel;
strLabel.Format(_T(" MyPane [%s] "),enabled ? _T("ON") : _T("OFF"));
this->SetText(strLabel);
this->SetStyle(enabled ? ACSB_NORMAL : ACSB_POPOUT);
AcApStatusBar* pStatus = acedGetApplicationStatusBar();
if (pStatus) pStatus->Update();
}
// ----------------------
virtual void OnRButtonDown(UINT nFlags, CPoint point) {;}
// ----------------------
virtual void OnLButtonDblClk(UINT nFlags, CPoint point) {;}
// ----------------------

private:
bool enabled;
};


In this example we are supporting only the LEFT BUTTON CLICK event. Note that you may support other events too. We have added a bool member to handle the switching between ON / OFF states.

After that we need to add this button when our application starts. Open your acrxEntryPoint.cpp file and add the following include:

#include "MyPane.h"

Now, add a MyPane pointer variable as a member of your application class:

MyPane* pMyPane;

Next, add the following two functions to your application class:

// ----------------------
void AddMyPaneButton()
{
AcApStatusBar* pStatus = acedGetApplicationStatusBar();
if (pStatus) {
pMyPane = new MyPane();
pStatus->Add(pMyPane);
}
}
// ----------------------
void RemoveMyPaneButton()
{
AcApStatusBar* pStatus = acedGetApplicationStatusBar();
if (pStatus && pMyPane) pStatus->Remove(pMyPane);
}
// ----------------------


Finally, we need to call these two functions from inside the kInitAppMsg and kUnloadAppMsg events:

// ----------------------
virtual AcRx::AppRetCode On_kInitAppMsg (void *pkt) {
AcRx::AppRetCode retCode =AcRxArxApp::On_kInitAppMsg (pkt) ;
AddMyPaneButton();
return (retCode) ;
}
// ----------------------
virtual AcRx::AppRetCode On_kUnloadAppMsg (void *pkt) {
AcRx::AppRetCode retCode =AcRxArxApp::On_kUnloadAppMsg (pkt) ;
RemoveMyPaneButton();
return (retCode) ;
}
// ----------------------


That's it. Now if you load your ARX application you will see a new Status Bar Button. Try it to switch between ON/OFF. Cool?

Fernando.

Sunday, May 14, 2006

User's Samples and Articles

Hello,

I have created a section at this Blog dedicated to all users that would like to send some samples and articles they want to share.

All articles and samples are supported and are responsibility of its authors.

The first article is from Nikolay N. Poleshchuk, a russian AutoCAD author, about a transparent splash screen.

Feel free to send me your articles and samples.

Regards,
Fernando.

Transparent Splash Screen

Transparent Splash Screen As a Modeless Dialog[1]

We would like the splash screen window automatically disappear e.g. after five seconds period. It would be good if an impatient user could close it by a click in the client area.
Moreover it is interesting for the splash screen to be transparent and we could see the current drawing entities under it. Transparency can be reached by Opacity property of the windows created with Windows Forms.

Create a new ObjectARX project named Book16 with the preferences as in the prevoius project[2].

Add book16 LISP function that would be called as ads_book16 C-function defined in acrxEntryPoint.cpp file (see listing 5.34).

Listing 5.34. The ads_book16 function

// Based on:
// N.Poleshchuk, Chapter 05\Book16\acrxEntryPoint.cpp
// In the book "AutoCAD: Application Development, Tuning and// Customization"
// (BHV-Petersburg Publishing House. Russia, 2006)
// http://poleshchuk.spb.ru/cad/eng.html
//
// ----- ads_book16 symbol (do not rename)
static int ads_book16(void)
{
splash16();
acedRetVoid () ;
return (RSRSLT) ;
}

splash16 will be a managed code function. Add to the project Splash16.h and Splash16.cpp files (declaration and body of the splash16 function) and insert #include "Splash16.h" statement at the beginning of the acrxEntryPoint.cpp file.
The Splash16.h file besides splash16 declaration will contain Wform16 class definition for a dialog box. Code for the Splash16.h file is given in listing 5.35.

Listing 5.35. The Splash16.h file

// Based on:
// N.Poleshchuk, Chapter 05\Book16\Splash16.h
// In the book "AutoCAD: Application Development, Tuning and// Customization"
// (BHV-Petersburg Publishing House. Russia, 2006)
// http://poleshchuk.spb.ru/cad/eng.html
//
#pragma once
#using <system.drawing.dll>
#using <system.windows.forms.dll>



#include "StdAfx.h"
using namespace System;
using namespace System::Drawing;
using namespace System::Windows::Forms;

void splash16();

public __gc class Wform16: public Form
{
public:
Wform16();
protected:
void OnTimerTick(Object *sender, System::EventArgs *ea);
void OnClick(Object *sender, System::EventArgs * ea);
};

Besides splash16 function prototype the Splash16.h file contains description of a managed class derived from the standard Form class. There are three member functions in the class:


  • Wform16 — Wform16 class constructor;
  • OnTimerTick — function handling the timer event (timer signal);
  • OnClick — function handling the second event (click in the client area of the dialog box).
Splash16.cpp file (shown in listing 5.36) includes all the function implementations.

Listing 5.36. The Splash16.cpp file

// Based on:
// N.Poleshchuk, Chapter 05\Book16\Splash16.cpp
// In the book "AutoCAD: Application Development, Tuning
// and Customization"
// (BHV-Petersburg Publishing House. Russia, 2006)
// http://poleshchuk.spb.ru/cad/eng.html
//
#include "StdAfx.h"
#include "Splash16.h"

using namespace System::Drawing;
using namespace System::Windows::Forms;
using namespace Autodesk::AutoCAD::ApplicationServices;

void splash16()
{
Wform16* pwf = new Wform16();
Autodesk::AutoCAD::ApplicationServices::Application::
ShowModelessDialog(pwf);
}

Wform16::Wform16()
{
// Dialog box parameters
Size = System::Drawing::Size(370, 300);
StartPosition = FormStartPosition::Manual;
Location = Point(300, 150);
BackColor = Color::Aquamarine;
Opacity = 0.5;
FormBorderStyle = FormBorderStyle::None;

// Font
FontFamily *pFf = new FontFamily(L"Arial");
System::Drawing::Font *pFont = new System::Drawing::Font(pFf, 36, FontStyle::Bold);

// Text in the middle of the window
Label *pTxt1 = new Label();
pTxt1->Text = S"Book16";
pTxt1->Location = Point(90, 120);
pTxt1->AutoSize = true;
pTxt1->ForeColor = Color::Black;
pTxt1->Font = pFont;
Controls->Add(pTxt1);

// Handler of the Click event for the form
this->Click += new EventHandler(this, &Wform16::OnClick);

// Timer creation
System::Windows::Forms::Timer *tm = new Timer();
tm->Interval = 5000; // Signal interval
tm->Tick += new EventHandler(this, &Wform16::OnTimerTick);
tm->Enabled = true;
}

void Wform16::OnClick(Object *sender, System::EventArgs *e)
{
// by click close the window
Wform16::Close();
}

// Reaction to the first (and the last) timer signal
void Wform16::OnTimerTick(Object *sender, System::EventArgs *ea)
{
System::Windows::Forms::Timer *t1 =
static_cast (sender);
// Timer stop
t1->Stop();
// Close the window after the first signal
Wform16::Close();
}

splash16 function creates an instance of the dialog class and opens the splash screen window with the special ShowModelessDialog method in modeless mode. The dialog object is deleted as a result of watching two events: Tick (timer signal) and Click (click inside window but not on the text). Standard event list is reviewed in the next section.

Pic. 5.63 shows splash screen of our application over the current drawing background. The window is opened by the LISP expression (book16), entered in the AutoCAD command line.


Pic. 5.63. Window with transparent splash screen

This transparent window looks like the window being shown while starting AutoCAD session.
Let’s consider window constructor (Wform16::Wform16 function in the Splash16.cpp file). At first window parameters are being defined. The Size, StartPosition, Location and BackColor properties sense is familiar to us from the previous example[3].
The Opacity property sets the opacity ratio. It can take real values from 0 to 1. When 1 — the window is opaque, when 0 — absolutely transparent (and not seen). We use an average value (0.5).

The FormBorderStyle property is set to None that is a member of the FormBorderStyle enumeration. It means that the window has neither frame nor caption (as the AutoCAD splash screen has).

To place an inscription in the center of the window it is necessary to prepare the font. That is why the FontFamily and Font class instances are created. The font is used in the Label class instance which is added by the Add method to the Controls collection of the dialog.
The next constructor text line adds the Click event handler, that is a click inside the form (but not on the text control having its own Click event):

this->Click = new EventHandler(this, &Wform16::OnClick);

Event handler is an object representing the EventHandler class. Using the above text line we connect the object with a delegate (i.e. pointer to the Wform16::OnClick function). This is the function that will be called if event occured.

The Wform16::OnClick function has a very simple body — only Close method is run. That’s why after click inside the form the splash screen closes.

The last four statements in the constructor create a timer with definite features. The timer that issues signals with some frequency is an object of the Timer class. The FCL[4] classes with the same names exist in three namespaces: System.Timer, System.Threading and System.Windows.Forms. In our Book16 project we chose the last class.
When creating a timer at last the two properties should get their values:

  • Interval — time slot, after which the timer signals; the slot is measured in milliseconds, therefore 5000 value corresponds to 5 seconds of the real time;
  • Enabled — timer state; the true value is obligatory to activate the timer object.
The following line creates a handler of the Tick event (timer signals in given time slots):

tm->add_Tick(new EventHandler(this,&Wform16::TimerOnTick));

The first timer signal must be emitted in five seconds after start. This signal also must become the last one: the Wform16::TimerOnTick function stops the timer (Stop method is used) and closes the dialog box.

The timer should be stopped (another way is to set false to Enabled property), else after that garbage collector will free the memory used by the timer.

So in our sample the splash screen appears in a modeless mode (that is we may do some drafting in the document while seeing the splash) and disappears if the user clicks it or five seconds pass after start.

If there is a nonzero probability of launching the second copy of the splash screen then it is useful to add to our program the analysis of our splash screen presence in the main memory (it is sufficient to create a corresponding global variable and to check its value).

Event handling

Adding controls to the form and their event handling were considered in the previous section[5] Book16 sample project. Now we will emphasize some moments. For example we will take the Button control which is a button of our dialog box.
In order to add a button to a dialog we must create a new instance of the corresponding class:

Button *bt = new Button();

After this it is possible to set values to the properties, e.g.: Text, Size, Location, BackGroundImage, BackColor, ForeColor, Font etc. The created object is then included into collection of the form controls:

Controls->Add(bt);

Each class is connected with many standard events that could be handled programmatically (your own events can be created too). Let’s mention some of the Button control events:


  • BackColorChanged — button background color was changed;
  • Click — there was a click on the button;
  • DragEnter — an object was dragged to the button;
  • ForeColorChanged — text color was changed;
  • GotFocus — button received focus;
  • KeyDown — key was pressed while button had focus;
  • MouseDown — mouse button was pressed when cursor was over the button;
  • MouseUp — mouse button was released when cursor was over the button;
  • VisibleChanged — button visibility state changed (connected with Visible property).
We must create an event callback function that will be called to analyze event parameters and running some actions. Mostly function names look like event names. For example we can expect that for Button object Click event the function name will be OnClick, OnButtonClick or OnBtClick.

Each of the selected events must be connected with the handler that is an object of the EventHandler class. Namely this handler calls necessary function if event occurs.
E.g. if we build for the Click event a handler that should call Wform16::OnBtClick function then the statement for the handler creation in the Wform16 form class constructor body will be as follows:

add_Click(new EventHandler(this, &Wform16::OnBtClick));

Just the same in a more modern manner (using overloaded += operator):

bt->Click += new EventHandler(this, &Wform16::OnBtClick);

Some events (for example Paint) have another form of the same thing:

Paint += new PaintEventHandler (this, OnPaint);

The last form looks like adding event handler in C# language (see chapter 7[6]).
Every callback function receives two arguments: pointer to an object whose event was generated and additional event data. For example:

void Wform16:: OnTimerTick(Object *sender, System::EventArgs *ea)

The first argument has universal System::Object* type therefore while applying we must bring it to the necessary type (use static_cast operator, see listing 5.36).

Downloads

http://poleshchuk.spb.ru/cad/Download/Book16eng-2002(AutoCAD2006).zip
http://poleshchuk.spb.ru/cad/Download/Book16eng-2005(AutoCAD2007).zip
English Download page is accessible by link http://poleshchuk.spb.ru/cad/Downloade.htm

[1] N.Poleshchuk “AutoCAD: Application Development, Tuning and Customization”, pp. 449–455.
[2] The previous project was created with ObjectARX Wizard using .NET mixed managed code support. (Author’s note)
[3] They define the size, coordinates and background color of the window. (Author’s note)
[4] Framework Class Library.
[5] Opaque Splash Screen As a Modal Dialog. (Author’s note)
[6] Chapter 7. Fortran, Delphi, C#, VB etc. (Author’s note)



Tuesday, May 09, 2006

Using MACROS inside ObjectARX

Hello,

Sometimes is very tedious to create functions to access your custom object members. In some situations we also forget to call the proper assert method before edit or read the variable. This can cause you some trouble later.

I have decided to make a bunch of macros to make our life easier. Using these macros you can speed up your development.

How to use:

First, place the following macros inside a general .H file, say ArxMacros.h, and place it inside your project:

#define _ARX_SGET(x) LPCTSTR Get##x(void) const { assertReadEnabled(); return _s##x; }
#define _ARX_SSET(x) void Set##x(LPCTSTR sValue) { assertWriteEnabled(); _s##x.Format("%s", sValue); }
#define _ARX_IGET(x) int Get##x(void) const { assertReadEnabled(); return _i##x; }
#define _ARX_ISET(x) void Set##x(int iValue) { assertWriteEnabled(); _i##x = iValue; }
#define _ARX_BGET(x) BOOL Get##x(void) const { assertReadEnabled(); return _b##x; }
#define _ARX_BSET(x) void Set##x(BOOL bValue) { assertWriteEnabled(); _b##x = bValue; }
#define _ARX_FGET(x) float Get##x(void) const { assertReadEnabled(); return _f##x; }
#define _ARX_FSET(x) void Set##x(float fValue) { assertWriteEnabled(); _f##x = fValue; }
#define _ARX_DTGET(x) COleDateTime Get##x(void) const { assertReadEnabled(); return _dt##x; }
#define _ARX_DTSET(x) void Set##x(COleDateTime dtValue) { assertWriteEnabled(); _dt##x = dtValue; }
#define _ARX_LGET(x) long Get##x(void) const { assertReadEnabled(); return _l##x; }
#define _ARX_LSET(x) void Set##x(long lValue) { assertWriteEnabled(); _l##x = lValue; }
#define _ARX_DGET(x) double Get##x(void) const { assertReadEnabled(); return _d##x; }
#define _ARX_DSET(x) void Set##x(double dValue) { assertWriteEnabled(); _d##x = dValue; }
#define _ARX_OBJIDGET(x) AcDbObjectId Get##x(void) const { assertReadEnabled(); return _id##x; }
#define _ARX_OBJIDSET(x) void Set##x(AcDbObjectId lValue) { assertWriteEnabled(); _id##x = lValue; }
#define _ARX_PT3DGET(x) const AcGePoint3d& Get##x(void) const { assertReadEnabled(); return _pt##x; }
#define _ARX_PT3DSET(x) void Set##x(AcGePoint3d ptValue) { assertWriteEnabled(); _pt##x = ptValue; }
#define _ARX_PT2DGET(x) const AcGePoint2d& Get##x(void) const { assertReadEnabled(); return _pt##x; }
#define _ARX_PT2DSET(x) void Set##x(AcGePoint2d ptValue) { assertWriteEnabled(); _pt##x = ptValue; }


Go to your custom object's class header and place them as follows:

_ARX_SGET(Name)
_ARX_SSET(Name)
_ARX_DGET(Number)
_ARX_DSET(Number)
_ARX_PT3DGET(Point)
_ARX_PT3DSET(Point)

When you compile the code these macros will expand as follows:
void SetName(LPCTSTR sValue);
LPCTSTR GetName(void) const;

void SetNumber(double dValue);
double GetNumber(void) const;

void SetPoint(AcGePoint3d ptValue);
const AcGePoint3d& GetPoint(void) const;

As they refer to local class variable we need to add them will proper naming convention (note that I put a prefix on each variable inside macros using a letter to identify its type):

CString _sName;
double _dNumber;
AcGePoint3d _ptPoint;

You don't need to place anything else inside your class CPP file. Of course if you need a special behavior when accessing your members you will need to fully create the access function.

That's it, now you have your access functions ready to use.

Fernando.

Wednesday, March 01, 2006

AutoCAD 2007

Hello,

Today, 1st March 2006, all beta sites are allowed do discuss about the next release of AutoCAD.
Named AutoCAD 2007 it will present several exciting new features specially on 3D, Visualization and Render engines.

Regarding to the programming side, there are several and important issues related to this new release. I will discuss them further but I would present below a brief introduction about these impacts:
  • Binary Compatibility Break: AutoCAD 2007 is being compiled with new Visual Studio 2005 which uses MFC 8.0 and .NET Framework 2.0. This will cause a binary break on your ObjectARX applications and they will need to be recompiled with the new ObjectARX 2007 SDK and Visual Studio 2005. Once you compile your application with the new ObjectARX 2007 SDK you can't load it on previous version so, for a couple of years and until your clients don't upgrade too, you will need to live with two releases using side by side versions of AutoCAD, ObjectARX and Visual Studio;
  • UNICODE support: AutoCAD 2007 requires that your ObjectARX application fully support and use unicode. This will require a full review in your code converting all non-unicode string variables and calls to unicode. This can take long time depending on how your code strings are used and organized;
  • New Solid and 3D API: AutoCAD 2007 brings several features on 3D solids modeling and 3D visualization. The Render engine was totally redesigned and now supports new types of materials, tranparency, visual styles and much more;
  • Several .NET API improvements: New CUI API and Tool Palette Management. AutoLISP interoperability from .NET (now you can call LISP function and send/receive parameters from .NET).

There are much more and you really need to see by yourself.

I'm working on a new set of tutorials and documents to explore the new Visual Studio 2005 compiler, ObjectARX 2007 and AutoCAD 2007. Stay tuned for the upcoming articles.

As soon as the AutoCAD 2007 is released Autodesk will make available the new ObjectARX 2007 SDK for downloading. Stay tuned on Autodesk website during the next days.

Cheers!
Fernando Malard.

Monday, August 08, 2005

Contest cancelled

Hello,
Our contest was cancelled because no application was submitted.
To allow other users to also participate, we have changed the Contest as follows:

- The contest will be about applications developed with ObjectARX (don't need to show AutoCAD 2006 new features anymore);
- The contest will be opened only through AUGI (Autodesk User Group International);
- Users who want to participate will need to register at AUGI (www.augi.com) and register to ATP086 (ObjectARX for Dummies) class (http://www.augi.com/education/schedule.asp)
- At the end of this ATP course we will judge the best ObjectARX application;

Kindest regards,
Fernando.

Wednesday, July 13, 2005

Contest registration postponed

Hello,

I would like to announce that the key dates of our Contest has been postponed.
New key dates are:

Registration and application deploy: July, 29
Contest results: August, 5

Hope this give more time to users decide to participate on this Contest.
Invite your friends to participate too!
Remember, we are providing 2 copies of AutoCAD 2006 (NFR)!

Cheers!
Fernando.

Monday, June 27, 2005

Register and submit your application by 15th July

Hello,

I would like to invite all users who not have submitted their applications yet and did not make the registration that both can be done until 15th July.

Regards,
Fernando.

Tuesday, June 21, 2005

AutoCAD 2006 Tips

Hello,

I would like to suggest a link that provides some tips for AutoCAD 2006. Maybe from within this content you may extract a great idea for the Contest Application:

AutoCAD 2006 Tips

Remember, submit your registration by 25th June.

Regards,
Fernando.

Monday, June 20, 2005

Contest Registration will close this week

Hello,

Please, remember that our Contest Registration will end on 25th June. Please send your registration form filled out and start to build your sample!

You can be one of the winners and get your full copy of AutoCAD 2006 (not for resale). First place also receives a full license of VisualAssistX.

Regards,
Fernando.

Tuesday, June 07, 2005

Contest registration will open next June 10th

Hello,

The First ObjectARX for Dummies Contest will be officially opened on next Friday (June 10th). By now everyone may download the contest rules here. Please fill this rules form and return to me from June 10th until June 25th.

For those who not have an AutoCAD 2006 version to build and test their samples I would suggest you to download a 30 day evaluation copy of AutoCAD or use one from a friend or your company's computer.

Be creative and explore the new AutoCAD 2006 features!

To be fare with everyone I will limit all support questions to basic features to avoid give any privilege to any contest user.

The winners will be judged by me, Shaan Hurley (from Autodesk) and Lynn Allen (from Autodesk).

Key Dates:

Contest Opens on: 10th June 2005
Last Date for Registration: 25th June 2005
Last Date for Submission: 15th July 2005
Contest Results: 25th July 2005

Good luck to everyone!

Cheers,
Fernando.

Saturday, June 04, 2005

Class 14 - Creating a Custom Entity (step by step)

Hello,

On this class I will complete the Class 13 with a tutorial to show you how to create a simple custom entity step by step. This entity has some features that will help you to see what is possible to do using ObjectARX. Please read carefully the following instructions.

-Create a Blank Solution called CustomEntitySample;
-Add a new DBX project called CustEntityDBX;
-Add a new ARX project called CustEntityARX;
-Remember to enable MFC Extension on both projects;
-Create a dependency from ARX project to DBX:


-Go to DBX project and open Autodesk Class Explorer;
-Right click on CustEntityDBX node and select "Add ObjectDBX Custom Object...":


-Choose MyCustomEntity as class name and derive it from AcDbEntity;
-Other fields will be filled automatically (you may change these values);


-Go to the Protocols TAB and enable both Osnap and Grip-points protocols:


-Rebuild your project to see if everything is ok;
-Now, open again the Autodesk Class Explorer, go to CustEntityDBX project;
-Select the MyCustomEntity node (if it is not there, click on Refresh icon);
-Right click on it and select "Add Variable...":


-Name it as "m_PtA", type will be an AcGePoint3d and its access will be protected;
-Enable "Participate to DWG filing protocol", disable "Increase Version number";
-Enable "Implement Get/Put methods" and fill desired comments for it;
-Repeat the process and create : "m_PtB", "m_PtAB" and "m_PtBA":


-Now, add a CString variable called "m_Text";
-Remember to correct the CString manipulation and filing as we have done in Lab3;
-Now you should compile your code ok;


Before continue, we will need to remove some functions that we will not use. First remove the following from .H file:

virtual Acad::ErrorStatus getGripPoints (
AcDbGripDataPtrArray &grips, const double curViewUnitSize, const int gripSize,
const AcGeVector3d &curViewDir, const int bitflags) const;

virtual Acad::ErrorStatus moveGripPointsAt (
const AcDbVoidPtrArray &gripAppData,
const AcGeVector3d &offset, const int bitflags);


There are 4 methods getOsnapPoints(). We will only use the following method:

virtual Acad::ErrorStatus getOsnapPoints (
AcDb::OsnapMode osnapMode, int gsSelectionMark, const AcGePoint3d &pickPoint,
const AcGePoint3d &lastPoint, const AcGeMatrix3d &viewXform,
AcGePoint3dArray &snapPoints, AcDbIntArray &geomIds) const ;


Now, you will need to delete the body of these functions from cpp file.

WORLDDRAW

-To draw our custom entity, we will need to add the following code to worldDraw() method:

assertReadEnabled();
// Bounding Polyline
AcGePoint3d pts[4];
pts[0] = m_PtA;
pts[1] = m_PtAB;
pts[2] = m_PtB;
pts[3] = m_PtBA;

mode->subEntityTraits().setSelectionMarker(1); // Mark 1
mode->subEntityTraits().setColor(1); // Red
mode->geometry().polygon(4,pts);

// Entity's Text
mode->subEntityTraits().setSelectionMarker(2); // Mark 2
mode->subEntityTraits().setColor(256); // ByLayer

AcGiTextStyle style;
style.setFileName(_T("txt.shx"));
style.setBigFontFileName(_T(""));
style.setTextSize(25);
style.loadStyleRec();

AcGePoint3d txtPt((m_PtB.x+m_PtA.x)/2.0, (m_PtB.y+m_PtA.y)/2.0, m_PtA.z);

mode->geometry().text(txtPt, AcGeVector3d::kZAxis,
(m_PtAB-m_PtA),m_Text, m_Text.GetLength(),Adesk::kFalse, style);

return Adesk::kTrue;


GRIP Points

-Open remaining getGripPoints() method inside MyCustomEntity implementation (cpp);
-We would like to enable 5 grips for this entity. One at each point (A,B,AB and BA) plus one at the center;
-Inside the getGripPoints(), add the following code:

gripPoints.append(m_PtA);
gripPoints.append(m_PtAB);
gripPoints.append(m_PtB);
gripPoints.append(m_PtBA);
gripPoints.append(AcGePoint3d((m_PtB.x+m_PtA.x)/2.0,
(m_PtB.y+m_PtA.y)/2.0,m_PtA.z));

-Now, when user clicks on each Grip we would like to perform an action. This is done using the moveGripPointsAt() method;
-We need to check the index of each fired grip accordingly to the getGripPoints() method and then apply the transformation;
-Inside remaining moveGripPointsAt() method, add the following code:

assertWriteEnabled();
for(int i=0; i<indices.length(); i++) {
int idx = indices.at(i);
// For A and center point
if (idx==0 idx==4) m_PtA += offset;
// For AB and center point
if (idx==1 idx==4) m_PtAB += offset;
// For B and center point
if (idx==2 idx==4) m_PtB += offset;
// For BA and center point
if (idx==3 idx==4) m_PtBA += offset;
}
return (Acad::eOk);


OSNAP Points


-Open the getOsnapPoints() method. We will add 3 Osnap modes: EndPoint, MidPoint and Center:

assertReadEnabled();
switch (osnapMode) {

case AcDb::kOsModeEnd:
snapPoints.append(m_PtA);
snapPoints.append(m_PtAB);
snapPoints.append(m_PtB);
snapPoints.append(m_PtBA);
break;

case AcDb::kOsModeMid:
snapPoints.append(m_PtA+((m_PtAB-m_PtA).length()/2.0)*((m_PtAB-m_PtA).normalize()));
snapPoints.append(m_PtAB+((m_PtB-m_PtAB).length()/2.0)*((m_PtB-m_PtAB).normalize()));
snapPoints.append(m_PtB+((m_PtBA-m_PtB).length()/2.0)*((m_PtBA-m_PtB).normalize()));
snapPoints.append(m_PtBA+((m_PtA-m_PtBA).length()/2.0)*((m_PtA-m_PtBA).normalize()));
break;

case AcDb::kOsModeCen:
snapPoints.append(AcGePoint3d((m_PtB.x+m_PtA.x)/2.0,
(m_PtB.y+m_PtA.y)/2.0, m_PtA.z));
break;

}
return (Acad::eOk);


TRANSFORMATION


-The custom entity transformation is done through transformBy() method;
-Open the Autodesk Class Explorer, expand the MyCustomEntity node, expand Base Classes node and then expand AcDbEntity node;
-Scroll down the list and select:

Acad::ErrorStatus transformBy(const AcGeMatrix3d &);

-Right click on this method and select "Implement Base Class Method";
-This function logic is very simple, just add the following code:

assertWriteEnabled();
m_PtA.transformBy(xform);
m_PtAB.transformBy(xform);
m_PtB.transformBy(xform);
m_PtBA.transformBy(xform);
return (Acad::eOk);

-This function will allow the custom entity to be transformed;

ARX PROJECT

-First we will need to add a new command called "MyCustEnt";
-This will create a method inside acxrEntryPoint.cpp file;
-Now, add the necessary include instruction after the #include "StdAfx.h":

#include "..\CustEntityDBX\MyCustomEntity.h"
-Inside the method we will create our entity:

// Input information
ads_point pt1,pt2;
if (acedGetPoint(NULL,_T("Set the first point:\n"),pt1) != RTNORM)
return;
if (acedGetCorner(pt1,_T("Set the second point:\n"),pt2) != RTNORM)
return;

TCHAR buffer[512];
if (acedGetString(0,_T("Enter the text:\n"),buffer) != RTNORM)
return;

// Setup entity
MyCustomEntity *pEnt = new MyCustomEntity();
pEnt->put_m_PtA(asPnt3d(pt1));
pEnt->put_m_PtAB(AcGePoint3d(pt2[X],pt1[Y],pt1[Z]));
pEnt->put_m_PtB(asPnt3d(pt2));
pEnt->put_m_PtBA(AcGePoint3d(pt1[X],pt2[Y],pt2[Z]));
pEnt->put_m_Text(buffer);

// Post to Database
AcDbBlockTable *pBlockTable;
acdbHostApplicationServices()->workingDatabase()->getSymbolTable(pBlockTable,
AcDb::kForRead);

AcDbBlockTableRecord *pBlockTableRecord;
pBlockTable->getAt(ACDB_MODEL_SPACE, pBlockTableRecord,AcDb::kForWrite);
pBlockTable->close();

AcDbObjectId retId = AcDbObjectId::kNull;
pBlockTableRecord->appendAcDbEntity(retId, pEnt);
pBlockTableRecord->close();
pEnt->close();


TEST

-Rebuild your project;
-Open AutoCAD and load DBX module first and then load the ARX;
-Fire command MYCUSTENT and create as many entities as you want;
-Test it against GRIP edit, MOVE, ROTATE, SCALE;
-Fire a LINE command and try to get precision points (EndPoint, MidPoint and Center) over your entities;