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

24874412-Open-Cad

Embed Size (px)

DESCRIPTION

open source cad , free cad developed in c++

Citation preview

Page 1: 24874412-Open-Cad

OpenCADA Step by Step Guide to

Developing a Professional CAD Application

Deelip Menezeswww.deelip.com

Page 2: 24874412-Open-Cad

OpenCADA Step by Step Guide to Developing a Professional CAD Application

Published by:SYCODES1/116, Nova Cidade ComplexNH 17, Alto Porvorim, Goa - 403521 Indiawww.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 WarrantyThe 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.

Page 3: 24874412-Open-Cad

To my wife Indira and sons - Reuben and Russell

Page 4: 24874412-Open-Cad
Page 5: 24874412-Open-Cad

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

Page 6: 24874412-Open-Cad

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

Page 7: 24874412-Open-Cad

AcknowledgementsI 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

Page 8: 24874412-Open-Cad

2

Page 9: 24874412-Open-Cad

IntroductionEver 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

Page 10: 24874412-Open-Cad

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

Page 11: 24874412-Open-Cad

Section IIn 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 ApplicationChapter 2: OpenCAD as a DWG ReaderChapter 3: OpenCAD as a DWG ViewerChapter 4: OpenCAD as a 3D Viewer

5

Page 12: 24874412-Open-Cad

6

Page 13: 24874412-Open-Cad

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.

Page 14: 24874412-Open-Cad

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

Page 15: 24874412-Open-Cad

Chapter 1: The Basic OpenCAD Application

Fig 1.2: Wizard Step 1

Click Next.

9

Page 16: 24874412-Open-Cad

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

Page 17: 24874412-Open-Cad

Chapter 1: The Basic OpenCAD Application

Fig 1.4: Wizard Step 3

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

11

Page 18: 24874412-Open-Cad

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

Page 19: 24874412-Open-Cad

Chapter 1: The Basic OpenCAD Application

Fig 1.6: Wizard Step 5

We will not need any database support. Click Next.

13

Page 20: 24874412-Open-Cad

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

Page 21: 24874412-Open-Cad

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

Page 22: 24874412-Open-Cad

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

Page 23: 24874412-Open-Cad

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

Page 24: 24874412-Open-Cad

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

Page 25: 24874412-Open-Cad

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

Page 26: 24874412-Open-Cad

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

Page 27: 24874412-Open-Cad

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

Page 28: 24874412-Open-Cad

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 classUninitializeDWGdirect();return __super::ExitInstance();

}

Copy the following DLLs from the DWGdirect SDK exe/VC8/Release folder into the Bin folder.DD_Alloc_2.06_8.dllDD_Db_2.06_8.dllDD_DbRoot_2.06_8.dllDD_Ge_2.06_8.dllDD_Gi_2.06_8.dllDD_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

Page 29: 24874412-Open-Cad

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.

// Attributespublic:

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

Page 30: 24874412-Open-Cad

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

Page 31: 24874412-Open-Cad

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() {}

Page 32: 24874412-Open-Cad

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.

// Attributespublic:

OdGsDevicePtr m_pDevice; // Vectorizer deviceODCOLORREF 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 herem_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 rectangleCRect rc;GetClientRect(&rc);

// Load the vectorization moduleOdGsModulePtr pGs = ::odrxDynamicLinker()->loadModule("WinDirectX_2.06_8.gs");

26

Page 33: 24874412-Open-Cad

Chapter 3: OpenCAD as a DWG Viewer

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

return;

// Return a pointer to the dictionary entity containing the device propertiesOdRxDictionaryPtr pProperties = m_pDevice->properties();

// Set the window handle for this GsDevicepProperties->putAt("WindowHWND", OdRxVariantValue((long)m_hWnd));

// Define a device coordinate rectangle equal to the client rectangleOdGsDCRect gsRect(rc.left, rc.right, rc.bottom, rc.top);

// Set the device background color and palettem_pDevice->setBackgroundColor(theApp.m_clrBackground);m_pDevice->setLogicalPalette(theApp.CurrentPalette(), 256);

if(!database())return;

// Set up the views for the active layoutm_pDevice = OdDbGsManager::setupActiveLayoutViews(m_pDevice, this);

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

// Set the viewport border propertiesSetViewportBorderProperties(m_pDevice, !bModelSpace);

if(bZoomExtents)ViewZoomExtents();

// Update the client rectangleOnSize(0, rc.Width(), rc.Height());

// Redraw the windowRedrawWindow();

}

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 viewportOdGsViewPtr pView = pDevice->viewAt(i);

27

Page 34: 24874412-Open-Cad

OpenCAD

// Make it visiblepView->setViewportBorderVisibility(true);

// Set the color and widthpView->setViewportBorderProperties(theApp.CurrentPalette()[7], 1);

}}

}

OdGsViewPtr COpenCADView::GetView(){

return m_pDevice->viewAt(0);}

void COpenCADView::ViewZoomExtents() {

// Get the overall viewportOdGsViewPtr pView = GetView();

// Modifies the viewport to fit the extentsOdAbstractViewPEPtr(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 classOdGiContextForDbDatabase::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 hereif(!m_pDevice.isNull() && cx && cy){

CRect rc;GetClientRect(rc);

// Update the client rectangle

28

Page 35: 24874412-Open-Cad

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 deviceif(!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.gsModelerGeometry_2.06_8.drxDD_AcisBuilder_2.06_8.dllDD_Br_2.06_8.dllDD_BrepRenderer_2.06_8.dllDD_Gs_2.06_8.dllDD_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

Page 36: 24874412-Open-Cad

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

Page 37: 24874412-Open-Cad

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

Page 38: 24874412-Open-Cad

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 viewOdGsViewPtr pView = GetView();

// Check if render mode needs to be changedif(pView->mode() == iRenderMode)

return;

// Set the render modepView->setMode(iRenderMode);

// Check if render mode was changedif(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 modePostMessage(WM_PAINT);m_iRenderMode = iRenderMode;

}}

Finally, add command handlers for the seven menu items.

void COpenCADView::OnViewRendermode2dwireframe() {

// TODO: Add your command handler code hereSetRenderMode(OdGsView::k2DOptimized);

}

void COpenCADView::OnUpdateViewRendermode2dwireframe(CCmdUI* pCmdUI) {

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

}

void COpenCADView::OnViewRendermode3dwireframe() {

// TODO: Add your command handler code hereSetRenderMode(OdGsView::kWireframe);

}

32

Page 39: 24874412-Open-Cad

Chapter 4: OpenCAD as a 3D Viewer

void COpenCADView::OnUpdateViewRendermode3dwireframe(CCmdUI* pCmdUI) {

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

}

void COpenCADView::OnViewRendermode3dhidden() {

// TODO: Add your command handler code hereSetRenderMode(OdGsView::kHiddenLine);

}

void COpenCADView::OnUpdateViewRendermode3dhidden(CCmdUI* pCmdUI) {

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

}

void COpenCADView::OnViewRendermodeFlatshaded() {

// TODO: Add your command handler code hereSetRenderMode(OdGsView::kFlatShaded);

}

void COpenCADView::OnUpdateViewRendermodeFlatshaded(CCmdUI* pCmdUI) {

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

}

void COpenCADView::OnViewRendermodeFlatshadedwireframe() {

// TODO: Add your command handler code hereSetRenderMode(OdGsView::kFlatShadedWithWireframe);

}

void COpenCADView::OnUpdateViewRendermodeFlatshadedwireframe(CCmdUI* pCmdUI) {

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

}

void COpenCADView::OnViewRendermodeSmoothshaded() {

// TODO: Add your command handler code hereSetRenderMode(OdGsView::kGouraudShaded);

}

void COpenCADView::OnUpdateViewRendermodeSmoothshaded(CCmdUI* pCmdUI) {

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

}

void COpenCADView::OnViewRendermodeSmoothshadedwireframe()

33

Page 40: 24874412-Open-Cad

OpenCAD

{// TODO: Add your command handler code hereSetRenderMode(OdGsView::kGouraudShadedWithWireframe);

}

void COpenCADView::OnUpdateViewRendermodeSmoothshadedwireframe(CCmdUI* pCmdUI) {

// TODO: Add your command update UI handler code herepCmdUI->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

Page 41: 24874412-Open-Cad

Chapter 4: OpenCAD as a 3D Viewer

Fig 4.4: 3D Hidden

Fig 4.5: Flat Shaded

35

Page 42: 24874412-Open-Cad

OpenCAD

Fig 4.6: Flat Shaded + Wireframe

Fig 4.7: Smooth Shaded

36

Page 43: 24874412-Open-Cad

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 viewOdGsViewPtr pView = GetView();

// Set up the dolly vectorOdGeVector3d Vector(-x, -y, 0.0);Vector.transformBy((pView->screenMatrix() * pView->projectionMatrix()).inverse());

// Perform the dolly

37

Page 44: 24874412-Open-Cad

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 defaultOdGsViewPtr 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_LBUTTONDOWNWM_LBUTTONUPWM_MBUTTONDOWNWM_MBUTTONUPWM_RBUTTONDOWNWM_RBUTTONUPWM_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 pressBOOL m_bMiddleButton; // Flag for middle mouse button pressBOOL m_bRightButton; // Flag for right mouse button pressCPoint m_MousePosition; // Position of mouse pointer

Add code to the overriden functions.

38

Page 45: 24874412-Open-Cad

Chapter 4: OpenCAD as a 3D Viewer

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

// TODO: Add your message handler code here and/or call defaultm_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 defaultm_bLeftButton = FALSE;__super::OnLButtonUp(nFlags, point);

}

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

// TODO: Add your message handler code here and/or call defaultm_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 defaultm_bMiddleButton = FALSE;__super::OnMButtonUp(nFlags, point);

}

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

// TODO: Add your message handler code here and/or call defaultm_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 defaultm_bRightButton = FALSE;__super::OnRButtonUp(nFlags, point);

}

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

// TODO: Add your message handler code here and/or call defaultif(m_bLeftButton == TRUE){}else if(m_bMiddleButton == TRUE){

OdGeVector3d Vector(m_MousePosition.x - point.x, m_MousePosition.y - point.y, 0.0);

39

Page 46: 24874412-Open-Cad

OpenCAD

// Screen to EyeVector.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

Page 47: 24874412-Open-Cad

Chapter 4: OpenCAD as a 3D Viewer

// TODO: Add your command handler code hereViewZoomExtents();

}

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

Page 48: 24874412-Open-Cad

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 modeOdGePoint3dArray 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 herem_Points.clear();m_bZoomWindow = !m_bZoomWindow;

}

void COpenCADView::OnUpdateViewZoomwindow(CCmdUI *pCmdUI){

// TODO: Add your command update UI handler code herepCmdUI->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 defaultm_bLeftButton = TRUE;m_MousePosition = point;

42

Page 49: 24874412-Open-Cad

Chapter 4: OpenCAD as a 3D Viewer

if(m_bZoomWindow == TRUE){

m_Points.clear(); // Empty point arraym_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 defaultm_bLeftButton = FALSE;

if(m_bZoomWindow == TRUE){

m_Points.append(GetWorldCoordinates(point)); // Record second pointif(m_Points.length() == 2)// Zoom rectangle has been completely defined{

ZoomWindow(m_Points[0], m_Points[1]);m_bZoomWindow = FALSE; // Turn off zoom window modem_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 defaultm_bLeftButton = TRUE;m_MousePosition = point;m_MouseClick = point;

if(m_bZoomWindow == TRUE){

43

Page 50: 24874412-Open-Cad

OpenCAD

m_Points.clear(); // Empty point arraym_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 viewCRect rcZoom, rcZoomOld;

// Get the old zoom windowrcZoomOld.SetRect(m_MouseClick.x, m_MouseClick.y, m_MousePosition.x,

m_MousePosition.y);rcZoomOld.NormalizeRect();rcZoomOld.InflateRect(1, 1);

// Redraw the old zoom windowRedrawWindow(&rcZoomOld);

// Create the new zoom windowrcZoom.SetRect(m_MouseClick.x, m_MouseClick.y, point.x, point.y);rcZoom.NormalizeRect();

// Draw a focus rectangle for the new zoom windowdc.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

Page 51: 24874412-Open-Cad

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

Page 52: 24874412-Open-Cad

OpenCAD

46

Page 53: 24874412-Open-Cad

Section IIWe 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 ArchitectureChapter 6: Creating a DRX Plug-inChapter 7: The Command PromptChapter 8: OpenCAD as a DWG Editor

47

Page 54: 24874412-Open-Cad

48

Page 55: 24874412-Open-Cad

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.

// Operationspublic:

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.

Page 56: 24874412-Open-Cad

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

Page 57: 24874412-Open-Cad

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

Page 58: 24874412-Open-Cad

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

Page 59: 24874412-Open-Cad

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

Page 60: 24874412-Open-Cad

OpenCAD

54

Page 61: 24874412-Open-Cad

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.

Page 62: 24874412-Open-Cad

OpenCAD

Fig 6.1: New Project

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

56

Page 63: 24874412-Open-Cad

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

Page 64: 24874412-Open-Cad

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

Page 65: 24874412-Open-Cad

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

Page 66: 24874412-Open-Cad

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

Page 67: 24874412-Open-Cad

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

Page 68: 24874412-Open-Cad

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

Page 69: 24874412-Open-Cad

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 hereExecuteCommand(_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

Page 70: 24874412-Open-Cad

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

Page 71: 24874412-Open-Cad

Chapter 7: The Command PromptIntroduction

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

Page 72: 24874412-Open-Cad

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

Page 73: 24874412-Open-Cad

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

Page 74: 24874412-Open-Cad

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 hereif(!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 herem_wndCommandPrompt.Resize(cx);

}

68

Page 75: 24874412-Open-Cad

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

Page 76: 24874412-Open-Cad

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

Page 77: 24874412-Open-Cad

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 accessif(pDbCmdCtx.isNull()){

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

return;}OdDbDatabasePtr pDatabase = pDbCmdCtx->database(); // Current databaseOdDbUserIOPtr 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

Page 78: 24874412-Open-Cad

OpenCAD

pwndPrompt->GetWindowTextW(strPrompt);

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

strPrompt += strText;

// Update prompt textpwndPrompt->SetWindowTextW(strPrompt);

// Scroll to last linepwndPrompt->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

Page 79: 24874412-Open-Cad

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 promptreturn 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 modem_bCancel = FALSE; // Reset Cancel flagwhile(theApp.PumpMessage()){

if(m_iCommandMode == eCommandModeNone) // Exit GetString modebreak;

}

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

73

Page 80: 24874412-Open-Cad

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 destroyedpwndCommand = (CEdit*)GetDlgItem(IDC_EDT_COMMAND);if(pwndCommand == NULL)

return FALSE;

pwndCommand->GetWindowTextW(strText); // Store entered textpwndCommand->SetWindowTextW(_T("")); // Empty command edit boxPutString(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 classif(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 accessif(pDbCmdCtx.isNull()){

74

Page 81: 24874412-Open-Cad

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

Page 82: 24874412-Open-Cad

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

Page 83: 24874412-Open-Cad

Chapter 7: The Command Prompt

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

// TODO: Add your specialized code here and/or call the base classif(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

Page 84: 24874412-Open-Cad

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 validif(pCommand == NULL)

putString(_T("Unknown command '") + sCommand + _T("'"));else{

pCommands->executeCommand(sCommand, pCmdCtx); // Execute the commandm_strLastCommand = sCommand; // Save as last commandGetView()->m_pDevice->invalidate(); // Refresh the drawing viewGetView()->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

Page 85: 24874412-Open-Cad

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 validif(pCommand == NULL)

putString(_T("Unknown command '") + sCommand + _T("'"));else{

79

Page 86: 24874412-Open-Cad

OpenCAD

m_bCommandActive = TRUE;pCommands->executeCommand(sCommand, pCmdCtx); // Execute the commandm_strLastCommand = sCommand; // Save as last commandGetView()->m_pDevice->invalidate();// Refresh the drawing viewGetView()->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 classif(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

Page 87: 24874412-Open-Cad

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 defaultPOSITION DocTemplatePos = AfxGetApp()->GetFirstDocTemplatePosition();while(DocTemplatePos != NULL){

// Get a document templateCDocTemplate *pDocTemplate = AfxGetApp()->GetNextDocTemplate(DocTemplatePos);

// Iterate through documents for the current templatePOSITION 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

Page 88: 24874412-Open-Cad

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

Page 89: 24874412-Open-Cad

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 templateCDocTemplate *pDocTemplate = AfxGetApp()->GetNextDocTemplate(DocTemplatePos);

// Iterate through documents for the current templatePOSITION DocPos = pDocTemplate->GetFirstDocPosition();while(DocPos != NULL){

COpenCADDoc *pDoc = (COpenCADDoc*)pDocTemplate->GetNextDoc(DocPos);if(pDoc->GetChildFrame()->m_wndCommandPrompt.m_iCommandMode !=

CCommandPrompt::eCommandModeNone){

83

Page 90: 24874412-Open-Cad

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

Page 91: 24874412-Open-Cad

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 accessif(pDbCmdCtx.isNull()){

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

return;}OdDbDatabasePtr pDatabase = pDbCmdCtx->database(); // Current databaseOdDbUserIOPtr 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 spaceOdDbBlockTableRecordPtr 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

Page 92: 24874412-Open-Cad

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

Page 93: 24874412-Open-Cad

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

Page 94: 24874412-Open-Cad

OpenCAD

88

Page 95: 24874412-Open-Cad

Chapter 8: OpenCAD as a DWG EditorIntroduction

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

Page 96: 24874412-Open-Cad

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 modem_bCancel = FALSE;// Reset Cancel flagwhile(theApp.PumpMessage()){

if(m_iCommandMode == eCommandModeNone)// Exit GetPoint modebreak;

}

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 destroyedpwndCommand = (CEdit*)GetDlgItem(IDC_EDT_COMMAND);if(pwndCommand == NULL)

return FALSE;

CString strText;pwndCommand->GetWindowTextW(strText);// Store entered textif(GetPointFromString(strText, Point) == FALSE){

PutString(strText, FALSE);// Append entered text to promptPutString(_T("Invalid point"));goto Repeat;

}

pwndCommand->SetWindowTextW(_T(""));// Empty command edit boxPutString(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

Page 97: 24874412-Open-Cad

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 hereExecuteCommand(_T("Line"));

}

91

Page 98: 24874412-Open-Cad

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 accessif(pDbCmdCtx.isNull()){

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

return;}OdDbDatabasePtr pDatabase = pDbCmdCtx->database(); // Current databaseOdDbUserIOPtr 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

Page 99: 24874412-Open-Cad

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

Page 100: 24874412-Open-Cad

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

Page 101: 24874412-Open-Cad

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 defaultm_bLeftButton = TRUE;m_MousePosition = point;m_MouseClick = point;

if(m_bZoomWindow == TRUE){

m_Points.clear(); // Empty point arraym_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

Page 102: 24874412-Open-Cad

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

Page 103: 24874412-Open-Cad

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

Page 104: 24874412-Open-Cad

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

Page 105: 24874412-Open-Cad

Chapter 8: OpenCAD as a DWG Editor

{// Clean out the trackerif(m_pTracker)

m_pTracker->removeDrawables(GetView());m_pTracker = NULL;

// Refresh the drawing viewm_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 validif(pCommand == NULL)

putString(_T("Unknown command '") + sCommand + _T("'"));else{

m_bCommandActive = TRUE;pCommands->executeCommand(sCommand, pCmdCtx); // Execute the commandm_strLastCommand = sCommand; // Save as last commandGetView()->Refresh();m_bCommandActive = FALSE;

99

Page 106: 24874412-Open-Cad

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 accessif(pDbCmdCtx.isNull()){

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

return;}OdDbDatabasePtr pDatabase = pDbCmdCtx->database(); // Current databaseOdDbUserIOPtr 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

Page 107: 24874412-Open-Cad

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

Page 108: 24874412-Open-Cad

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 modem_bCancel = FALSE;// Reset Cancel flagwhile(theApp.PumpMessage()){

if(m_iCommandMode == eCommandModeNone)// Exit GetPoint modebreak;

}

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 destroyedpwndCommand = (CEdit*)GetDlgItem(IDC_EDT_COMMAND);if(pwndCommand == NULL){

throw OdError(_T("Invalid command prompt"));return FALSE;

}

CString strText;pwndCommand->GetWindowTextW(strText);// Store entered textif(strText == _T("")){

throw OdEdCancel();return FALSE;

}if(GetPointFromString(strText, Point) == FALSE){

PutString(strText, FALSE);// Append entered text to promptPutString(_T("Invalid point"));goto Repeat;

}

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

return TRUE;}

102

Page 109: 24874412-Open-Cad

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 modem_bCancel = FALSE;// Reset Cancel flagwhile(theApp.PumpMessage()){

if(m_iCommandMode == eCommandModeNone)// Exit GetString modebreak;

}

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 destroyedpwndCommand = (CEdit*)GetDlgItem(IDC_EDT_COMMAND);if(pwndCommand == NULL){

throw OdError(_T("Invalid command prompt"));return FALSE;

}

pwndCommand->GetWindowTextW(strText);// Store entered textpwndCommand->SetWindowTextW(_T(""));// Empty command edit boxPutString(strText, FALSE);// Append entered text to prompt

return TRUE;

103

Page 110: 24874412-Open-Cad

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

elsepUserIO->putString(_T("You selected not to create a polyline"));

return;

104

Page 111: 24874412-Open-Cad

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

Page 112: 24874412-Open-Cad

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

Page 113: 24874412-Open-Cad

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

Page 114: 24874412-Open-Cad

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

Page 115: 24874412-Open-Cad

ConclusionI 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 [email protected].

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

Page 116: 24874412-Open-Cad

110