Вы находитесь на странице: 1из 116

OpenCAD

A Step by Step Guide to


Developing a Professional
CAD Application

Deelip Menezes
www.deelip.com
OpenCAD
A Step by Step Guide to Developing a Professional CAD Application

Published by:
SYCODE
S1/116, Nova Cidade Complex
NH 17, Alto Porvorim, Goa - 403521 India
www.sycode.com

Copyright © 2009 by SYCODE

ISBN: 978-0-557-05592-0

No part of this publication may be reproduced, stored in a retrieval system or transmitted in any form or by any
means, electronic, mechanical, photocopying, recording, scanning or otherwise, without either the prior written
permission of the publisher, or authorization through payment of the appropriate per copy fee to the publisher.

Limits of Liability a nd Disclaimer of Warranty


The author and publisher of this book have tried their best to ensure that the programs, procedures and functions
contained in the book are correct. However, the author and publisher make no warranty of any kind, expressed or
implied, with regard to these programs or the documentation contained in the book. The author and publisher shall
not be liable in any event for any damages, incidental or consequential, in connection with, or arising out of the
furnishing, performance or use of these programs, procedures and functions. Product names mentioned are used for
identification purposes only and may be trademarks of their respective companies.

All trademarks referred to in the book are acknowledged as properties of their respective owners.
To my wife Indira and sons - Reuben and Russell
Contents
Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Section I . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Chapter 1: The Basic OpenCAD Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .7
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .7
Creating the Visual C++ Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .8
Setting up the Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .17
Conclusion. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .18
Chapter 2: OpenCAD as a DWG Reader. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .19
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .19
Setting up the Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .19
Initializing and Uninitializing DWGdirect . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .21
Reading a DWG file . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .22
Conclusion. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .24
Chapter 3: OpenCAD as a DWG Viewer. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .25
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .25
Modifying the View class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .25
Vectorization. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .26
Conclusion. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .30
Chapter 4: OpenCAD as a 3D Viewer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .31
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .31
Render Modes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .31
3D Navigation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .37

Zoom Window and Zoom Extents . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .40


Conclusion. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .44
Section II. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
Chapter 5: Plug-in Architecture. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .49
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .49
The OdEdBaseIO class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .49
Loading DRX Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .51
Conclusion. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .53

i
Chapter 6: Creating a DRX Plug-in . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Creating a Visual C++ project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Setting up the Project. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
Creating a DRX Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
Calling the DRX Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
Organizing the Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
Chapter 7: The Command Prompt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
Connecting the Document and Child Frame. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
Designing the Command Prompt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
Creating the Command Prompt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
Docking the Command Prompt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
Getting the Command Prompt to work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
Launching commands from the command prompt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
Repeating commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
Handling the unexpected . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
Wrapping up the Import command . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
Chapter 8: OpenCAD as a DWG Editor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
Setting up the GetPoint mode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
Creating the Line command . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
Creating a line by picking points. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
Creating the rubber band visual feedback. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
Taking care of cancelled commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
Drawing a chain of lines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
Handling the unexpected . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
The get methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
Running the Line command in Bricscad V9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109

ii
Acknowledgements
I have been writing about the Open Design Alliance and its technologies on my blog at
www.deelip.com for quite some time now. But the idea of this book was born out of a meeting I
had with Arnold van der Weide, the President of the ODA and its CTO, Neil Peterson, on the
sidelines of COFES 2008. This book was made possible mainly due to the vision of these two
individuals who came to the conclusion that I was made up of author material even before I did.

The Open Design Alliance forum is a treasure trove of technical information, tips and example
code related to ODA technologies. Most of my questions were answered even before I asked
them. I thank all the forum members who helped me understand the intricacies of the
DWGdirect SDK

Shweta Chari, the web designer at SYCODE, was instrumental in helping me format this book.
While writing this book, I realized that while formatting a book may not be as complicated as
C++ programming, it is not that easy either.

I write software for a living, not books. So most of this book was written in my free time at
home. That would never have been possible without the encouragement and support of my wife
Indira, who spent a good deal of her time and energy distracting our two boys so that I could
concentrate on writing this book. Work takes up most of my time and I know how much she
wants me to spend time with the kids. Indira is the inspiration behind everything I do and the
most important reason why this book has seen the light of day.

1
2
Introduction
Ever since the Open Design Alliance (formerly the OpenDWG Alliance) was founded, it has
been busy reverse engineering the DWG file format as and when Autodesk changed it. Due to
this the ODA came to be known as the "hackers group" who give nothing but pain to Autodesk
by offering their members libraries to read and write DWG files. Autodesk already has a library
called RealDWG which reads and writes DWG files, but they are known not to license it to their
business rivals. Hence the need for an organization such the ODA grew and the ODA delivered
every time Autodesk changed the DWG file format.

My company, SYCODE (www.sycode.com), is a member of the Autodesk Developer Network


(ADN) as well as a member of the ODA. I have been keeping a close eye on the cat and mouse
game between Autodesk and ODA for quite some time now. In all the confusion, law suits and
out-of-court settlements, there is one important aspect of the ODA that has gone completely
unnoticed. And the purpose and motivation for me to write this book is to shed some light on
that particular and very interesting aspect.

Every time the ODA reverse engineered the DWG file format, they improved their technology,
not surprisingly, by cloning that of Autodesk. One thing led to another and they finally ended up
cloning Autodesk's ObjectARX SDK, the very foundation on which AutoCAD has been built.
The ODA called their clone DWGdirect and needless to say, ODA members started using
DWGdirect to read and write DWG files. And that is the problem which I hope to address by
means of this book. DWGdirect is not just a SDK to read and write DWG files. It actually offers
a full blown framework that can be used to develop a professional CAD application, complete
with plug-in architecture and all. Applications built using the DWGdirect SDK are called
DWGdirect hosted applications. The not yet released IntelliCAD 7 is one of them. Bricsys
rewrote Bricscad as a DWGdirect hosted application in V8 itself.

This book is my attempt to show that the ODA offers far more than libraries to read and write
DWG files. We will create the framework of a professional CAD application (which I have called
OpenCAD) using nothing but Visual C++ 2005 and a bunch of ODA libraries. You will also
learn how to create plug-ins that extend OpenCAD using the ODA's free DRX SDK. And of
course, OpenCAD will be able to read and write DWG files as well.

This book is divided into two sections. Section I deals with creating the basic OpenCAD
application, wiring it up with required ODA libraries and adding features to make it a full blown
professional DWG viewer. Section II deals with adding plug-in architecture to OpenCAD and
developing a plug-in that converts it into a DWG editor. We will also see how the plug-in
developed for OpenCAD loads and runs in Bricscad V9 as well.

3
If you are an ODA member then you already have access to the DWGdirect SDK and you can
start building OpenCAD or your own DWGdirect hosted application by following the
instructions in Section I of this book. If you are not an ODA member you can download the
OpenCAD source code and binaries from www.open-cad.com and start developing plug-ins for
it or any other DWGdirect hosted application by following the instructions in Section II of this
book.

The point of the OpenCAD software and this book is not to develop a full blown free CAD
application. Rather my intention is to showcase the various technologies offered by the ODA,
apart from reading and writing DWG files. We will first create OpenCAD as a DWG viewer and
then add features as we proceed.

OpenCAD is not open source for the simple reason that the DWGdirect SDK is not open
source. However, all the C++ source code used to build OpenCAD and its plug-ins are available
free of cost at www.open-cad.com. I have also organized the source code by chapter. So if you
want to skip a chapter or two you can do so.

I have used Microsoft Visual C++ 2005 and DWGdirect version 2.06 to develop OpenCAD and
its plug-in. The ODA offers libraries for other compilers as well and you can very well use
another compiler.

I write software for a living, not books. So I am not quite sure how this book is going to turn out.
I am going to need all the criticism that I can get - good, bad and ugly. Please do give it to me.

If this book ends up helping you in any way or gives you a better understanding of the
technologies offered by the ODA, do let me know. It will make me happy. 

So let's get right to it.

4
Section I
In this section we will start with a empty MFC Doc-View application. We will then wire it to the
DWGdirect SDK headers and libraries and give it the ability to read DWG files. We will then
create a drawing view where we will display the contents of a DWG file thereby converting it into
a DWG viewer. Finally, we will add 3D viewing features like orbit, pan and zoom to OpenCAD
and convert it into a professional 3D DWG Viewer.

Chapter 1: The Basic OpenCAD Application


Chapter 2: OpenCAD as a DWG Reader
Chapter 3: OpenCAD as a DWG Viewer
Chapter 4: OpenCAD as a 3D Viewer

5
6
Chapter 1: The Basic
OpenCAD Application
Introduction
In this chapter we will create the basic OpenCAD application using Visual Studio’s MFC
Application template. To keep things in order create a folder called OpenCAD in your C: drive. In
this folder create another folder called Bin. We will build the OpenCAD executable and other
plug-in DLL files in this Bin folder. We will also copy related DLLs into this folder so that the
OpenCAD executable can locate them as and when required.
OpenCAD

Creating the Visual C++ Project


Create a new project using MFC Application as the template. Select C:\OpenCAD as the project
location and enter OpenCAD as the project name.

Fig 1.1: New Project

Click OK. This takes us to the first step of the MFC Application Wizard.

8
Chapter 1: The Basic OpenCAD Application

Fig 1.2: Wizard Step 1

Click Next.

9
OpenCAD

Fig 1.3: Wizard Step 2

We will create OpenCAD as a Multiple Document Interface (MDI) application. This means it
will be able to open multiple DWG files at the same time. So select Multiple documents for
Application type. We will also be using MFC’s document/view architecture. So ensure that the
Document/View architecture support box is checked. Click Next.

10
Chapter 1: The Basic OpenCAD Application

Fig 1.4: Wizard Step 3

We will not need any compound document support. Click Next.

11
OpenCAD

Fig 1.5: Wizard Step 4

OpenCAD will use DWG as its native file format. Enter dwg for File extension. Change other
values according to the Fig 1.5 and click Next.

12
Chapter 1: The Basic OpenCAD Application

Fig 1.6: Wizard Step 5

We will not need any database support. Click Next.

13
OpenCAD

Fig 1.7: Wizard Step 6

I prefer to have the main frame and child frame windows maximized. If you prefer the same
check the respective boxes and click Next.

14
Chapter 1: The Basic OpenCAD Application

Fig 1.8: Wizard Step 7

I also find MAPI quite useful. It will allow a user to send the active DWG file by email directly
from within OpenCAD. Check the MAPI (Messaging API) box and click Next.

15
OpenCAD

Fig 1.9: Wizard Step 8

We will use CView as the base class for our drawing view.
Click Finish to generate the project. A Visual C++ solution will be created in the location we
specified.

16
Chapter 1: The Basic OpenCAD Application

Setting up the Project


We need the OpenCAD executable to be built in the Bin folder. Bring up the project properties
dialog box (Alt+F7) and change the value for Output file to ../Bin/OpenCADD.exe for the Debug
configuration and ../Bin/OpenCAD.exe for the Release configuration. Note the extra D for the
Debug configuration. Since Debug and Release versions of the executables will be located in the Bin
folder, we need to give them different names.

Fig 1.10: Output file

17
OpenCAD

Press Ctrl+F5 to build OpenCAD and run it.

Fig 1.11: The basic OpenCAD application

Conclusion
Congratulations! We have just built an application which does absolutely nothing useful. In the
next chapter we will change that. We will connect OpenCAD to the DWGdirect SDK and give it
the ability to read DWG files.

18
Chapter 2: OpenCAD as a
DWG Reader
Introduction
In the previous chapter we created the basic OpenCAD application as an empty MFC Doc-View
application. In this chapter we will give OpenCAD the ability to read DWG files. To achieve this
we will need to link OpenCAD to the DWGdirect libraries and include some helper C++ files
that are shipped along with the DWGdirect SDK.

Setting up the Project


In Project Properties set Additional Include Directories to the DWGdirect SDK include folder and
set Additional Library Directories to the DWGdirect SDK lib folder for Visual C++ 2005 as shown
in Fig 2.1 and 2.2.

Fig 2.1: Additional Include Directories

Fig 2.2: Additional Library Directories


OpenCAD

We will be using the DLL version of the DWGdirect libraries. For this it is necessary to add
_TOOLKIT_IN_DLL_ to Preprocessor Definitions.

Fig 2.3: Preprocessor Definitions

In the Solution Explorer add a new filter to Source Files and call it ExServices. Here is where we will
place the C++ helper files from the DWGdirect SDK so that they are not mixed with our regular
source files. Insert the files ExHostAppServices.cpp, ExSystemServices.cpp,
ExUndoController.cpp and OdFileBuf.cpp from the Extensions/ExServices folder of the
DWGdirect SDK.

Fig 2.4: Insert files into project

In Project Properties set these newly added files not to use Precompiled Headers.

Fig 2.5: Precompiled headers

Open stdafx.cpp and add the following lines of code.

#pragma comment(lib, "DD_Alloc_dll.lib")


#pragma comment(lib, "DD_Root_dll.lib")
#pragma comment(lib, "DD_Db_dll.lib")
#pragma comment(lib, "DD_DbRoot_dll.lib")
#pragma comment(lib, "DD_Ge_dll.lib")
#pragma comment(lib, "DD_Key.lib")

20
Chapter 2: OpenCAD as a DWG Reader

Initializing and Uninitializing DWGdirect


In OpenCAD.h add the following include statements before the COpenCADApp class definition.

#include "OdaCommon.h"
#include "OdToolKit.h"
#include "/SDK/OpenDesign/DWGdirect/2.06/Extensions/ExServices/ExHostAppServices.h"
#include "/SDK/OpenDesign/DWGdirect/2.06/Extensions/ExServices/ExSystemServices.h"

We need to derive COpenCADApp additionally from the ExSystemServices and ExHostAppServices


classes apart from CWinApp. This will give COpenCADApp the ability to interact directly with the
DWGdirect SDK. However, ExSystemServices and ExHostAppServices have new and delete operators
and hence we will need to resolve the ambiguity with the new and delete operators of MFC’s
CObject class. We also need to define member functions addRef() and release() since they are
defined as pure virtual functions in OdRxObject, the base class of the DWGdirect SDK. And
lastly, we need to add two public methods to initialize and uninitialize the DWGdirect library. So
we edit the class definition.

class COpenCADApp : public CWinApp, public ExSystemServices, public ExHostAppServices


{
protected:
using CWinApp::operator new;
using CWinApp::operator delete;
void addRef() {}
void release() {}
public:
BOOL InitializeDWGdirect();
void UninitializeDWGdirect();

Add the InitializeDWGdirect() and UninitializeDWGdirect() function definitions to OpenCAD.cpp

BOOL COpenCADApp::InitializeDWGdirect()
{
try
{
::odInitialize(this);
}
catch(OdError& /*err*/)
{
AfxMessageBox(_T("DWGdirect Initialization error"));
return FALSE;
}
catch(...)
{
AfxMessageBox(_T("DWGdirect Initialization error"));
return FALSE;
}

21
OpenCAD

return TRUE;
}

void COpenCADApp::UninitializeDWGdirect()
{
::odUninitialize();
}

We need to call InitializeDWGdirect() when OpenCAD starts up and before we use any of the
DWGdirect functionality. A good place to do that would be in COpenCADApp::InitInstance(). Add
the following code after the call to SetRegistryKey().

if(InitializeDWGdirect() == FALSE)
return FALSE;

We also need to call UnintializeDWGdirect() when OpenCAD shuts down. Override ExitInstance()
and call UninitializeDWGdirect() before the call to CWinApp::ExitInstance().

int COpenCADApp::ExitInstance()
{
// TODO: Add your specialized code here and/or call the base class
UninitializeDWGdirect();
return __super::ExitInstance();
}

Copy the following DLLs from the DWGdirect SDK exe/VC8/Release folder into the Bin folder.
DD_Alloc_2.06_8.dll
DD_Db_2.06_8.dll
DD_DbRoot_2.06_8.dll
DD_Ge_2.06_8.dll
DD_Gi_2.06_8.dll
DD_Root_2.06_8.dll

Build and run OpenCAD. It should build without errors. If it does not build verify that you have
followed the instructions properly. We now have an application that is linked to the DWGdirect
libraries and correctly initializes and unintializes the DWGdirect SDK on application startup and
shutdown respectively.

Reading a DWG file


Let us use the DWGdirect SDK to give OpenCAD the ability to read a DWG file and store its
contents. The DWGdirect SDK has a class called OdDbDatabase which is meant for exactly that
purpose. If you have used Autodesk’s ObjectARX SDK, you will find that the OdDbDatabase is a
clone of the ObjectARX AcDbDatabase class.

22
Chapter 2: OpenCAD as a DWG Reader

In OpenCADDoc.h add the following include statement before the COpenCADDoc class
definition.

#include "DbDatabase.h"

Next add a public variable of type OdDbDatabasePtr to the Attributes section of COpenCADDoc.
OdDbDatabasePtr is a smart pointer to a OdDbdatabase object. The DWGdirect SDK extensively
uses smart pointers.

// Attributes
public:
OdDbDatabasePtr m_pDatabase;

Further down the road when we read a DWG file, its contents will be stored in the object
pointed to by the m_pDatabase smart pointer. However, for a new empty document we need to
create a new database with the bare minimum requirements. This is done by the createDatabase()
method of the OdDbhostAppServices helper class. We will call this method in
COpenCADDoc::OnNewDocument() just before the return statement.

BOOL COpenCADDoc::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;

// TODO: add reinitialization code here


// (SDI documents will reuse this document)
m_pDatabase = theApp.createDatabase();
return TRUE;
}

Build and run OpenCAD. By default OpenCAD is designed to create an empty document on
startup. Although it may not seem obvious to you at this point in time, OpenCAD initializes the
DWGdirect SDK when it starts up, creates an empty database when a new empty document is
created and uninitializes the DWGdirect SDK when it shuts down.

Finally let us make OpenCAD actually read a DWG file and populate the m_pDatabase member
with some real life data. To do that we override the OnOpenDocument() method of CDocument and
use the readFile() method of the ExHostAppServices class. As a test, we will also let OpenCAD
report the file name and version.

BOOL COpenCADDoc::OnOpenDocument(LPCTSTR lpszPathName)


{
if (!CDocument::OnOpenDocument(lpszPathName))
return FALSE;

// TODO: Add your specialized creation code here

23
OpenCAD

try
{
m_pDatabase = theApp.readFile(lpszPathName, true, false);
}
catch(OdError& /*err*/)
{
MessageBox(AfxGetMainWnd()->m_hWnd, _T("Unable to read file"), _T("File Read
Error"), MB_ICONWARNING);
return FALSE;
}

CString s;
s.Format(_T("Filename: %s\nVersion: %d\n"),
m_pDatabase->getFilename().c_str(),
m_pDatabase->originalFileVersion());
AfxMessageBox(s);

return TRUE;
}

Build and run OpenCAD. Open a DWG file and a dialog box will pop up showing us the file
name and version, which is one of the values of the OdDb::DwgVersion enum.

Fig 2.6: File name and version

Conclusion
We have converted OpenCAD into a DWG reader. In order to make it into a DWG viewer we
need to display the contents of the DWG file in the drawing view, which is the subject of the next
chapter.

24
Chapter 3: OpenCAD as a
DWG Viewer
Introduction
In the previous chapter we made OpenCAD read a DWG file. In this chapter we will give
OpenCAD the ability to display the contents of a DWG file in the drawing view.

Modifying the View class


First delete the code in COpenCADDoc::OnOpenDocument() which displays the file name and
version as we do not need it anymore.

CString s;
s.Format(_T("Filename: %s\nVersion: %d\n"),
CString(m_pDatabase->getFilename().c_str()),
m_pDatabase->originalFileVersion());
AfxMessageBox(s);

We will now add DWG viewing capability to the COpenCADView class. In OpenCADView.h
include the header files GiContextForDbDatabase.h and Gs.h before the COpenCADView class
declaration. Then proceed to derive COpenCADView from OdGiContextForDbDatabase. Just as we
did for COpenCADApp we will need to prevent the ambiguity for the new and delete operators and
define the addRef() and release() methods. The modified code should look like this

#include "GiContextForDbDatabase.h"
#include "Gs/Gs.h"

class COpenCADView : public CView, OdGiContextForDbDatabase


{
protected:
using CView::operator new;
using CView::operator delete;
void addRef() {}
void release() {}

OpenCAD

The OdGiContextForDbDatabase class defines the methods and properties that are used in the
vectorization of an OdDbDatabase object. Basically, we will simply outsource the entire display of
a DWG file to this class.

Add two public variables to COpenCADView.

// Attributes
public:
OdGsDevicePtr m_pDevice; // Vectorizer device
ODCOLORREF m_clrBackground; // Drawing background color

Next add five public method declarations.

void ResetDevice(BOOL bZoomExtents = FALSE);


void SetViewportBorderProperties(OdGsDevice* pDevice, BOOL bModel);
OdGsViewPtr GetView();
void ViewZoomExtents();
const ODCOLORREF* CurrentPalette();

In OpenCADview.cpp include four header files.

#include "DbGsManager.h"
#include "RxVariantValue.h"
#include "AbstractViewPE.h"
#include "ColorMapping.h"

Initialize the background color to black in the COpenCADView constructor.

COpenCADView::COpenCADView()
{
// TODO: add construction code here
m_clrBackground = RGB(0, 0, 0);
}

Vectorization
Next add the bodies of the five newly added methods. Pay special attention to the comments. We
will use DirectX for vectorization.

void COpenCADView::ResetDevice(BOOL bZoomExtents)


{
// Get the client rectangle
CRect rc;
GetClientRect(&rc);

// Load the vectorization module


OdGsModulePtr pGs = ::odrxDynamicLinker()->loadModule("WinDirectX_2.06_8.gs");

26
Chapter 3: OpenCAD as a DWG Viewer

// Create a new OdGsDevice object, and associate with the vectorization GsDevice
m_pDevice = pGs->createDevice();
if(m_pDevice.isNull())
return;

// Return a pointer to the dictionary entity containing the device properties


OdRxDictionaryPtr pProperties = m_pDevice->properties();

// Set the window handle for this GsDevice


pProperties->putAt("WindowHWND", OdRxVariantValue((long)m_hWnd));

// Define a device coordinate rectangle equal to the client rectangle


OdGsDCRect gsRect(rc.left, rc.right, rc.bottom, rc.top);

// Set the device background color and palette


m_pDevice->setBackgroundColor(theApp.m_clrBackground);
m_pDevice->setLogicalPalette(theApp.CurrentPalette(), 256);

if(!database())
return;

// Set up the views for the active layout


m_pDevice = OdDbGsManager::setupActiveLayoutViews(m_pDevice, this);

// Return true if and only the current layout is a paper space layout
BOOL bModelSpace = (GetDocument()->m_pDatabase->getTILEMODE() == 0);

// Set the viewport border properties


SetViewportBorderProperties(m_pDevice, !bModelSpace);

if(bZoomExtents)
ViewZoomExtents();

// Update the client rectangle


OnSize(0, rc.Width(), rc.Height());

// Redraw the window


RedrawWindow();
}

void COpenCADView::SetViewportBorderProperties(OdGsDevice* pDevice, BOOL bModel)


{
// If current layout is Model, and it has more then one viewport then make their
borders visible.
// If current layout is Paper, then make visible the borders of all but the overall
viewport.
int n = pDevice->numViews();
if(n > 1)
{
for(int i = bModel ? 0 : 1; i < n; ++i)
{
// Get the viewport
OdGsViewPtr pView = pDevice->viewAt(i);

27
OpenCAD

// Make it visible
pView->setViewportBorderVisibility(true);

// Set the color and width


pView->setViewportBorderProperties(theApp.CurrentPalette()[7], 1);
}
}
}

OdGsViewPtr COpenCADView::GetView()
{
return m_pDevice->viewAt(0);
}

void COpenCADView::ViewZoomExtents()
{
// Get the overall viewport
OdGsViewPtr pView = GetView();

// Modifies the viewport to fit the extents


OdAbstractViewPEPtr(pView)->zoomExtents(pView);
}

const ODCOLORREF* COpenCADView::CurrentPalette()


{
const ODCOLORREF *pColor = odcmAcadPalette(m_clrBackground);
return pColor;
}

Next override the OnInitialUpdate(), OnSize(), OnPaint() and OnEraseBkgnd() functions and add
code to them.

void COpenCADView::OnInitialUpdate()
{
__super::OnInitialUpdate();

// TODO: Add your specialized code here and/or call the base class
OdGiContextForDbDatabase::setDatabase(GetDocument()->m_pDatabase);
enableGsModel(true);
ResetDevice(true);
}

void COpenCADView::OnSize(UINT nType, int cx, int cy)


{
__super::OnSize(nType, cx, cy);

// TODO: Add your message handler code here


if(!m_pDevice.isNull() && cx && cy)
{
CRect rc;
GetClientRect(rc);

// Update the client rectangle

28
Chapter 3: OpenCAD as a DWG Viewer

OdGsDCRect Rect(OdGsDCPoint(rc.left, rc.bottom), OdGsDCPoint(rc.right,


rc.top));
m_pDevice->onSize(Rect);
}
}

void COpenCADView::OnPaint()
{
CPaintDC dc(this); // device context for painting
// TODO: Add your message handler code here
// Do not call __super::OnPaint() for painting messages

// Paint the client rectangle with the GS device


if(!m_pDevice.isNull())
m_pDevice->update();
}

BOOL COpenCADView::OnEraseBkgnd(CDC* pDC)


{
// TODO: Add your message handler code here and/or call default

//return __super::OnEraseBkgnd(pDC);
return TRUE;
}

In COpenCADView::ResetDevice() we dynamically load the DirectX graphics system libary and


need to make it and its associated DLLs accessible to OpenCAD. Copy the following files from
the DWGdirect SDK exe/VC8/Release folder to the Bin folder.
WinDirectX_2.06_8.gs
ModelerGeometry_2.06_8.drx
DD_AcisBuilder_2.06_8.dll
DD_Br_2.06_8.dll
DD_BrepRenderer_2.06_8.dll
DD_Gs_2.06_8.dll
DD_SpatialIndex_2.06_8.dll

Build and run OpenCAD. You will notice that the new empty document created on application
startup has a black background. This means something has worked. Go ahead and open a DWG
file.

29
OpenCAD

Fig 3.1: DWG viewer

Congratulations!! We have just converted OpenCAD into a DWG viewer.

Conclusion
In its present condition OpenCAD reads a DWG file, orients the viewpoint to the last saved
viewpoint and zooms to the extents of the drawing. But a professional viewer needs to do much
more. It needs to be interactive. We need to add the ability to zoom, pan, orbit and navigate
around the drawing. This is what we will do in the next chapter. We will convert OpenCAD into
a 3D viewer.

And by the way, OpenCAD is not just a DWG viewer. It can read DXF files as well. Try opening
a DXF file and be pleased. 

30
Chapter 4: OpenCAD as a
3D Viewer
Introduction
As part of making OpenCAD a professional 3D viewer, we will give it the ability to display a
drawing in different display styles, technically called render modes. As we will see, this is as easy
as calling a single method. We will also add 3D navigation capability to OpenCAD.

Render Modes
Open the resource editor and add a sub menu to the View menu drop down containing seven
items as shown in the figure below.

Fig 4.1: Render mode sub menu

In OpenCADView.h add a public variable to COpenCADView to store the current render mode.

OdGsView::RenderMode m_iRenderMode; // Render mode

Declare a public method to set the render mode.

void SetRenderMode(OdGsView::RenderMode iRenderMode);


OpenCAD

In OpenCADView.cpp initialize the render mode to 2D Optimized in the COpenCADView


constructor.

m_iRenderMode = OdGsView::k2DOptimized;

Add the body of the SetRenderMode() function.

void COpenCADView::SetRenderMode(OdGsView::RenderMode iRenderMode)


{
// Get the first view
OdGsViewPtr pView = GetView();

// Check if render mode needs to be changed


if(pView->mode() == iRenderMode)
return;

// Set the render mode


pView->setMode(iRenderMode);

// Check if render mode was changed


if(pView->mode() != iRenderMode)
{
MessageBox(_T("Sorry, this render mode is not supported by the current device"),
_T("OpenCAD"), MB_ICONWARNING);
}
else
{
// Show the new render mode
PostMessage(WM_PAINT);
m_iRenderMode = iRenderMode;
}
}

Finally, add command handlers for the seven menu items.

void COpenCADView::OnViewRendermode2dwireframe()
{
// TODO: Add your command handler code here
SetRenderMode(OdGsView::k2DOptimized);
}

void COpenCADView::OnUpdateViewRendermode2dwireframe(CCmdUI* pCmdUI)


{
// TODO: Add your command update UI handler code here
pCmdUI->SetCheck(m_iRenderMode == OdGsView::k2DOptimized);
}

void COpenCADView::OnViewRendermode3dwireframe()
{
// TODO: Add your command handler code here
SetRenderMode(OdGsView::kWireframe);
}

32
Chapter 4: OpenCAD as a 3D Viewer

void COpenCADView::OnUpdateViewRendermode3dwireframe(CCmdUI* pCmdUI)


{
// TODO: Add your command update UI handler code here
pCmdUI->SetCheck(m_iRenderMode == OdGsView::kWireframe);
}

void COpenCADView::OnViewRendermode3dhidden()
{
// TODO: Add your command handler code here
SetRenderMode(OdGsView::kHiddenLine);
}

void COpenCADView::OnUpdateViewRendermode3dhidden(CCmdUI* pCmdUI)


{
// TODO: Add your command update UI handler code here
pCmdUI->SetCheck(m_iRenderMode == OdGsView::kHiddenLine);
}

void COpenCADView::OnViewRendermodeFlatshaded()
{
// TODO: Add your command handler code here
SetRenderMode(OdGsView::kFlatShaded);
}

void COpenCADView::OnUpdateViewRendermodeFlatshaded(CCmdUI* pCmdUI)


{
// TODO: Add your command update UI handler code here
pCmdUI->SetCheck(m_iRenderMode == OdGsView::kFlatShaded);
}

void COpenCADView::OnViewRendermodeFlatshadedwireframe()
{
// TODO: Add your command handler code here
SetRenderMode(OdGsView::kFlatShadedWithWireframe);
}

void COpenCADView::OnUpdateViewRendermodeFlatshadedwireframe(CCmdUI* pCmdUI)


{
// TODO: Add your command update UI handler code here
pCmdUI->SetCheck(m_iRenderMode == OdGsView::kFlatShadedWithWireframe);
}

void COpenCADView::OnViewRendermodeSmoothshaded()
{
// TODO: Add your command handler code here
SetRenderMode(OdGsView::kGouraudShaded);
}

void COpenCADView::OnUpdateViewRendermodeSmoothshaded(CCmdUI* pCmdUI)


{
// TODO: Add your command update UI handler code here
pCmdUI->SetCheck(m_iRenderMode == OdGsView::kGouraudShaded);
}

void COpenCADView::OnViewRendermodeSmoothshadedwireframe()

33
OpenCAD

{
// TODO: Add your command handler code here
SetRenderMode(OdGsView::kGouraudShadedWithWireframe);
}

void COpenCADView::OnUpdateViewRendermodeSmoothshadedwireframe(CCmdUI* pCmdUI)


{
// TODO: Add your command update UI handler code here
pCmdUI->SetCheck(m_iRenderMode == OdGsView::kGouraudShadedWithWireframe);
}

Build and run OpenCAD. Open a 3D DWG file and play around with the different render
modes. Here are some screenshots of a 3D solid created by a booleanm union of a sphere and a
torus.

Fig 4.2: 2D Wireframe

Fig 4.3: 3D Wireframe

34
Chapter 4: OpenCAD as a 3D Viewer

Fig 4.4: 3D Hidden

Fig 4.5: Flat Shaded

35
OpenCAD

Fig 4.6: Flat Shaded + Wireframe

Fig 4.7: Smooth Shaded

36
Chapter 4: OpenCAD as a 3D Viewer

Fig 4.8: Smooth Shaded + Wireframe

3D Navigation
Let us add 3D navigation capabilities to OpenCAD. Since we are designing OpenCAD to be a
3D viewer, we will set up the navigation as follows:
• Right mouse button click and drag orbits around the drawing.
• Middle mouse button click and drag pans over the drawing.
• Mouse wheel scroll zooms in and out of the drawing.

This leaves us with the left mouse button which we can use to click and drag a zoom window
rectangle, pick points, select objects and so on. First we will implement the mouse wheel scroll to
zoom in and out of the drawing. In OpenCADView.h declare a public method called Dolly().

void Dolly(int x, int y);

Define it in OpenCADView.cpp

void COpenCADView::Dolly(int x, int y)


{
// Get the view
OdGsViewPtr pView = GetView();

// Set up the dolly vector


OdGeVector3d Vector(-x, -y, 0.0);
Vector.transformBy((pView->screenMatrix() * pView->projectionMatrix()).inverse());

// Perform the dolly

37
OpenCAD

pView->dolly(Vector);
}

Add a handler for the WM_MOUSEWHEEL message.

BOOL COpenCADView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)


{
// TODO: Add your message handler code here and/or call default
OdGsViewPtr pView = GetView();
OdGePoint3d Position(pView->position());
Position.transformBy(pView->worldToDeviceMatrix());
int x, y;
x = (int)OdRound(Position.x);
y = (int)OdRound(Position.y);
x = pt.x - x;
y = pt.y - y;

Dolly(-x, -y);
pView->zoom(zDelta > 0 ? 1.0 / 0.9 : 0.9);
Dolly(x, y);

PostMessage(WM_PAINT);

return __super::OnMouseWheel(nFlags, zDelta, pt);


}

Build and run OpenCAD. Open a DWG file and scroll the mouse wheel. You will see that by
writing just two small functions we have implemented the zoom feature in OpenCAD.

Next we will implement orbit and pan. Override the following Windows messages:
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_MBUTTONDOWN
WM_MBUTTONUP
WM_RBUTTONDOWN
WM_RBUTTONUP
WM_MOUSEMOVE

In OpenCADView.h add three variables of type BOOL to keep track of the mouse buttons and
one varibale of type CPoint to keep track of the mouse position.

BOOL m_bLeftButton; // Flag for left mouse button press


BOOL m_bMiddleButton; // Flag for middle mouse button press
BOOL m_bRightButton; // Flag for right mouse button press
CPoint m_MousePosition; // Position of mouse pointer

Add code to the overriden functions.

38
Chapter 4: OpenCAD as a 3D Viewer

void COpenCADView::OnLButtonDown(UINT nFlags, CPoint point)


{
// TODO: Add your message handler code here and/or call default
m_bLeftButton = TRUE;
m_MousePosition = point;
__super::OnLButtonDown(nFlags, point);
}

void COpenCADView::OnLButtonUp(UINT nFlags, CPoint point)


{
// TODO: Add your message handler code here and/or call default
m_bLeftButton = FALSE;
__super::OnLButtonUp(nFlags, point);
}

void COpenCADView::OnMButtonDown(UINT nFlags, CPoint point)


{
// TODO: Add your message handler code here and/or call default
m_bMiddleButton = TRUE;
m_MousePosition = point;
__super::OnMButtonDown(nFlags, point);
}

void COpenCADView::OnMButtonUp(UINT nFlags, CPoint point)


{
// TODO: Add your message handler code here and/or call default
m_bMiddleButton = FALSE;
__super::OnMButtonUp(nFlags, point);
}

void COpenCADView::OnRButtonDown(UINT nFlags, CPoint point)


{
// TODO: Add your message handler code here and/or call default
m_bRightButton = TRUE;
m_MousePosition = point;
__super::OnRButtonDown(nFlags, point);
}

void COpenCADView::OnRButtonUp(UINT nFlags, CPoint point)


{
// TODO: Add your message handler code here and/or call default
m_bRightButton = FALSE;
__super::OnRButtonUp(nFlags, point);
}

void COpenCADView::OnMouseMove(UINT nFlags, CPoint point)


{
// TODO: Add your message handler code here and/or call default
if(m_bLeftButton == TRUE)
{
}
else if(m_bMiddleButton == TRUE)
{
OdGeVector3d Vector(m_MousePosition.x - point.x, m_MousePosition.y - point.y,
0.0);

39
OpenCAD

// Screen to Eye
Vector.transformBy((GetView()->screenMatrix() * GetView()-
>projectionMatrix()).inverse());
GetView()->dolly(Vector);
m_MousePosition = point;
PostMessage(WM_PAINT);
}
else if(m_bRightButton == TRUE)
{
GetView()->orbit((m_MousePosition.y - point.y) / 100.0, (m_MousePosition.x -
point.x) / 100.0);
m_MousePosition = point;
PostMessage(WM_PAINT);
}
__super::OnMouseMove(nFlags, point);
}

We will handle the left mouse button later. Build and run OpenCAD. Right click and drag the
mouse to orbit around the drawing. Middle click and drag the mouse to pan over the drawing. It’s
really that simple.

Zoom Window and Zoom Extents


Now that we have the basic zoom, pan and orbit working, let us give OpenCAD the ability to
zoom into a portion of the drawing view (Zoom Window) and the ability to zoom out to display
all objects (Zoom Extents). Add two menu items as show in the figure below.

Fig 4.9: Zoom Window and Zoom Extents

Lets implement Zoom Extents first. In OpenCADView.cpp include the following header file

#include "AbstractViewPE.h"

Add a handler for the Zoom Extents menu item and call ViewZoomExtents().

void COpenCADView::OnViewZoomextents()
{

40
Chapter 4: OpenCAD as a 3D Viewer

// TODO: Add your command handler code here


ViewZoomExtents();
}

To implement zoom window we will need to prompt the user to draw a rectangle in the drawing
view and then zoom into that rectanglular portion on the screen. To make this happen we will
need two functions, one to convert screen coordinates of the points picked to world coordinates
in the drawing and the other to zoom the view using the world coordinates of the rectangular
portion.

In OpenCADView.h add two methods to COpenCADView.

OdGePoint3d GetWorldCoordinates(CPoint Point);


void ZoomWindow(OdGePoint3d Point1, OdGePoint3d Point2);

In OpenCADView.cpp add their definitions

OdGePoint3d COpenCADView::GetWorldCoordinates(CPoint Point)


{
OdGsViewPtr pView = GetView();
OdGePoint3d WCSPoint(Point.x, Point.y, 0.0);

WCSPoint.transformBy((pView->screenMatrix() * pView-
>projectionMatrix()).inverse());
WCSPoint.z = 0.0;
WCSPoint.transformBy(OdAbstractViewPEPtr(pView)->eyeToWorld(pView));

return WCSPoint;
}

void COpenCADView::ZoomWindow(OdGePoint3d Point1, OdGePoint3d Point2)


{
OdGsViewPtr pView = GetView();
OdGeMatrix3d WorldToEye = OdAbstractViewPEPtr(pView)->worldToEye(pView);

Point1.transformBy(WorldToEye);
Point2.transformBy(WorldToEye);
OdGeVector3d Vector = Point2 - Point1;

if(OdNonZero(Vector.x) && OdNonZero(Vector.y))


{
OdGePoint3d NewPosition = Point1 + Vector / 2.0;

Vector.x = fabs(Vector.x);
Vector.y = fabs(Vector.y);

pView->dolly(NewPosition.asVector());

double wf = pView->fieldWidth() / Vector.x;


double hf = pView->fieldHeight() / Vector.y;

41
OpenCAD

pView->zoom(odmin(wf, hf));

PostMessage(WM_PAINT);
}
}

Now that we have set things up to do the Zoom Window, let us proceed to ask the user to define
the rectangular portion that we need to zoom into. We will use the left mouse button here. Since
this is an interactive operation, we need to put OpenCAD into a zoom window “mode”. We do
that by adding a boolean member variable to COpenCADView. We will also need to store the
points clicked by the user in order to supply it to ZoomWindow(). Instead of storing them as
separate OdGePoint3d member variables, we will use an OdGePoint3dArray instead. By doing it this
way we will be able to handle operations that need more than two points, say drawing a polyline.
Also at any point in time, by counting the number of items in the array, OpenCAD would know
the stage of the zoom window operation it is currently in.

BOOL m_bZoomWindow; // Flag for zoom window mode


OdGePoint3dArray m_Points; // Mouse clicks

Initialize it to FALSE in the COpenCADView constructor.

m_bZoomWindow = FALSE;

Add command handlers for the Zoom Extents menu item.

void COpenCADView::OnViewZoomwindow()
{
// TODO: Add your command handler code here
m_Points.clear();
m_bZoomWindow = !m_bZoomWindow;
}

void COpenCADView::OnUpdateViewZoomwindow(CCmdUI *pCmdUI)


{
// TODO: Add your command update UI handler code here
pCmdUI->SetCheck(m_bZoomWindow);
}

We will ask the user to specify the zoom rectangle by the normal left click - drag - release
method. So we need to look for the first point when the left mouse button is pressed and the
second point when the left mouse button is released. Add the code highlighted in bold to
COpenCADView::OnLButtonDown() and COpenCADView::OnLButtonUp()

void COpenCADView::OnLButtonDown(UINT nFlags, CPoint point)


{
// TODO: Add your message handler code here and/or call default
m_bLeftButton = TRUE;
m_MousePosition = point;

42
Chapter 4: OpenCAD as a 3D Viewer

if(m_bZoomWindow == TRUE)
{
m_Points.clear(); // Empty point array
m_Points.append(GetWorldCoordinates(point)); // Record first point
}

__super::OnLButtonDown(nFlags, point);
}

void COpenCADView::OnLButtonUp(UINT nFlags, CPoint point)


{
// TODO: Add your message handler code here and/or call default
m_bLeftButton = FALSE;

if(m_bZoomWindow == TRUE)
{
m_Points.append(GetWorldCoordinates(point)); // Record second point
if(m_Points.length() == 2)// Zoom rectangle has been completely defined
{
ZoomWindow(m_Points[0], m_Points[1]);
m_bZoomWindow = FALSE; // Turn off zoom window mode
m_Points.clear(); // Empty point array
}
}

__super::OnLButtonUp(nFlags, point);
}

Build and run OpenCAD. Try the Zoom Window command. It should work as expected.
However, you will notice something missing. We need to draw the rubber band zoom rectangle
as the user click-drags the zoom window. We will do that next. But before you close OpenCAD
try the Zoom Extents command.

To add the rubber band zoom rectangle feature, we need to keep track of where the mouse was
initially clicked. Add a CPoint member variable to COpenCADView.

CPoint m_MouseClick; // Location of mouse click

In COpenCADView::OnLButtonDown() record the mouse click position. Add the line highlighted
in bold in the following code snippet.

void COpenCADView::OnLButtonDown(UINT nFlags, CPoint point)


{
// TODO: Add your message handler code here and/or call default
m_bLeftButton = TRUE;
m_MousePosition = point;
m_MouseClick = point;

if(m_bZoomWindow == TRUE)
{

43
OpenCAD

m_Points.clear(); // Empty point array


m_Points.append(GetWorldCoordinates(point)); // Record first point
}

__super::OnLButtonDown(nFlags, point);
}

Finally in COpenCADView::OnMouseMove() add code to draw the zoom window rectangle.


Previously we had left an empty if block for the left mouse button. We will now fill it up with
code.

if(m_bLeftButton == TRUE)
{
if(m_bZoomWindow == TRUE)
{
CClientDC dc(this);// Get a client DC which accesses the client area of the view
CRect rcZoom, rcZoomOld;

// Get the old zoom window


rcZoomOld.SetRect(m_MouseClick.x, m_MouseClick.y, m_MousePosition.x,
m_MousePosition.y);
rcZoomOld.NormalizeRect();
rcZoomOld.InflateRect(1, 1);

// Redraw the old zoom window


RedrawWindow(&rcZoomOld);

// Create the new zoom window


rcZoom.SetRect(m_MouseClick.x, m_MouseClick.y, point.x, point.y);
rcZoom.NormalizeRect();

// Draw a focus rectangle for the new zoom window


dc.DrawFocusRect(&rcZoom);

m_MousePosition = point;
}
}

Build and run OpenCAD. This time around the Zoom Window command will feel much better
with the rubber band zoom window giving you visual feedback indicating the portion of the
drawing view you are going to zoom into.

Conclusion
So there you have it. OpenCAD is now a full blown professional DWG Viewer, complete with
3D navigational features. I am not sure whether you have noticed but we have set up 3D
navigation in very efficient manner. Usually a CAD application makes the user click a menu item
or button on a toolbar to get into orbit mode so that the user can then click and drag around the

44
Chapter 4: OpenCAD as a 3D Viewer

model. To pan he has to quit the orbit mode and click another menu item or toolbar button to
get into pan mode. We have set up OpenCAD in such a way that these frequent change of
navigation modes is eliminated. You can orbit using the right mouse button, pan using the
middle mouse button and zoom using the mouse wheel. The navigation modes depend entirely
upon the mouse buttons and change automatically as you mouse around the drawing view. You
need the menu only for Zoom Window and Zoom Extents. This makes OpenCAD a very
powerful and efficient CAD viewer.

45
OpenCAD

46
Section II
We started Section I of this book by creating OpenCAD as a skeleton MFC MDI application. We
then wired the DWGdirect SDK to it and gave it the ability to read DWG files. Next we
converted OpenCAD into a DWG viewer and finally added 3D navigational capability to it.

In Section II we will take OpenCAD to the next level. We will add plug-in architecture to it and
learn how to develop DRX plug-ins containing custom commands that can be called from within
OpenCAD. We will also give OpenCAD a command prompt, similar to AutoCAD, IntelliCAD,
Rhinoceros, etc., where a user can type in and run a custom command defined in an external
DRX plug-in. This is powerful stuff. As you will see, things are going to get quite interesting
from here on.

Chapter 5: Plug-in Architecture


Chapter 6: Creating a DRX Plug-in
Chapter 7: The Command Prompt
Chapter 8: OpenCAD as a DWG Editor

47
48
Chapter 5: Plug-in
Architecture
Introduction
In this chapter we will add plug-in architecture to OpenCAD. This will transform OpenCAD
into an extremely powerful CAD application. Anyone with C++ knowledge will be able to use
the free DRX SDK provided by the ODA to develop DRX plug-ins that will work inside of
OpenCAD and extend its capabilities.

The OdEdBaseIO class


We will start by modifying the COpenCADDoc class. In OpenCADDoc.h include
ExDbCommandContext.h. We will also need to add ExDbCommandContext.cpp to the Source
Files/ExServices filter in the Solution Explorer and set it not to use Precompiled Headers in
Project Properties. Next derive COpenCADDoc additionally from OdEdBaseIO. This will help in
the command prompt functionality that we will be adding later. As usual we will need to prevent
the ambiguity of the new and delete operators. Modify the COpenCADDoc class as listed below.

#include "/SDK/OpenDesign/DWGdirect/2.06/Extensions/ExServices/ExDbCommandContext.h"

class COpenCADDoc : public CDocument, protected OdStaticRxObject<OdEdBaseIO>


{
protected:
using CDocument::operator new;
using CDocument::operator delete;

If you try to build the project now you will get a “cannot instantiate abstract class” error. This
because we need to define two functions that were declared as pure virtual in OdEdBaseIO. The
reasons for this will become obvious a little later. For now lets just add the functions.

// Operations
public:
OdString getString(const OdString& prompt, int options, OdEdStringTracker*
pTracker);
void putString(const OdString& string);

In OpenCADDoc.cpp add their definitions. We will add code to these functions later.
OpenCAD

OdString COpenCADDoc::getString(const OdString& prompt, int options,


OdEdStringTracker* pTracker)
{
return OdString("");
}

void COpenCADDoc::putString(const OdString& string)


{
}

At this point, the project will build successfully. Now let us add an important member variable to
COpenCADDoc - a pointer to a command context object. The OdDbCommandContext class defines
the interface for I/O and database access for custom commands during their execution. Stuff
like asking the user for input at the command prompt or letting the user select a point or object
in the drawing window. It also needs to access the database of the document to read and write
information to it. Add a command context pointer to COpenCADDoc.

OdDbCommandContextPtr m_pCommandContext;

Next declare three methods.

OdEdBaseIO* GetIO();
OdDbCommandContextPtr GetCommandContext();
BOOL ExecuteCommand(const OdString& strCommand, BOOL bEcho = TRUE);

In OpenCADDoc.cpp include EdCommandStack.h.

#include "Ed/EdCommandStack.h"

Then add the bodies of the three methods declared earlier.

OdEdBaseIO* COpenCADDoc::GetIO()
{
return this;
}

OdDbCommandContextPtr COpenCADDoc::GetCommandContext()
{
if(m_pCommandContext.isNull())
m_pCommandContext = ExDbCommandContext::createObject(GetIO(), m_pDatabase);

return m_pCommandContext;
}

BOOL COpenCADDoc::ExecuteCommand(const OdString& strCommand, BOOL bEcho)


{
OdDbCommandContextPtr pCmdCtx = GetCommandContext();
try
{

50
Chapter 5: Plug-in Architecture

OdEdCommandStackPtr pCommands = ::odedRegCmds();


OdString sCommand = strCommand.spanExcluding(L" \t\r\n");
if(bEcho)
putString(_T("Command: ") + sCommand);
OdEdCommand* pCommand = pCommands->lookupCmd(sCommand);
if(pCommand == NULL)
putString(_T("Unknown command '") + sCommand + _T("'"));
else
pCommands->executeCommand(sCommand, pCmdCtx);
}
catch(const OdError& e)
{
putString(_T("Error: ") + e.description());
return FALSE;
}
return TRUE;
}

We have just set up COpenCADDoc to handle a custom command. All we now need to do is call
COpenCADDoc::ExecuteCommand() to execute a custom command. As you can see, the method
uses the odedRegCmds() function to get the global command stack and then calls executeComand() to
execute the command. The global command stack is basically a set of custom commands
organized in groups, usually one group for each DRX module. A DRX module is quite simply a
DLL with a .drx file extension. We will learn how to build these modules in the next chapter.

Loading DRX Modules


On application startup a DWGdirect hosted application loads DRX modules and fills its global
command stack with their custom commands. So now that we have given COpenCADDoc the
ability to execute custom commands from DRX modules we simply need to make OpenCAD
load DRX modules on startup and populate its global command stack. A good place to do that
would be in COpenCADApp::InitInstance().

Add a new method to COpenCADApp called LoadDRXModules.

void LoadDRXModules();

Define it in OpenCAD.cpp.

void COpenCADApp::LoadDRXModules()
{
try
{
::odrxDynamicLinker()->loadModule(L"OCKernel.drx", FALSE);
}
catch(OdError& e)
{

51
OpenCAD

AfxMessageBox((LPCTSTR)e.description());
}
}

And finally call it in COpenCADApp:InitInstance() just after the call to InitializeDWGdirect().

if(InitializeDWGdirect() == FALSE)
return FALSE;

LoadDRXModules();

If you build and run OpenCAD you will get an error message on startup.

Fig 5.1: Missing module

The error message is expected since we have asked OpenCAD to load a DRX module which we
have not yet built. But there is something very important that I would like to highlight here. Look
closely at COpenCADApp::LoadDRXModules() and the error message above. We asked OpenCAD
to load a DRX module called OCKernel.drx but it has reported that it cannot find a module called
OCKernel_2.06_8.drx. This is because of a peculiar versioning system that the ODA uses. The
2.06 stands for the version of the DWGdiect/DRX SDK and the 8 stands for compiler used. In
our case 8 stands for VC 8.0 (Visual C++ 2005).

One might question the necessity of such a naming and versioning system, especially when
Windows has a wonderful versioning system already in place. I took up this matter with the
ODA. This is what Neil Peterson, the CTO of the ODA, had to say:

“The Windows DLL versioning is not sufficient to prevent problems in client applications. For example, consider
a client application which uses plug-ins based on two different versions of DWGdirect. If the two different versions
of DWGdirect use identical DLL names then the plug-in that is loaded second will crash as it tries to use an
incompatible DWGdirect DLL with the same name (which was loaded as part of the first plug-in). We need to
change file names each time binary compatibility is broken to avoid this type of problem.”

Neil has a point. This is a common pitfall when developing DRX plug-ins. So be sure to append
the version of DWGdirect/DRX and the compiler to the name of your DRX file, or a
DWGdirect hosted application may not load it.

52
Chapter 5: Plug-in Architecture

Conclusion
Thats it! We have added plug-in architecture to OpenCAD. It was that simple. In the next chapter
we will create our first DRX plug-in, the OCKernel.drx module mentioned above. You may find
things quite fuzzy at the moment. By the time we reach the end of this section, you will get a far
greater understanding of what DWGdirect hosted applications and DRX modules are and how
they interact with each other and the user.

53
OpenCAD

54
Chapter 6: Creating a DRX
Plug-in
Introduction
At the outset, I would like to reiterate that the DWGdirect SDK, which is required to build
DWGdirect hosted applications like OpenCAD, is available to ODA members only. However,
the DRX SDK, which is required to build DRX plug-ins for DWGdirect hosted applications, is
available free of cost and can be downloaded from the ODA web site (www.opendesign.com). So
if you are not an ODA member and would like to develop DRX plug-ins for OpenCAD or any
other DWGdirect hosted application, this chapter is a good starting point. Thoughout this
chapter and all other chapters which deal with DRX plug-ins, we will be using the free DRX
SDK only. If you are an ODA member you can go ahead and point your compiler to use files
from the DWGdirect SDK because the DRX SDK isa mere subset of the DWGdirect SDK.

Creating a Visual C++ project


First let us organize our OpenCAD folder a little. Create a new folder called DRX in
C:\OpenCAD. We will create all our DRX plug-ins in this folder. However, we will continue to
create the .drx files in the Bin folder where OpenCAD.exe is.

Start another instance of Visual Studio 2005 and create a new project. This time we will use the
MFC DLL template. Name the project OCKernel and specify C:\OpenCAD\DRX as the location.
We will try and keep the main OpenCAD MFC application project as clean and simple as we
possibly can and make use of OpenCAD’s plug-in architecture to add features to it by means of
plug-ins. A first step in that direction is this OCKernel plug-in that we are about to create. OC
stands for OpenCAD and Kernel signifies that the core functionality of the application will be
programmed here.
OpenCAD

Fig 6.1: New Project

Click OK. This will take you to the first step of the MFC DLL Wizard.

56
Chapter 6: Creating a DRX Plug-in

Fig 6.2: Wizard Step 1

Click Next. Or you can click Finish. We will be accepting the default options anyways.

57
OpenCAD

Fig 6.3: Wizard Step 2

As mentioned before, a DRX module is actually a regular Windows DLL with a .drx file
extension. Click Finish to create the project.

Setting up the Project


First let us change the name of the output DLL file name taking into consideration the
versioning system mentioned in the previous chapter. Change the name of the output file for the
Debug configuration to ../../Bin/OCKernelD_2.06_8.drx and that of the Release configuration to
../../Bin/OCKernel_2.06_8.drx. And while you are here, add the DRX SDK’s lib/VC8md folder
to Additional Library Directories. Note that we have specified a folder of the free DRX SDK and
not of the DWGdirect SDK.

Fig 6.4: Output File and Additional Library Directories

58
Chapter 6: Creating a DRX Plug-in

Similarly, add the DRX SDK’s Include folder to Additional Include Directories.

Fig 6.5: Additional Include Directories

Finally, add _TOOLKIT_IN_DLL_ to Preprocessor Definitions since we will be using the DLL
version of the DRX SDK.

Fig 6.6: Preprocessor Definitions

Creating a DRX Module


Converting a normal Windows DLL into a DRX module is pretty simple. We need to simply
derive a class from OdRxModule, the base class for all DRX modules and override certain
members. In fact, just two of them - initApp() and uninitApp(). In initApp() we add custom
commands to the command stack of the DRX module and in uninitApp() we empty the
command stack.

For each custom command in the DRX module we derive a class from OdEdCommand, the base
class for all custom commands and override four methods, one of them being execute(), which is
called the DWGdirect hosted application to execute the custom command.

Lastly we use the ODRX_DEFINE_DYNAMIC_MODULE macro which adds code to create


module object and delete it.

Add the following code to OCKernel.cpp.

#include "OdaCommon.h"
#include "RxDynamicModule.h"
#include "Ed/EdCommandStack.h"
#include "StaticRxObject.h"
#include "DbCommandContext.h"

59
OpenCAD

#pragma comment(lib, "DD_Root_dll.lib")

class CMyCommand : public OdStaticRxObject<OdEdCommand>


{
public:
const OdString groupName() const { return DD_T("MyGroup"); }
const OdString globalName() const { return OdString("MyCommand"); }
const OdString localName() const { return globalName(); }
void execute(OdEdCommandContext* pCmdCtx) { AfxMessageBox(_T("Hello World")); }
};

class CMyModule : public OdRxModule


{
OdStaticRxObject<CMyCommand> m_cmdMyCommand;

public:
void initApp() { odedRegCmds()->addCommand(&m_cmdMyCommand); }
void uninitApp() { odedRegCmds()->removeCmd(DD_T("MyGroup"), DD_T("MyCommand")); }
};

ODRX_DEFINE_DYNAMIC_MODULE(CMyModule);

Here we have module class called CMyModule and custom command class called CMyCommand.
The command name is MyComand and belongs to a command group called MyGroup. When
executed, MyCommand simply displays a “Hello World” message. This is as simple as it can get.
We will get to more complicated things later.

Build the project. OCKernel_2.06_8.drx should be created in the Bin folder. To remove the “/
OUT:OCKernel.dll directive in .EXP differs from output filename” warning, you can insert a comment
(semi-colon) before the word LIBRARY in OCKernel.def.

;LIBRARY "OCKernel"

Calling the DRX Module


Switch to the OpenCAD project and call the MyCommand custom command in
COpenCADDoc::OnNewDocument().

BOOL COpenCADDoc::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;

// TODO: add reinitialization code here


// (SDI documents will reuse this document)
m_pDatabase = theApp.createDatabase();
ExecuteCommand(_T("MyCommand"));
return TRUE;

60
Chapter 6: Creating a DRX Plug-in

We have already added code in COpenCADApp::LoadDRXModules() to load the OCKernel


module on startup. So build OpenCAD and run it. You should see the “Hello World” message
when a new document is created.

Fig 6.7: Hello World

Congratulations!! The OpenCAD executable has just executed a custom command from an
external DRX plug-in DLL. All this using just 22 lines of C++ code. It’s really that simple!

Organizing the Project


But let us backtrack a little. The point of using just 22 lines of code was to show you how easy
and simple it is to make a DRX plug-in. However, this way of programming is not the best way
of doing it. In C++ object oriented programming, it is a good idea to have a separate header and
source file for each class. So let us split the code and organize it a little. And while we are at it we
will replace MyCommand with our first real custom command - Import.

Move the include statements to stdafx.h since we will need to include the files in all classes. Also
add a define for the group name for all the commands in this module since we are going to need
the group name in all our classes. I have the habit of prefixing names with the name of the
module. This is a good idea and I strongly encourage you to do this. Personalizing class names
will prevent name conflicts with other DRX modules. For example, if someone else writes a
DRX module that also has a class with a same name as yours, then OpenCAD will not load the
plug-in that it encounters second.

#define OCKERNEL_GROUPNAME DD_T("OCKernel Commands")

#include "OdaCommon.h"
#include "RxDynamicModule.h"
#include "Ed/EdCommandStack.h"
#include "StaticRxObject.h"
#include "DbCommandContext.h"

Next move the pragma statement to stdafx.cpp

61
OpenCAD

#pragma comment(lib, "DD_Root_dll.lib")

Split the CMyCommand class into a header and source file and rename it to COCKernelImport.

OCKernelImport.h

#ifndef _OCKERNELIMPORT_H_
#define _OCKERNELIMPORT_H_

class COCKernelImport : public OdStaticRxObject<OdEdCommand>


{
public:
const OdString groupName() const { return OCKERNEL_GROUPNAME; }
const OdString globalName() const { return OdString("Import"); }
const OdString localName() const { return globalName(); }
void execute(OdEdCommandContext* pCmdCtx);
};

#endif // _OCKERNELIMPORT_H_

OCKernelImport.cpp

#include "StdAfx.h"
#include "OCKernelImport.h"

void COCKernelImport::execute(OdEdCommandContext* pCmdCtx)


{
AfxMessageBox(_T("This is the Import command"));
}

Similarly split the CMyModule class into a header and source file and rename it to
COCKernelModule.

OCKernelModule.h

#ifndef _OCKERNELMODULE_H_
#define _OCKERNELMODULE_H_

#include "OCKernelImport.h"

class COCKernelModule : public OdRxModule


{
public:
OdStaticRxObject<COCKernelImport> m_cmdImport;

public:
void initApp();
void uninitApp();
};

#endif // _OCKERNELMODULE_H_

62
Chapter 6: Creating a DRX Plug-in

OCKernelModule.cpp

#include "StdAfx.h"
#include "OCKernelModule.h"

ODRX_DEFINE_DYNAMIC_MODULE(COCKernelModule);

void COCKernelModule::initApp()
{
OdEdCommandStackPtr pCommands = odedRegCmds();

pCommands->addCommand(&m_cmdImport);
}

void COCKernelModule::uninitApp()
{
OdEdCommandStackPtr pCommands = odedRegCmds();

pCommands->removeCmd(OCKERNEL_GROUPNAME, DD_T("Import"));
}

As we add new custom command classes to the OCKernel project, we will add new header and
source files and make necessary changes to the COCKernelModule class to add and remove the
commands from the command stack.

Build the OCKernel project. Switch to the OpenCAD project and remove the line of code last
added to COpenCADDoc::OnNewDocument(). Add a new menu item called Import in File menu
popup and add an event handler for the COpenCADDoc class.

void COpenCADDoc::OnFileImport()
{
// TODO: Add your command handler code here
ExecuteCommand(_T("Import"));
}

It is a good idea to specify the exact file name of the DRX module to load. So rename to
OCKernel.drx to OCKernel_2.06_8.drx in COpenCADApp::LoadDRXModules()

::odrxDynamicLinker()->loadModule(L"OCKernel_2.06_8.drx", FALSE);

Build and run OpenCAD. Click Import from the File menu. You should see the following
message.

63
OpenCAD

Fig 6.8: Import command

So as you can see, we have only shaken up the original 22 lines of code to give the same result - a
message box.

Conclusion
We have accomplished what we had set out to do at the start of this chapter - to build a DRX
plug-in and make it run from within OpenCAD. In the next chapter, we will take OpenCAD to a
higher level. In fact, we will put it into the league of AutoCAD, IntelliCAD, Rhinoceros and
similar CAD applications that have the all powerful command prompt. Yes, we will give our very
own OpenCAD a command prompt. And like just about everything else in this book, you will
see exactly how easy it is.

64
Chapter 7: The Command
Prompt
Introduction
I don’t know about you, but I have been fascinated with the command prompt ever since I first
saw it in AutoCAD. I have developed a few CAD programs during the past decade or so, mainly
small utilities. A few years ago I tried to add a command prompt to one of my programs. It came
out quite nice initially but as I started adding more complicated commands to the program things
started getting convoluted and eventually I had to it give up to attend to more pressing issues.

Today thanks for the wonderful DWGdirect SDK from the ODA, adding a command prompt to
a DWGdirect hosted application is a piece of cake, as you will now see.

Connecting the Document and Child Frame


We will add a command prompt to the bottom of child frame window. For that we need
COpenCADDoc to interact with CChildFrame through COpenCADView. In OpenCADDoc.h add
the following class forwards before the class definition.

class COpenCADView;
class CChildFrame;

Add two methods to get the view and the child frame window.

COpenCADView* GetView();
CChildFrame* GetChildFrame();

In OpenCADDoc.cpp include the header files.

#include "OpenCADView.h"
#include "ChildFrm.h"

And finally add the function bodies.

COpenCADView* COpenCADDoc::GetView()
{
POSITION pos = GetFirstViewPosition();
OpenCAD

return((COpenCADView*)GetNextView(pos));
}

CChildFrame* COpenCADDoc::GetChildFrame()
{
return (CChildFrame*)(GetView()->GetParentFrame());
}

Designing the Command Prompt


Using the resource editor, add a new dialog resource and specify IDD_COMMANDPROMPT
as the ID. Remove the OK and Cancel buttons. In the dialog’s properties set Border to None. This
will remove the title bar. Set Style to Child. Add two edit controls to the dialog as shown in the
figure below.

Fig 7.1: Dialog

Specify ID_EDT_PROMPT as the ID for the top edit control. Make it read only, multiline and
display the vertical scroll bar. Specify ID_EDT_COMMAND as the ID of the bottom edit
control. We will be repositioning and resizing the entire dialog and its controls at run time. So
you need not worry about the positions and sizes for now. The only size that we will be using for
our command prompt is the height of the dialog box. You can adjust it later on.

66
Chapter 7: The Command Prompt

Using the Class Wizard add a new class called CCommandPrompt for the dialog using CDialog as the
base class.

Fig 7.2: Class Wizard

In CommandPrompt.h and CommandPrompt.cpp rename CDialog to CDialogBar. We will also


need to remove the CWnd* parameter from the CCommandPrompt constructor.

In CommandPrompt.h add a method to CCommandPrompt to resize the window and controls.

void Resize(int iWidth);

In CommandPrompt.cpp add the function body.

void CConsoleDlg::Resize(int iWidth)


{
CEdit* pEdit;
CRect rcWindow, rcInput, rcPrompt;
GetWindowRect(&rcWindow);
this->MoveWindow(0, 0, iWidth, rcWindow.Height());

pEdit = (CEdit*)GetDlgItem(IDC_EDT_INPUT);
::GetWindowRect(pEdit->GetSafeHwnd(), &rcInput);
::MoveWindow(pEdit->GetSafeHwnd(), 2, rcWindow.Height() - rcInput.Height() - 2,
iWidth, rcInput.Height(), TRUE);

pEdit = (CEdit*)GetDlgItem(IDC_EDT_PROMPT);
::GetWindowRect(pEdit->GetSafeHwnd(), &rcPrompt);

67
OpenCAD

::MoveWindow(pEdit->GetSafeHwnd(), 2, 2, iWidth - 2, rcWindow.Height() -


rcInput.Height() - 4, TRUE);
}

Creating the Command Prompt


Now that we have the command prompt set up we will add it to the child frame. In ChildFrm.h
include the CCommandPrompt header file and add a variableof type CCommandPrompt to
CChildFrame.

CCommandPrompt m_wndCommandPrompt;

Override OnCreate() and add code to create the command prompt.

int CChildFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)


{
if (CMDIChildWnd::OnCreate(lpCreateStruct) == -1)
return -1;

// TODO: Add your specialized creation code here


if(!m_wndCommandPrompt.Create(
this,
IDD_COMMANDPROMPT,
WS_CHILD | WS_VISIBLE | CBRS_BOTTOM,
AFX_IDW_CONTROLBAR_FIRST + 32))
{
TRACE0("Failed to create console\n");
return -1;// fail to create
}

m_wndCommandPrompt.EnableDocking(CBRS_ALIGN_BOTTOM);
EnableDocking(CBRS_ALIGN_BOTTOM);
DockControlBar(&m_wndCommandPrompt, AFX_IDW_DOCKBAR_BOTTOM);

return 0;
}

Lastly override OnSize() and add code to resize the command prompt.

void CChildFrame::OnSize(UINT nType, int cx, int cy)


{
CMDIChildWnd::OnSize(nType, cx, cy);

// TODO: Add your message handler code here


m_wndCommandPrompt.Resize(cx);
}

68
Chapter 7: The Command Prompt

Build and run OpenCAD. We now have a neat little command prompt at the bottom of every
MDI child window as can be seen in the figure below.

Fig 7.3: Command prompt

Docking the Command Prompt


But there is a small problem. Since we allow docking to the bottom of a frame window, there is a
chance that the user will dock the command prompt to the bottom of the main application
window as can be seen in the figure below.

69
OpenCAD

Fig 7.4: Command prompt docked to main application window

Now I am not a MFC GUI guru and I am quite sure there is a more elegant way to do this, but
for lack of a better solution we will simply trap the WM_LBUTTONDOWN and
WM_LBUTTONDBLCLK messages for CCommandPrompt and do nothing.

void CCommandPrompt::OnLButtonDown(UINT nFlags, CPoint point)


{
// TODO: Add your message handler code here and/or call default

// CDialogBar::OnLButtonDown(nFlags, point);
}

void CCommandPrompt::OnLButtonDblClk(UINT nFlags, CPoint point)


{
// TODO: Add your message handler code here and/or call default

// CDialogBar::OnLButtonDblClk(nFlags, point);
}

So now the command prompt is conjoined with the child frame and will live and die with it.

70
Chapter 7: The Command Prompt

Getting the Command Prompt to work


It looks like we have the command prompt in place. So now let us get it to work. For starters, we
will try printing something to the prompt area of the command prompt. Better still, we will print
something from the OCKernel plug-in. At present the Import command displays a message box.
Lets print the same message to the prompt area instead.

Switch to the OCKernel project and edit COCKernelImport::execute().

void COCKernelImport::execute(OdEdCommandContext* pCmdCtx)


{
OdDbCommandContextPtr pDbCmdCtx(pCmdCtx); // downcast for database access
if(pDbCmdCtx.isNull())
{
MessageBox(AfxGetMainWnd()->m_hWnd, _T("This command needs an active drawing"),
_T("Command unavailable"), MB_ICONWARNING);
return;
}
OdDbDatabasePtr pDatabase = pDbCmdCtx->database(); // Current database
OdDbUserIOPtr pUserIO = pDbCmdCtx->userIO();

pUserIO->putString(_T("This is the Import command"));


}

Next add the following like to stdafx.cpp

#pragma comment(lib, "DD_Db_dll.lib")

Build OCKernel. We have now set up the Import command to print a message to the prompt area
of OpenCAD’s command prompt. Now let us write code to do the actual printing. This involves
adding a method to CCommandPrompt to print to the prompt area and then wiring the command
prompt to the document.

In CommandPrompt.h add a method called PutString() to the CCommandPrompt class.

BOOL PutString(const CString& strText, const BOOL bNewLine = TRUE);

In CommandPrompt.cpp add the function body.

BOOL CCommandPrompt::PutString(const CString& strText, BOOL bNewLine)


{
CString strPrompt;

CEdit* pwndPrompt = (CEdit*)GetDlgItem(IDC_EDT_PROMPT);


if(pwndPrompt == NULL)
return FALSE;

// Get original prompt text

71
OpenCAD

pwndPrompt->GetWindowTextW(strPrompt);

if(bNewLine == TRUE)
strPrompt += _T("\r\n");
strPrompt += strText;

// Update prompt text


pwndPrompt->SetWindowTextW(strPrompt);

// Scroll to last line


pwndPrompt->LineScroll(pwndPrompt->GetLineCount());

return TRUE;
}

Finally in OpenCADDoc.cpp call the command prompt’s PutString() function from


COpenCADDoc’s putString() function.

void COpenCADDoc::putString(const OdString& string)


{
GetChildFrame()->m_wndConsole.PutString((LPCTSTR)string);
}

Build and run OpenCAD. Click Import from the File menu. You should see two messages in the
prompt area as shown in the figure below.

Fig 7.5: Command prompt messages

The first message was due to the call to putString() in COpenCADDoc::ExecuteCommand(). The
second one was due to the Import command of the OCKernal plug-in. So we have successfully
been able to get the plug-in to send a message to OpenCAD. But for bi-directional interaction
between OpenCAD and OCKernel, we need to be able to get input from the OpenCAD
executable and send it to the plug-in. We do that using the getString() function of COpenCADDoc
which will call a GetString() function of CCommandPrompt.

Replace the lone statement in COpenCADDoc::getString() with the following code.

OdString COpenCADDoc::getString(const OdString& prompt, int options,


OdEdStringTracker* pTracker)
{
CString strText;
GetChildFrame()->m_wndCommandPrompt.GetString((LPCTSTR)prompt, strText);

72
Chapter 7: The Command Prompt

return OdString(strText);
}

In CommandPrompt.h add an enum for various command modes (for now, just two) and a
boolean member variables to tell us if the user cancelled the operation..

public:
enum CommandMode
{
eCommandModeNone = 0,
eCommandModeGetString = 1,
};
CommandMode m_iCommandMode;
BOOL m_bCancel;

Next add a method called GetString() to CCommandPrompt.

BOOL GetString(const CString& strPrompt, CString& strText);

In CommandPrompt.cpp initialize m_iCommandMode to eCommandModeNone in the constructor.

m_iCommandMode = eCommandModeNone;

Add the GetString() function body.

BOOL CCommandPrompt::GetString(const CString& strPrompt, CString& strText)


{
if(m_iCommandMode != eCommandModeNone)
{
throw OdError(_T("Unable to request input. Another command is active"));
return FALSE;
}

if(PutString(strPrompt) == FALSE) // Show prompt


return FALSE;

CEdit* pwndCommand = (CEdit*)GetDlgItem(IDC_EDT_COMMAND);


if(pwndCommand == NULL)
return FALSE;

pwndCommand->SetFocus(); // Set focus to the command edit box

m_iCommandMode = eCommandModeGetString; // Go into GetString mode


m_bCancel = FALSE; // Reset Cancel flag
while(theApp.PumpMessage())
{
if(m_iCommandMode == eCommandModeNone) // Exit GetString mode
break;
}

if(m_bCancel == TRUE) // User cancelled operation

73
OpenCAD

{
PutString(_T(" *Cancel*"), FALSE);
return FALSE;
}

// Once again get the control. This is important because in the while
// loop above the command prompt may have been destroyed
pwndCommand = (CEdit*)GetDlgItem(IDC_EDT_COMMAND);
if(pwndCommand == NULL)
return FALSE;

pwndCommand->GetWindowTextW(strText); // Store entered text


pwndCommand->SetWindowTextW(_T("")); // Empty command edit box
PutString(strText, FALSE); // Append entered text to prompt

return TRUE;
}

I will spend some time explaining what we are trying to achieve here. We intend to keep control
in the GetString() function till the user presses Enter or Esc and then pass on the text he entered
over to the plug-in. We do that by means of an infinite while loop that calls
CWinApp::PumpMessage(). The m_iCommandMode member variable keeps track whehter the
program is still in the GetString mode and m_bCancel tell us whether he pressed Esc. Before the
loop begins we set m_iCommandMode to eCommandModeGetString in order to get into the GetString
mode and reset the m_bCancel flag to FALSE. We now need to track the messages being pumped
and exit the while loop when the user presses Enter or Esc. We do that by overriding
OnCommand().

BOOL CCommandPrompt::OnCommand(WPARAM wParam, LPARAM lParam)


{
// TODO: Add your specialized code here and/or call the base class
if(m_iCommandMode != eCommandModeNone && (wParam == IDCANCEL || wParam == IDOK))
{
m_iCommandMode = eCommandModeNone;
if(wParam == IDCANCEL)
m_bCancel = TRUE;
}

return CDialogBar::OnCommand(wParam, lParam);


}

We are not quite done yet. But let try this out. Before we do so switch to the OCKernel project
add the lines highlighted in bold to COCKernelImport::execute()

void COCKernelImport::execute(OdEdCommandContext* pCmdCtx)


{
OdDbCommandContextPtr pDbCmdCtx(pCmdCtx); // downcast for database access
if(pDbCmdCtx.isNull())
{

74
Chapter 7: The Command Prompt

MessageBox(AfxGetMainWnd()->m_hWnd, _T("This command needs an active drawing"),


_T("Command unavailable"), MB_ICONWARNING);
return;
}
OdDbDatabasePtr pDatabase = pDbCmdCtx->database(); // Current database
OdDbUserIOPtr pUserIO = pDbCmdCtx->userIO();

pUserIO->putString(_T("This is the Import command"));


OdString strFileName = pUserIO->getString(_T("Enter file name to import : "));
pUserIO->putString(_T("You entered '") + strFileName + _T("'"));
}

Here the plug-in asks the user to enter the name of the file to be imported and then prints it back
to the command prompt. Let us see if this works. Build OCKernel and then OpenCAD. Run
OpenCAD and click Import from the File menu. Type a file name at the command prompt and
press Enter. You should see something like this.

Fig 7.6: Application and Plug-in communication

Great! So it did work. Lets put things into perspective to understand what exactly transpired. The
user clicked a menu item in OpenCAD. This called COpenDoc::ExecuteCommand() which called
COCKernelImport::execute() in the DRX plug-in. The plug-in then used the comand context to get a
OdDbUserIO pointer and called its putString() method, which ended up finding its way to the
OpenCAD executable and called the putString() method of COpenCADDoc. This in turn called
CCommandPrompt::PutString() which finally printed out the message (which was set in the DRX
plug-in) to the prompt area of the command prompt in OpenCAD. Whew! But wait, we have just
reached the half way mark. Control was then returned to the plug-in which called
OdDbUserIO::getString(), which in turn called COpenCADDoc::getString(), which finally called
CCommandPrompt::GetString(). This started an infinite while loop and control was stuck in it till the
user pressed Enter. On exiting CCommandPrompt::GetString(), control was passed to
COpenCADDoc::getString() which returned the text string (entered by the user in the OpenCAD
command prompt) to the plug-in where it was printed to the command prompt using putString().

So it looks like we have successfully achieved two way comunication between the OpenCAD and
OCKernel. Actually, three way if you take the user into account.

75
OpenCAD

Launching commands from the command prompt


The way this is set up, the only way to launch the Import command is from the menu. Let us use
the command prompt to start commands. For that we would need the command prompt to
access the document in order to call the ExecuteCommand() method. One way is to store a pointer
to the MDI child window in the command prompt itself and then use CMDIChildWnd’s
GetActiveDocument() method to arrive at the document.

In CommandPrompt.h declare a class forward for CChildFrame.

class CChildFrame;

Next add a CChildFrame pointer as a public member variable of CCommandPrompt.

CChildFrame* m_pChildFrame;

In ChildFrm.cpp add the following line after the call to creating the comand prompt.

m_wndCommandPrompt.m_pChildFrame = this;

Now the command prompt has access to its child frame. Let us add another method to access
the document. In CommandPrompt.h include OpenCADDoc.h

#include "OpenCADDoc.h"

Declare a method to get the document.

COpenCADDoc* GetDocument();

In CommandPrompt.cpp include ChildFrm.h.

#include "ChildFrm.h"

And finally, add the body of the GetDocument() method.

COpenCADDoc* CCommandPrompt::GetDocument()
{
return (COpenCADDoc*)m_pChildFrame->GetActiveDocument();
}

Now that we can access the document from the command prompt, we will add code to initiate a
command when the user presses Enter at the command prompt. Of course, we need to initiate a
command only if the command prompt is in the eCommandModeNone mode. Add the lines
highlighted in bold to CComandPrompt::OnCommand().

76
Chapter 7: The Command Prompt

BOOL CCommandPrompt::OnCommand(WPARAM wParam, LPARAM lParam)


{
// TODO: Add your specialized code here and/or call the base class
if(m_iCommandMode != eCommandModeNone && (wParam == IDCANCEL || wParam == IDOK))
{
m_iCommandMode = eCommandModeNone;
if(wParam == IDCANCEL)
m_bCancel = TRUE;
}
else if(m_iCommandMode == eCommandModeNone && wParam == IDOK)
{
CEdit* pwndCommand = (CEdit*)GetDlgItem(IDC_EDT_COMMAND);
if(pwndCommand != NULL)
{
CString strCommand;
pwndCommand->GetWindowTextW(strCommand);
pwndCommand->SetWindowTextW(_T(""));
GetDocument()->ExecuteCommand(OdString(strCommand));
return FALSE;
}
}

return CDialogBar::OnCommand(wParam, lParam);


}

Build and run OpenCAD. Type Import at the command prompt and press Enter. You should see
something quite similar to Fig 7.6.

Repeating commands
Let us add another useful feature that we see in programs that have a command prompt. If the
user presses the Enter key without first entering a command, the program repeats the last run
command. For this we will need to keep track of the last command in COpenCADDoc. In
OpenCADDoc.h add a member variable to store the last command.

OdString m_strLastCommand;

Initialize it to an empty string in the COpenCADDoc constructor.

m_strLastCommand = _T("");

Modify COpenCADDoc::ExecuteCommand() as highlighted in bold below.

BOOL COpenCADDoc::ExecuteCommand(const OdString& strCommand, BOOL bEcho)


{
OdDbCommandContextPtr pCmdCtx = GetCommandContext();

77
OpenCAD

try
{
OdEdCommandStackPtr pCommands = ::odedRegCmds();
OdString sCommand = strCommand.spanExcluding(L" \t\r\n");

if(sCommand.isEmpty()) // User did not enter a command


{
if(m_strLastCommand.isEmpty()) // There is not last saved command
{
putString(_T("Type a command name at the prompt and press 'Enter'"));
return FALSE;
}
sCommand = m_strLastCommand; // Use last command
}

if(bEcho)
putString(_T("Command: ") + sCommand);

OdEdCommand* pCommand = pCommands->lookupCmd(sCommand); // Check if command is


valid
if(pCommand == NULL)
putString(_T("Unknown command '") + sCommand + _T("'"));
else
{
pCommands->executeCommand(sCommand, pCmdCtx); // Execute the command
m_strLastCommand = sCommand; // Save as last command
GetView()->m_pDevice->invalidate(); // Refresh the drawing view
GetView()->PostMessage(WM_PAINT);
}
}
catch(const OdError& e)
{
putString(_T("Error: ") + e.description());
return FALSE;
}
return TRUE;
}

Build and run OpenCAD. Run the Import command. After the command ends simply press Enter.
The Import command should start again.

Handling the unexpected


While the command prompt appears to do what we expect it to do, we need to also put check in
place to let it handle the unexpected. For example, start OpenCAD and run the Import command.
While the prompt is waiting for you to enter a file name close the document window. The
window dissappears but we know that something has gone wrong here. The plug-in is still

78
Chapter 7: The Command Prompt

waiting for user input from a window that no longer exists. Close OpenCAD. It will crash for a
multitude of reasons.

The infinite while loop in CCommand PromptGetString() allows OpenCAD to wait for user input,
but it also responsible for events like the crash described above. We need to take care of such
events. We should not allow a document window to be closed if a command is active. In
OpenCADDoc.h add a member variable to keep track whether a command is active or not.

BOOL m_bCommandActive;

Initialize it to FALSE in the OpenCADDoc constructor.

m_bCommandActive = FALSE;

In COpenCADDoc::ExecuteCommand() add the code highlighted in bold.

BOOL COpenCADDoc::ExecuteCommand(const OdString& strCommand, BOOL bEcho)


{
if(m_bCommandActive == TRUE)
{
MessageBox(AfxGetMainWnd()->m_hWnd, _T("Cannot execute command while another
command is active"), _T("Warning"), MB_ICONWARNING);
return FALSE;
}

OdDbCommandContextPtr pCmdCtx = GetCommandContext();

try
{
OdEdCommandStackPtr pCommands = ::odedRegCmds();
OdString sCommand = strCommand.spanExcluding(L" \t\r\n");

if(sCommand.isEmpty()) // User did not enter a command


{
if(m_strLastCommand.isEmpty()) // There is no last saved command
{
putString(_T("Type a command name at the prompt and press 'Enter'"));
return FALSE;
}
sCommand = m_strLastCommand; // Use last command
}

if(bEcho)
putString(_T("Command: ") + sCommand);

OdEdCommand* pCommand = pCommands->lookupCmd(sCommand); // Check if command is


valid
if(pCommand == NULL)
putString(_T("Unknown command '") + sCommand + _T("'"));
else
{

79
OpenCAD

m_bCommandActive = TRUE;
pCommands->executeCommand(sCommand, pCmdCtx); // Execute the command
m_strLastCommand = sCommand; // Save as last command
GetView()->m_pDevice->invalidate();// Refresh the drawing view
GetView()->PostMessage(WM_PAINT);
m_bCommandActive = FALSE;
}
}
catch(const OdError& e)
{
m_bCommandActive = FALSE;
putString(_T("Error: ") + e.description());
return FALSE;
}
return TRUE;
}

Build and run OpenCAD. Run the Import command. While OpenCAD is waiting for you to enter
a file name, click Import form the File menu to start another Import command. You will see this
warning message and the second Import command will abort.

Fig 7.7: Command active warning

However, after we dismiss the warning message we can still close the document window and
cause a crash on application exit. To prevent this we override CDocument::CanCloseFrame().

BOOL COpenCADDoc::CanCloseFrame(CFrameWnd* pFrame)


{
// TODO: Add your specialized code here and/or call the base class
if(m_bCommandActive == TRUE)
{
MessageBox(AfxGetMainWnd()->m_hWnd, _T("Cannot close window while a command is
active"), _T("Warning"), MB_ICONWARNING);
return FALSE;
}

return __super::CanCloseFrame(pFrame);
}

Now if we try to close the document window during the Import command we will see the
following warning message and the window will not close.

80
Chapter 7: The Command Prompt

Fig 7.8: Cannot close window warning

But we can close OpenCAD and still cause a crash. To prevent this from happenning, before
shutting down OpenCAD, we will need to iterate through all open documents and check if they
have an active command. Override CMDIFrameWnd::OnClose().

#include "OpenCADDoc.h"

void CMainFrame::OnClose()
{
// TODO: Add your message handler code here and/or call default
POSITION DocTemplatePos = AfxGetApp()->GetFirstDocTemplatePosition();
while(DocTemplatePos != NULL)
{
// Get a document template
CDocTemplate *pDocTemplate = AfxGetApp()->GetNextDocTemplate(DocTemplatePos);

// Iterate through documents for the current template


POSITION DocPos = pDocTemplate->GetFirstDocPosition();
while(DocPos != NULL)
{
COpenCADDoc *pDoc = (COpenCADDoc*)pDocTemplate->GetNextDoc(DocPos);
if(pDoc->m_bCommandActive == TRUE)
{
MessageBox(_T("Cannot exit while a command is active"), _T("Warning"),
MB_ICONWARNING);
return;
}
}
}

CMDIFrameWnd::OnClose();
}

Build and run OpenCAD. With the Import command active, try and close OpenCAD. You will
see the following warning message and the program will not close.

81
OpenCAD

Fig 7.9: Cannot exit warning

Before we wrap this up, this is more more thing that we need to fix. Run OpenCAD. It will start
with the new empty document titled Drawing1. Run the Import command but do not enter a file
name yet. Click New from the File menu to create another empty document. It will be titled
Drawing2. Click Tile from the Window menu to see both windows clearly. Type Import at the
command prompt of Drawing2. Note that Drawing1 is still waiting for a file name. So now we
have two documents that are waiting for user input. Type C:\Drawing1.sat at the Drawing1
command prompt and press Enter. You should have seen the message “You entered
C:\Drawing1.sat” at the command prompt, but nothing happens. Now type C:\Drawing2.sat at the
Drawing2 command prompt and press Enter. Take a closer look at the command prompts of both
documents.

Fig 7.10: Two documents

82
Chapter 7: The Command Prompt

At the Drawing2 command prompt you will see the message “You entered C:\Drawing2.sat”. At the
Drawing1 command prompt you can now see the message that should have appeared when you
pressed Enter earlier.

So why did this happen? Take a look at CCommandPrompt::GetString(). When it was called the first
time for Drawing1, control was stuck in the infinite while loop till we pressed Enter. But instead of
entering a file name and pressing Enter, we ran the Import command in Drawing2 which called
another instance of GetString() and entered into another infinite while loop. So even though you
went back to Drawing1 and pressed Enter, program control was still stuck in the second while loop
waiting for Enter to be pressed in Drawing2. That is why nothing happenned in Drawing1. And
when you returned to Drawing2 and pressed Enter, program control broke free from the second
while loop and found its way to the first while loop, where the condition to break out from the
loop was already met and exited the first loop as well.

So this means that the way this is set up, OpenCAD cannot concurrently run two instances of
CCommandPrompt::GetString(). Technically it can, but in order to get immediate feedback the end
user would have to enter text in exactly the reverse order as the calls to
CCommandPrompt::GetString(). One way of fixing this problem is to rearchitecture just about
everything that we have done so far and set things up in a way that GetString() is called in a new
thread, something which is beyond the scope of this book and not critically important to
OpenCAD. So we will simply prevent a situation wherein a user will be asked for input
concurrently in two documents.

In OpenCAD.h add a method to COpenCADApp to check if user interaction is permissible.

BOOL AllowInteraction();

In OpenCAD.cpp add the function body. Basically, we will iterate through all open documents
and check if any of the command modes are not eCommandModeNone.

BOOL COpenCADApp::AllowInteraction()
{
POSITION DocTemplatePos = AfxGetApp()->GetFirstDocTemplatePosition();
while(DocTemplatePos != NULL)
{
// Get a document template
CDocTemplate *pDocTemplate = AfxGetApp()->GetNextDocTemplate(DocTemplatePos);

// Iterate through documents for the current template


POSITION DocPos = pDocTemplate->GetFirstDocPosition();
while(DocPos != NULL)
{
COpenCADDoc *pDoc = (COpenCADDoc*)pDocTemplate->GetNextDoc(DocPos);
if(pDoc->GetChildFrame()->m_wndCommandPrompt.m_iCommandMode !=
CCommandPrompt::eCommandModeNone)
{

83
OpenCAD

MessageBox(AfxGetMainWnd()->m_hWnd, _T("Unable to initiate user input. An


active command in another document is awaiting user input"), _T("Warning"),
MB_ICONWARNING);
return FALSE;
}
}
}

return TRUE;
}

At the lines highlighted in bold to COpenCADDoc::getString().

OdString COpenCADDoc::getString(const OdString& prompt, int options,


OdEdStringTracker* pTracker)
{
if(theApp.AllowInteraction() == FALSE)
return OdString(_T(""));

CString strText;
GetChildFrame()->m_wndCommandPrompt.GetString((LPCTSTR)prompt, strText);
return OdString(strText);
}

Build and run OpenCAD. Try running the Import command in two documents as was previously
done. You should see the following warning message and the second Import command will abort.

Fig 7.11: User input warning

I think we have taken care of the most common unexpected situations. That was quite an
oxymoron - most common unexpected situations. We will revisit this later if required.

Wrapping up the Import command


In the previous chapter we started out by creating a DRX plug-in. We then took a detour and
added the command prompt to OpenCAD and in the bargain left the Import command in
OCKernel incomplete. Let us now wrap it up so that we can move onto the next thing.

Switch to the OCKernel project. In OCKernelImport.cpp add three header files and edit
COCKernelImport::execute() as highlighted in bold below.

84
Chapter 7: The Command Prompt

#include "DbEntity.h"
#include "DbBody.h"
#include "DbBlockTableRecord.h"

void COCKernelImport::execute(OdEdCommandContext* pCmdCtx)


{
OdDbCommandContextPtr pDbCmdCtx(pCmdCtx); // downcast for database access
if(pDbCmdCtx.isNull())
{
MessageBox(AfxGetMainWnd()->m_hWnd, _T("This command needs an active drawing"),
_T("Command unavailable"), MB_ICONWARNING);
return;
}
OdDbDatabasePtr pDatabase = pDbCmdCtx->database(); // Current database
OdDbUserIOPtr pUserIO = pDbCmdCtx->userIO();

CFileDialog Dlg(
TRUE,
_T("sat"),
NULL,
OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
_T("ACIS SAT Files (*.sat)|*.sat||"));
if(Dlg.DoModal() == IDCANCEL)
return;

OdString strFileName(Dlg.GetPathName());

OdDbEntityPtrArray Entities;
if(OdDbBody::acisIn(strFileName, Entities) != eOk)
{
pUserIO->putString(_T("Error importing '") + strFileName + "'");
return;
}

// Get the model space


OdDbBlockTableRecordPtr pBlockTableRecord = pDatabase-
>getModelSpaceId().safeOpenObject(OdDb::kForWrite);
for(unsigned int i = 0; i < Entities.length(); i++)
pBlockTableRecord->appendOdDbEntity(Entities[i]); // Add entities to model
space

pUserIO->putString(_T("Successfully imported '") + strFileName + "'");


}

In stdafx.cpp add the following line

#pragma comment(lib, "DD_Alloc_dll.lib")

Build OCKernel. Start OpenCAD and run the Import command. You will be prompted to select
an ACIS SAT file. Note that the Open dialog box is called from the DRX plug-in and not from
OpenCAD.

85
OpenCAD

Fig 7.12: Select file name

After you select a SAT file, the DRX plug-in will read it and add its contents to the database of
the current document in OpenCAD. You should see something like the figure below. You may
need to Zoom Extents to see the objects.

86
Chapter 7: The Command Prompt

Fig 7.13: Imported SAT file

Congratulations!! We have successfully written our first DRX file import plug-in for OpenCAD.

Conclusion
In this chapter we added a command prompt to OpenCAD. In the next chapter we will do
something a little bolder. We will write code to add a line object to the drawing, and that too
from the OCKernel plug-in. We will add a new custom command to OCKernel called Line that
will ask the user to pick two points in the drawing view of an OpenCAD document and send the
data back to the plug-in. The plug-in will then create a line object and add it to the database of
the document and update the drawing view to display the line on the screen.

Yes, we are going to convert OpenCAD from a DWG viewer to a DWG editor!

87
OpenCAD

88
Chapter 8: OpenCAD as a
DWG Editor
Introduction
In this chapter we will add a custom command to OCKernel to draw a line to the active drawing
in OpenCAD. For that we will need the user to input the start and end points of the line. The
user can do that by either entering the coordinates of the points at the command prompt or
picking a point in the drawing window. We have already added functionality to COpenCADDoc
and CCommandPrompt to get a string from the user. We will now add similar functionality to get a
point.

Setting up the GetPoint mode


In CommandPrompt.h add another item to the CommandMode enum as highlighted in bold below

enum CommandMode
{
eCommandModeNone= 0,
eCommandModeGetString= 1,
eCommandModeGetPoint= 2
};

Add two methods to CCommandPrompt.

BOOL GetPoint(const CString& strPrompt, OdGePoint3d& Point);


BOOL GetPointFromString(const CString& strText, OdGePoint3d& Point);

In CommandPrompt.cpp add the function bodies.

BOOL CCommandPrompt::GetPoint(const CString& strPrompt, OdGePoint3d& Point)


{
if(m_iCommandMode != eCommandModeNone)
{
throw OdError(_T("Unable to request input. Another command is active"));
return FALSE;
}

Repeat:
if(PutString(strPrompt) == FALSE)// Show prompt
OpenCAD

return FALSE;

CEdit* pwndCommand = (CEdit*)GetDlgItem(IDC_EDT_COMMAND);


if(pwndCommand == NULL)
return FALSE;

pwndCommand->SetFocus();// Set focus to the command edit box

m_iCommandMode = eCommandModeGetPoint;// Go into GetPoint mode


m_bCancel = FALSE;// Reset Cancel flag
while(theApp.PumpMessage())
{
if(m_iCommandMode == eCommandModeNone)// Exit GetPoint mode
break;
}

if(m_bCancel == TRUE)// User cancelled operation


{
PutString(_T(" *Cancel*"), FALSE);
return FALSE;
}

// Once again get the control. This is important because in the while
// loop above the command prompt may have been destroyed
pwndCommand = (CEdit*)GetDlgItem(IDC_EDT_COMMAND);
if(pwndCommand == NULL)
return FALSE;

CString strText;
pwndCommand->GetWindowTextW(strText);// Store entered text
if(GetPointFromString(strText, Point) == FALSE)
{
PutString(strText, FALSE);// Append entered text to prompt
PutString(_T("Invalid point"));
goto Repeat;
}

pwndCommand->SetWindowTextW(_T(""));// Empty command edit box


PutString(strText, FALSE);// Append entered text to prompt

return TRUE;
}

BOOL CCommandPrompt::GetPointFromString(const CString& strText, OdGePoint3d& Point)


{
TCHAR strLine[255];
_tcscpy(strLine, strText);
for(unsigned int i = 0; i < _tcslen(strLine); i++)
{
if(strLine[i] == ',')
strLine[i] = ' ';
}

double x, y, z = 0.0;
if(_stscanf(strLine, _T("%lf %lf %lf"), &x, &y, &z) < 2)

90
Chapter 8: OpenCAD as a DWG Editor

return FALSE;

Point.set(x, y, z);
return TRUE;
}

Now that we have set up CCommandPrompt to accept a point from the user, let us wire in
COpenCADDoc as well. In OpenCADDoc.h add a method to get a point.

OdGePoint3d getPoint(const OdString& prompt, int options, OdEdPointTracker* pTracker);

And in OpenCADDoc.cpp add the function body.

OdGePoint3d COpenCADDoc::getPoint(const OdString& prompt, int options,


OdEdPointTracker* pTracker)
{
OdGePoint3d Point;
if(theApp.AllowInteraction() == FALSE)
return Point;

CString strText;
GetChildFrame()->m_wndCommandPrompt.GetPoint((LPCTSTR)prompt, Point);
return Point;
}

Creating the Line command


Using the resource editor add a new menu dropdown called Draw and add a new item to it called
Line.

Fig 8.1: Line menu command

Add a event handler for the document class and call ExecuteCommand().

void COpenCADDoc::OnDrawLine()
{
// TODO: Add your command handler code here
ExecuteCommand(_T("Line"));
}

91
OpenCAD

Build OpenCAD. Don’t run it yet. We have set up OpenCAD to run a command called Line. We
now need to add the command to OCKernel. Switch to the OCKernel project and create a
header file called OCKernelLine.h and a source file called OCKernelLine.cpp. Add code similar
to he we previously did for the Import command.

OCKernelLine.h

#ifndef _OCKERNELLINE_H_
#define _OCKERNELLINE_H_

class COCKernelLine : public OdStaticRxObject<OdEdCommand>


{
public:
const OdString groupName() const { return OCKERNEL_GROUPNAME; }
const OdString globalName() const { return OdString("Line"); }
const OdString localName() const { return globalName(); }
void execute(OdEdCommandContext* pCmdCtx);
};

#endif // _OCKERNELLINE_H_

OCKernelLine.cpp

#include "StdAfx.h"
#include "OCKernelLine.h"
#include "DbLine.h"
#include "DbBlockTableRecord.h"

void COCKernelLine::execute(OdEdCommandContext* pCmdCtx)


{
OdDbCommandContextPtr pDbCmdCtx(pCmdCtx); // downcast for database access
if(pDbCmdCtx.isNull())
{
MessageBox(AfxGetMainWnd()->m_hWnd, _T("This command needs an active drawing"),
_T("Command unavailable"), MB_ICONWARNING);
return;
}
OdDbDatabasePtr pDatabase = pDbCmdCtx->database(); // Current database
OdDbUserIOPtr pUserIO = pDbCmdCtx->userIO();

OdGePoint3d Start = pUserIO->getPoint(_T("From point: "));


OdGePoint3d End = pUserIO->getPoint(_T("To point: "));

OdDbLinePtr pLine = OdDbLine::createObject();


pLine->setDatabaseDefaults(pDatabase);
pLine->setStartPoint(Start);
pLine->setEndPoint(End);

OdDbBlockTableRecordPtr pBlockTableRecord = pDatabase-


>getModelSpaceId().safeOpenObject(OdDb::kForWrite);
pBlockTableRecord->appendOdDbEntity(pLine);
}

92
Chapter 8: OpenCAD as a DWG Editor

We also need to add an entry for the Line command to COKKernelModule. In OCKernelModule.h
include OCKernelLine.h and add a member to COCKernelModule for the Line command. Add the
code highlighted in bold below.

#include "OCKernelLine.h"

class COCKernelModule : public OdRxModule


{
public:
OdStaticRxObject<COCKernelImport> m_cmdImport;
OdStaticRxObject<COCKernelLine> m_cmdLine;

public:
void initApp();
void uninitApp();
};

In OCKernelModule.cpp add code to initApp() and uninitApp() to add and remove the Line
command from the command stack. Add the code highlighted in bold below.

void COCKernelModule::initApp()
{
OdEdCommandStackPtr pCommands = odedRegCmds();

pCommands->addCommand(&m_cmdImport);
pCommands->addCommand(&m_cmdLine);
}

void COCKernelModule::uninitApp()
{
OdEdCommandStackPtr pCommands = odedRegCmds();

pCommands->removeCmd(OCKERNEL_GROUPNAME, DD_T("Import"));
pCommands->removeCmd(OCKERNEL_GROUPNAME, DD_T("Line"));
}

Build OCKernel. Run OpenCAD. Invoke the Line command and when prompted for the start
and end point, enter the x, y and x coordinates at the command prompt. Zoom Extents to see
the line that was just created.

93
OpenCAD

Fig 8.2: Line command

Congratulations!! We have just converted OpenCAD into a DWG editor. A rudimentary one, but
an editor nonetheless.

Creating a line by picking points


The next step is to allow the user to specify a point by clicking in the drawing window. To keep it
simple, we will use the command prompt workflow that we just created. Simply put, when the
user clicks the left mouse button in the drawing window at a time when he is expected to input a
point, we will programatically enter the coordinates of the point that he has just clicked and press
the Enter key on his behalf. Let us add a method to CCommandPrompt to do just that.

In CommandPrompt.h declare a method for CCommandPrompt called SetCommand().

BOOL SetCommand(const CString& strText, BOOL bEnter);

In CommandPrompt.cpp add the function body.

94
Chapter 8: OpenCAD as a DWG Editor

BOOL CCommandPrompt::SetCommand(const CString& strText, BOOL bEnter)


{
CEdit* pwndCommand = (CEdit*)GetDlgItem(IDC_EDT_COMMAND);
if(pwndCommand == NULL)
return FALSE;

pwndCommand->SetWindowText(strText);
if(bEnter)
PostMessage(WM_COMMAND, IDOK);

return TRUE;
}

The best place to get the point picked by the user would be COpenCADView::OnLButtonDown().
For that we need to access the command prompt from the drawing view. In OpenCADView.h
include ChildFrm.h and a method to COpenCADView to retrieve the chile frame window.

#include "ChildFrm.h"

CChildFrame* GetChildFrame() { return (CChildFrame*)GetParentFrame(); };

And finally in COpenCADView::OnLButtonDown() add the code highlighted in bold below.

void COpenCADView::OnLButtonDown(UINT nFlags, CPoint point)


{
// TODO: Add your message handler code here and/or call default
m_bLeftButton = TRUE;
m_MousePosition = point;
m_MouseClick = point;

if(m_bZoomWindow == TRUE)
{
m_Points.clear(); // Empty point array
m_Points.append(GetWorldCoordinates(point)); // Record first point
}
else if(GetChildFrame()->m_wndCommandPrompt.m_iCommandMode ==
CCommandPrompt::eCommandModeGetPoint)
{
OdGePoint3d Point = GetWorldCoordinates(point);
CString strCoordinates;
strCoordinates.Format(_T("%f,%f,%f"), Point.x, Point.y, Point.z);
GetChildFrame()->m_wndCommandPrompt.SetCommand(strCoordinates, TRUE);
}
__super::OnLButtonDown(nFlags, point);
}

Build and run OpenCAD. Run the Line command and pick two points in the drawing window.
Observe what happens at the command prompt as you pick the points.

95
OpenCAD

Fig 8.3: Line drawn by picking points

While the Line command works the way we expect it to, anyone with even a litle experience in
using CAD software will notice that there is something missing here - the rubber band like visual
feedback when the user moves the mouse to select the second point. The DWGdirect SDK
offers a wonderful way to provide precisely such visual feedback. You will now see that by using
minimal code we can have the same rubber band visual feedback as any other CAD application.

Creating the rubber band visual feedback


Apart from the prompt the OpenCADDoc::getPoint() method takes two more parameters - an
options flag and a pointer to OdEdPointTracker. Both these parameters will help us get the rubber
band visual feedback.

In OpenCADView.h add an OdEdInputTracker pointer as a member variable to COpenCADView.

OdEdInputTracker* m_pTracker; // Input tracker

Add a method to COpenCADView.

96
Chapter 8: OpenCAD as a DWG Editor

void Track(OdEdInputTracker* pTracker);

In OpenCADView.cpp initialize the tracker to NULL in the constructor

m_pTracker = NULL;

Add the body of the Track() method.

void COpenCADView::Track(OdEdInputTracker* pTracker)


{
if(m_pTracker)
m_pTracker->removeDrawables(GetView());

m_pTracker = pTracker;

if(m_pTracker)
m_pTracker->addDrawables(GetView());
}

And finally add the following else if block towards the end of COpenCADView::OnMouseMove().

else if(GetChildFrame()->m_wndCommandPrompt.m_iCommandMode ==
CCommandPrompt::eCommandModeGetPoint)
{
if(m_pTracker)
{
static_cast<OdEdPointTracker*>(m_pTracker)-
>setValue(GetWorldCoordinates(point));
if(!GetView()->isValid())
PostMessage(WM_PAINT);
}
}

Thats it! Build OpenCAD, but don’t run it just yet. Switch to the OCKernel project and add a
parameter to the second call to getPoint() as highlighted in bold below

OdGePoint3d End = pUserIO->getPoint(_T("To point: "), OdEd::kGptRubberBand);

Build OCKernel. Start OpenCAD and run the Line command. Click two points and you will see
the rubber band visual feedback. Its really that simple.

While we are here I want to show you something really nice. Open a 3D DWG file in OpenCAD
and run the Line command. Pick the first point, but do not pick the second point yet. Now use
the 3D navigation features of OpenCAD - orbit (eight mouse button), pan (middle mouse
button) and zoom(mouse wheel) to navigate while the Line command is still active. This is due to
the way we have set things up in OpenCAD.

97
OpenCAD

Taking care of cancelled commands


A closer look at the getString() and getPoint() methods of the OdDbUserIO class reveals that they
assume the user will always supply the required input. But what happens if the user presses Esc
and cancels the command instead? The getString() and getPoint() methods will return values to the
calling functions without letting them know that the user did not actually supply input. To solve
this problem we make use of exceptions. When the user presses the Esc key we throw an
expection in CCommandPrompt and catch it in the DRX plug-in.

In CommandPrompt.cpp throw an exception at the relevant places in the GetString() and


GetPoint() methods. Add the code highlighted in bold below.

if(m_bCancel == TRUE) // User cancelled operation


{
PutString(_T(" *Cancel*"), FALSE);
throw OdEdCancel();
return FALSE;
}

In OCKernelLine.cpp wrap the two calls to getPoint() in a try block.

OdGePoint3d Start, End;


try
{
Start = pUserIO->getPoint(_T("From point: "));
End = pUserIO->getPoint(_T("To point: "), OdEd::kGptRubberBand);
}
catch(OdEdCancel& /*e*/)
{
return;
}

Build both projects and run OpenCAD. Run the Line command. Pick the start point of the line
and press Esc. The command gracefully exits. However you will still see the rubber band line in
the drawing window. In fact, this rubber band line was always present, even when we actually
created a line. Just that we did not notice it because it coincided with the line we had just created.
We need to remove this rubber band line. In fact, we always need to refresh the drawing window
after a command completes or is cancelled. Let us add a function to COpenCADView to do that.

In OpenCADView.h add a method to COpenCADView called Refresh().

void Refresh();

In OpenCADView.cpp add the function body.

void COpenCADView::Refresh()

98
Chapter 8: OpenCAD as a DWG Editor

{
// Clean out the tracker
if(m_pTracker)
m_pTracker->removeDrawables(GetView());
m_pTracker = NULL;

// Refresh the drawing view


m_pDevice->invalidate();
PostMessage(WM_PAINT);
}

In COpenCADDoc::ExecuteCommand() call Refresh() after the call to executeCommand() and when an


exception is caught. Add the code highlighted in bold below.

BOOL COpenCADDoc::ExecuteCommand(const OdString& strCommand, BOOL bEcho)


{
if(m_bCommandActive == TRUE)
{
MessageBox(AfxGetMainWnd()->m_hWnd, _T("Cannot execute command while another
command is active"), _T("Warning"), MB_ICONWARNING);
return FALSE;
}

OdDbCommandContextPtr pCmdCtx = GetCommandContext();

try
{
OdEdCommandStackPtr pCommands = ::odedRegCmds();
OdString sCommand = strCommand.spanExcluding(L" \t\r\n");

if(sCommand.isEmpty()) // User did not enter a command


{
if(m_strLastCommand.isEmpty()) // There is no last saved command
{
putString(_T("Type a command name at the prompt and press 'Enter'"));
return FALSE;
}
sCommand = m_strLastCommand; // Use last command
}

if(bEcho)
putString(_T("Command: ") + sCommand);

OdEdCommand* pCommand = pCommands->lookupCmd(sCommand); // Check if command is


valid
if(pCommand == NULL)
putString(_T("Unknown command '") + sCommand + _T("'"));
else
{
m_bCommandActive = TRUE;
pCommands->executeCommand(sCommand, pCmdCtx); // Execute the command
m_strLastCommand = sCommand; // Save as last command
GetView()->Refresh();
m_bCommandActive = FALSE;

99
OpenCAD

}
}
catch(const OdError& e)
{
m_bCommandActive = FALSE;
putString(_T("Error: ") + e.description());
GetView()->Refresh();
return FALSE;
}
return TRUE;
}

Build and run OpenCAD. Run the Line command, pick the start point and press Esc. The rubber
band line no longer exists in the drawing view.

Drawing a chain of lines


Now let us modify the Line command a little. Instead of drawing single line segments, we will
allow the user to draw a chain of lines, just like any CAD application. Modify
COCKernelLine::execute() as highlighted in bold below.

void COCKernelLine::execute(OdEdCommandContext* pCmdCtx)


{
OdDbCommandContextPtr pDbCmdCtx(pCmdCtx); // downcast for database access
if(pDbCmdCtx.isNull())
{
MessageBox(AfxGetMainWnd()->m_hWnd, _T("This command needs an active drawing"),
_T("Command unavailable"), MB_ICONWARNING);
return;
}
OdDbDatabasePtr pDatabase = pDbCmdCtx->database(); // Current database
OdDbUserIOPtr pUserIO = pDbCmdCtx->userIO();

OdGePoint3d Start, End;


OdDbBlockTableRecordPtr pBlockTableRecord = pDatabase-
>getModelSpaceId().safeOpenObject(OdDb::kForWrite);
try
{
Start = pUserIO->getPoint(_T("From point: "));
while(1)
{
End = pUserIO->getPoint(_T("To point: "), OdEd::kGptRubberBand);

OdDbLinePtr pLine = OdDbLine::createObject();


pLine->setDatabaseDefaults(pDatabase);

pLine->setStartPoint(Start);
pLine->setEndPoint(End);

pBlockTableRecord->appendOdDbEntity(pLine);

100
Chapter 8: OpenCAD as a DWG Editor

Start = End;
}
}
catch(OdEdCancel& /*e*/)
{
return;
}
}

Build OCKernel and run OpenCAD. Run the Line command and pick a set of points. A chain of
line segments will be drawn as you pick points, just like how we wanted. However, to stop
drawing lines, we need to cancel the command by pressing Esc, which may not be the most
elegant way of doing it. Ideally we would want the user to press Enter to let OpenCAD know that
he is done drawing lines.

In CCommandPrompt::GetPoint() add the following code just before the call to GetPointFromString().

if(strText == _T(""))
{
throw OdEdCancel();
return FALSE;
}

This cancels the command without printing *Cancel* to the prompt area. Build and run
OpenCAD. This time the press Enter after you are done drawing lines. The command will exit
gracefully.

Handling the unexpected


A little thought will tell us that it is a good idea to throw an exception before the GetString() or
GetPoint() methods of CCommandPrompt return a FALSE. Add the lines highlighted in bold below.

BOOL CCommandPrompt::GetPoint(const CString& strPrompt, OdGePoint3d& Point)


{
if(m_iCommandMode != eCommandModeNone)
{
throw OdError(_T("Unable to request input. Another command is active"));
return FALSE;
}

Repeat:
if(PutString(strPrompt) == FALSE)// Show prompt
{
throw OdError(_T("Unable to output to prompt area"));
return FALSE;
}

101
OpenCAD

CEdit* pwndCommand = (CEdit*)GetDlgItem(IDC_EDT_COMMAND);


if(pwndCommand == NULL)
{
throw OdError(_T("Invalid command prompt"));
return FALSE;
}

pwndCommand->SetFocus();// Set focus to the command edit box

m_iCommandMode = eCommandModeGetPoint;// Go into GetPoint mode


m_bCancel = FALSE;// Reset Cancel flag
while(theApp.PumpMessage())
{
if(m_iCommandMode == eCommandModeNone)// Exit GetPoint mode
break;
}

if(m_bCancel == TRUE)// User cancelled operation


{
PutString(_T(" *Cancel*"), FALSE);
throw OdEdCancel();
return FALSE;
}

// Once again get the control. This is important because in the while
// loop above the command prompt may have been destroyed
pwndCommand = (CEdit*)GetDlgItem(IDC_EDT_COMMAND);
if(pwndCommand == NULL)
{
throw OdError(_T("Invalid command prompt"));
return FALSE;
}

CString strText;
pwndCommand->GetWindowTextW(strText);// Store entered text
if(strText == _T(""))
{
throw OdEdCancel();
return FALSE;
}
if(GetPointFromString(strText, Point) == FALSE)
{
PutString(strText, FALSE);// Append entered text to prompt
PutString(_T("Invalid point"));
goto Repeat;
}

pwndCommand->SetWindowTextW(_T(""));// Empty command edit box


PutString(strText, FALSE);// Append entered text to prompt

return TRUE;
}

102
Chapter 8: OpenCAD as a DWG Editor

Similarly add code to CCommandPrompt::GetString().

BOOL CCommandPrompt::GetString(const CString& strPrompt, CString& strText)


{
if(m_iCommandMode != eCommandModeNone)
{
throw OdError(_T("Unable to request input. Another command is active"));
return FALSE;
}

if(PutString(strPrompt) == FALSE)// Show prompt


{
throw OdError(_T("Unable to output to prompt area"));
return FALSE;
}

CEdit* pwndCommand = (CEdit*)GetDlgItem(IDC_EDT_COMMAND);


if(pwndCommand == NULL)
{
throw OdError(_T("Invalid command prompt"));
return FALSE;
}

pwndCommand->SetFocus();// Set focus to the command edit box

m_iCommandMode = eCommandModeGetString;// Go into GetString mode


m_bCancel = FALSE;// Reset Cancel flag
while(theApp.PumpMessage())
{
if(m_iCommandMode == eCommandModeNone)// Exit GetString mode
break;
}

if(m_bCancel == TRUE)// User cancelled operation


{
PutString(_T(" *Cancel*"), FALSE);
throw OdEdCancel();
return FALSE;
}

// Once again get the control. This is important because in the while
// loop above the command prompt may have been destroyed
pwndCommand = (CEdit*)GetDlgItem(IDC_EDT_COMMAND);
if(pwndCommand == NULL)
{
throw OdError(_T("Invalid command prompt"));
return FALSE;
}

pwndCommand->GetWindowTextW(strText);// Store entered text


pwndCommand->SetWindowTextW(_T(""));// Empty command edit box
PutString(strText, FALSE);// Append entered text to prompt

return TRUE;

103
OpenCAD

This should take care of some unexpected situations. We need to take care of something else. We
called the COpenCADView::Track() method in COpenCADDoc::getPoint(). We need to call it in
COpenCADDoc::getString() as well. Add the code highlighted in bold.

OdString COpenCADDoc::getString(const OdString& prompt, int options,


OdEdStringTracker* pTracker)
{
if(theApp.AllowInteraction() == FALSE)
return OdString(_T(""));

GetView()->Track(pTracker);

CString strText;
GetChildFrame()->m_wndCommandPrompt.GetString((LPCTSTR)prompt, strText);
return OdString(strText);
}

The get methods


We have added a bunch of user interaction methods to COpenCADDoc:
putString() - Outputs a text string to the command prompt.
getString() - Prompts the user to enter a text string from the command prompt.
getPoint() - Prompts the user to pick a point in the drawing view or enter its coordinates at the
command prompt.

COpenCADDoc additionally derives from OdEdBaseIO and these three methods are actually
virtual members of OdEdBaseIO. The first two methods, putString() and getString() are pure virtual
methods which means that it is mandatory for them to be defined in the derived class, which is
what we did earlier on. There is a reason for that. These two methods are used by the other get
methods of the OdDbUserIO class, namely getAngle(), getColor(), getDist(), getFilePath(), getInt(),
getKeyword() and getReal(). We can simply go ahead and call these get methods and the putString()
and getString() methods of COpenCADDoc will be called internally to make things happen.

Lets see this how this works in the case of the getKeyword(). In COCKernelLine::execute() add the
following test code just after pUserIO is declared and assigned.

int iIndex = pUserIO->getKeyword(_T("Create polyline [Yes/No]<Yes>: "), _T("Yes No"),


0);
if(iIndex == 0)
pUserIO->putString(_T("You selected to create a polyline"));
else
pUserIO->putString(_T("You selected not to create a polyline"));
return;

104
Chapter 8: OpenCAD as a DWG Editor

Here I am assuming you already know the uppercase-lowercase method of formulating keywords
used in AutoCAD and its clones. Build OCKernel and run the Import command in OpenCAD.
Type y or yes at the command prompt and you should see something like the figure below.

Fig 8.4: The getKeyword() method

Other get methods behave in a similar fashion. This means that if you do not want your
DWGdirect hosted application to have a command prompt, but something else instead, all you
need to do is set up a putString() and getString() function that will put and get strings from whatever
GUI you have created. The DWGdirect SDK already has the framework in place to let
commands interact with the GUI of your choice.

The OdDbUserIO class has a getPoint() method which is not a pure virtual function. If we had not
added a getPoint() method to COpenCADDoc it would have use the putString() and getString()
methods to get input from the user. Just that the user would have to enter coordinates at the
command prompt. Since we wanted the user to be able to pick points in the drawing view, we
went ahead and implemented getPoint() in COpenCADDoc.

Another interesting method is getDist(). If we had not implemented getPoint(), the putString() and
getString() methods would have been called to get a distance value from the user. But since we
implemented getPoint(), the user will be asked to pick two points in the drawing view, the distance
between which will be returned by the getDist() method. Let us see this in action. Replace the
temporary code we just added to COCKernelLine::execute() with the following.

double fDistance = pUserIO->getDist(_T("Distance: "));


CString s;
s.Format(_T("You entered a distance of %f"), fDistance);
pUserIO->putString(OdString(s));
return;

Build OCKernel and run the Line command in OpenCAD. Pick two points in the drawing view
and you should see something like the figure below.

105
OpenCAD

Fig 8.5: The getDist method

Delete the temporary code we just added and build OCKernel.

Running the Line command in Bricscad V9


And now for something really fascinating. If you have Bricscad V9 installed on your computer
you can do this yourself. Remember at the beginning, I mentioned that a DRX plug-in can load
and run inside any DWGdirect hosted application. Bricscad V9 is one such DWGdirect hosted
application and so by that logic our OCKernel DRX plug-in should work inside Bricscad V9 as
well. Lets see if it does.

We will try and load OCKernel into Bricscad V9, run our Line command and draw a line object
in the active Bricscad document. Yes, I know it sounds like a crazy idea, if not impossible. It is
one thing to make OCKernel interact with the command prompt and drawing view of
OpenCAD. We wrote both those pieces of software from scratch and spent this entire section
wiring them together. But how can OCKernel possibly interact with the Bricscad command
prompt, something that we know absolutely nothing about. Remember putString(), getString() and
getPoint()? If you did not understand what I was said back then, I am pretty sure you will now.

First we need to rename our Line command since Bricscad already has a command called Line. In
OCKernelLine.h modify globalName() to return “Line1”.

const OdString globalName() const { return OdString("Line1"); }

Build OCKernel. We will now try and load OCKernel_2.06_8.drx into Bricscad V9. Bricsys used
Visual C++ 2005 and DWGdirect version 2.06 to build Bricscad V9 and that is one the reasons I
chose to use those exact versions of compiler and SDK to build OpenCAD and OCKernel - so
that we can do what we are about to do now.

Start Bricscad V9. If you do not have Bricscad V9 installed on your computer, sit back, relax and
enjoy the show. Click Load Applications from the Tools menu. The Load Application Files dialog box
will pop up. Click Add. Browse to C:\OpenCAD\Bin and select OCKernel_2.06_8.drx. It will be
added to the list in the Load Application Files dialog box. Select it and click Load.

106
Chapter 8: OpenCAD as a DWG Editor

Fig 8.6: Load Application Files dialog box

Notice the Bricscad command prompt. If our DRX module could not be loaded, Bricscad will
report an error in the prompt area.

Type Line1 at the Bricscad command prompt. the Line1 command from OCKernel will begin.
Pick points in the drawing view or enter coordinates at the command prompt. Line segments will
be added to the document.

107
OpenCAD

Fig 8.7: Line1 command in Bricscad V9

So it did work. This is what actually happenned. Bricsys implemented the putString(), getString()
and getPoint() methods for their DWGdirect hosted application - Bricscad V9. To us, it really does
not matter what they did and how they did it. All we need to do is call the methods from within
our DRX plug-in and control will be passed to the appropriate GUI components in Bricscad V9.
It’s that simple and yet so powerful.

108
Conclusion
I guess you can now see my point that the DWGdirect SDK does not just read and write DWG
files. It offers a full blown framework to create a professional CAD application. At the start of
this book we started out with a empty MFC Doc-View MDI application and now we have a
CAD application that looks, feels and works a lot like AutoCAD. I believe the IntelliCAD
Technology Consortium (ITC) is developing the new IntelliCAD 7 pretty much how we
developed OpenCAD, using the same core framework of the DWGdirect SDK. And we know
that Bricsys developed Bricscad V9 using the same framework, otherwise our OCKernel plug-in
would not be able to interact with its command prompt and drawing view. Of course, IntelliCAD
7 and Bricscad are far more complicated and powerful than our simple OpenCAD, but they are
built using the powerful DWGdirect application framework and can be extended using DRX
plug-ins.

I know that in its current state, OpenCAD with just two commands, is nowhere close to being a
complete CAD application. But that was not the point of this book. The aim was not to create a
usable CAD application. Rather it was to show you what can be achieved with the DWGdirect
SDK and completely shred the myth that the DWGdirect SDK is good only to read and write
DWG files. I hope I have done that.

I also hope that you have enjoyed this book as much as I have enjoyed writing it. This is my first
book and if you are still reading this, I guess it means that I have not done that bad a job after all.
I look forward to hearing from you. Drop me a line at deelip@sycode.com.

As far as OpenCAD itself is concerned, I believe it has served its intended purpose of helping
me explain the DWGdirect and DRX SDK’s to you in this book. I really do not have the time or
the need to add commands to it and make it a usable CAD application. The DWGdirect SDK
offers a whole lot more than what I have shown in this book, stuff like object selection, object
snap, editing by means of grips, etc. In fact, I have only scratched the surface.

If you want to take OpenCAD forward, let me know. Doing it all by yourself may be too large a
task for a single person. If there are sufficient people willing to work together on this, we could
actually do something with OpenCAD. For starters we could rewrite it from scratch. To keep it
simple for this book, I left out a lot of stuff. Who knows, we may come up with an OpenCAD
Technology Consortium (OTC) and give the ITC a run for their money. That will indeed be
quite weird because we will, in effect, be making a clone of a clone.

Just kidding. Or am I?

109
110

Вам также может понравиться