0% found this document useful (0 votes)
138 views858 pages

ObjectARX Developers Guide

The ObjectARX Developer's Guide provides comprehensive documentation for developers working with Autodesk's ObjectARX technology, including system requirements, installation instructions, and an overview of the programming environment. It covers essential topics such as database management, application basics, and creating custom classes within AutoCAD. The guide also includes information on Autodesk's trademarks and third-party software credits relevant to the ObjectARX framework.

Uploaded by

dev.junior3159
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
138 views858 pages

ObjectARX Developers Guide

The ObjectARX Developer's Guide provides comprehensive documentation for developers working with Autodesk's ObjectARX technology, including system requirements, installation instructions, and an overview of the programming environment. It covers essential topics such as database management, application basics, and creating custom classes within AutoCAD. The guide also includes information on Autodesk's trademarks and third-party software credits relevant to the ObjectARX framework.

Uploaded by

dev.junior3159
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 858

OBJECTARX™ DEVELOPER’S GUIDE

00120-010000-5060 January 19, 1999


Copyright © 1999 Autodesk, Inc.
All Rights Reserved
AUTODESK, INC. MAKES NO WARRANTY, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY
IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, REGARDING THESE MATERIALS
AND MAKES SUCH MATERIALS AVAILABLE SOLELY ON AN “AS-IS” BASIS.

IN NO EVENT SHALL AUTODESK, INC. BE LIABLE TO ANYONE FOR SPECIAL, COLLATERAL, INCIDENTAL, OR
CONSEQUENTIAL DAMAGES IN CONNECTION WITH OR ARISING OUT OF PURCHASE OR USE OF THESE MATERIALS. THE
SOLE AND EXCLUSIVE LIABILITY TO AUTODESK, INC., REGARDLESS OF THE FORM OF ACTION, SHALL NOT EXCEED THE
PURCHASE PRICE OF THE MATERIALS DESCRIBED HEREIN.

Autodesk, Inc. reserves the right to revise and improve its products as it sees fit. This publication describes the state of this product
at the time of its publication, and may not reflect the product at all times in the future.
Autodesk Trademarks
The following are registered trademarks of Autodesk, Inc., in the USA and/or other countries: 3D Plan, 3D Props, 3D Studio, 3D
Studio MAX, 3D Studio VIZ, 3D Surfer, ADE, ADI, Advanced Modeling Extension, AEC Authority (logo), AEC-X, AME, Animator
Pro, Animator Studio, ATC, AUGI, AutoCAD, AutoCAD Data Extension, AutoCAD Development System, AutoCAD LT, AutoCAD
Map, Autodesk, Autodesk Animator, Autodesk (logo), Autodesk MapGuide, Autodesk University, Autodesk View, Autodesk
WalkThrough, Autodesk World, AutoLISP, AutoShade, AutoSketch, AutoSolid, AutoSurf, AutoVision, Biped, bringing information
down to earth, CAD Overlay, Character Studio, Design Companion, Drafix, Education by Design, Generic, Generic 3D Drafting,
Generic CADD, Generic Software, Geodyssey, Heidi, HOOPS, Hyperwire, Inside Track, Kinetix, MaterialSpec, Mechanical Desktop,
Multimedia Explorer, NAAUG, Office Series, Opus, PeopleTracker, Physique, Planix, Rastation, Softdesk, Softdesk (logo), Solution
3000, Tech Talk, Texture Universe, The AEC Authority, The Auto Architect, TinkerTech, WHIP!, WHIP! (logo), Woodbourne,
WorkCenter, and World-Creating Toolkit.
The following are trademarks of Autodesk, Inc., in the USA and/or other countries: 3D on the PC, ACAD, ActiveShapes, Actrix,
Advanced User Interface, AEC Office, AME Link, Animation Partner, Animation Player, Animation Pro Player, A Studio in Every
Computer, ATLAST, Auto-Architect, AutoCAD Architectural Desktop, AutoCAD Architectural Desktop Learning Assistance,
AutoCAD DesignCenter, Learning Assistance, AutoCAD LT Learning Assistance, AutoCAD Simulator, AutoCAD SQL Extension,
AutoCAD SQL Interface, AutoCDM, Autodesk Animator Clips, Autodesk Animator Theatre, Autodesk Device Interface, Autodesk
PhotoEDIT, Autodesk Software Developer’s Kit, Autodesk View DwgX, AutoEDM, AutoFlix, AutoLathe, AutoSnap, AutoTrack, Built
with ObjectARX (logo), ClearScale, Concept Studio, Content Explorer, cornerStone Toolkit, Dancing Baby (image), Design Your
World, Design Your World (logo), Designer’s Toolkit, DWG Linking, DWG Unplugged, DXF, Exegis, FLI, FLIC, GDX Driver, Generic
3D, Heads-up Design, Home Series, Kinetix (logo), MAX DWG, ObjectARX, ObjectDBX, Ooga-Chaka, Photo Landscape,
Photoscape, Plugs and Sockets, PolarSnap, Powered with Autodesk Technology, Powered with Autodesk Technology (logo),
ProConnect, ProjectPoint, Pro Landscape, QuickCAD, RadioRay, SchoolBox, SketchTools, Suddenly Everything Clicks,
Supportdesk, The Dancing Baby, Transforms Ideas Into Reality, Visual LISP, and Volo.
Third Party Trademarks
Élan License Manager is a trademark of Élan Computer Group, Inc.
Microsoft, Visual Basic, Visual C++, and Windows are registered trademarks and Visual FoxPro and the Microsoft Visual Basic
Technology logo are trademarks of Microsoft Corporation in the United States and other countries.
All other brand names, product names or trademarks belong to their respective holders.
Third Party Software Program Credits
ACIS ® Copyright © 1994, 1997, 1999 Spatial Technology, Inc., Three-Space Ltd., and Applied Geometry Corp. All rights reserved.
Copyright © 1997 Microsoft Corporation. All rights reserved.
International CorrectSpell™ Spelling Correction System © 1995 by Lernout & Hauspie Speech Products, N.V. All rights reserved.
InstallShield™ 3.0. Copyright © 1997 InstallShield Software Corporation. All rights reserved.
Portions Copyright © 1991-1996 Arthur D. Applegate. All rights reserved.
Portions of this software are based on the work of the Independent JPEG Group.
Typefaces from the Bitstream ® typeface library copyright 1992.
Typefaces from Payne Loving Trust © 1996. All rights reserved.
The license management portion of this product is based on Élan License Manager © 1989, 1990, 1998 Élan Computer Group,
Inc. All rights reserved.
GOVERNMENT USE
Use, duplication, or disclosure by the U. S. Government is subject to restrictions as set forth in FAR 12.212 (Commercial Computer
Software-Restricted Rights) and DFAR 227.7202 (Rights in Technical Data and Computer Software), as applicable.

1 2 3 4 5 6 7 8 9 10
Contents

About ObjectARX Documentation . . . . . . . . . . . . . . . . . . . . . . . . . . 1


The ObjectARX Documentation Set . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Printed Guides . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Online Documentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
ObjectARX Logo Program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Where to Start . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Using This Guide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Organization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

Part I Using ObjectARX. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

Chapter 1 Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
The ObjectARX Programming Environment . . . . . . . . . . . . . . . . . . . . . . . . 8
Accessing the AutoCAD Database . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Interacting with the AutoCAD Editor . . . . . . . . . . . . . . . . . . . . . . . . . 8
Creating User Interfaces with MFC . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Supporting MDI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Creating Custom Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Building Complex Applications. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Interacting with Other Environments . . . . . . . . . . . . . . . . . . . . . . . . . 9
ObjectARX Class Libraries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
AcRx Library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
AcEd Library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
AcDb Library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
AcGi Library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
AcGe Library. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Getting Started . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16

iii
System Requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Installing ObjectARX. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16

Chapter 2 Database Primer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19


AutoCAD Database Overview. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
Multiple Databases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Obtaining Object IDs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Essential Database Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Creating Objects in AutoCAD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Creating Objects in ObjectARX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Creating Entities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Creating a New Layer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Opening and Closing ObjectARX Objects . . . . . . . . . . . . . . . . . . . . 27
Adding a Group to the Group Dictionary. . . . . . . . . . . . . . . . . . . . . 28

Chapter 3 ObjectARX Application Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29


Creating an ObjectARX Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Creating Custom Classes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Responding to AutoCAD Messages . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Implementing an Entry Point for AutoCAD . . . . . . . . . . . . . . . . . . . 36
Initializing an ObjectARX Application . . . . . . . . . . . . . . . . . . . . . . . 37
Preparing for Unloading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
Example Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Registering New Commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
Command Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
Lookup Order . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
Global versus Local Command Names . . . . . . . . . . . . . . . . . . . . . . . 42
Transparent versus Modal Commands . . . . . . . . . . . . . . . . . . . . . . . 42
Loading an ObjectARX Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
The Library Search Path . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
Listing Loaded ObjectARX Applications . . . . . . . . . . . . . . . . . . . . . . 43
Unloading an ObjectARX Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Unlocking Applications. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Demand Loading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
AutoCAD, the Windows System Registry, and ObjectARX Applica-
tions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
Modification of the Registry at ObjectARX Application Installation 47
The DEMANDLOAD System Variable . . . . . . . . . . . . . . . . . . . . . . . . 49
Demand Loading on Detection of Custom Objects . . . . . . . . . . . . . 50
Demand Loading on Command . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Demand Loading on AutoCAD Startup . . . . . . . . . . . . . . . . . . . . . . 52
Managing Applications with the System Registry . . . . . . . . . . . . . . 52
ARX Command. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53

iv | Contents
?—List Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Load . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Unload . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Commands. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Options. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Running ObjectARX Applications from AutoLISP . . . . . . . . . . . . . . . . . . . 55
Error Handling. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55

Chapter 4 Database Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59


Initial Database . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
Creating and Populating a Database . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
Saving a Database . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
Setting the Default File Format . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
Global Save Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
The wblock Operation. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
Creating a New Database from an Existing Database . . . . . . . . . . . . 63
Creating a New Database with Entities . . . . . . . . . . . . . . . . . . . . . . . 64
Inserting a Database . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
Setting Current Database Values. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
Database Color Value. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
Database Linetype Value . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
Database Linetype Scale Value. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
Database Layer Value . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
Example of Database Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
Long Transactions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
Class and Function Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
Long Transaction Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
External References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
External Reference Pre- and Post-Processing . . . . . . . . . . . . . . . . . . . 75
File Locking and Consistency Checks . . . . . . . . . . . . . . . . . . . . . . . . 76
Indexes and Filters. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
Drawing Summary Information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
Last Saved by Autodesk Software . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80

Chapter 5 Database Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81


Opening and Closing Database Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
Deleting Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
Database Ownership of Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
Adding Object-Specific Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
Extended Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
Extension Dictionary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
Erasing Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
Object Filing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95

Contents | v
Chapter 6 Entities. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
Entities Defined . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
Entity Ownership . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
AutoCAD Release 12 Entities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
Common Entity Properties. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
Entity Color . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
Entity Linetype . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
Entity Linetype Scale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
Entity Visibility . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
Entity Layer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
Common Entity Functions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
Object Snap Points . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
Transform Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
Intersecting for Points . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
GS Markers and Subentities. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
Exploding Entities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
Creating Instances of AutoCAD Entities . . . . . . . . . . . . . . . . . . . . . . . . . 125
Creating a Simple Entity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
Creating a Simple Block Table Record . . . . . . . . . . . . . . . . . . . . . . 126
Creating a Block Table Record with Attribute Definitions . . . . . . . 126
Creating a Block Reference with Attributes . . . . . . . . . . . . . . . . . . 129
Iterating through a Block Table Record . . . . . . . . . . . . . . . . . . . . . 133
Complex Entities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
Creating a Complex Entity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
Iterating through Vertices in a Polyline . . . . . . . . . . . . . . . . . . . . . 135
Coordinate System Access . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
Entity Coordinate System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
AcDb2dPolylineVertex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
Curve Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
Associating Hyperlinks with Entities . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
AcDbHyperlink Class. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
AcDbHyperlinkCollection Class . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
AcDbEntityHyperlinkPE Class. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
Hyperlink Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140

Chapter 7 Container Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143


Comparison of Symbol Tables and Dictionaries . . . . . . . . . . . . . . . . . . . 144
Symbol Tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
Block Table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
Layer Table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
Iterators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
Dictionaries. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
Groups and the Group Dictionary . . . . . . . . . . . . . . . . . . . . . . . . . 153
MLINE Style Dictionary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156

vi | Contents
Layout Dictionary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
Creating a Dictionary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
Iterating over Dictionary Entries . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
Layouts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
ObjectARX Layout Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
Xrecords. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
DXF Group Codes for Xrecords . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163

Part II User Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165

Chapter 8 MFC Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167


Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
Using MFC with ObjectARX Applications . . . . . . . . . . . . . . . . . . . . . . . . 168
ObjectARX Applications with Dynamically Linked MFC. . . . . . . . . . . . . 169
Visual C++ Project Settings for Dynamically Linked MFC . . . . . . . 169
Debugging ObjectARX Applications with Dynamic MFC. . . . . . . . 169
Resource Management . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170
Built-In MFC User Interface Support. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
Class Hierarchy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
AdUi Messaging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
AdUi Tip Windows. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
AdUi Dialog Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
AcUi Dialog Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
AdUi Classes Supporting Tab Extensibility . . . . . . . . . . . . . . . . . . . 176
AdUi and AcUi Control Bar Classes . . . . . . . . . . . . . . . . . . . . . . . . . 176
AdUi and AcUi Edit Controls. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
AdUi and AcUi Combo Box Controls . . . . . . . . . . . . . . . . . . . . . . . 178
AcUi MRU Combo Boxes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
AdUi Button Classes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
AcUi Button Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182
Dialog Data Persistency . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182
Using and Extending the AdUi Tab Dialog System . . . . . . . . . . . . . 183
Constructing a Custom Tab Dialog That Is Extensible . . . . . . . . . . 183
Extending the AutoCAD Built-In Tab Dialogs. . . . . . . . . . . . . . . . . 184
Using AdUi and AcUi with VC++ AppWizard. . . . . . . . . . . . . . . . . . . . . . 186
Create the ObjectARX MFC Application Skeleton . . . . . . . . . . . . . 186
Create the MFC Dialog Using App Studio . . . . . . . . . . . . . . . . . . . . 188
Create the Classes and Controls . . . . . . . . . . . . . . . . . . . . . . . . . . . 189
Create the Handlers for the Dialog . . . . . . . . . . . . . . . . . . . . . . . . . 190
Add Code to the Handlers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191

Contents | vii
Chapter 9 Selection Set, Entity, and Symbol Table Functions . . . . . . . . . . . 199
Selection Set and Entity Names . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
Handling Selection Sets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
Selection Set Filter Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
Selection Set Manipulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
Transformation of Selection Sets. . . . . . . . . . . . . . . . . . . . . . . . . . . 211
Entity Name and Data Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214
Entity Name Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214
Entity Data Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223
Entity Data Functions and Graphics Screen . . . . . . . . . . . . . . . . . . 233
Notes on Extended Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235
Xrecord Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241
Symbol Table Access . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242

Chapter 10 Global Functions for Interacting with AutoCAD . . . . . . . . . . . . . 245


AutoCAD Queries and Commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246
General Access . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246
Getting User Input . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
User-Input Functions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
Control of User-Input Function Conditions. . . . . . . . . . . . . . . . . . 260
Graphically Dragging Selection Sets . . . . . . . . . . . . . . . . . . . . . . . . 263
User Breaks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264
Returning Values to AutoLISP Functions . . . . . . . . . . . . . . . . . . . . 265
Conversions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 266
String Conversions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 266
Real-World Units . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
Character Type Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
Coordinate System Transformations . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271
Display Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273
Interactive Output. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273
Control of Graphics and Text Screens . . . . . . . . . . . . . . . . . . . . . . 275
Control of Low-Level Graphics and User Input . . . . . . . . . . . . . . . 275
Tablet Calibration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 276
Wild-Card Matching. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278

Part III Defining New Classes . . . . . . . . . . . . . . . . . . . . . . . . 281

Chapter 11 Deriving a Custom ObjectARX Class. . . . . . . . . . . . . . . . . . . . . . . 283


Custom Class Derivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284
Runtime Class Identification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
Class Declaration Macro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286
Class Implementation Macros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287

viii | Contents
Class Initialization Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289

Chapter 12 Deriving from AcDbObject . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 291


Overriding AcDbObject Virtual Functions . . . . . . . . . . . . . . . . . . . . . . . . 292
AcDbObject: Essential Functions to Override . . . . . . . . . . . . . . . . . 292
AcDbObject: Functions Often Overridden . . . . . . . . . . . . . . . . . . . 292
AcDbObject: Functions Sometimes Overridden . . . . . . . . . . . . . . . 293
AcDbObject: Functions Rarely Overridden . . . . . . . . . . . . . . . . . . . 293
AcRxObject: Functions Rarely Overridden . . . . . . . . . . . . . . . . . . . 294
AcDbEntity: Functions to Override . . . . . . . . . . . . . . . . . . . . . . . . . 294
AcDbCurve: Functions to Override . . . . . . . . . . . . . . . . . . . . . . . . . 295
Implementing Member Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 297
Filing Objects to DWG and DXF Files. . . . . . . . . . . . . . . . . . . . . . . . . . . . 298
dwgOut() Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 299
dwgIn() Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 299
dxfOut() Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 299
dxfIn() Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 299
Error Checking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 300
Implementing the DWG Filing Functions. . . . . . . . . . . . . . . . . . . . 300
Implementing the DXF Filing Functions. . . . . . . . . . . . . . . . . . . . . 302
Object References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310
Ownership References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311
Uses of Ownership . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 312
Types of Ownership . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 312
Building an Ownership Hierarchy . . . . . . . . . . . . . . . . . . . . . . . . . . 312
Pointer References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 320
Hard Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 320
Soft Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 321
Long Transaction Issues for Custom Objects . . . . . . . . . . . . . . . . . . . . . . 321
Purge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 324
Undo and Redo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 324
Automatic Undo. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 325
Partial Undo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 325
Redo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 327
subErase, subOpen, subClose, and subCancel . . . . . . . . . . . . . . . . . . . . . 328
Example of a Custom Object Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338
Header File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338
Source File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338
Object Version Support . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 343
Class Versioning. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 343
Class Renaming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 346
Class Data or Xdata Version Numbers. . . . . . . . . . . . . . . . . . . . . . . 347

Contents | ix
Chapter 13 Deriving from AcDbEntity. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349
Deriving Custom Entities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 350
AcDbEntity Functions to Override . . . . . . . . . . . . . . . . . . . . . . . . . 350
AcDbEntity Functions Usually Overridden. . . . . . . . . . . . . . . . . . . 351
AcDbEntity Functions Rarely Overridden. . . . . . . . . . . . . . . . . . . . 352
Overriding Common Entity Functions . . . . . . . . . . . . . . . . . . . . . . . . . . 353
Overriding worldDraw() and viewportDraw() . . . . . . . . . . . . . . . . 353
Overriding saveAs() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 355
Implementing the Object Snap Point Function . . . . . . . . . . . . . . . 357
Implementing the Grip Point Functions . . . . . . . . . . . . . . . . . . . . 359
Implementing the Stretch Point Functions . . . . . . . . . . . . . . . . . . 361
Transformation Functions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363
Intersecting with Other Entities . . . . . . . . . . . . . . . . . . . . . . . . . . . 364
Intersecting a Custom Entity with Another Entity. . . . . . . . . . . . . 369
Exploding an Entity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 370
Extending Entity Functionality . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 370
Using AcEdJig . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371
Deriving a New Class from AcEdJig . . . . . . . . . . . . . . . . . . . . . . . . 371
General Steps for Using AcEdJig . . . . . . . . . . . . . . . . . . . . . . . . . . . 371
Setting Up Parameters for the Drag Sequence . . . . . . . . . . . . . . . . 372
Drag Loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 372
Implementing the sampler(), update(), and entity() Functions . . 375
Adding the Entity to the Database . . . . . . . . . . . . . . . . . . . . . . . . . 378
Sample Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 378

Part IV Specialized Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . 385

Chapter 14 Proxy Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 387


Proxy Objects Defined . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 388
Proxy Object Life Cycle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 388
User Encounters with Proxy Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . 389
Displaying Proxy Entities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 390
Editing Proxy Entities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 390
Unloading an Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 391

Chapter 15 Notification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393


Notification Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 394
Reactor Classes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 394
Types of Object Reactors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 395
Using Reactors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 396
AcDbObject and Database Notification Events . . . . . . . . . . . . . . . 398
Custom Notifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 398

x | Contents
Using an Editor Reactor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 398
Using a Database Reactor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 399
Using an Object Reactor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 402
Notification Use Guidelines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412

Chapter 16 The Multiple Document Interface . . . . . . . . . . . . . . . . . . . . . . . . 415


Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 416
Document Execution Contexts . . . . . . . . . . . . . . . . . . . . . . . . . . . . 416
Data Instances . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 416
Document Locking. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 417
Document Management Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . 417
Terminology . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 418
Active Document . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 418
Application. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 418
Application Context . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 418
Command . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 418
Command, Multi-Document . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 419
Command, Nonreentrant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 419
Command Processor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 419
Current Document. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420
Database . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420
Document . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420
Drawing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420
Edit Session . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420
Execution Context, Application . . . . . . . . . . . . . . . . . . . . . . . . . . . 421
MDI-Aware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 421
Per-Application. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 421
Per-Context . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 421
Per-Document . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 421
Quiescent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 422
Session . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 422
Undo Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 422
SDI System Variable. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 422
Levels of Compatibility . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 423
SDI-Only Level . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 423
MDI-Aware Level . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 424
MDI-Capable Level. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 427
MDI-Enhanced Level . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 427
Interacting with Multiple Documents . . . . . . . . . . . . . . . . . . . . . . . . . . . 428
Accessing the Current Document and Its Related Objects . . . . . . . 428
Accessing Databases Associated with Noncurrent Documents . . . . 429
Setting the Current Document without Activating It . . . . . . . . . . . 430
Document Event Notification. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 430
Application-Specific Document Objects . . . . . . . . . . . . . . . . . . . . . . . . . . 431

Contents | xi
Nonreentrant Commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 431
Making a Command Nonreentrant . . . . . . . . . . . . . . . . . . . . . . . . 432
Nonreentrant AutoCAD Commands . . . . . . . . . . . . . . . . . . . . . . . 432
Multi-Document Commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 432
Disabling Document Switching . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 435
Application Execution Context . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 436
Code Invoked under the Application Execution Context . . . . . . . 436
Code Differences under the Application Execution Context . . . . . 436
Other Application Execution Context Considerations. . . . . . . . . . 437
Database Undo and Transaction Management Facilities. . . . . . . . . . . . . 438
Document-Independent Databases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 439
An MDI-Aware Example Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . 440

Chapter 17 Transaction Management. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 449


Overview of Transaction Management . . . . . . . . . . . . . . . . . . . . . . . . . . 450
Transaction Manager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 451
Nesting Transactions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 451
Transaction Boundaries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 452
Obtaining Pointers to Objects in a Transaction. . . . . . . . . . . . . . . . . . . . 453
Newly Created Objects and Transactions. . . . . . . . . . . . . . . . . . . . . . . . . 454
Commit-Time Guidelines. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 454
Undo and Transactions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 455
Mixing the Transaction Model with the Open and Close Mechanism . . 455
Transactions and Graphics Generation . . . . . . . . . . . . . . . . . . . . . . . . . . 455
Transaction Reactors. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 456
Example of Nested Transactions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 457

Chapter 18 Deep Cloning . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 467


Deep Clone Basics. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 468
Using clone() versus deepClone() . . . . . . . . . . . . . . . . . . . . . . . . . . 468
Key Concepts of Cloning . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 469
Typical Deep Clone Operation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 470
Cloning Objects from Different Owners. . . . . . . . . . . . . . . . . . . . . 472
Implementing deepClone() for Custom Classes . . . . . . . . . . . . . . . . . . . 476
AutoCAD Commands That Use Deep Clone
and Wblock Clone . . . . . . . . . . . . . . . . . . . . . . . . . . . 476
Cloning Phase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 477
Translation Phase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 477
Named Object Dictionary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 480
Overriding the deepClone() Function . . . . . . . . . . . . . . . . . . . . . . 484
Overriding the wblockClone() Function . . . . . . . . . . . . . . . . . . . . 488
Using appendAcDbEntity() During Cloning. . . . . . . . . . . . . . . . . . 498
Handling Hard References to AcDbEntities During wblockClone() 501

xii | Contents
Insert . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 504
Editor Reactor Notification Functions . . . . . . . . . . . . . . . . . . . . . . . 504

Chapter 19 Protocol Extension . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 511


Protocol Extension Defined . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 512
Implementing Protocol Extension . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 512
Declaring and Defining Protocol Extension Classes . . . . . . . . . . . . 512
Registering Protocol Extension Classes . . . . . . . . . . . . . . . . . . . . . . 513
Default Class for Protocol Extension . . . . . . . . . . . . . . . . . . . . . . . . 515
Unloading the Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 515
Using Protocol Extension Functionality in an Application . . . . . . 515
Protocol Extension for the MATCH Command . . . . . . . . . . . . . . . . . . . . 516
Protocol Extension Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 516

Chapter 20 ObjectARX Global Utility Functions . . . . . . . . . . . . . . . . . . . . . . . 521


Common Characteristics of ObjectARX Library Functions . . . . . . . . . . . 522
ObjectARX Global Function Calls Compared to AutoLISP Calls . . 522
Function Return Values versus Function Results. . . . . . . . . . . . . . . 523
External Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 524
Error Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 527
Communication between Applications. . . . . . . . . . . . . . . . . . . . . . 528
Handling External Applications. . . . . . . . . . . . . . . . . . . . . . . . . . . . 532
Variables, Types, and Values Defined in ObjectARX . . . . . . . . . . . . . . . . 533
General Types and Definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 533
Useful Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 539
Result Buffers and Type Codes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 540
ObjectARX Function Result Type Codes . . . . . . . . . . . . . . . . . . . . . 544
User-Input Control Bit Codes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 545
Lists and Other Dynamically Allocated Data . . . . . . . . . . . . . . . . . . . . . . 546
Result-Buffer Memory Management . . . . . . . . . . . . . . . . . . . . . . . . 548
Extended Data Exclusive Data Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . 554
Text String Globalization Issues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 555

Chapter 21 Input Point Processing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 557


Custom Object Snap Modes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 558
Creating and Registering a Custom Object
Snap Mode. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 558
Creating Protocol Extension Classes . . . . . . . . . . . . . . . . . . . . . . . . 559
Creating a Custom Glyph . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 561
Custom Object Snap Mode Example . . . . . . . . . . . . . . . . . . . . . . . . 561
Input Point Management . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 567
Input Point Manager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 567

Contents | xiii
Input Context Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 569
Input Point Filters and Monitors . . . . . . . . . . . . . . . . . . . . . . . . . . 575

Chapter 22 Application Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 585


Profile Manager. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 586
AcApProfileManager Class. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 586
AcApProfileManagerReactor Class . . . . . . . . . . . . . . . . . . . . . . . . . 587
Profile Manager Sample. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 588

Part V Interacting with Other Environments . . . . . . . . . . . . 591

Chapter 23 COM, ActiveX Automation, and the Object Property Manager 593
Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 594
Using AutoCAD COM Objects from ObjectARX and Other
Environments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 594
Accessing COM Interfaces from ObjectARX . . . . . . . . . . . . . . . . . . 595
AutoCAD ActiveX Automation Implementation. . . . . . . . . . . . . . . . . . . 605
The Relationship between AcDbObjects and Automation Objects 605
Creating the COM Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 608
Interacting with AutoCAD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 611
Document Locking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 612
Creating a Registry File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 613
Exposing Automation Functionality . . . . . . . . . . . . . . . . . . . . . . . . . . . . 615
Setting Up an ATL Project File . . . . . . . . . . . . . . . . . . . . . . . . . . . . 615
Writing a COM Wrapper. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 616
Building and Registering a COM DLL . . . . . . . . . . . . . . . . . . . . . . . 621
Object Property Manager API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 622
AutoCAD COM Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . 623
Static OPM COM Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 624
ICategorizeProperties Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . 624
IPerPropertyBrowsing Interface. . . . . . . . . . . . . . . . . . . . . . . . . . . . 624
IOPMPropertyExtension Interface . . . . . . . . . . . . . . . . . . . . . . . . . 625
IOPMPropertyExpander Interface . . . . . . . . . . . . . . . . . . . . . . . . . . 625
Implementing Static OPM Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . 625
Dynamic Properties and OPM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 630
IDynamicProperty . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 631

Chapter 24 AutoCAD DesignCenter COM API . . . . . . . . . . . . . . . . . . . . . . . . 633


AutoCAD DesignCenter API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 634
IAcDcContentBrowser Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . 634
IAcDcContentView Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 634

xiv | Contents
IAcDcContentFinderSite Interface . . . . . . . . . . . . . . . . . . . . . . . . . . 634
IAcDcContentFinder Interface. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 635
IAcPostDrop Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 635
Registry Requirements for an AutoCAD DesignCenter Component . . . . 635
Applications Key . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 635
Extensions Key . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 636
CLASSID Registration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 637
Implementing the Interfaces for AutoCAD DesignCenter . . . . . . . . . . . . 638
Customizing AutoCAD DesignCenter. . . . . . . . . . . . . . . . . . . . . . . . . . . . 640
Create an ActiveX Template Library Project . . . . . . . . . . . . . . . . . . 641
Add Registry Support and a New ATL COM Object . . . . . . . . . . . . 641
Add Code to Support the New ATL COM Object . . . . . . . . . . . . . . 644

Part VI ObjectARX Libraries . . . . . . . . . . . . . . . . . . . . . . . . . . 653

Chapter 25 The ObjectDBX Libraries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 655


Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 656
Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 656
Host Applications. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 656
ObjectDBX Libraries. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 656
User Interface and Database Access . . . . . . . . . . . . . . . . . . . . . . . . . 657
Using ObjectDBX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 657
Getting Started with ObjectDBX . . . . . . . . . . . . . . . . . . . . . . . . . . . 657
ObjectDBX Library Changes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 658
The Application Services Class. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 658
Differences between ObjectDBX and ObjectARX . . . . . . . . . . . . . . . . . . . 659
AcEditorReactor Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 660
AcGi API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 660
Localization and XMX Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 661
Transaction Management . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 663
AcTransaction and AcTransactionReactor Classes. . . . . . . . . . . . . . 663
AcTransactionManager and AcDbTransactionManager Classes . . . 663
Creating a Viewer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 663
Viewer Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 664
AcGi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 664
AcGix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 665
AcGix Differences from AutoCAD Viewing. . . . . . . . . . . . . . . . . . . 666
SimpleView. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 667
WhipView . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 668
ViewAcDb. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 669
Basic Viewer Operation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 670
Configuration Suggestions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 671
Demand Loading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 671

Contents | xv
Installing the ObjectDBX Libraries. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 672
Use COMMONFILES . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 672
Install by Version and as SHAREDFILE . . . . . . . . . . . . . . . . . . . . . . 673
Ensure the Files Are on the Path . . . . . . . . . . . . . . . . . . . . . . . . . . . 673
Ensure Smart Pathing Updates . . . . . . . . . . . . . . . . . . . . . . . . . . . . 674
Tips and Techniques. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 676
ACAD_OBJID_INLINE_INTERNAL . . . . . . . . . . . . . . . . . . . . . . . . . 676
AcDbDatabase Notes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 676
AcDbDatabase::insert() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 678
Finding the Active Viewports in Model Space . . . . . . . . . . . . . . . . 678
Details About Viewports . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 679
Always Test Your Drawings in AutoCAD 2000 . . . . . . . . . . . . . . . . 680
Using DWG Files from Earlier Releases . . . . . . . . . . . . . . . . . . . . . . 680
Extended Entity Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 681
Raster Images . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 682
Known Limitations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 682

Chapter 26 The Graphics Interface Library . . . . . . . . . . . . . . . . . . . . . . . . . . . 683


AcGi Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 684
The setAttributes Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 686
The worldDraw() Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 687
The viewportDraw() Function. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 688
Viewport Regeneration Type. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 689
Setting Entity Traits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 690
Subentity Traits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 691
Useful AcGi Constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 692
Example of Using AcGi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 693
Primitives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 696
Mesh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 696
Shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 700
Arc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 703
Polyline . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 704
Text . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 704
Using Drawables in Your Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 708
Tessellation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 709
Isolines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 710
Transformations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 710
Model Coordinate System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 711
World Coordinate System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 712
Eye Coordinate System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 712
Display Coordinate System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 712
Transformation Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 712
Using Clip Boundaries in AcGi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 723
Background . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 723

xvi | Contents
Clip Boundary Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 724

Chapter 27 Using the Geometry Library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 725


Overview of the AcGe Library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 726
Global Data and Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 728
Tolerances . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 729
Using Basic Geometry Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 730
Using the Line and Plane Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 732
Parametric Geometry. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 733
Curves. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 733
Surfaces. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 736
Special Evaluation Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 738
Tips for Efficient Use of Curve and Surface Evaluators . . . . . . . . . . 744
Persistent AcGe Entities. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 746
AcGe Persistency Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 746

Chapter 28 Using the Boundary Representation Library . . . . . . . . . . . . . . . . 751


Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 752
Domain . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 753
Limitations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 754
Class Hierarchy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 755
Topological Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 756
Using Topological Objects in Your Program . . . . . . . . . . . . . . . . . . 757
Using Topological Traversers in Your Program . . . . . . . . . . . . . . . . 758
From Topological Traversers to Objects. . . . . . . . . . . . . . . . . . . . . . 759
From Mesh Traversers to Mesh Objects . . . . . . . . . . . . . . . . . . . . . . 760
AcBr Class Descriptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 761
Entity Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 761
Containment Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 762
Mesh Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 762
Traverser Classes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 762
Enumerated Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 764
Error Return Codes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 764
Validation Level . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 764
ShellType . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 765
LoopType . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 765
Mesh Element Shape Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 765
Building an Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 765
Sample Application Using the AcBr Library . . . . . . . . . . . . . . . . . . 766

Contents | xvii
Part VII Appendixes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 767

Appendix A Migrating ADS Programs to ObjectARX. . . . . . . . . . . . . . . . . . . . 769


Migrating to ObjectARX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 770
The acrxEntryPoint() Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . 770
Header Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 771
Loading Applications: ADS versus ObjectARX . . . . . . . . . . . . . . . . . . . . . 772
Building ADS Applications in the ObjectARX Program Environment . . 773
Sample ObjectARX Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 773
ObjectARX-Exclusive Data Type. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 778

Appendix B Programmable Dialog Boxes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 779


Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 780
Function Sequence Outline . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 780
Example Dialog Box . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 781
Callback Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 785
Default Actions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 786
Passing Arguments in Callback Functions . . . . . . . . . . . . . . . . . . . 786
Hiding Dialog Boxes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 789
Definitions and Declarations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 792
Handles for Dialog Boxes and Tiles. . . . . . . . . . . . . . . . . . . . . . . . . 792
Callback Function Definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 793
Status Codes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 793
Handling Tiles. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 795
Initializing Modes and Values. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 795
Changing Callback Modes and Values . . . . . . . . . . . . . . . . . . . . . . 796
Setting Up List Boxes and Pop-Up Lists . . . . . . . . . . . . . . . . . . . . . 797
Handling List Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 799
Handling Radio Clusters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 803
Handling Sliders . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 804
Handling Edit Boxes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 806
Application-Specific Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 806

Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 809

xviii | Contents
About ObjectARX
Documentation

In This Chapter

ObjectARX™, the AutoCAD® Runtime Extension ■ The ObjectARX Documentation


Set
programming environment, includes C++ libraries that
■ Using This Guide

are the building blocks you can use to develop AutoCAD

applications, extend AutoCAD classes and protocol, and

create new commands that operate in the same manner

as built-in AutoCAD commands.

The ObjectARX documentation set contains both

printed and online guides. This chapter gives a brief

overview of these guides, and discusses the organization

and conventions of the ObjectARX Developer’s Guide.

1
The ObjectARX Documentation Set
The ObjectARX documentation set includes the following printed guides and
online documentation.

Printed Guides
Two printed manuals are provided with ObjectARX:

■ ObjectARX Developer’s Guide. Explains the concepts of developing an


ObjectARX application, with example code and step-by-step procedures.
■ Migration Guide for Applications. Provides an overview of the new features
and changes in the latest versions of the AutoCAD programming environ-
ments, including ObjectARX, Visual LISP™, and Visual Basic® for
Applications.

Online Documentation
You can access the ObjectARX online documentation from the
\objectarx\docs directory. The following online documents are provided in
Windows help file format:

■ ObjectARX Reference. A programmer’s reference that provides detailed


information on each class and function in the ObjectARX API.
■ ObjectARX Developer’s Guide. The same content as the printed ObjectARX
Developer’s Guide in online format.
■ Migration Guide for Applications. The same content as the printed Migration
Guide for Applications in online format.
■ ObjectARX Readme. Describes last-minute changes and additions to
ObjectARX.

The online documentation may include updates to the printed material.

ObjectARX Logo Program


Autodesk now offers a “Built with ObjectARX” logo program for AutoCAD
applications that use ObjectARX. If you are creating AutoCAD products
based on ObjectARX technology, you should look into this program. A guide-
line for making your application logo-compliant is available from VeriTest,
the company that performs the certification process. To find out more about
this logo program or to get a copy of the guide, go online to
http://www.veritest.com/autodesk/main(f).htm and follow the process listed
there. Developers with products that meet with the “Built with ObjectARX”

2 | Introduction About ObjectARX Documentation


test criteria will be eligible to license and use ObjectARX branding on product
packaging, collateral items, and Web sites, and to participate with Autodesk
in related marketing initiatives.

NOTE The logo program guidelines contain information about how to regis-
ter your Registered Developer Symbol (RDS) with Autodesk, in addition to the
other logo requirements.

Where to Start
New users should start with the ObjectARX Developer’s Guide. Experienced
users and those upgrading from previous versions of ObjectARX should start
with the Migration Guide for Applications, and then move on to the more
detailed material on the new subjects in the ObjectARX Developer’s Guide.

Using This Guide


To help you use this book more effectively, the following sections explain the
organization of the ObjectARX Developer’s Guide and the conventions it uses.

Organization
The ObjectARX Developer’s Guide is organized in seven parts:
Part I: Using ObjectARX describes the fundamental concepts of ObjectARX.
Part II: User Interfaces shows how to work with ObjectARX global functions
and MFC to create and interact with user interfaces.
Part III: Defining New Classes describes how to create custom classes in
ObjectARX.
Part IV: Specialized Topics examines topics of interest to more advanced users,
such as proxy objects, notification, and protocol extension.
Part V: Interacting with Other Environments discusses working with external
programming environments such as COM and ActiveX® Automation.
Part VI: ObjectARX Libraries describes several of the ObjectARX libraries,
including the ObjectDBX™ libraries and the graphics interface library.
Part VII: Appendixes gives detailed information about migrating ADS pro-
grams to ObjectARX, and using programmable dialog boxes.

Using This Guide | 3


4
Part I
Using ObjectARX

5
6
Overview

In This Chapter
1
An ObjectARX application is a dynamic link library ■ The ObjectARX Programming
Environment
(DLL) that shares the address space of AutoCAD and
■ ObjectARX Class Libraries

makes direct function calls to AutoCAD. You can add ■ Getting Started

new classes to the ObjectARX program environment

and export them for use by other programs. The

ObjectARX entities you create are virtually indistin-

guishable from built-in AutoCAD entities. You can also

extend the ObjectARX protocol by adding functions at

runtime to existing AutoCAD classes.

This chapter provides an overview of the class libraries

that compose ObjectARX and gives information for get-

ting started with ObjectARX. The ObjectARX Developer’s

Guide assumes that you are familiar with C++, object-

oriented programming, and AutoCAD.

7
The ObjectARX Programming Environment
The ObjectARX programming environment provides an object-oriented C++
application programming interface for developers to use, customize, and
extend AutoCAD. The ObjectARX libraries comprise a versatile set of tools for
application developers to take advantage of AutoCAD’s open architecture,
providing direct access to AutoCAD database structures, the graphics system,
and native command definition. In addition, these libraries are designed to
work in conjunction with Visual LISP and other application programming
interfaces so that developers can choose the programming tools best suited
to their needs and experience.
As a developer, you can use ObjectARX to accomplish the following tasks:

■ Access the AutoCAD database


■ Interact with the AutoCAD editor
■ Create user interfaces using the Microsoft® Foundation Classes (MFC)
■ Support the multiple document interface (MDI)
■ Create custom classes
■ Build complex applications
■ Interact with other programming environments

The next sections take a brief look at these topics. They will be discussed in
greater detail throughout the book.

Accessing the AutoCAD Database


An AutoCAD drawing is a collection of objects stored in a database. These
objects represent not only graphical entities, but also internal constructs
such as symbol tables and dictionaries. ObjectARX provides your application
with access to these database structures. In addition, you can create new data-
base objects for your specific application.

Interacting with the AutoCAD Editor


ObjectARX provides classes and member functions to interact with the
AutoCAD editor. You can register commands with AutoCAD that will be
treated as built-in commands. Your application can receive and respond to
notification about a variety of events that occur within AutoCAD.

8 | Chapter 1 Overview
Creating User Interfaces with MFC
ObjectARX applications can be built with a dynamically linked MFC library
that is shared with AutoCAD. You can use this library to create standard
Microsoft Windows graphical user interfaces (GUIs).

Supporting MDI
With ObjectARX, you can create applications that will support the AutoCAD
multiple document interface, and you can ensure that your applications
will interact properly with other applications in the Microsoft Windows
environment.

Creating Custom Classes


You can leverage the classes in the ObjectARX hierarchy to create your own
custom classes. In addition, you can make use of the extensive graphics
libraries of ObjectARX when creating custom classes.

Building Complex Applications


ObjectARX supports the development of complex applications, providing
the following features:

■ Notification
■ Transaction management
■ Deep cloning
■ Reference editing
■ Protocol extension
■ Proxy object support

Interacting with Other Environments


ObjectARX applications can communicate with other programming inter-
faces, such as Visual LISP, ActiveX, and COM. In addition, ObjectARX
applications can interact with the Internet, by associated URLs with entities,
and by loading and saving drawing files from the World Wide Web (WWW).

The ObjectARX Programming Environment | 9


ObjectARX Class Libraries
The ObjectARX environment consists of the following groups of classes and
functions:
AcRx Classes for binding an application and for runtime class
registration and identification.
AcEd Classes for registering native AutoCAD commands and for
AutoCAD event notification.
AcDb AutoCAD database classes.
AcGi Graphics classes for rendering AutoCAD entities.
AcGe Utility classes for common linear algebra and geometric
objects.
The following table lists the libraries required to link ObjectARX applica-
tions. All ObjectARX applications must link with acad.lib and rxapi.lib. Other
libraries may also be required, depending on the prefix of the ObjectARX
classes and functions that you are using.

Required ObjectARX libraries

Prefix Required Libraries

AcRx acad.lib, rxapi.lib, acrx15.lib

AcEd acad.lib, rxapi.lib, acedapi.lib, acrx15.lib

AcDb acad.lib, rxapi.lib, acdb15.lib, acrx15.lib

AcGi acad.lib, rxapi.lib, acgiapi.lib, acrx15.lib

AcGe acad.lib, rxapi.lib, acge15.lib, acrx15.lib

The following sections take a closer look at each of the ObjectARX libraries.
For more information about specific classes and member functions, see the
ObjectARX Reference.

AcRx Library
The AcRx library provides system-level classes for DLL initialization and link-
ing and for runtime class registration and identification. The base class of this
library is AcRxObject, which provides the following facilities:

10 | Chapter 1 Overview
■ Object runtime class identification and inheritance analysis
■ Runtime addition of new protocol to an existing class (see chapter 19,
“Protocol Extension”)
■ Object equality and comparison testing
■ Object copy

The AcRx library also provides a set of C++ macros to help you create new
ObjectARX classes derived from AcRxObject (see chapter 11, “Deriving a
Custom ObjectARX Class”).
AcRxDictionary is another important class in this library. A dictionary is a
mapping from a text string to another object. The AcRx library places its
objects, classes, and service dictionaries in a global object dictionary, which
is an instance of the AcRxDictionary class. Applications can add objects to
this dictionary so that they are accessible to other applications.
The class hierarchy for the AcRx library is as follows:

AcRxClass
AcRxDictionary
AcRxDynamicLinker
AcRxEvent
AcEditor
AcRxService
AcRxKernal
AcDbServices
AcEdServices
AcadAppInfo

Runtime Type Identification


Every subclass of AcRxObject has an associated class descriptor object (of type
AcRxClass) that is used for runtime type identification. ObjectARX provides
functions for testing whether an object is of a particular class or derived class,
functions for determining whether two objects are of the same class, and
functions for returning the class descriptor object for a given class.
For more information on using AcRx classes, see chapter 3, “ObjectARX
Application Basics,” chapter 11, “Deriving a Custom ObjectARX Class,” and
chapter 19, “Protocol Extension.”

ObjectARX Class Libraries | 11


AcEd Library
The AcEd library provides classes for defining and registering new AutoCAD
commands that operate in the same manner as built-in AutoCAD com-
mands. The new commands you define are referred to as “native” commands
because they reside in the same internal structure (the AcEdCommandStack) as
built-in commands. The AcEd library also provides an editor reactor and a set
of global functions for interacting with AutoCAD. An important class in this
library is AcEditorReactor; it monitors the state of the AutoCAD editor and
notifies the application when specified events occur, such as starting, end-
ing, or canceling a command.
The class hierarchy for the AcEd library is as follows:

For information on registering new AutoCAD commands using ObjectARX,


see chapter 3, “ObjectARX Application Basics.” For an example of using an
editor reactor, see chapter 15, “Notification.”

AcDb Library
The AcDb library provides the classes that compose the AutoCAD database.
This database stores all the information for the graphical objects, called
entities, that compose an AutoCAD drawing, as well as the nongraphical
objects (for example, layers, linetypes, and text styles) that are also part of a
drawing. You can query and manipulate existing instances of AutoCAD
entities and objects with the AcDb library, and you can create new instances
of database objects.
The AutoCAD database contains these major elements:

■ A set of nine symbol tables that own uniquely named symbol table entry
objects. These objects represent various commonly used AcDbDatabase
objects and data members.

12 | Chapter 1 Overview
■ A named object dictionary (of class AcDbDictionary), which provides the
“table of contents” for an AutoCAD drawing. Initially, this table of con-
tents contains the IDs of the four other dictionaries used by AutoCAD.
Applications you develop, however, are free to add other objects to the
dictionary.
■ A fixed set of about 200 header variables, whose values are set by
AutoCAD.

The class hierarchy for the AcDb library is as follows:

AcDbDictionary AcDbSymbolTable AcDbSymbolTableRecord


AcDbDictionaryWithDefault AcDbAbstractViewTable AcDbAbstractViewTableRecord
AcDbFilter AcDbViewportTable AcDbViewportTableRecord
AcDbLayerFilter AcDbViewTable AcDbViewTableRecord
AcDbSpatialFilter AcDbBlockTable AcDbBlockTableRecord
AcDbGroup AcDbDimStyleTable AcDbDimStyleTableRecord
AcDbIDBuffer AcDbFontTable AcDbFontTableRecord
AcDbIndex AcDbLayerTable AcDbLayerTableRecord
AcDbLayerIndex AcDbLinetypeTable AcDbLinetypeTableRecord
AcDbSpatialIndex AcDbRegAppTable AcDbRegAppTableRecord
AcDbLongTransaction AcDbTextStyleTable AcDbTextStyleTableRecord
AcDbMlineStyle AcDbUCSTable AcDbUCSTableRecord
AcDbPlaceholder
AcDbPlotSettings
AcDbLayout
AcDbProxyObject AcDbRasterImageDef
AcDbXrecord AcDbRasterImageDefReactor
AcDbEntity AcDbRasterVariables

For more information on the AcDb library, see chapter 2, “Database Primer,”
chapter 4, “Database Operations,” chapter 5, “Database Objects,” chapter 6,
“Entities,” and chapter 7, “Container Objects.” For information on deriving
new classes from AcDbObject and AcDbEntity, see chapter 12, “Deriving from
AcDbObject” and chapter 13, “Deriving from AcDbEntity.”

AcGi Library
The AcGi library provides the graphics interface used for drawing AutoCAD
entities. This library is used by the AcDbEntity member functions
worldDraw(), viewportDraw(), and saveAs(), all part of the standard entity
protocol. The worldDraw() function must be defined by all custom entity
classes. The AcGiWorldDraw object provides an API through which
AcDbEntity::worldDraw() can produce its graphical representation in all
viewports simultaneously. Similarly, the AcGiViewportDraw object provides
an API through which the AcDbEntity::viewportDraw() function can pro-
duce different graphical representations for each viewport.

ObjectARX Class Libraries | 13


The class hierarchy for the AcGi library is as follows:

AcGiCommonDraw
AcGiWorldDraw
AcGiWorldDraw
AcGiContext
AcGiEdgeData
AcGiFaceData
AcGiGeometry
AcGiViewportGeometry
AcGiWorldGeometry
AcGiLinetypeEngine
AcGiSubEntityTraits
AcGiDrawableTraits
AcGiTextStyle
AcGiVertexData
AcGiViewport
AcGiDrawable
AcGiGlyph

For more information on using AcGi classes, see chapter 13, “Deriving from
AcDbEntity.”

AcGe Library
The AcGe library is used by the AcDb library and provides utility classes such
as vectors and matrices that are used to perform common 2D and 3D geomet-
ric operations. It also provides basic geometric objects such as points, curves,
and surfaces.
The AcGe library consists of two major subsets: classes for 2D geometry and
classes for 3D geometry. The major abstract base classes are AcGeEntity2d
and AcGeEntity3d. Several basic classes not derived from any other class
include AcGePoint2d, AcGeVector2d, and AcGeMatrix2d (shown at the begin-
ning of the class hierarchy). These basic classes can be used to perform many
types of common operations, such as adding a vector to a point, computing
the dot or cross product of two vectors, and computing the product of two
matrices. The higher-level classes of this library are implemented using these
basic classes. The class hierarchy for the AcGe library is as follows:

14 | Chapter 1 Overview
AcGeBoundBlock2d AcGeBoundBlock3d
AcGeClipBoundary2d AcGeCurve3d
AcGeCurve2d AcGeCircArc3de
AcGeCircArc2d AcGeCompositeCurve3d
AcGeCompositeCurve2d AcGeEllipArc3e
AcGeEllipArc2d AcGeExternalCurve3d
AcGeExternalCurve2d AcGeLinearEnt3d
AcGeLinearEnt2d AcGeLine3d
AcGeLine2d AcGeLineSeg3d
AcGeLineSeg2d AcGeRay3d
AcGeRay2d AcGeMatrix3d
AcGeOffsetCurve2d AcGeOffsetCurve3d
AcGeSplineEnt2d AcGeSplineEnt3d
AcGeCubicSplineCurve2d AcGeCubicSplineCurve3d
AcGeNurbCurve2d AcGeNurbCurve3d
AcGePolyline2d AcGePolyline3d
AcGeCurveCurveInt2d AcGeAugPolyline3d
AcGePointEnt2d AcGeCurveCurveInt3d
AcGePointOnCurve2d AcGeCurveSurfInt
AcGePosition2d AcGePointEnt3d
AcGePointOnCurve3d
AcGePointOnSurface
AcGeCurveBoundary
AcGePosition3d
AcGe
AcGeSurfSurfInt
AcGeContext
AcGeSurface
AcGeDwgIO
AcGeCone
AcGeDxfIO
AcGeCylinder
AcGeFileIO
AcGeExternalBoundedSurface
AcGeFiler
AcGeExternalSurface
AcGeInterval
AcGeNurbSurface
AcGeKnotVector
AcGeOffsetSurface
AcGeLibVersion
AcGePlanarEnt
AcGeMatrix2d
AcGeBoundedPlanet
AcGeMatrix3d
AcGePlane
AcGePoint2d
AcGeSphere
AcAxPoint2d
AcGeTorus
AcGePoint3d
AcAxPoint3d
AcGeScale2d
AcGeScale3d
AcGeTol
AcGeVector2d
AcGeVector3d

The AcGe library provides several different coordinate systems. For more
information, see chapter 27, “Using the Geometry Library.” The sample pro-
grams in this manual illustrate numerous common uses of AcGe classes.

ObjectARX Class Libraries | 15


Getting Started
The following sections discuss the system requirements for ObjectARX and
provide installation instructions.

System Requirements
Developing applications with ObjectARX requires the following software and
hardware:

■ Windows NT® 4.0


■ Microsoft Visual C++® 32bit Edition Release 6.0
■ Pentium® PC running at 90MHz or better, with 32MB RAM or more
■ 800 x 600 SVGA display or better

Installing ObjectARX
When you install ObjectARX, a setup program guides you through the
process.

To install ObjectARX
1 Insert the CD into the CD-ROM drive.
2 If you are running Windows NT 4.0 with AutoPlay, follow the on-screen
instructions.
3 If you have turned off AutoPlay in Windows NT 4.0, from the Start menu on
the taskbar, choose Run, designate the CD-ROM drive, enter the path name,
and then enter setup.

ObjectARX Directory Tree


The default installation for ObjectARX software creates the base directory
c:\objectarx. The nine main subdirectories under the objectarx directory are
described here.
arxlabs The arxlabs directory consists of a set of subdirectories,
each containing a lab (tutorial) that demonstrates
implementation of one aspect of the ObjectARX API. Each
subdirectory includes instructions for the lab, a source
code file with key pieces missing that are to be filled in by
the user according to the instructional comments in the
code, and a solved subdirectory containing the completed
source code file for the lab.

16 | Chapter 1 Overview
classmap The classmap directory contains an AutoCAD drawing
illustrating the ObjectARX class hierarchy.
docs The docs directory contains Windows online help files for
ObjectARX developers, including the ObjectARX
Developer’s Guide, the ObjectARX Reference, the Migration
Guide for Applications, and the ObjectARX Readme file.
docsamps The docsamps directory contains subdirectories for each of
the programs from which examples were extracted for the
ObjectARX Developer’s Guide. Each subdirectory contains
the full set of source code for the application and an
explanatory Readme file.
inc The inc directory contains the ObjectARX header files.
lib The lib directory contains the ObjectARX library files.
redistrib The redistrib directory contains a set of DLLs, some of
which may be required for an ObjectARX application to
run. Developers should copy the DLLs that they need for
application development to a directory in the AutoCAD
search path, and package the necessary DLLs with their
ObjectARX applications for distribution.
samples The samples directory includes subdirectories containing
examples of ObjectARX applications. These subdirectories
include source code and Readme files. The most significant
set of sample ObjectARX applications is in the polysamp
subdirectory.
utils The utils directory contains subdirectories for applications
that are extensions to ObjectARX, including brep for
boundary representation and istorage for compound
document storage. Each application directory includes
inc, lib, and sample subdirectories.

Getting Started | 17
18
Database Primer

In This Chapter
2
The AutoCAD database stores the objects and entities ■ AutoCAD Database Overview
■ Essential Database Objects
that make up an AutoCAD drawing. This chapter
■ Creating Objects in AutoCAD
discusses the key elements of the database: entities, ■ Creating Objects in ObjectARX

symbol tables, and the named object dictionary.

This chapter also introduces object handles, object IDs,

and the protocol for opening and closing database

objects. Sample code gives an example of creating

entities, layers, and groups, and adding objects to

the database.

19
AutoCAD Database Overview
An AutoCAD drawing is a collection of objects stored in a database. Some of
the basic database objects are entities, symbol tables, and dictionaries. Enti-
ties are a special kind of database object that have a graphical representation
within an AutoCAD drawing. Lines, circles, arcs, text, solids, regions, splines,
and ellipses are examples of entities. A user can see an entity on the screen
and can manipulate it.
Symbol tables and dictionaries are containers used to store database objects.
Both container objects map a symbol name (a text string) to a database
object. An AutoCAD database includes a fixed set of symbol tables, each of
which contains instances of a particular class of symbol table record. You
cannot add a new symbol table to the database. Examples of symbol tables
are the layer table (AcDbLayerTable), which contains layer table records, and
the block table (AcDbBlockTable), which contains block table records. All
AutoCAD entities are owned by block table records.
Dictionaries provide a more generic container for storing objects than sym-
bol tables. A dictionary can contain any object of the type AcDbObject or sub-
class thereof. The AutoCAD database creates a dictionary called the named
object dictionary when it creates a new drawing. The named object
dictionary can be viewed as the master “table of contents” for all of the dic-
tionaries associated with the database. You can create new dictionaries
within the named object dictionary and add new database objects to them.
The following figure shows the key components of the AutoCAD database.

Database

Named Object Dictionary

Objects

Layer Table Block Table Other Symbol


Tables

Their Symbol
Layer Table Record Block Table Record
Table Records

Entity

20 | Chapter 2 Database Primer


During AutoCAD edit sessions, you can obtain the database for the current
drawing by calling the following global function:
acdbHostApplicationServices()->workingDatabase()

Multiple Databases
Multiple databases can be loaded in a single AutoCAD session. Each object in
the session has a handle and an object ID. A handle uniquely identifies the
object within the scope of a particular database, whereas an object ID
uniquely identifies the object across all databases loaded at one time. An
object ID only persists during an edit session, but a handle gets saved with
the drawing. In contrast to the object ID, an object handle is not guaranteed
to be unique when multiple databases are loaded in an AutoCAD session.

Obtaining Object IDs


With an object ID, you can obtain a pointer to an actual database object so
that you can perform operations on it. For an example, see “Opening and
Closing ObjectARX Objects” on page 27.
You can obtain an object ID in a number of ways:

■ Create an object and append it to the database. The database then gives
the object an ID and returns it to you.
■ Use the database protocol for obtaining the object ID of the objects that
are created automatically when a database is created (such as the fixed set
of symbol tables and the named object dictionary).
■ Use class-specific protocol for obtaining object IDs. Certain classes, such
as symbol tables and dictionaries, define objects that own other objects.
These classes provide protocol for obtaining the object IDs of the owned
objects.
■ Use an iterator to step through a list or set of objects. The AcDb library
provides a number of iterators that can be used to step through various
kinds of container objects (AcDbDictionaryIterator,
AcDbObjectIterator).
■ Query a selection set. After the user has selected an object, you can ask the
selection set for the list of entity names of the selected objects, and from
the names convert to the object IDs. For more information on selection
sets, see chapter 6, “Entities.”

AutoCAD Database Overview | 21


Essential Database Objects
As objects are created in AutoCAD, they are added to their appropriate con-
tainer object in the database. Entities are added to the records in the block
table. Symbol table records are added to the appropriate symbol tables. All
other objects are added to the named object dictionary or to objects that are
owned by other objects (and, ultimately, by the named object dictionary), or
to an extension dictionary. The scenario in the following section, “Creating
Objects in AutoCAD,” details this process. Extension dictionaries are dis-
cussed in the section “Extension Dictionary” on page 89.
To be usable, a database must have at least the following set of objects:

■ A set of nine symbol tables that includes the block table, layer table, and
linetype table. The block table initially contains three records: a record
called *MODEL_SPACE, and two paper space records called *PAPER_SPACE
and *PAPER_SPACE0. These block table records represent model space and
the two predefined paper space layouts. The layer table initially contains
one record, layer 0. The linetype table initially contains the
CONTINUOUS linetype.
■ A named object dictionary. When a database is created, this dictionary
already contains four database dictionaries: the GROUP dictionary, MLINE
style dictionary, layout dictionary, and plot style name dictionary. Within
the MLINE style dictionary, the STANDARD style is always present.

These objects can be automatically created in a new database by passing


kTrue in for its constructor’s buildDefaultDrawing argument. Passing in
kFalse creates an empty database into which a DWG or DXF™ file can be
loaded.
AcDbDatabase(Adesk::Boolean buildDefaultDrawing = Adesk::kTrue);

Creating Objects in AutoCAD


This section describes creating a line, circle, layer, and group in AutoCAD and
shows how AutoCAD adds these objects to the database. First, suppose the
user creates a line in model space with the following command:
line 4,2 10,7

22 | Chapter 2 Database Primer


In the database, AutoCAD creates an instance of class AcDbLine and then
stores it in the model space block table record as shown in the following
illustration:

Paper Space

Block Table

Model Space
Line

When you first invoke AutoCAD and the database is in its default state, enti-
ties are added to model space, the main space in AutoCAD, which is used for
model geometry and graphics. Paper space is intended to support “documen-
tation” geometry and graphics, such as drafting sheet outlines, title blocks,
and annotational text. The entity creation commands in AutoCAD ( LINE, in
this case) cause the entity to be added to the current database as well as to
the model space block. You can ask any entity which database and which
block it belongs to.
Next, suppose the user creates a circle with this command:
circle 9,3 2

Again, AutoCAD creates an instance of the appropriate entity—here,


AcDbCircle—and adds it to the model space block table record.

Paper Space

Block Table

Model Space
Line
Circle

Creating Objects in AutoCAD | 23


Next, the user creates a layer:
layer _make mylayer

AutoCAD creates a new layer table record to hold the layer and then adds it
to the layer table.

Paper Space

Block Table

Model Space
Line
Circle

Layer Table

layer 0

my layer

Finally, the user groups all the entities together:


group 3,2 9,3

AutoCAD creates a new group object and adds it to the GROUP dictionary,
which is contained in the named object dictionary. The new group contains
a list of the object IDs of the objects that compose the group.

Group
Dictionary New Group
Named Object
Dictionary

MLINE Style
Dictionary

24 | Chapter 2 Database Primer


Creating Objects in ObjectARX
The sample ObjectARX code in this section creates the same entities as those
in the previous section (a line and a circle). Code for creating a new layer,
changing the color of the line, and adding a group to the GROUP dictionary
are also shown.

Creating Entities
The following ObjectARX code creates the line and adds it to the model space
block table record:
AcDbObjectId
createLine()
{
AcGePoint3d startPt(4.0, 2.0, 0.0);
AcGePoint3d endPt(10.0, 7.0, 0.0);
AcDbLine *pLine = new AcDbLine(startPt, endPt);

AcDbBlockTable *pBlockTable;
acdbHostApplicationServices()->workingDatabase()
->getSymbolTable(pBlockTable, AcDb::kForRead);

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

AcDbObjectId lineId;
pBlockTableRecord->appendAcDbEntity(lineId, pLine);

pBlockTableRecord->close();
pLine->close();

return lineId;
}
The createLine() routine obtains the block table for the current drawing.
Then it opens the model space block table record for writing. After closing
the block table, it adds the entity to the block table record and then closes
the block table record and the entity.

NOTE When you are done using any ObjectARX objects, you must explicitly
close them as soon as possible.

Creating Objects in ObjectARX | 25


The following createCircle() routine creates the circle and adds it to the
model space block table record:
AcDbObjectId
createCircle()
{
AcGePoint3d center(9.0, 3.0, 0.0);
AcGeVector3d normal(0.0, 0.0, 1.0);
AcDbCircle *pCirc = new AcDbCircle(center, normal, 2.0);

AcDbBlockTable *pBlockTable;
acdbHostApplicationServices()->workingDatabase()
->getSymbolTable(pBlockTable, AcDb::kForRead);

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

AcDbObjectId circleId;
pBlockTableRecord->appendAcDbEntity(circleId, pCirc);

pBlockTableRecord->close();
pCirc->close();

return circleId;
}

Creating a New Layer


The following code obtains the layer symbol table from the database, creates
a new layer table record, and names it (ASDK_MYLAYER). The layer table record
is then added to the layer table.
void
createNewLayer()
{
AcDbLayerTable *pLayerTable;
acdbHostApplicationServices()->workingDatabase()
->getSymbolTable(pLayerTable, AcDb::kForWrite);

AcDbLayerTableRecord *pLayerTableRecord =
new AcDbLayerTableRecord;
pLayerTableRecord->setName("ASDK_MYLAYER");

// Defaults are used for other properties of


// the layer if they are not otherwise specified.
//
pLayerTable->add(pLayerTableRecord);
pLayerTable->close();
pLayerTableRecord->close();
}

26 | Chapter 2 Database Primer


Opening and Closing ObjectARX Objects
All code examples shown in this chapter illustrate the protocol for opening
and closing objects that you’ll need to observe whenever you work with
database-resident objects. This protocol ensures that objects are physically in
memory when they need to be accessed but can be paged out to disk when
they’re not needed. Before you can modify an object, you need to open it, as
shown in the following example:
acdbOpenObject(pObject, objId, AcDb::kForWrite);
The open functions have a mode parameter that specifies whether you are
opening the object for read, write, or notify. While the object is open for
write, you can modify it. When you are finished, you must explicitly close
the object as shown in the following example, regardless of the mode in
which it was opened:
pObject->close();
The following is sample code for changing the color of an entity:
Acad::ErrorStatus
changeColor(AcDbObjectId entId, Adesk::UInt16 newColor)
{
AcDbEntity *pEntity;
acdbOpenObject(pEntity, entId,
AcDb::kForWrite);

pEntity->setColorIndex(newColor);
pEntity->close();

return Acad::eOk;
}
New instances of an object are considered to be open for write. Some func-
tions, such as the AcDbBlockTable::getAt() function, obtain an object ID
and open the object at the same time. An object can’t be closed until it has
been added to the database. You own the object and can freely delete it at any
time before the object is added to the database.
However, once the object has been added to the database, you cannot delete
it directly. You can call the AcDbObject::erase() function, which marks the
object as erased. Erased objects remain in the database until the database is
destroyed, but do not get saved when the drawing is saved.

WARNING! Directly deleting an object that has been added to the database
will cause AutoCAD to terminate.

Creating Objects in ObjectARX | 27


Adding a Group to the Group Dictionary
The following code creates a group (pGroup) out of the line and circle created
in the createLine() and createCircle() functions and puts the group into
the GROUP dictionary. The object IDs of the line and circle are passed into the
function. Notice how the GROUP dictionary is opened for writing, modified,
and then explicitly closed.
void
createGroup(AcDbObjectIdArray& objIds, char* pGroupName)
{
AcDbGroup *pGroup = new AcDbGroup(pGroupName);
for (int i = 0; i < objIds.length(); i++) {
pGroup->append(objIds[i]);
}

// Put the group in the group dictionary that resides


// in the named object dictionary.
//
AcDbDictionary *pGroupDict;
acdbHostApplicationServices()->workingDatabase()
->getGroupDictionary(pGroupDict, AcDb::kForWrite);

AcDbObjectId pGroupId;
pGroupDict->setAt(pGroupName, pGroup, pGroupId);
pGroupDict->close();
pGroup->close();
}

28 | Chapter 2 Database Primer


ObjectARX Application
Basics

In This Chapter
3
This chapter describes how to write and run an ■ Creating an ObjectARX
Application
ObjectARX application. It lists the messages passed by
■ Example Application

AutoCAD to the ObjectARX application and shows how ■ Registering New Commands
■ Loading an ObjectARX
the application typically responds to those Application

messages. This chapter also discusses the registration ■ Unloading an ObjectARX


Application
of new commands, how to load and unload an ■ Demand Loading
■ ARX Command
application, AutoCAD’s demand loading feature,
■ Running ObjectARX Applications
and error handling. from AutoLISP
■ Error Handling

29
Creating an ObjectARX Application
An ObjectARX application is a DLL that shares AutoCAD’s address space and
makes direct function calls to AutoCAD. ObjectARX applications typically
implement commands that can be accessed from within AutoCAD. These
commands are often implemented using custom classes. Creating an
ObjectARX application involves the following general steps.

To create an ObjectARX application


1 Create custom classes to implement new commands.
You can derive custom classes from most of the ObjectARX hierarchy and
symbol table classes.
2 Determine which AutoCAD messages your ObjectARX application will
handle.
AutoCAD sends a variety of messages to ObjectARX applications, indicating
that particular events have occurred within AutoCAD. You decide which
messages your application will respond to, and which actions will be trig-
gered.
3 Implement an entry point for AutoCAD.
AutoCAD calls into an ObjectARX application through the
acrxEntryPoint() function, which replaces the main() function of a C++
program. You are responsible for implementing the acrxEntryPoint() func-
tion in your application. The acrxEntryPoint() function calls the functions
that you’ve associated with specific AutoCAD messages.
4 Implement initialization.
Within your ObjectARX application, you will need to initialize any custom
classes that you have created, and rebuild the ObjectARX runtime class tree.
Additionally, if you are adding commands, you must register them with
AutoCAD.
5 Prepare for unloading.
To create a well-behaved ObjectARX application, you must remove any cus-
tom classes and commands when your application is unloaded.
The following sections discuss the general steps of developing an ObjectARX
application in more detail.

NOTE An ObjectARX Wizard is available for creating ObjectARX projects. See


the objectarx\utils directory in the ObjectARX SDK.

30 | Chapter 3 ObjectARX Application Basics


Creating Custom Classes
You can derive custom classes from most of the ObjectARX class hierarchy.
This allows you to leverage the functionality of the ObjectARX classes when
creating your own objects. Defining custom classes is discussed in detail in
chapter 11, “Deriving a Custom ObjectARX Class.”

Responding to AutoCAD Messages


There are four categories of messages that AutoCAD sends to ObjectARX
applications:

■ Messages that are sent to all applications


■ Messages that are sent only if the application has registered an
AutoLISP® function with acedDefun()
■ Messages that are sent to applications that have registered a service with
ObjectARX
■ Messages only responded to by applications that use ActiveX Automation

The following five tables describe the messages that AutoCAD sends to
ObjectARX applications. The first table lists messages sent to all applications.

Messages sent to all applications

Message Description

kInitAppMsg Sent when the ObjectARX application is loaded to open


communications between AutoCAD and the application.

kUnloadAppMsg Sent when the ObjectARX application is unloaded (either when


the user unloads the application or when AutoCAD itself is
terminated). Closes files and performs cleanup operations.

kLoadDwgMsg Sent once when the drawing is opened. Then, if the application
registers any functions with AutoLISP, AutoCAD sends this
message once for each drawing loaded into the editor. The
AutoCAD editor is fully initialized at this point, and all global
functions are available. However, you cannot use an
acedCommand() function from a kLoadDwgMsg.

kPreQuitMsg Sent when AutoCAD quits, but before it begins to unload all
ObjectARX applications.

Creating an ObjectARX Application | 31


The next table lists messages that AutoCAD sends to applications that have
registered an AutoLISP function with acedDefun():

Messages sent only if the application has registered an AutoLISP function

Message Description

kUnloadDwgMsg Sent when the user quits a drawing session.

kInvkSubrMsg Sent to invoke functions registered using acedDefun().

kEndMsg Sent only when the END command is entered and there are
changes that need to be saved (when dbmod != 0). kEndMsg is
not sent for a NEW or OPEN, instead, kSaveMsg and
kLoadDwgMsg are sent. For END, if dbmod = 0, then kQuitMsg
is sent instead of kEndMsg.

kQuitMsg Sent when AutoCAD quits (ends without saving) the drawing
because a QUIT command was entered. The kQuitMsg can also
be received with the END command, as noted above. If the END
command is sent and dbmod = 0, then kQuitMsg is sent.

kSaveMsg Sent when AutoCAD is saving the drawing because a SAVE,


SAVEAS, NEW, or OPEN command is entered.

kCfgMsg Sent when AutoCAD returns from the configuration program,


and used only for a change to the display driver.

The next table lists the messages that an application receives if it has
registered a service with ObjectARX.

Messages only received by applications that have registered a service

Message Description

kDependencyMsg Sent when the ObjectARX application has registered an


AcRxService object and the dependency count on that service
changes from 0 to 1.

kNoDependencyMsg Sent when the ObjectARX application has registered an


AcRxService object and the dependency count on that service
changes from 1 to 0.

32 | Chapter 3 ObjectARX Application Basics


The next table lists the messages that an application needs to respond
to if it is using ActiveX Automation. See chapter 23, “COM, ActiveX Automa-
tion, and the Object Property Manager.”

Messages only responded to by applications that use ActiveX Automation

Message Description

kOleUnloadAppMsg Sent to determine if the application can be unloaded (that is,


none of its ActiveX objects or interfaces are being referenced by
other applications).

See the rxdefs.h file where these enumeration constants are defined by the
AppMsgCode type declaration.

You will need to decide which messages your ObjectARX application will
respond to. The following table describes recommended actions upon receipt
of a given message.

ObjectARX application reactions to AutoCAD messages

Message Recommended Actions

kInitAppMsg Do register services, classes, AcEd commands and reactors, and


AcRxDynamicLinker reactors. Initialize application’s system
resources, such as devices and windows. Perform all one-time early
initialization. AcRx, AcEd, and AcGe are all active. Store the value of
the pkt parameter if you want to unlock and relock your
application.

Don’t expect device drivers to be initialized, any user interface


resources to be active, applications to be loaded in a particular
order, AutoLISP to be present, or any databases to be open. Calls
involving any of these assumptions will result in an error condition,
sometimes fatal. AcDb and AcGi libraries are generally not yet
active, although related AcRx and other structures are in place.

kUnloadAppMsg Do perform final system resource cleanup. Anything started or


created in kInitAppMsg should now be stopped or destroyed.

Don’t expect things to be any different from the description of


kInitAppMsg. AutoCAD could be mostly dismantled by the time
this call is made, except for the libraries listed as active in the
kInitAppMsg Do description.

Creating an ObjectARX Application | 33


ObjectARX application reactions to AutoCAD messages (continued)

Message Recommended Actions

kOleUnloadAppMsg This message should be responded to only by applications using


ActiveX Automation.

Do respond with AcRx::kRetOK, if the application can be unloaded


(none of its ActiveX objects or interfaces are being referenced by
other applications). If it cannot be unloaded, respond with
AcRx::kRetError.

kLoadDwgMsg Do perform initialization relevant to the current drawing edit


session. AcDb, AcGi, and the user interface API are all now active.
Whether anything has been done to the drawing is not specified.
All AutoCAD-supplied APIs are now active. You can perform
AutoLISP function registration at this time, and initialize the user
interface. Other operations to perform now include polling
AutoCAD drivers and querying AcEditorReactor events if you want
the earliest possible access to
acdbHostApplicationServices()->workingDatabase().

Don’t do anything you would not want to happen for every


drawing edit session. Assume this message is sent more than once
per program execution.

kUnloadDwgMsg Do release or clean up everything started or registered in response


to kLoadDwgMsg code. Release all AcDb reactors, excluding
persistent reactors.

Don’t release system resources that are not tied to an edit session,
or clean up AcRx classes, AcEd reactors, or commands; they remain
valid across edit sessions.

kDependencyMsg Do perform any actions that are necessary for your application
when other applications become dependent on it, such as locking
your application so that it cannot be unloaded.

kNoDependencyMsg Do perform any actions that are necessary for your application
when there are no longer any other applications dependent on
yours, such as unlocking your application so that it can be
unloaded by the user if desired.

kInvkSubrMsg Do invoke the functions registered with acedDefun(). Determine


the function by making a call to acedGetFuncode(). Return values
with acedRetxxx().

Don’t do much here except function invocation.

kPreQuitMsg Do unload any dependencies (applications, DLLs, and so on) that


your application controls to ensure that they are unloaded before
your application.

34 | Chapter 3 ObjectARX Application Basics


ObjectARX application reactions to AutoCAD messages (continued)

Message Recommended Actions

kEndMsg Do consider using the AcEditorReactor event callbacks as an


kCfgMsg alternative to responding to these messages.
kQuitMsg
kSaveMsg Don’t respond to these messages if you’re responding to the
equivalent event callbacks made through AcEditorReactor.

Sequence of Events in an ObjectARX Application


The process of passing messages between AutoCAD and the ObjectARX appli-
cation flows almost completely in one direction—from AutoCAD to the
ObjectARX application. The following diagram shows a typical sequence for
passing messages.

Time

Start AutoCAD kInitAppMsg


acedRegCmds -> addCommand
("Test2", test2)

Open drawing 1 kLoadDwgMsg


ads_defun"c:TEST1"

Invoke TEST1 command kInvkSubr

Invoke TEST2 command Control transfers directly


to routine "test2"

Invoke SAVE command kSaveMsg

Quit kUnloadDwgMsg
kQuit
kUnloadApp

If an application is loaded when a drawing is already open, the kInitAppMsg


and kLoadDwgMsg messages are sent in succession. When an ObjectARX appli-
cation is unloaded while an edit session is in progress, the kUnloadDwg and
kUnloadApp messages are sent in succession.

Creating an ObjectARX Application | 35


Implementing an Entry Point for AutoCAD
AutoCAD calls into the ObjectARX module through acrxEntryPoint(),
which replaces the main() function of a C++ program. You are responsible for
implementing the acrxEntryPoint() function, as described in this section.
The acrxEntryPoint() function serves as the entry point for AutoCAD (or
other host programs) to communicate with an ObjectARX application.
ObjectARX programs can in turn communicate with AutoCAD by returning
status codes. All requests to invoke functions defined with acedDefun() are
made by the acrxEntryPoint() function. If you define a new command with
ObjectARX or with the acedRegFunc() function, AutoCAD immediately exe-
cutes the function associated with the command (see “Loading an
ObjectARX Application” on page 43).
The acrxEntryPoint() function has the following signature:
extern "C"
AcRx::AppRetCode
acrxEntryPoint(AcRx::AppMsgCode msg, void* pkt);
msg Represents the message sent from the ObjectARX kernel
to the application.
pkt Holds packet data values.
AppRetCode Contains the status code returned to AutoCAD.
Within the definition of the acrxEntryPoint() function, you write a switch
statement or similar code to decipher messages from AutoCAD, perform
appropriate actions related to each message, and return an integer status
value.

WARNING! Using kRetError for the final return value from the
acrxEntryPoint() function will cause your application to be unloaded, except
for the messages kOleUnloadAppMsg and kUnloadAppMsg. In these cases, if
kRetError is returned, the application will not be unloaded.

36 | Chapter 3 ObjectARX Application Basics


The following code shows the skeleton of a valid switch statement:
AcRx::AppRetCode
acrxEntryPoint(AcRx::AppMsgCode msg, void* pkt)
{
switch(msg) {
case AcRx::kInitAppMsg:
break;
case AcRx::kUnloadAppMsg:
break;
...
default:
break;
}
return AcRx::kRetOK;
}

Initializing an ObjectARX Application


You must initialize any custom classes and commands that your application
defines. This initialization can take place in either the AcRx::kInitAppMsg
case of your acrxEntryPoint() function, or in a function called from that
case.

To initialize an ObjectARX application


1 If you have defined a custom class, invoke its rxInit() function.
Defining custom classes is discussed in detail in chapter 11, “Deriving a
Custom ObjectARX Class.”
2 If you have defined custom classes, call acrxBuildClassHierarchy() to
rebuild the ObjectARX runtime class tree.
For efficiency, call acrxBuildClassHierarchy() once after calling the
rxinit() function for each of your custom classes.
3 Perform any other initialization that you need.
4 Register a service name.
Registering a service name is suggested if other applications will depend
upon your application. Registering a service name allows other applications
to register depending on the service, and allows your application to check if
it has any dependencies before unloading. Registering a service name for
your application is also necessary if you are going to export symbolic func-
tions from your application using the ObjectARX mechanism. You can use
the function acrxRegisterService(), or use the AcRxService class. For more
information on registering services, see the documentation on AcRxService
in the ObjectARX Reference.
5 Register commands with the AutoCAD command mechanism.

Creating an ObjectARX Application | 37


Use acedRegCmds->addCommand() to make AutoCAD aware of the commands
that your application defines. For more information, see “Registering New
Commands” on page 40.

Preparing for Unloading


When your application is unloaded, you must clean up any custom classes or
commands that your application has created. This should take place in the
AcRx::kUnloadAppMsg case of your acrxEntryPoint() function, or in a func-
tion called from that case.

To unload an ObjectARX application


1 If you have created commands with the acedRegCmds macro or acedDefun(),
remove them.
Usually ObjectARX commands are removed by groups, using
acedRegCmds->removeGroup().
2 If you have created custom classes, remove them.
Use the deleteAcRxClass() function to remove your custom classes from the
AcRx runtime tree. Classes must be removed starting with the leaves of
derived classes first, working up the class tree to parent classes.
3 Delete any objects added by the application.
There is no way to tell AutoCAD to forget about AcDbObject instances that
are currently resident in a database. However, when an application is
unloaded, AutoCAD will automatically turn such objects into instances of
AcDbProxyObject or AcDbProxyEntity.
4 Remove any reactors that have been attached to any AcDbObject,
AcDbDatabase, AcRxDynamicLinker, or AcEditor object. (Persistent reactors
on AcDbObjects are an exception; they will become proxy objects when the
application is unloaded.)
5 If you have created a service name, remove it.
You can use the acrxServiceDictionary->remove() function to remove any
service that your application has registered. See the listing for
acrxServiceDictionary in the ObjectARX Reference.

38 | Chapter 3 ObjectARX Application Basics


Example Application
The following example application implements functions that are called
when the application is loaded and unloaded. Its initialization function adds
two new commands to AutoCAD: CREATE and ITERATE. It also initializes the
new class AsdkMyClass and adds it to the ObjectARX hierarchy with the
acrxBuildClassHierarchy() function. (AsdkMyClass is described in
“Example of a Custom Object Class” on page 338.)
// The initialization function called from the acrxEntryPoint()
// function during the kInitAppMsg case is used to add commands
// to the command stack and to add classes to the ACRX class
// hierarchy.
//
void
initApp()
{
acedRegCmds->addCommand("ASDK_DICTIONARY_COMMANDS",
"ASDK_CREATE", "CREATE", ACRX_CMD_MODAL,
createDictionary);

acedRegCmds->addCommand("ASDK_DICTIONARY_COMMANDS",
"ASDK_ITERATE", "ITERATE", ACRX_CMD_MODAL,
iterateDictionary);

AsdkMyClass::rxInit();
acrxBuildClassHierarchy();
}

// The cleanup function called from the acrxEntryPoint()


// function during the kUnloadAppMsg case removes this application’s
// command set from the command stack and removes this application’s
// custom classes from the ACRX runtime class hierarchy.
//
void
unloadApp()
{
acedRegCmds->removeGroup("ASDK_DICTIONARY_COMMANDS");

// Remove the AsdkMyClass class from the ACRX runtime


// class hierarchy. If this is done while the database is
// still active, it should cause all objects of class
// AsdkMyClass to be turned into proxies.
//
deleteAcRxClass(AsdkMyClass::desc());
}

Example Application | 39
Registering New Commands
This section describes adding new commands using the AcEd command
registration mechanism. For information on adding new commands using
the functions acedDefun() and acedRegFunc(), see chapter 20, “ObjectARX
Global Utility Functions.” For information on adding new commands using
the AutoLISP defun function, see the AutoCAD Customization Guide.

Command Stack
AutoCAD commands are stored in groups in the command stack, which is
defined by the AcEdCommandStack class. One instance of the command stack
is created per AutoCAD session. This stack consists of the custom commands
that you have defined. The acedRegCmds() macro gives you access to the
command stack.
When you add a command, you also assign it a group name. A good policy
is to use your registered developer prefix for the group name to avoid name
collisions with other commands. Command names within a given group
must be unique, and group names must be unique. However, multiple appli-
cations can add a command of the same name, because the group name
makes the commands unambiguous.

NOTE Autodesk supports a developer registration scheme to prevent


namespace conflicts between different applications. Each registered developer
chooses one or more registered developer symbols (RDS) to use exclusively. Reg-
istered developer symbols are one of the requirements of the “Built with
ObjectARX” logo program. For more information, go online to
http://www.veritest.com/autodesk/main(f).htm.

You usually add commands one at a time with the


AcEdCommandStack::addCommand() function, and you remove commands by
group with the AcEdCommandStack::removeGroup() function. You can also
use the AcEdCommandStack::removeCmd() function to remove commands
one at a time. As part of its cleanup before exiting, your application needs to
remove any commands it registered.
The signature for the addCommand() function is
Acad::ErrorStatus
addCommand(
const char* cmdGroupName,
const char* cmdGlobalName,
const char* cmdLocalName,

40 | Chapter 3 ObjectARX Application Basics


Adesk::Int32 commandFlags,
AcRxFunctionPtr functionAddr,
AcEdUIContext *UIContext=NULL,
int fcode=-1,
HINSTANCE hResourceHandle=NULL);
cmdGroupName
ASCII representation of the group to add the command to.
If the group doesn’t exist, it is created before the
command is added.
cmdGlobalName
ASCII representation of the command name to add. This
name represents the global or untranslated name (see
“Global versus Local Command Names” on page 42).
cmdLocalName
ASCII representation of the command name to add. This
name represents the local or translated name.
commandFlags
Flags associated with the command. Possible values are
ACRX_CMD_TRANSPARENT, ACRX_CMD_MODAL,
ACRX_CMD_USEPICKSET, and ACRX_CMD_REDRAW
(see “Transparent versus Modal Commands” on page 42).
functionAddr
Address of the function to be executed when this
command is invoked by AutoCAD.
UiContext
Input pointer to AcEdUIContext callback class.
fcode
Input integer code assigned to the command.

NOTE It is strongly recommended that all command names be prefixed with


your four-letter registered developer prefix to avoid possible conflicts with com-
mands of the same name in other applications. For example, the name of a
MOVE command for a developer with the prefix ASDK should be ASDKMOVE.
Using your registered developer prefix is also recommended for group names.

Registering New Commands | 41


The signature for the removeCmd() function is
virtual Acad::ErrorStatus
AcEdCommandStack::removeCmd
(const char* cmdGroupName,
const char* cmdGlobalName) = 0;
The signature for the removeGroup() function is
virtual Acad::ErrorStatus
AcEdCommandStack::removeGroup
(const char* groupName);

Lookup Order
When a command is invoked, the command stack is searched by group name,
then by command name within the group. In general, the first group registered
will be the first one searched, but you cannot always predict what this order
will be. Use the AcEdCommandStack::popGroupToTop() function to specify that
a particular group should be searched first. At the user level, the Group option
of the ARX command allows the user to specify which group to search first.

Global versus Local Command Names


When you add a command to AutoCAD, you need to specify both a global
name that can be used in any language and a localized name that is a trans-
lated version of the command name to be used in a foreign-language version
of AutoCAD. If you don’t need to translate the command name into a local
language, the same name can be used for both the global and local names.

Transparent versus Modal Commands


A command can be either transparent or modal. A transparent command can
be invoked when the user is being prompted for input. A modal command
can be invoked only when AutoCAD is posting the command prompt and no
other commands or programs are currently active. The commandFlags argu-
ment to the AcEdCommandStack::addCommand() function specifies whether
the new command is modal (ACRX_CMD_MODAL) or transparent
(ACRX_CMD_TRANSPARENT). The commandFlags argument also specifies
other options for the command. See AcEdCommandStack in the ObjectARX
Reference. Transparent commands can be nested only one level (that is, the
main command is invoked, which invokes one transparent command).
If you create multiple commands that operate on a common set of global
objects, consider whether you should make them modal so that they won’t
interfere with each other. If such collisions are not a problem, making new
commands transparent results in greater flexibility of use.

42 | Chapter 3 ObjectARX Application Basics


Loading an ObjectARX Application
You can load an ObjectARX application using any of the following methods:

■ Provide the application with features that allow it to be demand loaded by


AutoCAD. These features include application-specific entries in the
Windows NT (or Windows® 95) system registry. See “Demand Loading”
on page 45.
■ Specify the application in the initial module file, acad.rx. This file contains
ASCII text with the names of all programs AutoCAD should load when it
is started. Each line in the file contains a program name (with the path if
the file is not in a directory on the AutoCAD library search path). The
acad.rx file must also be in a directory on the AutoCAD search path.
■ Make an application load request from another ObjectARX application
using AcRxDynamicLinker::loadModule().
■ Use the APPLOAD dialog box defined in the AutoCAD bonus program
loadapp.arx.
■ Use the arxload() function from AutoLISP.
■ Use the acedArxLoad() function from ObjectARX.
■ Enter the ARX command on the AutoCAD command line and use the
Load option.

The Library Search Path


If you don’t specify a search path, loading functions such as arxload search
for the application in the directories specified by the AutoCAD library path.
The AutoCAD library path includes the following directories in the order
shown:
1 The current directory.
2 The directory that contains the current drawing file.
3 The directories specified by the support path (see the AutoCAD Customization
Guide).
4 The directory that contains the AutoCAD program files.

Listing Loaded ObjectARX Applications


To see the names of all the ObjectARX programs currently loaded, use the
Commands option of the ARX command. For more information, see “ARX
Command” on page 53. The APPLOAD dialog box (defined in the AutoCAD
bonus program loadapp.arx) also lists the names of the ObjectARX programs
currently loaded.

Loading an ObjectARX Application | 43


Unloading an ObjectARX Application
You can unload an ObjectARX application with any of the following
methods (if it is unlocked):

■ Make an application unload request from another ObjectARX application


using AcRxDynamicLinker::unloadModule().
■ Use the APPLOAD dialog box defined in the AutoCAD bonus program
loadapp.arx. This file defines a user interface for the AutoLISP arxload and
arxunload functions.
■ Use the arxunload function from AutoLISP.
■ Use the acedArxUnload() function from ObjectARX.
■ Enter the ARX command on the AutoCAD command line and use the
Unload option.

Unlocking Applications
By default, applications are locked and cannot be unloaded. To be classified
as an “unloadable” application, the application must ensure that AutoCAD
and other applications no longer refer to any objects or structures the appli-
cation has defined. Before you make an application unloadable, be very
careful that no client applications contain active pointers to any objects in
your address space. For the list of cleanup operations an application must
perform to be unloadable, see “Preparing for Unloading” on page 38.
If you want to make your application unloadable, you need to store the value
of the pkt parameter sent with the AcRx::kInitAppMsg. The pkt parameter
will be used by the unlockApplication() function. By default, an application
is locked. If you unlock an application, it can be unloaded.
Use the following two functions to lock and unlock an application:
bool
AcRxDynamicLinker::lockApplication(void* pkt) const;

bool
AcRxDynamicLinker::unlockApplication(void* pkt) const;
The following function checks whether or not an application is locked:
bool
AcRxDynamicLinker::isApplicationLocked(const char* name)
const;
Analogous global functions are also provided:
bool
acrxLockApplication(void* pkt);

44 | Chapter 3 ObjectARX Application Basics


bool
acrxUnlockApplication(void* pkt);

bool
acrxApplicationIsLocked(const char* modulename);

Demand Loading
Demand loading is a feature of AutoCAD that automatically attempts to load
an ObjectARX application that is not resident in AutoCAD. ObjectARX appli-
cations can be designed for loading by AutoCAD under one or more of the
following circumstances:

■ When a drawing file that contains custom objects created by the absent
application is read
■ When a user or another application issues one of the absent application’s
commands
■ When AutoCAD is started

NOTE Applications that implement demand loading on AutoCAD startup will


be loaded before those listed in acad.rx.

Autodesk recommends developing ObjectARX applications that take


advantage of AutoCAD’s demand-loading feature because demand loading
provides the following benefits:

■ Limits the creation of proxy objects (see chapter 14, “Proxy Objects”)
■ Provides greater flexibility for loading ObjectARX applications
■ Conserves memory by loading applications only when their functionality
is required

For an application to be accessible for demand loading, application-specific


information must be present in the Windows system registry. In addition,
ObjectARX applications with more than one DLL may need a “controller”
module that is responsible for loading all other components of the
application. Finally, the DEMANDLOAD system variable must be set to the
appropriate value for demand loading.

NOTE An ObjectARX application can be demand loaded from a path on the


local machine, or by using an Internet address.

Demand Loading | 45
AutoCAD, the Windows System Registry, and
ObjectARX Applications
AutoCAD uses the Windows system registry to maintain a wide range of
application information, including information that uniquely identifies dif-
ferent AutoCAD releases, language versions, and products (such as AutoCAD
Map®) that may be installed on any given computer. The registry informa-
tion that identifies different versions of AutoCAD is of particular significance
for ObjectARX developers. The installation program for an ObjectARX appli-
cation must associate information about that ObjectARX application with
information about the version(s) of AutoCAD with which it is supposed to
run.
The AutoCAD installation program creates a unique time stamp key in the
system registry immediately below the release number key (as well as adding
the same installation ID to the executable itself). This key ensures that differ-
ent versions of AutoCAD from the same release will be able to populate their
own sections of the system registry. Within this key, values are stored for the
location of AutoCAD files, the language version, and the product name, as
illustrated in this example:
\\HKEY_LOCAL_MACHINE\SOFTWARE\Autodesk\AutoCAD\R15.0\
ACAD-1:409\
...
AcadLocation:REG_SZ:f:\ACAD2000
Language:REG_SZ:English
ProductName:REG_SZ:AutoCAD Map R15.0
...
The installation program for an ObjectARX application must be able to locate
the appropriate AutoCAD release key, as well as the appropriate language and
product values.
The time stamp key is also used to identify the version of AutoCAD that is
currently loaded (or the version that was most recently loaded). This identi-
fication is necessary, because the “current” version of AutoCAD resets the
information in the global HKEY_CLASSES_ROOT section of the registry for its
own use when it is loaded.
The CurVer value in the release key section of the registry is used to identify
the current version, for example:
\\HKEY_LOCAL_MACHINE\SOFTWARE\Autodesk\AutoCAD\R15.0\
...
CurVer:REG_SZ:ACAD-1:409
When AutoCAD attempts to demand load an ObjectARX application, it looks
in the section of the registry that belongs to the latest release of AutoCAD for
information about the ObjectARX application. If it does not find the

46 | Chapter 3 ObjectARX Application Basics


ObjectARX information there, it checks the section for the previous release
of AutoCAD, and so on in reverse order, until the information is found or the
AutoCAD release information is exhausted.

Modification of the Registry at ObjectARX


Application Installation
AutoCAD uses the Windows NT (or Windows 95) system registry to locate
ObjectARX applications for demand loading. A part of the AutoCAD section
of the registry is used for information about the location of ObjectARX appli-
cations’ registry information.
The installation program for an ObjectARX application must create the spe-
cific keys and values in the system registry that are required for demand load-
ing. Some of the required keys and values must be created in the AutoCAD
section of the registry, and others must be created in the ObjectARX applica-
tion’s section of the registry.
If the ObjectARX application is designed to run with more than one version
of AutoCAD (that is, different language versions or related products, such as
AutoCAD Map), the installation program must add the appropriate informa-
tion to the section of the registry for each version of AutoCAD.
The installation process for ObjectARX applications must therefore include:

■ Verification that the sections of the system registry for the appropriate
version of AutoCAD exist. (If the AutoCAD section of the registry does not
exist, the user should be warned that a compatible version of AutoCAD
has not been installed, and the installation should be aborted.)
■ Creation of a specific set of keys and values for the application within the
section(s) of the system registry for the appropriate version(s) of
AutoCAD.
■ Creation of a major key for the application itself, and population of that
key with another set of specific keys and values.

See the \objectarx\samples\polysamp\demandload directory of the ObjectARX


SDK for information about how the system registry is modified for demand
loading the sample program polysamp.
The following two sections describe how an application’s installation pro-
gram should create the system registry information required for demand
loading. A sample installation program is included in the \objectarx\utils
directory of the ObjectARX SDK.

Demand Loading | 47
Creating AutoCAD Subkeys and Values
The ObjectARX application’s installation program must be designed to man-
age a set of keys and values for that application within the section of system
registry for each version of AutoCAD with which it is intended to run. The
following example shows the layout of the keys and values in the section of
the registry that must be created and maintained for the application:
\\HKEY_LOCAL_MACHINE\SOFTWARE\Autodesk\AutoCAD\releaseNum\
ACAD-1:LocaleID\
Applications\
ApplicationName\
LoadCtrls:REG_DWORD:acrxAppLoadReason
RegPath:REG_SZ:RegistryPathWhereLoaderIsSpecified
The releaseNum and ACAD-1:LocaleID keys are created by the AutoCAD
installation program.
The ApplicationName key must be the logical name of the application, which
is used internally by AutoCAD to identify the program.
The acrxAppLoadReason value defines the conditions under which the appli-
cation will be loaded, using one or more logical ORs of the following hex
values listed with their associated meanings:
0x01 Load the application upon detection of proxy object.
0x02 Load the application upon AutoCAD startup.
0x04 Load the application upon invocation of a command.
0x08 Load the application upon request by the user or another
application.
0x10 Do not load the application.
The RegistryPathWhereLoaderIsSpecified value must identify the registry
path for the application’s own section of the registry.
The ObjectARX API includes the acrxRegisterApp() function, which may be
used in an ObjectARX application to enter information about the application
into the AutoCAD section of the system registry. Typically,
acrxRegisterApp() would enter this information the first time the applica-
tion is loaded, and confirm the presence of that information on subsequent
loads.

Creating ObjectARX Application Keys and Values


The ObjectARX application’s installation program must be designed to man-
age the application’s section of the system registry. This section of the
registry must include keys and values identifying the main module of the
application and the command set for the application.

48 | Chapter 3 ObjectARX Application Basics


The value in the Loader key must include the full path and file name of the
module that AutoCAD should load first. The loader module is subsequently
responsible for loading any other modules that make up the application.
The following example illustrates the layout and value types of the
application section of the system registry:
\\HKEY_LOCAL_MACHINE\SOFTWARE\
...
RegistryPathWhereLoaderIsIdentified\
Loader\Module:REG_SZ:DirPathFileName
Name\DescriptiveName:REG_SZ:User Friendly App Name
Commands\GlobalCommandName1:REG_SZ:LocalCommandName1
GlobalCommandName2:REG_SZ:LocalCommandName2
GlobalCommandName3:REG_SZ:LocalCommandName3
GlobalCommandName4:REG_SZ:LocalCommandName4
GlobalCommandName5:REG_SZ:LocalCommandName5
Groups\
GroupName:REG_SZ:GroupName
...
The Module value must be present but is not used except as a placeholder in
the registry. Similarly, User Friendly App Name must be present, but is
currently not used.
The value in the Groups key may be used to uniquely identify an ObjectARX
application’s command groups and therefore the commands as well.

Removing System Registry Information


It may be useful to remove ObjectARX application information from the sys-
tem registry if an application is upgraded or removed. The ObjectARX API
includes the function acrxUnregisterApp(), which is the counterpart of
acrxRegisterApp(). It removes information about an application from the
AutoCAD section of the system registry.

The DEMANDLOAD System Variable


The AutoCAD DEMANDLOAD system variable controls the demand loading
options of ObjectARX applications.
By default the DEMANDLOAD system variable is set (when AutoCAD is
installed) to enable demand loading of applications on command invocation
or on proxy detection, when either option is specified in the system registry
entry for an application. The setting of DEMANDLOAD does not affect
demand loading on AutoCAD startup, or on request by a user or application
when either of these options is specified in the system registry. (See “Creating
AutoCAD Subkeys and Values” on page 48).

Demand Loading | 49
The legitimate values for the system variable may be used in combination.
They are defined as follows:
0 Disables demand loading of all ObjectARX applications.
1 Enables demand loading of ObjectARX applications upon
detection of proxy objects.
2 Enables demand loading of ObjectARX applications upon
command invocation.
3 Enables demand loading for both proxy objects and
command invocation (the default).
The DEMANDLOAD system variable allows the user to disable demand loading
of all ObjectARX applications that have system registry settings specifying
demand loading on command invocation, and proxy detection. It cannot
cause an application to be demand loaded if the appropriate system registry
settings do not exist.

Demand Loading on Detection of Custom


Objects
When a DWG or DXF file containing custom objects is loaded, AutoCAD
determines whether or not the associated application is loaded. If the appli-
cation is not loaded, and the first bit of the system variable DEMANDLOAD is
set, AutoCAD searches the Windows system registry for information about
the application and its loader module. If AutoCAD finds the appropriate
information in the system registry, it loads the application.

NOTE Demand loading on detection of custom classes will only work with
classes that are derived from AcDbObject, either directly or indirectly.

As a hypothetical example, let’s assume that AutoCAD reads a file created by


the ObjectARX application polysamp (a product of PolySamp Inc.).
1 Upon reading the drawing file, AutoCAD encounters custom objects created
with the application polysamp, and determines that the application is not
loaded.
2 AutoCAD finds that the DEMANDLOAD system variable is set to enable
demand loading of applications on proxy detection, so it searches the
AutoCAD Applications section of the system registry for the polysamp key.
Within this key, it finds the LoadCtrls value, which defines the conditions
under which the application should be loaded, and the RegPath value, which

50 | Chapter 3 ObjectARX Application Basics


provides the full registry path for the polysamp module. This section of the
registry would look something like this:
\\HKEY_LOCAL_MACHINE\SOFTWARE\Autodesk\AutoCAD\R15.0\
ACAD-1:409\
Applications\PolyCAD\
LoadCtrls:REG_DWORD:0xd
RegPath:REG_SZ:
\\HKEY_LOCAL_MACHINE\SOFTWARE\PolySampInc\polysamp
3 AutoCAD reads the polysamp\Loader key to determine the directory, path,
and file name of the module to be loaded. This section of the directory would
look something like this:
\\HKEY_LOCAL_MACHINE\SOFTWARE\
PolySampInc\polysamp\
Loader\MODULE:REG_SZ:c:\polysampinc\arx\polyui.arx
Name\PolySamp:REG_SZ:PolyCad
4 AutoCAD then attempts to load the ObjectARX module. If the module loads
successfully, AutoCAD adds the application’s handle to the list of application
handles to be sent the kLoadDwgMsg message. AutoCAD then verifies that the
application has been loaded properly, and verifies that the custom class is
registered. If the application was loaded successfully, AutoCAD will continue
to load the drawing file. If the ObjectARX module cannot be loaded, or if
there still isn’t a class implementation available, custom objects are treated
as proxies and the load continues.

Demand Loading on Command


AutoCAD will attempt to load the appropriate ObjectARX application if the
user invokes a command that is not registered with AutoCAD.
To support demand loading on command invocation, the ObjectARX appli-
cation’s installation program must create the appropriate keys and values in
the system registry for the application’s commands. The application’s
Commands section of the system registry should contain command informa-
tion like this:
\\HKEY_LOCAL_MACHINE\SOFTWARE\
Autodesk\ ...
...
PolySampInc\polysamp\
Loader\MODULE:REG_SZ:c:\polysampinc\arx\polyui.arx
Name\PolySamp:REG_SZ:PolyCad
Commands\
ASDKPOLY:REG_SZ:ASDKPOLY
ASDKDRAGPOLY:REG_SZ:ASDKDRAGPOLY
ASDKPOLYEDIT:REG_SZ:ASDKPOLYEDIT
Groups\
ASDK:REG_SZ:ASDK
...

Demand Loading | 51
In this example, the developer’s registered developer prefix (ASDK) is used as
the prefix for all commands to ensure that there will be no possible conflict
with commands of the same name in other applications.
The ObjectARX application must also include the appropriate calls to the
acedRegCmds macro for demand loading on command to work.

Demand Loading on AutoCAD Startup


Demand loading of an ObjectARX application on AutoCAD startup can be
specified by using 0x02 (or you can perform an OR 0x02 with another legiti-
mate value) with the LoadCtrls value in the system registry, as shown here.
\\HKEY_LOCAL_MACHINE\SOFTWARE\Autodesk\AutoCAD\R15.0\
ACAD-1:409\
Applications\PolyCAD\
LoadCtrls:REG_DWORD:0x02
RegPath:REG_SZ:

Managing Applications with the System Registry


Once system registry information has been created for demand loading, that
same information can be used by a set of ObjectARX functions to load,
unload, and monitor the presence of ObjectARX applications independent of
the demand-loading feature. The AppName argument used by the first two of
these functions is the logical application name.
The following ObjectARX functions can be used with registered application
names:
bool acrxLoadApp ("AppName")
This function takes a single argument, which represents the case-insensitive
logical name of the application to be loaded. The function returns 0 if the
load failed, or 1 if the load succeeds.
bool acrxUnloadApp ("AppName")
This function takes a single argument, which represents the case-insensitive
logical name of the application that was previously loaded. The function
returns 0 if the unload fails, or 1 if it succeeds.
void *acrxLoadedApps ()
This function returns an array of strings as a void *, containing the logical
application name of each application that is currently loaded. The function
returns NULL if no applications are loaded. It is the caller’s responsibility to
release the space allocated for the returned strings.

52 | Chapter 3 ObjectARX Application Basics


ARX Command
The following sections describe the ARX command and its options. The
initial prompt is as follows:
?/Load/Unload/Commands/Options: Enter an option or press ENTER

?—List Applications
Lists the currently loaded ARX applications.

Load
Loads the .arx file that you specify in the standard file dialog box. If FILEDIA
is set to 0, a dialog box is not displayed, and you enter the name of the file
to load in response to the following prompt:
Runtime extension file: Enter a name

Unload
Unloads the specified ARX program. Some applications cannot be unloaded.
See “Unloading an ObjectARX Application” on page 44 for a description of
how the programmer decides whether a program can be unloaded by the user
with this command.

Commands
Displays all command names in all command groups registered from ARX
programs.

Options
Presents developer-related ARX application options.
Options (Group/CLasses/Services): Enter an option

■ Group
Moves the specified group of commands registered from ARX applications
to be the first group searched when resolving the names of AutoCAD
commands. Other registered groups, if there are any, are subsequently
searched, in the same order as before the ARX command was executed.
Command Group Name: Enter the command group name

ARX Command | 53
The search order is important only when a command name is listed in
multiple groups. This mechanism allows different ARX applications to
define the same command names in their own separate command groups.
ARX applications that define command groups should publish the group
name in their documentation.
Group is not intended to be selected by the user directly. The user specifies
which group is searched first by interacting with a script that executes the
ARX command with the Group option. This capability is usually embed-
ded in key menu item scripts. The user selects a menu item from the script.
The key menu item script executes the Group option to establish which
group is searched first, giving commands of the same name (but probably
different functionality) from one application precedence over commands
from another.
For example, applications called ABC Construction and XYZ Interiors
define command groups ABC and XYZ, respectively. Most of ABC Con-
struction’s commands are named with construction terminology, while
most of XYZ Interiors’ commands are named with interior decorating ter-
minology, but both applications define commands named INVENTORY
and ORDERS. When working on the construction aspects of a drawing, the
user chooses a menu item defined by ABC Construction, and the follow-
ing script runs:
ARX
Group
ABC
The script pops the ABC Construction command set to give it top priority
and to resolve INVENTORY to the ABC Construction version of the com-
mand. Later, when an interior designer is working on the drawing with
the same set of applications loaded, selecting a key icon ensures that the
XYZ Interiors commands have precedence.

NOTE Command groups are not related to commands defined in AutoLISP


or defined by a call to acedDefun() by ObjectARX applications. The software
mechanism that defines command groups is described in “Lookup Order” on
page 42.

■ Classes
Displays a class hierarchy of C++ classes derived from objects registered in
the system, whether registered by AutoCAD or by an ARX program.
■ Services
Lists the names of all services registered by AutoCAD and by loaded ARX
programs.

54 | Chapter 3 ObjectARX Application Basics


Running ObjectARX Applications from
AutoLISP
An ObjectARX application can define a set of functions known to AutoLISP
as external functions, by using acedDefun(). After the application is loaded,
you can invoke an external function exactly as you can invoke a built-in or
user-defined AutoLISP function. AutoLISP variables can be passed as argu-
ments to the external function, and the external function can return a result.
The external function can also prompt the user to enter data, either from the
keyboard or by specifying points or objects with the pointing device, and the
external function can set Windows or AutoCAD platform-independent help.
The external function can be invoked by an AutoLISP function, as well as
interactively. ObjectARX applications cannot call AutoLISP functions. An
ObjectARX application can retrieve and set the value of AutoLISP symbols
(the symbol’s data type must be recognizable to a C++ program).
An ObjectARX application can define a new AutoCAD command with the
same C:XXX convention as AutoLISP. You invoke the external function by
entering its name at the Command prompt, with no parentheses.
Defining an external function replaces any previous definition of the same
name. If two ObjectARX applications define functions with the same name,
the function in the first application to be loaded is lost; if you unload the
second application, you cannot call the duplicate function.

Error Handling
The examples in this guide have omitted necessary error checking to simplify
the code. However, you’ll always want to check return status and take appro-
priate action. The following example shows appropriate use of error checking
for several examples shown first in chapter 2, “Database Primer.”
Acad::ErrorStatus
createCircle(AcDbObjectId& circleId)
{
circleId = AcDbObjectId::kNull;

AcGePoint3d center(9.0, 3.0, 0.0);


AcGeVector3d normal(0.0, 0.0, 1.0);
AcDbCircle *pCirc = new AcDbCircle(center, normal, 2.0);

Running ObjectARX Applications from AutoLISP | 55


if (pCirc == NULL)
return Acad::eOutOfMemory;

AcDbBlockTable *pBlockTable;
Acad::ErrorStatus es =
acdbHostApplicationServices()->workingDatabase()->
getSymbolTable(pBlockTable, AcDb::kForRead);
if (es != Acad::eOk) {
delete pCirc;
return es;
}

AcDbBlockTableRecord *pBlockTableRecord;
es = pBlockTable->getAt(ACDB_MODEL_SPACE,
pBlockTableRecord, AcDb::kForWrite);
if (es != Acad::eOk) {
Acad::ErrorStatus es2 = pBlockTable->close();
if (es2 != Acad::eOk) {
acrx_abort("\nApp X failed to close Block"
" Table. Error: %d",
acadErrorStatusText(es2));
}
delete pCirc;
return es;
}

es = pBlockTable->close();
if (es != Acad::eOk) {
acrx_abort("\nApp X failed to close Block Table."
" Error: %d", acadErrorStatusText(es));
}

es = pBlockTableRecord->appendAcDbEntity(circleId,
pCirc);
if (es != Acad::eOk) {
Acad::ErrorStatus es2 = pBlockTableRecord->close();
if (es2 != Acad::eOk) {
acrx_abort("\nApp X failed to close"
" Model Space Block Record. Error: %s",
acadErrorStatusText(es2));
}
delete pCirc;
return es;
}

es = pBlockTableRecord->close();
if (es != Acad::eOk) {
acrx_abort("\nApp X failed to close"
" Model Space Block Record. Error: %d",
acadErrorStatusText(es));
}

56 | Chapter 3 ObjectARX Application Basics


es = pCirc->close();
if (es != Acad::eOk) {
acrx_abort("\nApp X failed to"
" close circle entity. Error: %d",
acadErrorStatusText(es));
}
return es;
}

Acad::ErrorStatus
createNewLayer()
{
AcDbLayerTableRecord *pLayerTableRecord
= new AcDbLayerTableRecord;

if (pLayerTableRecord == NULL)
return Acad::eOutOfMemory;

Acad::ErrorStatus es
= pLayerTableRecord->setName("ASDK_MYLAYER");
if (es != Acad::eOk) {
delete pLayerTableRecord;
return es;
}

AcDbLayerTable *pLayerTable;
es = acdbHostApplicationServices()->workingDatabase()->
getSymbolTable(pLayerTable, AcDb::kForWrite);
if (es != Acad::eOk) {
delete pLayerTableRecord;
return es;
}

// The linetype object ID default is 0, which is


// not a valid ID. Therefore, it must be set to a
// valid ID, the CONTINUOUS linetype.
// Other data members have valid defaults, so
// they can be left alone.
//
AcDbLinetypeTable *pLinetypeTbl;
es = acdbHostApplicationServices()->workingDatabase()->
getSymbolTable(pLinetypeTbl, AcDb::kForRead);
if (es != Acad::eOk) {
delete pLayerTableRecord;
es = pLayerTable->close();
if (es != Acad::eOk) {
acrx_abort("\nApp X failed to close Layer"
" Table. Error: %d",
acadErrorStatusText(es));
}
return es;
}

Error Handling | 57
AcDbObjectId ltypeObjId;
es = pLinetypeTbl->getAt("CONTINUOUS", ltypeObjId);
if (es != Acad::eOk) {
delete pLayerTableRecord;
es = pLayerTable->close();
if (es != Acad::eOk) {
acrx_abort("\nApp X failed to close Layer"
" Table. Error: %d",
acadErrorStatusText(es));
}
return es;
}
pLayerTableRecord->setLinetypeObjectId(ltypeObjId);
es = pLayerTable->add(pLayerTableRecord);
if (es != Acad::eOk) {
Acad::ErrorStatus es2 = pLayerTable->close();
if (es2 != Acad::eOk) {
acrx_abort("\nApp X failed to close Layer"
" Table. Error: %d",
acadErrorStatusText(es2));
}
delete pLayerTableRecord;
return es;
}

es = pLayerTable->close();
if (es != Acad::eOk) {
acrx_abort("\nApp X failed to close Layer"
" Table. Error: %d",
acadErrorStatusText(es));
}

es = pLayerTableRecord->close();
if (es != Acad::eOk) {
acrx_abort("\nApp X failed to close Layer"
" Table Record. Error: %d",
acadErrorStatusText(es));
}
return es;
}

58 | Chapter 3 ObjectARX Application Basics


Database Operations

In This Chapter
4
This chapter describes basic database protocol including ■ Initial Database
■ Creating and Populating a
how to create a database, how to read in a drawing file,
Database

and how to save the database. The wblock and insert ■ Saving a Database
■ The wblock Operation
operations are also described here.
■ Inserting a Database

For more detailed information on the deepClone and ■ Setting Current Database Values
■ Example of Database Operations
wblock operations, see chapter 18, “Deep Cloning.”
■ Long Transactions
■ External References
■ Indexes and Filters
■ Drawing Summary Information
■ Last Saved by Autodesk Software

59
Initial Database
When an AutoCAD session begins, the database contains the following
elements:

■ A set of nine symbol tables.


Block table (AcDbBlockTable)
Dimension style table (AcDbDimStyleTable)
Layer table (AcDbLayerTable)
Linetype table (AcDbLinetypeTable)
Registered applications table (AcDbRegAppTable)
Text style table (AcDbTextStyleTable)
User Coordinate System table (AcDbUCSTable)
Viewport table (AcDbViewportTable)
View table (AcDbViewTable)
Some of the symbol tables already contain one or more records. The layer
table in a pristine database contains one record, layer 0. The block table
initially contains three records: *MODEL_SPACE, *PAPER_SPACE, and
*PAPER_SPACE0. The linetype table always has CONTINUOUS,
BY_LAYER, and BY_BLOCK linetype table records. The registered applica-
tions table always has an ACAD table record. The text style table always
has a STANDARD table record.
■ A named object dictionary. When a database is created, this dictionary
already contains the two database dictionaries: the GROUP dictionary and
the MLINE style dictionary. Within the MLINE style dictionary, the
STANDARD style is always present.
■ A fixed set of header variables. (These are not database objects.)

Creating and Populating a Database


Use new to create a database and delete to destroy one. The AcDbDatabase
constructor has one argument with a default value of Adesk::kTrue. If this
argument is Adesk::kTrue, then the database is populated with the standard
database objects, described in “Initial Database.” If the argument is
Adesk::kFalse, then an empty database is created and can be populated by
reading in a drawing file.
Use the following function to read in a drawing file:
AcadErrorStatus
AcDbDatabase::readDwgFile(char* fileName);

60 | Chapter 4 Database Operations


If you receive any of the following error codes, you probably want to recover
the drawing with the standard AutoCAD recover mechanism provided by the
user interface:
kDwgNeedsRecovery
kDwgCRCDoesNotMatch
kDwgSentinelDoesNotMatch
kDwgObjectImproperlyRead

WARNING! Never delete the database returned by the


acdbHostApplicationServices()->workingDatabase() function.

Saving a Database
To save a database, use the AcDbDatabase::saveAs() function:
Acad::ErrorStatus
AcDbDatabase::saveAs(char* fileName);
The file name can be a path to a local file, or an Internet address.

Setting the Default File Format


ObjectARX provides the ability to specify the default file format for the
SAVEAS, SAVE, and QSAVE commands. (The AUTOSAVE command always saves
drawings in the AutoCAD 2000 drawing file format.)
The class AcApDocument contains an enumeration that defines the format
used when saving a drawing to a file. Its values are shown in the following
table:

Save format

Name Usage (file extension)

kR12_dxf AutoCAD Release 12/LT2 DXF (*.dxf)

kR13_dwg AutoCAD Release 13/LT95 Drawing (*.dwg)

kR13_dxf AutoCAD Release 13/LT95 DXF (*.dxf)

kR14_dwg AutoCAD Release 14/LT97 Drawing (*.dwg)

kR14_dxf AutoCAD Release 14/LT97 DXF (*.dxf)

Saving a Database | 61
Save format (continued)

Name Usage (file extension)

kR15_dwg AutoCAD 2000 Drawing (*.dwg)

kR15_dxf AutoCAD 2000 DXF (*.dxf)

kR15_Template AutoCAD 2000 Drawing Template File (*.dwt)

kNative Current DWG version is AutoCAD 2000

kUnknown Invalid format

The AcApDocument::formatForSave() function returns the current save


format being used by the SAVEAS, SAVE, and QSAVE commands:
AcApDocument::SaveFormat
formatForSave();
The value returned may be either the session-wide default setting, or a differ-
ent setting that the user has selected for this document. If it is an override for
this document, it will not persist across sessions.
The AcApDocmanager::setDefaultFormatForSave() function uses one of the
SaveFormat values to set the file format to use when saving a drawing with
the SAVEAS, SAVE, and QSAVE commands. This sets the session-wide default,
which the user may choose to temporarily override for an individual
document:
Acad::ErrorStatus
setDefaultFormatForSave(
AcApDocument::SaveFormat format);
These functions only directly report on or set the file format for interactive
commands entered by the user. If you want your application to use the cur-
rent save format, every time you wish to save the database, you will first need
to call formatForSave(), and then use the returned SaveFormat value to
determine which function to call. For example, if formatForSave() returned
kR14_dxf, you would call acdbDxfOutAsR14() to write the database as a
Release 14 DXF file.
Be sure to take the following into account:

■ Either you or your user may set a persistent session-wide default format for
save that will be honored by all save commands except AUTOSAVE.
■ Only the user can temporarily (not persistently between sessions) override
this setting for a particular document.

62 | Chapter 4 Database Operations


■ The formatForSave() method returns the format in which the user wishes
an individual document to be saved; this will be either the session-wide
default or the temporary override, as appropriate.

Global Save Functions


ObjectARX also contains two global functions for saving drawings:
Acad::ErrorStatus
acdbSaveAsR13(
AcDbDatabase* pDb,
const char* fileName);
Acad::ErrorStatus
acdbSaveAsR14(
AcDbDatabase* pDb,
const char* fileName);
Both functions accept a database pointer and a filename, and write out the
drawing in AutoCAD Release 13 or Release 14 DWG format, respectively.

The wblock Operation


The AcDbDatabase class contains an overloaded wblock() function with
three forms that correspond to the options of the AutoCAD WBLOCK
command.

Creating a New Database from an Existing


Database
The following function is the equivalent of the WBLOCK* command:
Acad::ErrorStatus
AcDbDatabase::wblock(AcDbDatabase*& newDb);
This function creates a new database from the invoked database (“this”).
Any unreferenced symbols in the input database are omitted in the new data-
base (which makes the new database potentially cleaner and smaller than the
original). However, it does not take care of copying application-defined
objects whose ownership is rooted in the named object dictionary. You need
to transfer application data from the source database to the target database
using the AcEditorReactor notification functions.

The wblock Operation | 63


Creating a New Database with Entities
The other two forms of the AcDbDatabase::wblock() function create a new
database whose model space block table record contains the specified entities
from the input database. The first form of this function copies the entities
from a named block table record. The second form of the function copies an
array of entities.

Copying a Named Block


The following function is equivalent to invoking the WBLOCK command
with the name of a block definition:
Acad::ErrorStatus
AcDbDatabase::wblock(AcDbDatabase*& newDb,
AcDbObjectId recordId);
The recordId argument represents a block table record in the input database.
The entities in this block table record are copied into the new database’s
model-space block table record. The insert base of the new database is the
block table record’s origin.

Copying an Array of Entities


The following function is equivalent to invoking the WBLOCK command and
then using the option to select specific objects and specify an insertion base
point:
Acad::ErrorStatus
AcDbDatabase::wblock(AcDbDatabase*& newDb,
const AcDbObjectIdArray& idArray,
const AcGePoint3d* point);
This function creates a new database that includes the entities specified in
the idArray argument. The entities, which can be in the model space or
paper space block table records of the input database, are placed in the model
space of the new database. Also included in the new database are the objects
owned by or referred to by those entities, as well as the owners of those
objects. The specified point is the origin point, in world coordinates, for the
new drawing (that is, it is the insert base point in the model space of the new
database).

64 | Chapter 4 Database Operations


Inserting a Database
The AcDbDatabase::insert() functions copy one database into the database
that the member function is invoked on. AutoCAD merges the objects that
it defines, such as the MLINE style and GROUP dictionaries; however, it does
not take care of copying application-defined objects whose ownership is
rooted in the named object dictionary. You need to transfer application data
from the source database to the target database using the AcEditorReactor
notification functions.

NOTE The insert() functions perform deep cloning, as described in


chapter 18, “Deep Cloning.”

If conflicts arise when the source and target databases are being merged (for
example, if both databases have the same linetype name), AutoCAD uses the
version in the target database.
The following function is equivalent to a standard drawing INSERT
command:
Acad::ErrorStatus
AcDbDatabase::insert(AcDbObjectId& blockId,
const char* pBlockName,
AcDbDatabase* pDb);
This function copies the entities from the model space of the input database
(pDb) into the specified block table record (pBlockName) and returns the block
ID of the new block table record (blockId). The application must then create
the reference to the block table record and add it to the database.
The following function is equivalent to an AutoCAD INSERT* command:
Acad::ErrorStatus
AcDbDatabase::insert(const AcGeMatrix3d& xform,
AcDbDatabase* pDb);
This function copies the entities from the model space of the input database
(pDb) and puts them into the current space of the new database (paper space
or model space), applying the specified transformation (xform) to the
entities.

Inserting a Database | 65
Setting Current Database Values
If a data property such as color or linetype is not specified for an entity, the
database’s current value for that data is used. The following sections outline
the functions used to specify the current data values associated with the
database.

Database Color Value


If a color is not specified for an entity, the database’s current color value,
stored in the CECOLOR system variable, is used. The following functions set
and retrieve the current color value in the database:
Acad::ErrorStatus
AcDbDatabase::setCecolor(const AcCmColor& color);

AcCmColor AcDbDatabase::cecolor() const;

Database Linetype Value


The following functions set and retrieve the current linetype value in the
database:
Acad::ErrorStatus
AcDbDatabase::setCeltype(AcDbObjectId);

AcDbObjectId AcDbDatabase::celtype() const;

Database Linetype Scale Value


The database has three linetype scale settings:

■ A linetype scale setting for the current entity, stored in the CELTSCALE
system variable.
■ A linetype scale setting for the current drawing, stored in the LTSCALE
system variable.
■ A flag that indicates whether to apply linetype scaling to the space the
entity resides in or to the entity’s appearance in paper space. This setting
is stored in the PSLTSCALE system variable.

66 | Chapter 4 Database Operations


The global LTSCALE and PSLTSCALE settings are used when a drawing is regen-
erated (see chapter 6, “Entities”). Use the following functions to set and
inquire these values:
Acad::ErrorStatus
AcDbDatabase::setLtscale(double);

double AcDbDatabase::ltScale() const;

Acad::ErrorStatus
AcDbDatabase::setCeltscale(double);

double AcDbDatabase::celtscale() const;

Acad::ErrorStatus
AcDbDatabase::setPsltscale(Adesk::Boolean)

Adesk::Boolean AcDbDatabase::psltscale() const;

Database Layer Value


The following functions set and retrieve the current layer value in the
database:
Acad::ErrorStatus
AcDbDatabase::setClayer(AcDbObjectId);

AcDbObjectId AcDbDatabase::clayer() const;

Example of Database Operations


The following example shows the createDwg() routine, which creates a new
database, obtains the model space block table record, and creates two circles
that are added to model space. It uses the AcDbDatabase::saveAs() function
to save the drawing. The second routine, readDwg(), reads in the saved
drawing, opens the model space block table record, and iterates through it,
printing the class names of the entities it contains.
void
createDwg()
{
AcDbDatabase *pDb = new AcDbDatabase();

AcDbBlockTable *pBtbl;
pDb->getSymbolTable(pBtbl, AcDb::kForRead);

Example of Database Operations | 67


AcDbBlockTableRecord *pBtblRcd;
pBtbl->getAt(ACDB_MODEL_SPACE, pBtblRcd,
AcDb::kForWrite);
pBtbl->close();

AcDbCircle *pCir1 = new AcDbCircle(AcGePoint3d(1,1,1),


AcGeVector3d(0,0,1),
1.0),
*pCir2 = new AcDbCircle(AcGePoint3d(4,4,4),
AcGeVector3d(0,0,1),
2.0);
pBtblRcd->appendAcDbEntity(pCir1);
pCir1->close();

pBtblRcd->appendAcDbEntity(pCir2);
pCir2->close();
pBtblRcd->close();

// AcDbDatabase::saveAs() does not automatically


// append a DWG file extension, so it
// must be specified.
//
pDb->saveAs("test1.dwg");
delete pDb;
}

void
readDwg()
{
// Set constructor parameter to kFalse so that the
// database will be constructed empty. This way only
// what is read in will be in the database.
//
AcDbDatabase *pDb = new AcDbDatabase(Adesk::kFalse);

// The AcDbDatabase::readDwgFile() function


// automatically appends a DWG extension if it is not
// specified in the filename parameter.
//
pDb->readDwgFile("test1.dwg");

// Open the model space block table record.


//
AcDbBlockTable *pBlkTbl;
pDb->getSymbolTable(pBlkTbl, AcDb::kForRead);

AcDbBlockTableRecord *pBlkTblRcd;
pBlkTbl->getAt(ACDB_MODEL_SPACE, pBlkTblRcd,
AcDb::kForRead);
pBlkTbl->close();

AcDbBlockTableRecordIterator *pBlkTblRcdItr;
pBlkTblRcd->newIterator(pBlkTblRcdItr);

68 | Chapter 4 Database Operations


AcDbEntity *pEnt;
for (pBlkTblRcdItr->start(); !pBlkTblRcdItr->done();
pBlkTblRcdItr->step())
{
pBlkTblRcdItr->getEntity(pEnt,
AcDb::kForRead);
acutPrintf("classname: %s\n",
(pEnt->isA())->name());
pEnt->close();
}
pBlkTblRcd->close();

delete pBlkTblRcdItr;
delete pDb;
}

Long Transactions
Long Transactions are used to support the AutoCAD Reference Editing fea-
ture and are very useful for ObjectARX applications. These classes and
functions provide a scheme for applications to check out entities for editing
and check them back in to their original location. This operation replaces the
original objects with the edited ones. There are three types of long transac-
tion check out:

■ From a normal block within the same drawing


■ From an external reference (xref) of the drawing
■ From an unrelated, temporary database

Class and Function Overview


The main classes and functions are

■ AcDbLongTransaction class
■ AcDbLongTransWorkSetIterator class
■ AcApLongTransactionReactor class
■ AcApLongTransactionManager class
■ AcDbDatabase::wblockCloneObjects() function

AcDbLongTransaction Class
AcDbLongTransaction is the class that contains the information needed to
track a long transaction. The AcDbLongTransactionManager class takes the
responsibility for creating and appending AcDbLongTransaction objects to
the database. It then returns the AcDbObjectId of the AcDbLongTransaction

Long Transactions | 69
object. Like all other database-resident objects, its destruction is handled by
the database.

NOTE The AcDbLongTransaction objects are added to a database while they


are active and are erased once the transaction has completed. They are not
stored in DWG or DXF files, and therefore are not persistent.

AcDbLongTransWorkSetIterator Class
AcDbLongTransWorkSetIterator provides read-only access to the objects in
the work set. During construction in
AcDbLongTransaction::newWorkSetIterator(), it can be set up to include
only the active work set, or include objects added to the work set because
they are referenced by objects in the work set (secondary objects). It can also
handle objects removed from the work set, either by
AcDbLongTransaction::removeFromWorkSet(), or by being erased.

AcApLongTransactionReactor Class
AcApLongTransactionReactor provides notification specific to long transac-
tion operations. It is designed to be used in conjunction with the deep clone
notifications that will also be sent, but will vary depending upon which type
of check out/in is being executed. To connect these notifications with the
deep clone notifications, the AcDbIdMapping object used for cloning can be
retrieved by calling the AcDbLongTransaction::activeIdMap() function.

AcApLongTransactionManager Class
AcApLongTransactionManager is the manager for starting and controlling
long transactions. There is only one for each AutoCAD session, and it is
accessed via a pointer returned by the acapLongTransactionManager object.

AcDbDatabase::wblockCloneObjects() Function
The wblockCloneObjects() function is a member of AcDbDatase. It will deep
clone objects from one database to another and follow hard references so
that all dependent objects are also cloned. The behavior of symbol table
records, when duplicates are found, is determined by the type parameter. The

70 | Chapter 4 Database Operations


following chart shows the relationship between a symbol table type (enum
DuplicateRecordCloning) and a deep clone type (enum DeepCloneType).

Relationship between DeepCloneTypes and DuplicateRecordCloning for


different commands and functions

Command or API Function DeepCloneType DuplicateRecordCloning

COPY kDcCopy kDrcNotApplicable

EXPLODE kDcExplode kDrcNotApplicable

BLOCK kDcBlock kDrcNotApplicable

INSERT/BIND kDcXrefInsert kDrcIgnore

XRESOLVE kDcSymTableMerge kDrcXrefMangleName

INSERT kDcInsert kDrcIgnore

insert() kDcInsertCopy kDrcIgnore

WBLOCK kDcWblock kDrcNotApplicable

deepCloneObjects() kDcObjects kDrcNotApplicable

wblockObjects() kDcObjects kDrcIgnore

wblockObjects() kDcObjects kDrcReplace

wblockObjects() kDcObjects kDrcMangleName

wblockObjects() kDcObjects kDrcUnmangleName

Long Transaction Example


This simple example shows how to check out entities from another database,
modify them in the current database, and then check them back in to the
original database. The calls that are part of the long transaction process are
indicated in bold print.
void
refEditApiExample()
{
AcDbObjectId transId;
AcDbDatabase* pDb;
char *fname;
struct resbuf *rb;

Long Transactions | 71
// Get a dwg file from the user.
//
rb = acutNewRb(RTSTR);
acedGetFileD("Pick a drawing", NULL, "dwg", 0, rb);
fname = (char*)acad_malloc(strlen(rb->resval.rstring) + 1);
strcpy(fname, rb->resval.rstring);
acutRelRb(rb);

// Open the dwg file.


//
pDb = new AcDbDatabase(Adesk::kFalse);
pDb->readDwgFile(fname);

// Get the block table and then the model space record.
//
AcDbBlockTable *pBlockTable;
pDb->getSymbolTable(pBlockTable, AcDb::kForRead);
AcDbBlockTableRecord *pOtherMsBtr;
pBlockTable->getAt(ACDB_MODEL_SPACE, pOtherMsBtr,
AcDb::kForRead);
pBlockTable->close();

// Create an iterator
//
AcDbBlockTableRecordIterator *pIter;
pOtherMsBtr->newIterator(pIter);

// Set up an object ID array.


//
AcDbObjectIdArray objIdArray;

// Iterate over the model space BTR. Look specifically


// for Lines and append their object ID to the array.
//
for (pIter->start(); !pIter->done(); pIter->step()) {
AcDbEntity *pEntity;
pIter->getEntity(pEntity, AcDb::kForRead);
// Look for only AcDbLine objects and add them to the
// objectId array.
//
if (pEntity->isKindOf(AcDbLine::desc())) {
objIdArray.append(pEntity->objectId());
}
pEntity->close();
}
delete pIter;
pOtherMsBtr->close();

72 | Chapter 4 Database Operations


// Now get the current database and the object ID for the
// current database’s model space BTR.
//
AcDbBlockTable *pThisBlockTable;
acdbHostApplicationServices()->workingDatabase()
->getSymbolTable(pThisBlockTable, AcDb::kForRead);
AcDbBlockTableRecord *pThisMsBtr;
pThisBlockTable->getAt(ACDB_MODEL_SPACE, pThisMsBtr,
AcDb::kForWrite);
pThisBlockTable->close();

AcDbObjectId id = pThisMsBtr->objectId();
pThisMsBtr->close();

// Create the long transaction. This will check all the entities
// out of the external database.
acapLongTransactionManagerPtr()->checkOut(transId,
objIdArray, id);
// Now modify the color of these entities.
//
int colorIndex;
acedGetInt("\nEnter color number to change entities to: ",
&colorIndex);
AcDbObject* pObj;
if (acdbOpenObject(pObj, transId, AcDb::kForRead) == Acad::eOk)
{
// Get a pointer to the transaction
//
AcDbLongTransaction* pLongTrans =
AcDbLongTransaction::cast(pObj);
if (pLongTrans != NULL) {
// Get a work set iterator
//
AcDbLongTransWorkSetIterator* pWorkSetIter =
pLongTrans->newWorkSetIterator();
// Iterate over the entities in the work set
// and change the color.
for (pWorkSetIter->start(); !pWorkSetIter->done();
pWorkSetIter->step()) {
AcDbEntity *pEntity;
acdbOpenAcDbEntity(pEntity, pWorkSetIter->objectId(),
AcDb::kForWrite);
pEntity->setColorIndex(colorIndex);
pEntity->close();
}
delete pWorkSetIter;
}
pObj->close();
}

Long Transactions | 73
// Pause just to see the change.
//
char str[132];
acedGetString(0, "\nNote the new colors. Press return to \
check the objects back in to the original database", str);
// Check the entities back in to the orginal database.
//
acapLongTransactionManagerPtr()->checkIn(transId);
// Save the original database, since we made changes
//
pDb->saveAs(fname);
// Close and Delete the database.
//
delete pDb;
pDb = NULL;
acad_free(fname);
}

External References
External references (xrefs) can be created and manipulated through several
global functions. These global functions mimic the AutoCAD XREF command
capabilities. The functions provided are

■ acedXrefAttach()
■ acedXrefOverlay()
■ acedXrefUnload()
■ acedXrefDetach()
■ acedXrefReload()
■ acedXrefBind()
■ acedXrefXBind()
■ acedXrefCreateBlockname()
■ acedXrefReload()

For information on the AutoCAD XREF command, see the


AutoCAD User’s Guide.
The main programming consideration concerning xrefs is that, for every xref
that is attached to a drawing, a separate database is created to represent the
drawing containing the xref. A block table record in the main drawing con-
tains the name of the external drawing and points to the entities of the
model space of the externally referenced drawing. The xref database also con-
tains other block table records and symbol table entries required to resolve
all references from the main block table record (layers, linetypes, and so on).

74 | Chapter 4 Database Operations


You can create an editor reactor, as described in chapter 15, “Notification,”
to monitor xref events. The AcEditorReactor class provides the following
reactor callback functions:

■ beginAttach()
■ otherAttach()
■ abortAttach()
■ endAttach()
■ redirected()
■ comandeered()

When using these functions, be careful to notice which database is being


returned. Also, be aware that the xref drawing can itself contain xrefs to addi-
tional drawings. For more information on the AcEditorReactor class, see the
ObjectARX Reference.
Xref entities in a drawing can be modified, but they cannot be saved to the
original xref drawing (the original drawing is read-only).

External Reference Pre- and Post-Processing


External reference (xref) pre- and post-processing makes it possible to restore
an attached xref’s in-memory AcDbDatabase so that it can be saved back to a
file. During xref resolve, many symbol table records are mangled, and some
are erased. Historically, this was done to simplify the resolve process, and was
acceptable because the databases were read-only. This processing makes it
possible to temporarily reverse the resolve changes so that the xref database
can be modified and written back to its file.
The functions that aid in pre- and post-processing are added to
AcDbDatabase. They include a utility function to find the associated block
table record from an xref database, as well as the ability to restore the
resolved xref, and to reset it back to the proper resolved condition after res-
toration.
The customary usage for these functions would be to do the restore to the
original symbols, make the modifications to the database, save the database,
and then restore the forwarded symbols. These steps must be written into a
single block of code, to prevent attempts to regenerate the host drawing, exe-
cute any xref commands, or provide user prompts while the xref database is
in its restored condition.
The functions are

■ AcDbDatabase::xrefBlockId()
■ AcDbDatabase::restoreOriginalXrefSymbols()
■ AcDbDatabase::restoreForwardingXrefSymbols()

External References | 75
AcDbDatabase::xrefBlockId() Function
The xrefBlockId() function will get the AcDbObjectId of the block table
record, which refers to this database as an xref. When an xref is reloaded for
any reason (for example, XREF Reload or XREF Path commands), the former
database is kept in memory for Undo. This means that more than one data-
base may point to the same xref block table record. However only one will be
the currently active xref database for that record. The database pointer
returned by the AcDbBlockTableRecord::xrefDatabase() function on the
found record will be the active database for that xref.

AcDbDatabase::restoreOriginalXrefSymbols() Function
The restoreOriginalXrefSymbols() function restores a resolved xref data-
base to its original form, as it would be if just loaded from its file. The xref is
then in a condition where it can be modified and saved back to a file. After
calling this function, the host drawing is no longer in a valid state for regen
or for any xref command modifications or reloads. The database modifica-
tions, save back, and the restoreForwardingXrefSymbols() function must
be called before anything that might allow a regen.

AcDbDatabase::restoreForwardingXrefSymbols() Function
The restoreForwardingXrefSymbols() function restores the xref back to a
valid, attached state. Not only does it restore the original resolved symbols,
but it also seeks out newly added symbols and resolves them as well. The
restoreForwardingXrefSymbols() function cannot handle newly added,
nested xref block table records unless they already exist and are resolved in
the host drawing.

File Locking and Consistency Checks


The AcDbXrefFileLock base class is provided to handle the management of
xref file locking. Its main purpose is to prepare the xref block in a drawing for
in-place editing, though, it can be used for other purposes. It is assumed that
these xref file methods operate on the current database drawing.
The acdbXrefReload() global function processes the list of xref block table
record object IDs for xref reload. It is assumed that each xref block table
record object ID references an xref drawing file that can be reloaded to the
current drawing. It has the same functionality as the AutoCAD XREF subcom-
mand for Reload.

76 | Chapter 4 Database Operations


Indexes and Filters
The index and filter classes and functions provide a scheme for applications
to define custom indexes and custom filtering of block data. An application
can define its custom implementations of AcDbFilter, AcDbIndex, and
AcDbFilteredBlockIterator. It will register the AcDbFilter with a block
reference through AcIndexFilterManager::addFilter(), and an AcDbIndex
with the corresponding block table record through
AcIndexFilterManager::addIndex(). After that, regens of xrefs and blocks
will respect the query defined by the AcDbFilter, and use the
AcDbFilteredBlockIterator to decide what object IDs will be processed
during regen. The indexes will be kept up to date through either the applica-
tion explicitly calling AcIndexFilterManager::updateIndexes(), or the
application can rely on the AutoCAD save operation calling
AcIndexFilterManager::updateIndexes() on the AcDbDatabase being
saved.
The AcDbIndex::rebuildFull() or the AcDbIndex::rebuildModified() gets
invoked during the AcIndexFilterManager::updateIndexes() call.
A current use of the indexing scheme in AutoCAD is fast demand loading of
clipped xrefs. A spatial index (an AcDbSpatialIndex object) is stored in the
xrefed drawing. An AcDbSpatialFilter object defines the clip volume of the
block reference to the xref in the host drawing. When demand loading is
turned on for the xref, the spatial filter volume is used to traverse the xref
data through the spatial index, in order to page in from the DWG file only
those entities whose graphics intersect the clip volume.
These classes and functions provide an interface for:

■ Updating indexes
■ Adding and removing indexes to block table records
■ Adding and removing filters to block references
■ Querying for indexes from block table records
■ Querying for filters from block references
■ Iterating through block table records and visiting only a subset of entities

The main classes and functions involved are

■ AcDbIndexFilterManager namespace
■ AcDbIndex class
■ AcDbFilter class
■ AcDbFilteredBlockIterator class
■ AcDbCompositeFilteredBlockIterator class

Indexes and Filters | 77


AcDbIndexFilterManager Namespace
The AcDbIndexFilterManager namespace is a collection of functions that
provides index and filter access and maintenance functionality.

AcDbIndex Class
The AcDbIndex class is the base class for all index objects. AcDbSpatialIndex
and AcDbLayerIndex derive from this class.
Keeping the index up to date is achieved through the
AcDbIndexFilterManager::updateIndexes() function calls being explicitly
invoked (either by an application or AutoCAD).
The AcDbFilteredBlockIterator will serve as the means to visit all the
AcDbObjectIds that are “hits” from the query defined by the AcDbFilter
passed to its constructor. For example, in the spatial index case, the
AcDbSpatialFilter object instance passed to the newIterator() method
will define a query region. The AcDbSpatialIndex object, through its
newIterator() method, will provide an AcDbSpatialIndexIterator that will
return object IDs that correspond to entities that fit within the query
volume.

AcDbFilter class
The AcDbFilter class is meant to define a “query.” It provides the “key” to
the AcDbCompositeFilteredBlockIterator, for which the corresponding
index is obtained through the indexClass() method.

AcDbFilteredBlockIterator Class
The AcDbFilteredBlockIterator class provides a method to process a
“query” on an index. It is used by the
AcDbCompositeFilteredBlockIterator.

AcDbCompositeFilteredBlockIterator Class
The AcDbCompositeFilteredBlockIterator class provides the alternate to
normal block iteration. By providing the filter list in the init() method, the
AcDbCompositeFilteredBlockIterator object looks for corresponding
AcDbIndex derived objects through the AcDbFilter::indexClass() method,
and creates AcDbFilteredBlockIterator objects. If the matching up-to-date
indexClass() objects are not available, it creates an
AcDbFilteredBlockIterator through the AcDbFilter::newIterator()
method. It then orders the composition of the AcDbFilteredBlockIterator
objects based on the AcDbFilteredBlockIterator::estimatedHits() and
AcDbFilteredBlockIterator::buffersForComposition() methods. The col-

78 | Chapter 4 Database Operations


lection of filters is a conjunction of conditions. This means an object ID is
output from the iterator only if the accepts() method of each filter would
accept the object ID.

Drawing Summary Information


The Drawing Property Dialog allows AutoCAD users to embed ancillary data
(called summary information) in their DWG files, and assists in retrieving
DWG files based on this data. This provides AutoCAD users with base-level
file retrieval and management capabilities.
Through Windows Explorer, the properties of a drawing can be viewed out-
side of AutoCAD. Used in conjunction with the AutoCAD DesignCenter
Advanced Find feature, summary information allows users to search for
drawings containing predefined or custom data.
The AcDbDatabaseSummaryInfo, AcDbSummaryInfoReactor, and
AcDbSummaryInfoManager classes provide an API to work with summary
information and are discussed below. For more detail on these classes, see the
ObjectARX Reference.

AcDbDatabaseSummaryInfo Class
The AcDbDatabaseSummaryInfo class encapsulates a set of character strings
that can be used to add additional information to a DWG file. The maximum
length of these strings is 511 characters. This information is stored and
retrieved in the Summary Information object with specific methods for each
information field. The predefined fields are

■ Title
■ Subject
■ Author
■ Keywords
■ Comments
■ Last saved by
■ Revision number
■ Hyperlink base

You can create your own custom fields in addition to the predefined fields.
These custom fields are stored in a list, and you can manipulate custom fields
either by their name (or key) or position (index) in the list. Custom fields are
indexed starting at 1, and there is no limit to the number of fields you can
create.

Drawing Summary Information | 79


AcDbSummaryInfoReactor Class
This class provides a reactor to let you know if the summary information is
changed.

AcDbSummaryInfoManager Class
The AcDbSummaryInfoManager class organizes the summary information reac-
tors, with methods to add and remove reactors, and to send notification that
the summary information has changed.

Global Summary Information Functions


ObjectARX contains several global functions for accessing summary
information:
Acad::ErrorStatus
acdbGetSummaryInfo(
AcDbDatabase* pDb,
AcDbDatabaseSummaryInfo*& pInfo);
Acad::ErrorStatus
acdbPutSummaryInfo(
const AcDbDatabaseSummaryInfo* pInfo);
AcDbSummaryInfoManager*
acdbGetSummaryInfoManager();
For more information on these functions, see the ObjectARX Reference.

Last Saved by Autodesk Software


The following AcDbDatabase method returns Adesk::kTrue if it determines
that the database was last saved by Autodesk software (such as AutoCAD or
AutoCAD LT® ):
Adesk::Boolean
dwgFileWasSavedByAutodeskSoftware();

80 | Chapter 4 Database Operations


Database Objects

In This Chapter
5
This chapter describes topics that relate to all AutoCAD ■ Opening and Closing Database
Objects
database objects, including entities, symbol table
■ Deleting Objects

records, and dictionaries. Major concepts included ■ Database Ownership of Objects


■ Adding Object-Specific Data
are opening and closing objects, managing objects in
■ Erasing Objects
memory, object ownership, and extending an object ■ Object Filing

using xdata or the object’s extension dictionary. Other

common operations on objects, such as filing and

erasing, are also discussed.

81
Opening and Closing Database Objects
Each AcDbObject object can be referred to in three different ways:

■ By its handle
■ By its object ID
■ By a C++ instance pointer

When AutoCAD is not running, the drawing is stored in the file system.
Objects contained in a DWG file are identified by their handles.
After the drawing is opened, the drawing information is accessible through
the AcDbDatabase object. Each object in the database has an object ID, which
persists throughout the current edit session, from creation until deletion of
the AcDbDatabase in which the object resides. The open functions take an
object ID as an argument and return a pointer to an AcDbObject object. This
pointer is valid until the object is closed, as shown in the following figure.

DWG
Handle open drawing

SAVE or
WBLOCK AcDbDatabase
command ObjectID open object

close object
C++
Pointer

You can open an object using the acdbOpenObject() function:


Acad::ErrorStatus
AcDbDatabase::acdbOpenObject(AcDbObject*& obj,
AcDbObjectId id,
AcDb::OpenMode mode,
Adesk::Boolean
openErasedObject =
Adesk::kFalse)
You can map a handle to an object ID using this function:
Acad::ErrorStatus
getAcDbObjectId(AcDbObjectId& retId,
Adesk::Boolean createIfNotFound,
const AcDbHandle& objHandle,
Adesk::UInt32 xRefId=0);

82 | Chapter 5 Database Objects


You can also open an object and then request its handle:
AcDbObject* pObject;
AcDbHandle handle;

pObject->getAcDbHandle(handle);

NOTE Whenever a database object is opened, it should be closed at the earli-


est possible opportunity. You can use the AcDbObject::close() function to
close a database object.

An ads_name is equivalent to an AcDbObjectId. The AcDb library provides


two standalone functions that allow you to translate between an
AcDbObjectId and an ads_name:

// Returns an ads_name for a given object ID.


//
acdbGetAdsName(ads_name& objName,
AcDbObjectId objId);

// Returns an object ID for a given ads_name.


//
acdbGetObjectId(AcDbObjectId& objId,
ads_name objName);
Generally, you obtain an object through a selection, and it is returned in
ads_name form. You then need to exchange the ads_name for an
AcDbObjectId and open it. The following function demonstrates this process:

AcDbEntity*
selectEntity(AcDbObjectId& eId, AcDb::OpenMode openMode)
{
ads_name en;
ads_point pt;
acedEntSel("\nSelect an entity: ", en, pt);

// Exchange the ads_name for an object ID.


//
acdbGetObjectId(eId, en);

AcDbEntity * pEnt;
acdbOpenObject(pEnt, eId, openMode);

return pEnt;
}

Opening and Closing Database Objects | 83


You can open an object in one of three modes:

■ kForRead. An object can be opened for read by up to 256 readers as long


as the object is not already open for write or for notify.
■ kForWrite. An object can be opened for write if it is not already open.
Otherwise, the open fails.
■ kForNotify. An object can be opened for notification when the object is
closed, open for read, or open for write, but not when it is already open
for notify. See chapter 15, “Notification.” Applications will rarely need to
open an object for notify and send it notification.

The following table shows the error codes returned when you attempt to
open an object in different modes and the object is already open.

Opening objects in different modes

Object already opened for: kForRead kForWrite kForNotify

openedForRead eAtMaxReaders eWasOpenForRead (Succeeds)


(if readCount = 256;
otherwise succeeds)

openedForWrite eWasOpenForWrite eWasOpenForWrite (Succeeds)

openedForNotify eWasOpenForNotify eWasOpenForNotify eWasOpenForNotify

wasNotifying (Succeeds) eWasNotifying eWasNotifying

Undo eWasOpenForUndo eWasOpenForUndo (Succeeds)

If you are trying to open an object for write and you receive an error
eWasOpenForRead, you can use upgradeOpen() to upgrade the open status to
write if there is only one reader of the object. Then you would use
downgradeOpen() to downgrade its status to read. Similarly, if your object is
open for notify—for example, when you are receiving notification—and you
want to open it for write, you can use upgradeFromNotify() to upgrade its
open status to write. Then you would use downgradeToNotify() to down-
grade its status to notify.
For more information about how to manage complex sequences of opening
and closing objects, see “Transaction Manager” on page 451.

84 | Chapter 5 Database Objects


Deleting Objects
When you create an instance of an AcDbObject object with the intent of
appending it to the database, use the AcDbObject::new() function. When an
object is first created and has not yet been added to the database, you can
delete it. However, once an object has been added to the database, you can-
not delete it; AutoCAD manages the deletion of all database-resident objects.

Database Ownership of Objects


An object that is implicitly owned by the database rather than another data-
base object is called a root object. The database contains ten root objects: the
nine symbol tables and the named object dictionary. All filing operations
begin by filing out the root objects of the database. See “Object Filing” on
page 95.
With the exception of root objects, every object in the database must have
an owner, and a given object can have only one owner. The database is a tree
created by this hierarchy of owned objects. The following call adds an object
to the database and assigns an ID to it, but the object does not yet have an
owner:
db->addAcDbObject(...);
Usually, you will add the object to its owner using a member function that
simultaneously adds it to the database, such as the
AcDbBlockTableRecord::appendAcDbEntity() function, which performs
both tasks at once.
AutoCAD ownership connections are as follows:

■ The block table records own entities.


■ Each symbol table owns a particular type of symbol table record.
■ An AcDbDictionary object can own any AcDbObject object.
■ Any AcDbObject object can have an extension dictionary; an object owns
its extension dictionary.

In addition, applications can set up their own ownership connections.

Deleting Objects | 85
Adding Object-Specific Data
You can use any of four mechanisms for adding instance-specific data in your
application:

■ Extended data (xdata)


■ Xrecords (see chapter 7, “Container Objects”)
■ Extension dictionaries of any object
■ Custom objects that can hold data
(see chapter 12, “Deriving from AcDbObject”)

Extended Data
Extended data (xdata) is created by applications written with ObjectARX or
AutoLISP and can be added to any object. Xdata consists of a linked list of
resbufs used by the application. (AutoCAD maintains the information but
doesn’t use it.) The data is associated with a DXF group code in the range of
1000 to 1071.
This mechanism is space-efficient and can be useful for adding lightweight
data to an object. However, xdata is limited to 16K and to the existing set of
DXF group codes and types.
For a more detailed description of xdata, see the
AutoCAD Customization Guide.
Use the AcDbObject::xData() function to obtain the resbuf chain contain-
ing a copy of the xdata for an object:
virtual resbuf*
AcDbObject::xData(const char* regappName = NULL) const;
Use the AcDbObject::setXData() function to specify the xdata for an object:
virtual Acad::ErrorStatus
AcDbObject::setXData(const resbuf* xdata);
The following example uses the xData() function to obtain the xdata for a
selected object and then prints the xdata to the screen. It then adds a string
(testrun) to the xdata and calls the setXdata() function to modify the
object’s xdata. This example also illustrates the use of the upgradeOpen() and
downgradeOpen() functions.

// This function calls the selectObject() function to allow


// the user to pick an object; then it accesses the xdata of
// the object and sends the list to the printList() function
// that lists the restype and resval values.
//

86 | Chapter 5 Database Objects


void
printXdata()
{
// Select and open an object.
//
AcDbObject *pObj;
if ((pObj = selectObject(AcDb::kForRead)) == NULL) {
return;
}

// Get the application name for the xdata.


//
char appname[133];
if (acedGetString(NULL,
"\nEnter the desired Xdata application name: ",
appname) != RTNORM)
{
return;
}

// Get the xdata for the application name.


//
struct resbuf *pRb;
pRb = pObj->xData(appname);
if (pRb != NULL) {
// Print the existing xdata if any is present.
// Notice that there is no -3 group, as there is in
// LISP. This is ONLY the xdata, so
// the -3 xdata-start marker isn’t needed.
//
printList(pRb);
acutRelRb(pRb);
} else {
acutPrintf("\nNo xdata for this appname");
}
pObj->close();
}

void
addXdata()
{
AcDbObject* pObj = selectObject(AcDb::kForRead);
if (!pObj) {
acutPrintf("Error selecting object\n");
return;
}

// Get the application name and string to be added to


// xdata.
//

Adding Object-Specific Data | 87


char appName[132], resString[200];
appName[0] = resString[0] = ’\0’;
acedGetString(NULL, "Enter application name: ",
appName);
acedGetString(NULL, "Enter string to be added: ",
resString);

struct resbuf *pRb, *pTemp;

pRb = pObj->xData(appName);
if (pRb != NULL) {
// If xdata is present, then walk to the
// end of the list.
//
for (pTemp = pRb; pTemp->rbnext != NULL;
pTemp = pTemp->rbnext)
{ ; }
} else {
// If xdata is not present, register the application
// and add appName to the first resbuf in the list.
// Notice that there is no -3 group as there is in
// AutoLISP. This is ONLY the xdata so
// the -3 xdata-start marker isn’t needed.
//
acdbRegApp(appName);
pRb = acutNewRb(AcDb::kDxfRegAppName);
pTemp = pRb;
pTemp->resval.rstring
= (char*) malloc(strlen(appName) + 1);
strcpy(pTemp->resval.rstring, appName);
}

// Add user-specified string to the xdata.


//
pTemp->rbnext = acutNewRb(AcDb::kDxfXdAsciiString);
pTemp = pTemp->rbnext;
pTemp->resval.rstring
= (char*) malloc(strlen(resString) + 1);
strcpy(pTemp->resval.rstring, resString);

// The following code shows the use of upgradeOpen()


// to change the entity from read to write.
//
pObj->upgradeOpen();
pObj->setXData(pRb);

pObj->close();
acutRelRb(pRb);
}

88 | Chapter 5 Database Objects


Extension Dictionary
Every object can have an extension dictionary, which can contain an arbi-
trary set of AcDbObject objects. Using this mechanism, several applications
can attach data to the same object. The extension dictionary requires more
overhead than xdata, but it also provides a more flexible mechanism with
higher capacity for adding data.
For an example of using an extension dictionary to attach an arbitrary string
to any AcDbObject, see the edinvent program in the samples directory.
ObjectARX Example
The following example shows instantiating an xrecord and adding it to an
extension dictionary in the named object dictionary:
void
createXrecord()
{
AcDbXrecord *pXrec = new AcDbXrecord;
AcDbObject *pObj;
AcDbObjectId dictObjId, xrecObjId;
AcDbDictionary* pDict;

pObj = selectObject(AcDb::kForWrite);
if (pObj == NULL) {
return;
}

// Try to create an extension dictionary for this


// object. If the extension dictionary already exists,
// this will be a no-op.
//
pObj->createExtensionDictionary();

// Get the object ID of the extension dictionary for the


// selected object.
//
dictObjId = pObj->extensionDictionary();
pObj->close();

// Open the extension dictionary and add the new


// xrecord to it.
//
acdbOpenObject(pDict, dictObjId, AcDb::kForWrite);
pDict->setAt("ASDK_XREC1", pXrec, xrecObjId);
pDict->close();

Adding Object-Specific Data | 89


// Create a resbuf list to add to the xrecord.
//
struct resbuf* head;
ads_point testpt = {1.0, 2.0, 0.0};
head = acutBuildList(AcDb::kDxfText,
"This is a test Xrecord list",
AcDb::kDxfXCoord, testpt,
AcDb::kDxfReal, 3.14159,
AcDb::kDxfAngle, 3.14159,
AcDb::kDxfColor, 1,
AcDb::kDxfInt16, 180,
0);

// Add the data list to the xrecord. Notice that this


// member function takes a reference to a resbuf NOT a
// pointer to a resbuf, so you must dereference the
// pointer before sending it.
//
pXrec->setFromRbChain(*head);
pXrec->close();
acutRelRb(head);
}

// The listXrecord() function gets the xrecord associated with the


// key "ASDK_XREC1" and lists out its contents by passing the resbuf
// list to the function printList().
//
void
listXrecord()
{
AcDbObject *pObj;
AcDbXrecord *pXrec;
AcDbObjectId dictObjId;
AcDbDictionary *pDict;
pObj = selectObject(AcDb::kForRead);
if (pObj == NULL) {
return;
}

// Get the object ID of the object’s extension dictionary.


//
dictObjId = pObj->extensionDictionary();
pObj->close();

// Open the extension dictionary and get the xrecord


// associated with the key ASDK_XREC1.
//
acdbOpenObject(pDict, dictObjId, AcDb::kForRead);
pDict->getAt("ASDK_XREC1", (AcDbObject*&)pXrec,
AcDb::kForRead);
pDict->close();

90 | Chapter 5 Database Objects


// Get the xrecord’s data list and then close the xrecord.
//
struct resbuf *pRbList;
pXrec->rbChain(&pRbList);
pXrec->close();

printList(pRbList);
acutRelRb(pRbList);
}
Global Function Example
The following example uses global ObjectARX functions to create an xrecord
and add it to the dictionary associated with the key ASDK_REC.
int
createXrecord()
{
struct resbuf *pXrec, *pEnt, *pDict, *pTemp, *pTemp2;
ads_point dummy, testpt = {1.0, 2.0, 0.0};
ads_name xrecname, ename, extDict = {0L, 0L};

// Have the user select an entity. Then get its data.


//
if (acedEntSel("\nselect entity: ", ename, dummy)
!= RTNORM)
{
acutPrintf("\nNothing selected");
acedRetVoid();
return RTNORM;
}

pEnt = acdbEntGet(ename);

// Now check to see if the entity already has an


// extension dictionary.
//
for (pTemp = pEnt; pTemp->rbnext != NULL;
pTemp = pTemp->rbnext)
{
if (pTemp->restype == 102) {
if (!strcmp("{ACAD_XDICTIONARY",
pTemp->resval.rstring))
{
ads_name_set(pTemp->rbnext->resval.rlname, extDict);
break;
}
}
}

Adding Object-Specific Data | 91


// If no extension dictionary exists, add one.
//
if (extDict[0] == 0L) {
pDict = acutBuildList(RTDXF0, "DICTIONARY", 100,
"AcDbDictionary", 0);
acdbEntMakeX(pDict, extDict);
acutRelRb(pDict);

pDict = acutBuildList(102, "{ACAD_XDICTIONARY", 360,


extDict, 102, "}", 0);

for (pTemp = pEnt; pTemp->rbnext->restype != 100;


pTemp = pTemp->rbnext)
{ ; }
for (pTemp2 = pDict; pTemp2->rbnext != NULL;
pTemp2 = pTemp2->rbnext)
{ ; }

pTemp2->rbnext = pTemp->rbnext;
pTemp->rbnext = pDict;
acdbEntMod(pEnt);
acutRelRb(pEnt);
}

// At this point the entity has an extension dictionary.


// Create a resbuf list of the xrecord’s entity information
// and data.
//
pXrec = acutBuildList(RTDXF0, "XRECORD",
100, "AcDbXrecord",
1, "This is a test Xrecord list", //AcDb::kDxfText
10, testpt, //AcDb::kDxfXCoord
40, 3.14159, //AcDb::kDxfReal
50, 3.14159, //AcDb::kDxfAngle
60, 1, //AcDb::kDxfColor
70, 180, //AcDb::kDxfInt16
0);

// Create the xrecord with no owner set. The xrecord’s


// new entity name will be placed into the xrecname
// argument.
//
acdbEntMakeX (pXrec, xrecname);
acutRelRb (pXrec);
// Set the xrecord’s owner to the extension dictionary
//
acdbDictAdd(extDict, "ASDK_XRECADS", xrecname);
acedRetVoid();
return RTNORM;
}

// Accesses the xrecord associated with the key ASDK_XRECADS in


// the extension dictionary of a user-selected entity. Then
// list out the contents of this xrecord using the printList
// function.
//

92 | Chapter 5 Database Objects


int
listXrecord()
{
struct resbuf *pXrec, *pEnt, *pTemp;
ads_point dummy;
ads_name ename, extDict = {0L, 0L};

// Have the user select an entity; then get its data.


//
if (acedEntSel("\nselect entity: ", ename, dummy) != RTNORM) {
acutPrintf("\nNothing selected");
acedRetVoid();
return RTNORM;
}

pEnt = acdbEntGet(ename);

// Get the entity name of the extension dictionary.


//
for (pTemp = pEnt;pTemp->rbnext != NULL;pTemp = pTemp->rbnext) {
if (pTemp->restype == 102) {
if (!strcmp("{ACAD_XDICTIONARY", pTemp->resval.rstring)){
ads_name_set(pTemp->rbnext->resval.rlname, extDict);
break;
}
}
}

if (extDict[0] == 0L) {
acutPrintf("\nNo extension dictionary present.");
return RTNORM;
}

pXrec = acdbDictSearch(extDict, "ASDK_XRECADS", 0);


if(pXrec) {
printList(pXrec);
acutRelRb(pXrec);
}

acedRetVoid();
return RTNORM;
}

Adding Object-Specific Data | 93


Erasing Objects
Any object in the database can be erased with the following function:
Acad::ErrorStatus
AcDbObject::erase(Adesk::Boolean Erasing = Adesk::kTrue);

NOTE The erase() function has different results for database objects and
entities, with consequences for unerasing them:

■ When a database object is erased, information about that object is


removed from the dictionary. If the object is unerased with
erase(kfalse), the information is not automatically reintroduced. You
must use the setAt() function to add the information to the dictionary
again.
■ When an entity is erased, it is simply flagged as erased in the block table
record. The entity can be unerased with erase(kfalse).

By default, you cannot open an erased object with the acdbOpenObject()


function. If you attempt to do so, the eWasErased error code will be returned.
extern Acad::ErrorStatus
acdbOpenObject(AcDbObject*& obj,
AcDbObjectId objId,
AcDb::OpenMode openMode,
Adesk::Boolean openErasedObject =
Adesk::kFalse);
To open an erased object, use kTrue for the last parameter of the
acdbOpenObject() function.

Container objects such as polylines and block table records usually provide
the option of skipping erased elements when iterating over their contents.
The default behavior is to skip erased elements.
Erased objects are not filed out to DWG or DXF files.

94 | Chapter 5 Database Objects


Object Filing
Object filing refers to the conversion process between an object’s state and a
single sequence of data, for purposes such as storing it on disk, copying it, or
recording its state for an undo operation. Filing out is sometimes called seri-
alizing. Filing an object in is the process of turning a sequence of data back
into an object, sometimes called deserializing.
Filing is used in several contexts in AutoCAD:

■ Writing and reading DWG files (uses DWG format)


■ Writing and reading DXF files (uses DXF format)
■ Communicating among AutoCAD, AutoLISP, and ObjectARX (uses DXF
format)
■ Undo recording and restoring (uses DWG format)
■ Copying operations such as INSERT, XREF, and COPY (uses DWG format)
■ Paging (uses DWG format)

AcDbObject has two member functions for filing out: dwgOut() and
dxfOut(), and two member functions for filing in: dwgIn() and dxfIn().
These member functions are primarily called by AutoCAD; object filing is
almost never explicitly controlled by applications that use the database.
However, if your application implements new database object classes, you’ll
need a more in-depth understanding of object filing. See chapter 12,
“Deriving from AcDbObject.”
The dwg- and dxf- prefixes indicate two fundamentally different data
formats, the first typically used in writing to and from DWG files, and the
second primarily for DXF files and AutoLISP entget, entmake, and entmod
functions. The primary difference between the two formats is that for DWG
filers (an object that writes data to a file), the data is not explicitly tagged.
The DXF filers, in contrast, associate a data group code with every element of
data in a published data format (see chapter 12, “Deriving from
AcDbObject”).

Object Filing | 95
96
Entities

In This Chapter
6
This chapter describes entities—database objects with a ■ Entities Defined
■ Entity Ownership
graphical representation. It lists the properties and
■ AutoCAD Release 12 Entities
operations all entities have in common. Examples show ■ Common Entity Properties

how to create blocks, inserts, and complex entities, and ■ Common Entity Functions
■ Creating Instances of AutoCAD
how to select and highlight subentities. Entities
■ Complex Entities
■ Coordinate System Access
■ Curve Functions
■ Associating Hyperlinks with
Entities

97
Entities Defined
An entity is a database object that has a graphical representation. Examples
of entities include lines, circles, arcs, text, solids, regions, splines, and
ellipses. The AcDbEntity class is derived from AcDbObject.
With a few exceptions, entities contain all necessary information about their
geometry. A few entities contain other objects that hold their geometric
information or attributes. Complex entities include the following:

■ AcDb2dPolyline, which owns AcDb2dPolylineVertex objects


■ AcDb3dPolyline, which owns AcDb3dPolylineVertex objects
■ AcDbPolygonMesh, which owns AcDbPolygonMeshVertex objects
■ AcDbPolyFaceMesh, which owns AcDbPolyFaceMeshVertex objects and
AcDbFaceRecord objects
■ AcDbBlockReference, which owns AcDbAttribute objects
■ AcDbMInsertBlock, which owns AcDbAttribute objects

Examples of creating and iterating through complex entities are provided in


“Complex Entities” on page 134.

98 | Chapter 6 Entities
Entity Ownership
Entities in the database normally belong to an AcDbBlockTableRecord. The
block table in a newly created database has three predefined records,
*MODEL_SPACE, *PAPER_SPACE, and *PAPER_SPACE0, which represent
model space and the two pre-defined paper space layouts. Additional records
are added whenever the user creates new blocks (block records), typically by
issuing a BLOCK, HATCH, or DIMENSION command.
The ownership structure for database entities is as follows:

Entity Ownership | 99
AcDbDatabase

AcDbBlockTable

AcDbBlockTableRecord

AcDbBlockBegin AcDbEntity AcDbBlockEnd

AcDbxxxVertex or AcDbSequenceEnd
AcDbFaceRecord or
AcDbAttribute

AutoCAD Release 12 Entities


The following entities were included in AutoCAD Release 12 and are declared
in the dbents.h file. You cannot safely derive new classes from the following
Release 12 entities:

■ AcDb2dPolyline
■ AcDb3dPolyline
■ AcDbPolygonMesh
■ AcDbPolyFaceMesh
■ AcDbSequenceEnd
■ AcDbBlockBegin
■ AcDbBlockEnd
■ AcDbVertex
■ AcDbFaceRecord
■ AcDb2dVertex
■ AcDb3dPolylineVertex
■ AcDbPolygonMeshVertex
■ AcDbPolyFaceMeshVertex
■ AcDbMInsertBlock

100 | Chapter 6 Entities


Common Entity Properties
All entities have a number of common properties and include member func-
tions for setting and getting their values. These properties, which can also be
set by user commands, are the following:

■ Color
■ Linetype
■ Linetype scale
■ Visibility
■ Layer
■ Line weight
■ Plot style name

When you add an entity to a block table record, AutoCAD automatically


invokes the AcDbEntity::setDatabaseDefaults() function, which sets the
properties to their default values if you have not explicitly set them.
AcDbViewport acquires the settings of the current graphics window.

If a property has not been explicitly specified for an entity, the database’s cur-
rent value for that property is used. See chapter 4, “Database Operations,” for
a description of the member functions used for setting and getting the cur-
rent property values associated with the database.

Entity Color
Entity color can be set and read as numeric index values ranging from 0 to
256, or by instances of AcCmColor, which is provided for future use by an
expanded color model. Currently, AutoCAD uses color indexes only. The cor-
rect color index can be obtained from an instance of AcCmColor using the
AcCmColor::getColorIndex() member function.

Color indexes 1 through 7 are used for standard colors, as shown in the fol-
lowing table:

Colors 1 to 7

Color Number Color Name

1 Red

2 Yellow

3 Green

Common Entity Properties | 101


Colors 1 to 7 (continued)

Color Number Color Name

4 Cyan

5 Blue

6 Magenta

7 White or Black

Colors 8 through 255 are defined by the display device.


The following index values have special meanings:
0 Specifies BYBLOCK. Entities inherit the color of the
current block reference that points to the block table
record that the entity resides in, or black/white if the
entity resides directly in the model space or paper space
block table record.
256 Specifies BYLAYER. Entities assume the color of the
entity’s associated layer.
257 No color. Only present from the time an entity is first
instantiated until its color is set to a value between 0 and
256, or the entity is added to the database and assumes
the database’s current color index.
If a color value is specified for an entity, the current database default color
value is ignored. Use the following functions to set and query an entity color:
virtual Acad::ErrorStatus
AcDbEntity::setColorIndex(Adesk::UInt16 color);

Adesk::UInt16
AcDbEntity::colorIndex() const;

Entity Linetype
The linetype value points to a symbol table entry that specifies a series of dots
and dashes used for drawing lines. When an entity is instantiated, its line-
type is set to NULL. When the entity is added to the database, if a linetype has
not been specified for the entity, the linetype is set to the database’s current
linetype value. This default value is stored in the CELTYPE system variable.
Linetype can be specified by name, by a string, or by the object ID of an
AcDbLineTypeTableRecord in the entity’s target database.

102 | Chapter 6 Entities


Special linetype entries are as follows:
CONTINUOUS Default linetype, which is automatically created in the
linetype symbol table
BYLAYER Linetype value of the entity’s layer
BYBLOCK Linetype value of the entity’s surrounding block
definition’s current block reference
If a linetype value is specified for an entity, the current database default line-
type value is ignored.
The following functions enable you to set the linetype for an entity, either by
name or by object ID:
virtual Acad::ErrorStatus
AcDbEntity::setLinetype(const char* newVal);

virtual Acad::ErrorStatus
AcDbEntity::setLinetype(AcDbObjectId newVal);
This function returns the name of the current entity linetype:
char* AcDbEntity::linetype() const;
This function returns the object ID for the symbol table record specifying the
linetype:
AcDbObjectId AcDbEntity::linetypeId() const;

Entity Linetype Scale


When an entity is first instantiated, its linetype scale is initialized to an
invalid value. When the entity is added to the database, if a linetype scale has
not been specified for the entity, it is set to the database’s current linetype
scale value. This database default value is stored in the CELTSCALE system
variable.

Linetype Scale Specified Per Entity


If a linetype scale value is specified for an entity, the current database default
linetype scale value is ignored.
The following functions allow you to set and inquire the linetype scale for an
entity:
Acad::ErrorStatus
AcDbEntity::setLinetypeScale(double newVal);

double
AcDbEntity::linetypeScale() const;

Common Entity Properties | 103


Regenerating a Drawing
When an entity is regenerated, its effective linetype scale is a product of both
the entity linetype scale and the global database linetype scale. For nonpaper
space entities, the linetype scale is calculated as follows:
effltscale = ent->linetypeScale() *
ent->database()->ltscale();
If PSLTSCALE is 1, the effective linetype scale is then applied to the appearance
of the model space entity when viewed in paper space. If PSLTSCALE is 0, then
all linetype scaling is performed with respect to model space views. See the
AutoCAD User’s Guide for further explanation of linetype scales.

Entity Visibility
If you specify that an entity is invisible, it will be invisible regardless of other
settings in the database. Other factors can also cause an entity to be invisible.
For example, an entity will not be displayed if its layer is turned off or frozen.
The value of AcDb::Visibility can be either kInvisible or kVisible.
Acad::ErrorStatus
AcDbEntity::setVisibility(AcDb::Visibility newVal);

AcDb::Visibility
AcDbEntity::visibility() const;

Entity Layer
All entities have an associated layer. The database always contains at least
one layer (layer 0). As with linetypes, you can specify a layer for an entity. If
you don’t specify a layer, the default database layer value is used for a new
entity.
Each layer also has associated properties, which include frozen/thawed,
on/off, locked/unlocked, color, linetype, and viewport (see chapter 7,
“Container Objects”). When an entity’s color or linetype is BYLAYER, the
value of the layer property is used for the entity.
If a layer value is specified for an entity, the current database layer value is
ignored.
The following functions enable you to set the layer for an entity, either by
name or by object ID:
Acad::ErrorStatus
AcDbEntity::setLayer(const char* newVal);

Acad::ErrorStatus
AcDbEntity::setLayer(AcDbObjectId newVal);

104 | Chapter 6 Entities


This function returns the name of the current entity layer:
char* AcDbEntity::layer() const;
This function returns the object ID for the current layer (an object of type
AcDbLayerTableRecord):

AcDbObjectId AcDbEntity::layerId() const;

Common Entity Functions


Entities also have a number of common functions, primarily intended for use
by AutoCAD. This section provides general background on using some of
these functions. For examples of implementing the functions for new classes,
see chapter 13, “Deriving from AcDbEntity.”
Common entity functions include the following:

■ intersectWith() is used in trim, extend, fillet, chamfer, break, and object


snap Intersection operations
■ transformBy() is used to pass in a transform matrix that moves, scales, or
rotates points in the object
■ getTransformedCopy() creates a copy of the object and applies a transfor-
mation to it
■ getOsnapPoints() returns the snap points and the kind of snap points
■ getGripPoints() returns the grip points, which are a superset of the
stretch points
■ getStretchPoints() defaults to getGripPoints() and usually has the
same implementation
■ moveStretchPointsAt() is used by the AutoCAD STRETCH command to
move specified points and defaults to transformBy()
■ moveGripPointsAt() is used by AutoCAD grip editing to move specified
points and defaults to transformBy()
■ worldDraw() creates a view-independent geometric representation of an
entity
■ viewportDraw() creates a view-dependent geometric representation of an
entity
■ draw() queues up the entity and flushes the graphics queue so that the
entity and anything else in the queue are drawn
■ list() is used by the AutoCAD LIST command and produces
acutPrintf() statements
■ getGeomExtents() returns the corner points of a box that encloses the 3D
extents of your entity
■ explode() decomposes an entity into a set of simpler elements

Common Entity Functions | 105


■ getSubentPathsAtGsMarker() returns the subentity paths that corre-
spond to the given GS marker (see “GS Markers and Subentities” on page
109)
■ getGsMarkersAtSubentPath() returns the GS marker that corresponds to
the given subentity path
■ subentPtr() returns a pointer corresponding to the given subentity path
■ highlight() highlights the specified subentity (see “GS Markers and Sub-
entities” on page 109)

Object Snap Points


Objects can have certain characteristic points defined for them, such as a
center point, midpoint, or endpoint. When AutoCAD is acquiring points and
is in Object Snap mode, it invokes the getOsnapPoints() function to acquire
the relevant snap points for the specified Object Snap mode. The following
table lists the possible Object Snap modes.

Object Snap modes

Mode Description

kOsModeEnd Endpoint

kOsModeMid Midpoint

kOsModeCen Center

kOsModeNode Node

kOsModeQuad Quadrant

kOsModeIns Insertion

kOsModePerp Perpendicular

kOsModeTan Tangent

kOsModeNear Nearest

106 | Chapter 6 Entities


The signature for AcDbEntity::getOsnapPoints() is
virtual Acad::ErrorStatus
AcDbEntity::getOsnapPoints(
AcDb::OsnapMode osnapMode,
int gsSelectionMark,
const AcGePoint3d& pickPoint,
const AcGePoint3d& lastPoint,
const AcGeMatrix3d& viewXform,
AcGePoint3dArray& snapPoints,
AcDbIntArray& geomIds) const;
The geomIds argument is not currently used. Intersection object snap does
not use this function.

Transform Functions
The AcDbEntity class provides two transformation functions:
virtual Acad::ErrorStatus
AcDbEntity::transformBy(const AcGeMatrix3d& xform);

virtual Acad::ErrorStatus
AcDbEntity::getTransformedCopy(const AcGeMatrix3d& xform,
AcDbEntity*& ent) const;
The transformBy() function modifies the entity using the specified matrix.
In AutoCAD, it is called by the grip move, rotate, scale, and mirror modes. In
some cases, however, applying the transformation requires that a new entity
be created. In such cases, the getTransformedCopy() function is used so that
the resulting entity can be an instance of a different class than the original
entity.
When you explode a block reference that has been nonuniformly scaled, the
getTransformedCopy() function is called on the entities in the block refer-
ence to create the new entities (see “Exploding Entities” on page 123).

Intersecting for Points


The intersectWith() function returns the points where an entity intersects
another entity in the drawing. Input values for this function are the entity
and the intersection type, which can be one of the following:

■ kOnBothOperands (neither entity is extended)


■ kExtendThis
■ kExtendArg
■ kExtendBoth

For example, suppose a drawing contains the three lines shown in the
following illustration. Line1 is “this” and line3 is the argument entity. If the

Common Entity Functions | 107


intersection type is kExtendThis, point A is returned as the point where line1
(“this”) would intersect line3 if line1 were extended. If the intersection type
is kExtendArgument and line2 is the argument entity, no data is returned
because, even if it were extended, line2 would not intersect line1. If the
intersection type is kExtendBoth and line2 is the argument entity, point B is
returned. If the intersection type is kExtendNone and line2 is the argument
entity, no data is returned.

line1 ("this") line3

line2

The intersectWith() function is an overloaded function with two forms.


The second form takes an additional argument, which is a projection plane
for determining the apparent intersection of two entities. These are the sig-
natures for the intersectWith() function:
virtual Acad::ErrorStatus
AcDbEntity::intersectWith(
const AcDbEntity* ent,
AcDb::Intersect intType,
AcGePoint3dArray& points,
int thisGsMarker = 0,
int otherGsMarker = 0) const;

virtual Acad::ErrorStatus
AcDbEntity::intersectWith(
const AcDbEntity* ent,
AcDb::Intersect intType,
const AcGePlane& projPlane,
AcGePoint3dArray& points,
int thisGsMarker = 0,
int otherGsMarker = 0) const;
The returned points are always on the entity (“this”). Therefore, in cases of
apparent intersection, the intersected points are projected back to the entity
before they are returned.
Both versions of the intersectWith() function allow you to supply optional
GS markers to optimize performance for this function. If the entity’s
intersectWith() function has implemented the use of GS markers, then

108 | Chapter 6 Entities


supplying GS markers can localize the intersection area and speed up the test.
For example, in the following drawing, if the user selects one line of the poly-
gon, passing in the GS marker for that line eliminates the need to test the
other five lines of the polygon.

GS Markers and Subentities


To draw itself, every entity makes calls to graphics primitives such as
polylines, circles, and arcs, contained in the AcGi library. Any class derived
from AcDbEntity can associate a graphics system (GS) marker with the dis-
play vectors it uses to draw itself. Each entity subclass controls where it
inserts its GS markers. When a user selects an entity, the GS marker is used to
identify which part of the entity was picked.
Solids derived from AcDb3dSolid are composed of vertices, edges, and faces.
Each of these elements can be identified by a GS marker. The creator of the
entity class decides where GS markers should be inserted, depending on what
is most natural for the entity. A box, for example, creates a GS marker for
each line used to draw the box. A cylinder creates three GS markers—one for
its top, bottom, and outside faces.

3
2 12
1

9 10 8 11

6
5 7
4

An entity is composed of subentities of the following type: vertex, edge, or


face. Currently, the only entities that support subentities are bodies, regions,
solids, and mlines. Use the getSubentPathsAtGsMarker() function to obtain
the paths to the subentities that are associated with a particular GS marker.
More than one subentity can be associated with a single marker. In the case
of the box, for example, marker 4 identifies the lower front edge of the box.
If you ask for the vertices associated with this marker, the two vertices that

Common Entity Functions | 109


form the endpoints of this line are returned. If you ask for the edges associ-
ated with this marker, one entity—the line—is returned. If you ask for the
faces associated with this marker, data for the front face and the bottom face
of the box are returned.

Subentity Path
A subentity path uniquely identifies a subentity within a particular entity in
a drawing. This path, of class AcDbFullSubentPath, consists of an array of
object IDs and a subentity ID object:
{AcDbObjectIdArray mObjectIds;
AcDbSubentId mSubentId;
}
The array contains the object IDs that specify the path to the “main” entity.
For example, a block reference (an entity that references a block table record)
might contain two boxes, each of type AcDb3dSolid. The object ID array con-
tains two entries: the ID of the block reference, followed by the ID of the
main entity [InsertID, SolidID].
The second element of an AcDbFullSubentPath is an AcDbSubentId object,
which has a subentity type (vertex, edge, or face) and the index of the sub-
entity in the list. Use the AcDbSubentId functions type() and index() to
access the member data.
Using the previous example, the second edge of the solid will have its
AcDbFullSubentPath as

{(InsertID, solid1ID)
(kEdgeSubentType, 2)};
If you have a solid only, AcDbFullSubentPath would be as follows for the first
face of the solid.
{(solidID)
(kFaceSubentType, 1)};

110 | Chapter 6 Entities


Simple Highlighting Example
The code example later in this section shows how to highlight a subentity.
The following procedure lists the basic steps.

To highlight a subentity
1 Obtain the GS marker for the selected entity from the selection set.
2 Pass the GS marker to the entity class to be converted to a subentity path
using the getSubentPathsAtGsMarker() function. Specify the type of suben-
tity you’re interested in (vertex, edge, face).
3 Once you have the path to the selected subentity, you’re ready to call the
highlight() function, passing in the correct subentity path.

Selecting an Entity
For selection, you’ll use a combination of global functions. First, use the
acedSSGet() function to obtain the selection set. Then, use the
acedSSNameX() function to obtain a subentity GS marker for the selected
entity.
int acedSSGet(
const char *str,
const void *pt1,
const ads_point pt2,
const struct resbuf *entmask,
ads_name ss);
int acedSSNameX(
struct resbuf** rbpp,
const ads_name ss,
const longvi);
Converting GS Markers to Subentity Paths
Use the getSubentPathsAtGsMarker() function to obtain the subentity for
the GS marker returned by the acedSSNameX() function. The complete syntax
for this function is
virtual Acad::ErrorStatus
AcDbEntity::getSubentPathsAtGsMarker(
AcDb::SubentType type,
int gsMark,
const AcGePoint3d& pickPoint,
const AcGeMatrix3d& viewXform,
int& numPaths,
AcDbFullSubentPath*& subentPaths
int numInserts = 0,
AcDbObjectId* entAndInsertStack = NULL) const;
The first argument to this function is the type of subentity you’re interested
in (vertex, edge, or face). In the example code in “Highlighting the Suben-
tity,” the first call to this function specifies kEdgeSubentType because you’re

Common Entity Functions | 111


going to highlight the corresponding edge. The second call to the
getSubentPathsAtGsMarker() function specifies kFaceSubentType because
you’re going to highlight each face associated with the selected subentity.
The pickPoint and viewXform arguments are used as additional input for
some entities (such as mlines) when the GS marker alone does not provide
enough information to return the subentity paths. In the example code in
“Highlighting the Subentity,” they are not used.
The numInserts and entAndInsertStack arguments are used for nested
inserts. Both the acedNEntSel() and acedNEntSelP() functions return the
name of the leaf-level entity, plus a stack of inserts.
Highlighting the Subentity
Once you’ve obtained the subentity path to the selected entity, the hardest
part of this process is finished. Now, you need only call the highlight()
function and pass in the subentity path. If you call the highlight() function
without any arguments, the default is to highlight the whole entity.
The following sample code illustrates the steps described for selecting an
entity, obtaining a subentity path, and highlighting different types of suben-
tities associated with a GS marker. This code also illustrates another useful
subentity function:
virtual AcDbEntity*
AcDbEntity::subentPtr(const AcDbFullSubentPath& id) const;
This function returns a pointer to a copy of the subentity described by the
specified path, which can then be added to the database (as shown in the
example).

NOTE It is expected that you will need to override the functions


getSubentPathsAtGsMarker(), getGsMarkersAtSubentPath() and
subentPtr() when you are creating new subclasses of AcDbEntity. The
highlight() function, however, is implemented at the AcDbEntity level and is
not generally expected to be overridden. However, if it is overridden, any new
implementation of this function must call AcDbEntity::highlight() to per-
form the highlighting.

// This function calls getObjectAndGsMarker() to get the


// object ID of a solid and its gsmarker. It then calls
// highlightEdge(), highlightFaces(), and highlightAll() to
// highlight the selected edge, all faces surrounding that
// edge, and then the whole solid.
//

112 | Chapter 6 Entities


void
highlightTest()
{
AcDbObjectId objId;
int marker;

if (getObjectAndGsMarker(objId, marker) != Acad::eOk)


return;
highlightEdge(objId, marker);
highlightFaces(objId, marker);
highlightAll(objId);
}
// This function uses acedSSGet() to let the user select a
// single entity. It then passes this selection set to
// acedSSNameX() to get the gsmarker. Finally, the entity name
// in the selection set is used to obtain the object ID of
// the selected entity.
//
Acad::ErrorStatus
getObjectAndGsMarker(AcDbObjectId& objId, int& marker)
{
ads_name sset;
if (acedSSGet("_:S", NULL, NULL, NULL, sset) != RTNORM) {
acutPrintf("\nacedSSGet has failed");
return Acad::eInvalidAdsName;
}

// Get the entity from the selection set and its


// subentity ID. This code assumes that the user
// selected only one item, a solid.
//
struct resbuf *pRb;
if (acedSSNameX(&pRb, sset, 0) != RTNORM) {
acedSSFree(sset);
return Acad::eAmbiguousOutput;
}
acedSSFree(sset);

// Walk the list to the third item, which is the selected


// entity’s entity name.
//
struct resbuf *pTemp;
int i;
for (i=1, pTemp = pRb;i<3;i++, pTemp = pTemp->rbnext)
{ ; }
ads_name ename;
ads_name_set(pTemp->resval.rlname, ename);

Common Entity Functions | 113


// Move on to the fourth list element, which is the gsmarker.
//
pTemp = pTemp->rbnext;
marker = pTemp->resval.rint;
acutRelRb(pRb);
acdbGetObjectId(objId, ename);

return Acad::eOk;
}

// This function accepts an object ID and a gsmarker.


// The object is opened, the gsmarker is used to get the
// AcDbFullSubentIdPath, which is then used to highlight
// and unhighlight the edge used to select the object.
// Next, the object’s subentPtr() function is used to get
// a copy of the edge. This copy is then added to the
// database. Finally, the object is closed.
//
void
highlightEdge(const AcDbObjectId& objId, const int marker)
{
char dummy[133]; // space for acedGetString pauses below
AcDbEntity *pEnt;
acdbOpenAcDbEntity(pEnt, objId, AcDb::kForRead);
// Get the subentity ID for the edge that is picked
//
AcGePoint3d pickpnt;
AcGeMatrix3d xform;
int numIds;
AcDbFullSubentPath *subentIds;

pEnt->getSubentPathsAtGsMarker(AcDb::kEdgeSubentType,
marker, pickpnt, xform, numIds, subentIds);
// At this point the subentId’s variable contains the
// address of an array of AcDbFullSubentPath objects.
// The array should be one element long, so the picked
// edge’s AcDbFullSubentPath is in subentIds[0].
//
// For objects with no edges (such as a sphere), the
// code to highlight an edge is meaningless and must
// be skipped.
//
if (numIds > 0) {
// Highlight the edge.
//
pEnt->highlight(subentIds[0]);

// Pause to let user see the effect.


//
acedGetString(0, "\npress <RETURN> to continue...",
dummy);

114 | Chapter 6 Entities


// Unhighlight the picked edge.
//
pEnt->unhighlight(subentIds[0]);

// Get a copy of the edge, and add it to the database.


//
AcDbEntity *pEntCpy = pEnt->subentPtr(subentIds[0]);
AcDbObjectId objId;
addToModelSpace(objId, pEntCpy);
}
delete []subentIds;
pEnt->close();
}

// This function accepts an object ID and a gsmarker.


// The object is opened, the gsmarker is used to get the
// AcDbFullSubentIdPath, which is then used to highlight
// and unhighlight faces that share the edge used to
// select the object. The object is then closed.
//
void
highlightFaces(const AcDbObjectId& objId, const int marker)
{
char dummy[133];
AcDbEntity *pEnt;

acdbOpenAcDbEntity(pEnt, objId, AcDb::kForRead);

// Get the subentIds for the faces.


//
AcGePoint3d pickpnt;
AcGeMatrix3d xform;
int numIds;
AcDbFullSubentPath *subentIds;

pEnt->getSubentPathsAtGsMarker(AcDb::kFaceSubentType,
marker, pickpnt, xform, numIds, subentIds);

// Walk the subentIds list, highlighting each face subentity.


//
for (int i = 0;i < numIds; i++) {
pEnt->highlight(subentIds[i]); // Highlight face.

// Pause to let the user see the effect.


//
acedGetString(0, "\npress <RETURN> to continue...",
dummy);
pEnt->unhighlight(subentIds[i]);
}
delete []subentIds;
pEnt->close();
}

Common Entity Functions | 115


// This function accepts an object ID. The object is opened,
// and its highlight() and unhighlight() functions are
// used with no parameters, to highlight and
// unhighlight the edge used to select the object. The
// object is then closed.
//
void
highlightAll(const AcDbObjectId& objId)
{
char dummy[133];
AcDbEntity *pEnt;

acdbOpenAcDbEntity(pEnt, objId, AcDb::kForRead);

// Highlight the whole solid.


//
pEnt->highlight();

// Pause to let user see the effect.


//
acedGetString(0, "\npress <RETURN> to continue...",
dummy);
pEnt->unhighlight();
pEnt->close();
}

Acad::ErrorStatus
addToModelSpace(AcDbObjectId &objId, AcDbEntity* pEntity)
{
AcDbBlockTable *pBlockTable;
AcDbBlockTableRecord *pSpaceRecord;

acdbHostApplicationServices()->workingDatabase()
->getSymbolTable(pBlockTable, AcDb::kForRead);
pBlockTable->getAt(ACDB_MODEL_SPACE, pSpaceRecord,
AcDb::kForWrite);

pSpaceRecord->appendAcDbEntity(objId, pEntity);

pBlockTable->close();
pEntity->close();
pSpaceRecord->close();

return Acad::eOk;

Highlighting Nested Block References


The example that follows shows highlighting nested block references. As
shown in the following figure, the example creates six entities: three polys (a
custom entity) and three boxes. It also creates three block references (inserts).
Insert 3 (ins3) is an insert of a block that contains poly3 and box3. Insert 2
(ins2) is an insert of a block that contains poly2, box2, and ins3. Insert 1
(ins1) is an insert of a block that contains poly1, box1, and ins2.

116 | Chapter 6 Entities


After the inserts are created, the example highlights the different
components.

ins1

ins2

ins3

poly1 poly2 poly3

box1 box2 box3

void
createInsert()
{
// Create a nested insert and try highlighting its
// various subcomponents.
//
// There are six entities in total -- three polys and
// three boxes (solids). We’ve named them: poly1, poly2,
// poly3, and box1, box2, box3. We also have three
// inserts: ins1, ins2, ins3.
//
// ins3 is an insert of a block that contains (poly3, box3)
// ins2 is an insert of a block that contains (poly2, box2,
// ins3).
// ins1 is an insert of a block that contains (poly1, box1,
// ins2).
//
// Let's create these entities first.
//
// Polys
//
AsdkPoly *poly1, *poly2, *poly3;
AcGeVector3d norm(0, 0, 1);
if ((poly1=new AsdkPoly)==NULL){
acutPrintf("\nOut of Memory.");
return;
}
if (poly1->set(AcGePoint2d(2, 8),AcGePoint2d(4, 8), 6, norm,
"POLY1",0)!=Acad::eOk){
acutPrintf("\nCannot create object with given parameters.");
delete poly1;
return;
}

Common Entity Functions | 117


if ((poly2=new AsdkPoly)==NULL){
acutPrintf("\nOut of Memory.");
delete poly1;
return;
}
if (poly2->set(AcGePoint2d(7, 8), AcGePoint2d(9, 8), 6, norm,
"POLY2",0)!=Acad::eOk){
acutPrintf("\nCannot create object with given parameters.");
delete poly1;
delete poly2;
return;
}
if ((poly3=new AsdkPoly)==NULL){
acutPrintf("\nOut of Memory.");
delete poly1;
delete poly2;
return;
}
if (poly3->set(AcGePoint2d(12, 8),AcGePoint2d(14, 8), 6, norm,
"POLY3",0)!=Acad::eOk){
acutPrintf("\nCannot create object with given parameters.");
delete poly1;
delete poly2;
delete poly3;
return;
}
postToDb(poly1);
postToDb(poly2);
postToDb(poly3);

// Boxes
//
AcDb3dSolid *box1, *box2, *box3;
box1 = new AcDb3dSolid();
box2 = new AcDb3dSolid();
box3 = new AcDb3dSolid();

box1->createBox(2, 2, 2);
box2->createBox(2, 2, 2);
box3->createBox(2, 2, 2);

AcGeMatrix3d mat;

mat(0, 3) = 2; mat(1, 3) = 2;
box1->transformBy(mat);
mat(0, 3) = 7; mat(1, 3) = 2;
box2->transformBy(mat);
mat(0, 3) = 12; mat(1, 3) = 2;
box3->transformBy(mat);

postToDb(box1);
postToDb(box2);
postToDb(box3);

118 | Chapter 6 Entities


// Inserts
//
// Arguments to BLOCK are:
// blockname,
// insert point,
// select objects,
// empty string for selection complete
// Arguments to INSERT are:
// blockname,
// insertion point,
// xscale,
// yscale,
// rotation angle
//
acedCommand_command(RTSTR, "_globcheck", RTSHORT, 0, RTNONE);
acedCommand(RTSTR, "BLOCK", RTSTR, "blk3", RTSTR, "0,0",
RTSTR, "14,8", RTSTR, "11,1", RTSTR, "",
RTNONE);
acedCommand(RTSTR, "INSERT", RTSTR, "blk3", RTSTR,
"0,0", RTSHORT, 1, RTSHORT, 1, RTSHORT,
0, RTNONE);
acedCommand(RTSTR, "BLOCK", RTSTR, "blk2", RTSTR, "0,0",
RTSTR, "9,8", RTSTR, "6,1", RTSTR, "11,1",
RTSTR, "", RTNONE);
acedCommand(RTSTR, "INSERT", RTSTR, "blk2", RTSTR,
"0,0", RTSHORT, 1, RTSHORT, 1, RTSHORT,
0, RTNONE);
acedCommand(RTSTR, "BLOCK", RTSTR, "blk1", RTSTR, "0,0",
RTSTR, "4,8", RTSTR, "1,1", RTSTR, "6,1",
RTSTR, "", RTNONE);
acedCommand(RTSTR, "INSERT", RTSTR, "blk1", RTSTR,
"0,0", RTSHORT, 1, RTSHORT, 1, RTSHORT,
0, RTNONE);
return;
}

void
hilitInsert()
{
Adesk::Boolean interrupted = Adesk::kFalse;
acutPrintf("\nSelect an insert");

Acad::ErrorStatus es = Acad::eOk;
AcDbEntity *ent = NULL;
AcDbEntity *ent2 = NULL;
AcDbBlockReference *blRef = NULL;
AcDbObjectId objectId, blRefId;
ads_name ename, sset;

Common Entity Functions | 119


for (;;) {
switch (acedSSGet(NULL, NULL, NULL, NULL, sset)) {
case RTNORM:
{
struct resbuf *rb;
if (acedSSNameX(&rb, sset, 0) != RTNORM) {
acutPrintf("\n acedSSNameX failed");
acedSSFree(sset);
return;
}

int sel_method;
ads_name subname;
short marker;
AcGePoint3d pickpnt;
AcGeVector3d pickvec;

if (!extractEntityInfo(rb,
sel_method,
ename,
subname,
marker,
pickpnt,
pickvec)) {
acutPrintf("\nextractEntityInfo failed");
acedSSFree(sset);
return;
}

acedSSFree(sset);

assert(marker != 0);
if (marker == 0) {
acutPrintf("\nmarker == 0");
return;
}

// Get the insert first.


//
AOK(acdbGetObjectId(blRefId, ename));
AOK(acdbOpenAcDbEntity(ent, blRefId,
AcDb::kForRead));
assert(ent != NULL);
blRef = AcDbBlockReference::cast(ent);
if (blRef == NULL) {
acutPrintf("\nNot an insert.");
AOK(ent->close());
continue;
}
struct resbuf *insStack;
ads_point pickpoint;
ads_matrix adsmat;
pickpoint[0] = pickpnt[0];
pickpoint[1] = pickpnt[1];
pickpoint[2] = pickpnt[2];

120 | Chapter 6 Entities


// Now get details on the entity that was
// selected.
//
if (acedNEntSelP(NULL, ename, pickpoint, TRUE,
adsmat, &insStack) != RTNORM)
{
acutPrintf("\nFailure in acedNEntSelP");
return;
}
assert(insStack != NULL);
AOK(acdbGetObjectId(objectId, ename));
AOK(acdbOpenAcDbEntity(ent2, objectId,
AcDb::kForRead));
assert(ent2 != NULL);

// Make an array of AcDbObjectIds from the


// insertStack. Don’t use the "smart array"
// AcDbObjectIdArray class, because the
// getSubentPathsAtGsMarker() function expects argument
// eight to be of type AcDbObjectId*. Just
// make room for approximately 100 IDs in the array.
//
AcDbObjectId *idArray = new AcDbObjectId[100];
int count = 0;
struct resbuf *rbIter = insStack;
AcDbObjectId objId;
acdbGetObjectId(objId, ename);
idArray[count++] = objId;

while (rbIter != NULL) {


ename[0] = rbIter->resval.rlname[0];
ename[1] = rbIter->resval.rlname[1];
acdbGetObjectId(objId, ename);
idArray[count++] = objId;
rbIter = rbIter->rbnext;
}

count--;
acutRelRb(insStack);

// First, we’ll highlight an edge.


//
int numPaths;
AcDbFullSubentPath *subentPaths;
AcGeMatrix3d xform;
es = blRef->getSubentPathsAtGsMarker(
AcDb::kEdgeSubentType,
marker,
pickpnt,
xform,
numPaths,
subentPaths,
count,
idArray);
assert(numPaths == 1);

Common Entity Functions | 121


// Highlight and unhighlight the selected edge.
//
acutPrintf("\nHighlighting the first edge.");
es = blRef->highlight(subentPaths[0]);
pressEnterToContinue();
es = blRef->unhighlight(subentPaths[0]);

// If this is a solid, it will have faces.


// In this case, let’s highlight them.
//
if(ent2->isKindOf(AcDb3dSolid::desc())) {
es = blRef->getSubentPathsAtGsMarker(
AcDb::kFaceSubentType,
marker,
pickpnt,
xform,
numPaths,
subentPaths,
count,
idArray);
assert(numPaths == 2);

// Highlight and unhighlight the selected


// faces.
//
acutPrintf("\nHighlighting the first"
" face.");
es = blRef->highlight(subentPaths[0]);
pressEnterToContinue();
es = blRef->unhighlight(subentPaths[0]);
acutPrintf("\nHighlighting the next face.");
es = blRef->highlight(subentPaths[1]);
pressEnterToContinue();
es = blRef->unhighlight(subentPaths[1]);
}
delete []subentPaths;

// Now, let’s highlight the whole entity.


//
acutPrintf("\nHighlighting the entire entity");

AcDbFullSubentPath subPath;
for (int i = count; i >= 0; i--) {
subPath.objectIds().append(idArray[i]);
}
es = blRef->highlight(subPath);
pressEnterToContinue();
es = blRef->unhighlight(subPath);

// Finally, let’s highlight each enclosing


// insert.
//
for (i = count -1; i >= 0; i --) {
subPath.objectIds().removeAt(
subPath.objectIds().length() - 1);
acutPrintf("\nHighlighting insert layer %d",

122 | Chapter 6 Entities


i + 1);
blRef->highlight(subPath);
pressEnterToContinue();
es = blRef->unhighlight(subPath);
}
} // case RTNORM
break;
case RTNONE:
case RTCAN:
return;
default:
continue;
} // switch
break;
} //for (;;)
AOK(ent->close());
AOK(ent2->close());
return;
}

Exploding Entities
Some entities can be exploded, or decomposed, into a set of simpler ele-
ments. The specific behavior depends on the class. For example, boxes can be
exploded into regions, then lines. Polylines can be exploded into line seg-
ments. An mtext entity can be exploded into a separate text entity for each
line of the original object. An mline entity can be exploded into individual
lines. When you explode a block reference, AutoCAD copies all entities in the
block reference and then splits them into their components.
The explode() function creates an array of objects derived from AcDbEntity.
The following table shows what happens when you explode each entity,
when it is by itself and when it is in a block insert that is nonuniformly
scaled.

Exploding entities

Nonuniform Scaling
Entity By Itself (when in a block)

AcDb3dSolid Regions, bodies NA; can’t be exploded

AcDbBody Regions, bodies NA

Ac2dDbPolyline Lines, arcs Self/NA

Ac3dPolyline Lines Self

AcDbArc Self Ellipse

Common Entity Functions | 123


Exploding entities (continued)

Nonuniform Scaling
Entity By Itself (when in a block)

AcDbCircle Self Ellipse

AcDbDimension Solids, lines, text NA


strings, points

AcDbEllipse Self Self

AcDbLeader Self NA

AcDbLine Self Self

AcDbRay Self Self

AcDbSpline Self Self

AcDbXline Self Self

AcDbFace Self Self

AcDbMline Lines Self

AcDbMText One text entity for Self


each line

AcDbPoint Self Self

AcDbPolyFaceMesh AcDbFace Self

AcDbPolygonMesh Self Self

AcDbRegion Curves (splines, lines, NA


arcs, circles)

AcDbShape Self Self

AcDbSolid Self Self

AcDbText Self Self

AcDbTrace Self Self

The explode() function is a read-only function that does not modify the
original entity. It returns a set of entities for the application to handle as
desired. One potential use of this function is to explode a complex entity to

124 | Chapter 6 Entities


produce simpler entities and then operate on those entities. For example, if
you were implementing an intersectForPoints() function for a polyline, it
might be easier to deal with the individual pieces of the polyline rather than
the complete entity.
The following statements are true for the EXPLODE command (but not for the
explode() function):

■ Visual appearance is constant.


■ The entity being exploded is erased from the database.
■ One or more new entities are created and appended to the database.

Creating Instances of AutoCAD Entities


This section demonstrates how to create simple and complex entities and
add them to the database. It also illustrates creating a simple entity, a simple
block, a block with attributes, and a block insert (a block reference).

Creating a Simple Entity


The following example demonstrates creating a line and appending it to the
model space block table record, as described in chapter 2, “Database Primer.”
AcDbObjectId
createLine()
{
AcGePoint3d startPt(4.0, 2.0, 0.0);
AcGePoint3d endPt(10.0, 7.0, 0.0);
AcDbLine *pLine = new AcDbLine(startPt, endPt);

AcDbBlockTable *pBlockTable;
acdbHostApplicationServices()->workingDatabase()
->getSymbolTable(pBlockTable, AcDb::kForRead);

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

AcDbObjectId lineId;
pBlockTableRecord->appendAcDbEntity(lineId, pLine);

pBlockTableRecord->close();
pLine->close();

return lineId;
}

Creating Instances of AutoCAD Entities | 125


Creating a Simple Block Table Record
The following example demonstrates creating a new block table record and
appending it to the block table. Then it creates a line and appends it to the
new block table record.
void
makeABlock()
{
// Create and name a new block table record.
//
AcDbBlockTableRecord *pBlockTableRec
= new AcDbBlockTableRecord();
pBlockTableRec->setName("ASDK-NO-ATTR");

// Get the block table.


//
AcDbBlockTable *pBlockTable = NULL;
acdbHostApplicationServices()->workingDatabase()
->getSymbolTable(pBlockTable, AcDb::kForWrite);

// Add the new block table record to the block table.


//
AcDbObjectId blockTableRecordId;
pBlockTable->add(blockTableRecordId, pBlockTableRec);
pBlockTable->close();

// Create and add a line entity to the component’s


// block record.
//
AcDbLine *pLine = new AcDbLine();
AcDbObjectId lineId;
pLine->setStartPoint(AcGePoint3d(3, 3, 0));
pLine->setEndPoint(AcGePoint3d(6, 6, 0));
pLine->setColorIndex(3);

pBlockTableRec->appendAcDbEntity(lineId, pLine);
pLine->close();
pBlockTableRec->close();
}

Creating a Block Table Record with Attribute


Definitions
An AutoCAD block is a collection of entities that is stored in a block table
record. Each block has an AcDbBlockBegin object, followed by one or more
AcDbEntity objects, and ends with an AcDbBlockEnd object (see the illustra-
tion on page 100).
A block can contain attribute definitions, which are templates for creating
attributes. An attribute is informational text associated with a block. Depend-

126 | Chapter 6 Entities


ing on a user-supplied setting, attribute values may or may not be copied
when a block is inserted into a drawing. Often, the application prompts the
user for the attribute value at runtime.

To create a block table record


1 Create a new block table record.
2 Add the block table record to the block table.
3 Create entities and add them to the block table record.
4 Create attribute definitions, set their values, and add them to the block table
record.

When you close the block table record, the block begin and block end objects
are added to the block automatically.
The following example creates a new block table record named
ASDK-BLOCK-WITH-ATTR and adds it to the block table. Next it creates a circle
entity and adds it to the new block table record. It creates two attribute defi-
nition entities (the second is a clone of the first) and appends them to the
same block table record.
void
defineBlockWithAttributes(
AcDbObjectId& blockId, // This is a returned value.
const AcGePoint3d& basePoint,
double textHeight,
double textAngle)
{
int retCode = 0;
AcDbBlockTable *pBlockTable = NULL;
AcDbBlockTableRecord* pBlockRecord = new AcDbBlockTableRecord;
AcDbObjectId entityId;
// Step 1: Set the block name and base point of the
// block definition.
//
pBlockRecord->setName("ASDK-BLOCK-WITH-ATTR");
pBlockRecord->setOrigin(basePoint);

// Open the block table for write.


//
acdbHostApplicationServices()->workingDatabase()
->getSymbolTable(pBlockTable, AcDb::kForWrite);

// Step 2: Add the block table record to block table.


//
pBlockTable->add(blockId, pBlockRecord);

Creating Instances of AutoCAD Entities | 127


// Step 3: Create a circle entity.
//
AcDbCircle *pCircle = new AcDbCircle;
pCircle->setCenter(basePoint);
pCircle->setRadius(textHeight * 4.0);
pCircle->setColorIndex(3);

// Append the circle entity to the block record.


//
pBlockRecord->appendAcDbEntity(entityId, pCircle);
pCircle->close();

// Step 4: Create an attribute definition entity.


//
AcDbAttributeDefinition *pAttdef
= new AcDbAttributeDefinition;

// Set the attribute definition values.


//
pAttdef->setPosition(basePoint);
pAttdef->setHeight(textHeight);
pAttdef->setRotation(textAngle);
pAttdef->setHorizontalMode(AcDb::kTextLeft);
pAttdef->setVerticalMode(AcDb::kTextBase);
pAttdef->setPrompt("Prompt");
pAttdef->setTextString("DEFAULT");
pAttdef->setTag("Tag");
pAttdef->setInvisible(Adesk::kFalse);
pAttdef->setVerifiable(Adesk::kFalse);
pAttdef->setPreset(Adesk::kFalse);
pAttdef->setConstant(Adesk::kFalse);
pAttdef->setFieldLength(25);

// Append the attribute definition to the block.


//
pBlockRecord->appendAcDbEntity(entityId, pAttdef);

// The second attribute definition is a little easier


// because we are cloning the first one.
//
AcDbAttributeDefinition *pAttdef2
= AcDbAttributeDefinition::cast(pAttdef->clone());

// Set the values that are specific to the


// second attribute definition.
//
AcGePoint3d tempPt(basePoint);
tempPt.y -= pAttdef2->height();
pAttdef2->setPosition(tempPt);
pAttdef2->setColorIndex(1); // Red
pAttdef2->setConstant(Adesk::kTrue);

128 | Chapter 6 Entities


// Append the second attribute definition to the block.
//
pBlockRecord->appendAcDbEntity(entityId, pAttdef2);
pAttdef->close();
pAttdef2->close();
pBlockRecord->close();
pBlockTable->close();
return;
}

Creating a Block Reference with Attributes


A block reference is an entity that references a block table record. It contains
an insertion point, ECS information, X,Y,Z scale factors, rotation, and a nor-
mal vector (parameters for viewing the block in its new location). When you
insert a block into a drawing, AutoCAD conserves memory by creating a
block reference rather than copying the block itself into the drawing.
If you insert a block with attribute definitions, the attribute values can be
filled in by the user at runtime or by the application when the block is
inserted.

To insert a block with attributes into a drawing


1 Create a block reference entity (AcDbBlockReference).
2 Call the setBlockTableRecord() function to specify the object ID of the ref-
erenced block table record. (The object ID can also be specified directly in the
constructor of the block reference.)
3 Append the block reference to a block table record (model space, paper space,
or some other block).
4 Use a block table record iterator on the referenced block table record, search-
ing for attribute definitions. For each one found, create a new AcDbAttribute
entity, fill it in with the attribute definition’s data, and then append it to the
block reference using the appendAttribute() function.

Creating Instances of AutoCAD Entities | 129


The following example creates a block reference, fills in the attributes, and
appends the reference to the database. It uses global functions to obtain user
input. The createBlockWithAttributes() function shown in the previous
section is used to create the block reference. This example uses a block table
record iterator to step through the attribute definitions and create a corre-
sponding attribute for each attribute definition. The attribute values are set
from the original attribute definition using the setPropertiesFrom()
function.
void
addBlockWithAttributes()
{
// Get an insertion point for the block reference,
// definition, and attribute definition.
//
AcGePoint3d basePoint;
if (acedGetPoint(NULL, "\nEnter insertion point: ",
asDblArray(basePoint)) != RTNORM)
return;

// Get the rotation angle for the attribute definition.


//
double textAngle;
if (acedGetAngle(asDblArray(basePoint),
"\nEnter rotation angle: ", &textAngle) != RTNORM)
return;

// Define the height used for the attribute definition text.


//
double textHeight;
if (acedGetDist(asDblArray(basePoint),
"\nEnter text height: ", &textHeight) != RTNORM)
return;

// Build the block definition to be inserted.


//
AcDbObjectId blockId;
defineBlockWithAttributes(blockId, basePoint,
textHeight, textAngle);

// Step 1: Allocate a block reference object.


//
AcDbBlockReference *pBlkRef = new AcDbBlockReference;

// Step 2: Set up the block reference to the newly


// created block definition.
//
pBlkRef->setBlockTableRecord(blockId);

130 | Chapter 6 Entities


// Give it the current UCS normal.
//
struct resbuf to, from;
from.restype = RTSHORT;
from.resval.rint = 1; // UCS
to.restype = RTSHORT;
to.resval.rint = 0; // WCS
AcGeVector3d normal(0.0, 0.0, 1.0);
acedTrans(&(normal.x), &from, &to, Adesk::kTrue,
&(normal.x));

// Set the insertion point for the block reference.


//
pBlkRef->setPosition(basePoint);

// Indicate the LCS 0.0 angle, not necessarily the UCS 0.0 angle.
//
pBlkRef->setRotation(0.0);
pBlkRef->setNormal(normal);

// Step 3: Open the current database’s model space


// block Table Record.
//
AcDbBlockTable *pBlockTable;
acdbHostApplicationServices()->workingDatabase()
->getSymbolTable(pBlockTable, AcDb::kForRead);
AcDbBlockTableRecord *pBlockTableRecord;
pBlockTable->getAt(ACDB_MODEL_SPACE, pBlockTableRecord,
AcDb::kForWrite);
pBlockTable->close();

// Append the block reference to the model space


// block Table Record.
//
AcDbObjectId newEntId;
pBlockTableRecord->appendAcDbEntity(newEntId, pBlkRef);
pBlockTableRecord->close();

// Step 4: Open the block definition for read.


//
AcDbBlockTableRecord *pBlockDef;
acdbOpenObject(pBlockDef, blockId, AcDb::kForRead);

// Set up a block table record iterator to iterate


// over the attribute definitions.
//

Creating Instances of AutoCAD Entities | 131


AcDbBlockTableRecordIterator *pIterator;
pBlockDef->newIterator(pIterator);
AcDbEntity *pEnt;
AcDbAttributeDefinition *pAttdef;
for (pIterator->start(); !pIterator->done();
pIterator->step())
{
// Get the next entity.
//
pIterator->getEntity(pEnt, AcDb::kForRead);

// Make sure the entity is an attribute definition


// and not a constant.
//
pAttdef = AcDbAttributeDefinition::cast(pEnt);
if (pAttdef != NULL && !pAttdef->isConstant()) {
// We have a non-constant attribute definition,
// so build an attribute entity.
//
AcDbAttribute *pAtt = new AcDbAttribute();
pAtt->setPropertiesFrom(pAttdef);
pAtt->setInvisible(pAttdef->isInvisible());

// Translate the attribute by block reference.


// To be really correct, the entire block
// reference transform should be applied here.
//
basePoint = pAttdef->position();
basePoint += pBlkRef->position().asVector();
pAtt->setPosition(basePoint);
pAtt->setHeight(pAttdef->height());
pAtt->setRotation(pAttdef->rotation());
pAtt->setTag("Tag");
pAtt->setFieldLength(25);
char *pStr = pAttdef->tag();
pAtt->setTag(pStr);
free(pStr);
pAtt->setFieldLength(pAttdef->fieldLength());

// The database column value should be displayed.


// INSERT prompts for this.
//
pAtt->setTextString("Assigned Attribute Value");
AcDbObjectId attId;
pBlkRef->appendAttribute(attId, pAtt);
pAtt->close();
}
pEnt->close(); // use pEnt... pAttdef might be NULL
}
delete pIterator;
pBlockDef->close();
pBlkRef->close();
}

132 | Chapter 6 Entities


Iterating through a Block Table Record
The following example demonstrates how to iterate through the elements in
a block table record and print out the elements.
The printAll() function opens the block table for reading, and then it opens
the block name supplied by the user. A new iterator steps through the block
table records. If the record contains an entity, the iterator prints a message
about the entity.
void
printAll()
{
int rc;
char blkName[50];
rc = acedGetString(Adesk::kTrue,
"Enter Block Name <CR for current space>: ",
blkName);
if (rc != RTNORM)
return;
if (blkName[0] == ’\0’) {
if (acdbHostApplicationServices()->workingDatabase()
->tilemode() == Adesk::kFalse) {
struct resbuf rb;
acedGetVar("cvport", &rb);
if (rb.resval.rint == 1) {
strcpy(blkName, ACDB_PAPER_SPACE);
} else {
strcpy(blkName, ACDB_MODEL_SPACE);
}
} else {
strcpy(blkName, ACDB_MODEL_SPACE);
}
}

AcDbBlockTable *pBlockTable;
acdbHostApplicationServices()->workingDatabase()
->getSymbolTable(pBlockTable, AcDb::kForRead);

AcDbBlockTableRecord *pBlockTableRecord;
pBlockTable->getAt(blkName, pBlockTableRecord,
AcDb::kForRead);
pBlockTable->close();

AcDbBlockTableRecordIterator *pBlockIterator;
pBlockTableRecord->newIterator(pBlockIterator);

Creating Instances of AutoCAD Entities | 133


for (; !pBlockIterator->done();
pBlockIterator->step())
{
AcDbEntity *pEntity;
pBlockIterator->getEntity(pEntity, AcDb::kForRead);

AcDbHandle objHandle;
pEntity->getAcDbHandle(objHandle);
char handleStr[20];
objHandle.getIntoAsciiBuffer(handleStr);
const char *pCname = pEntity->isA()->name();

acutPrintf("Object Id %lx, handle %s, class %s.\n",


pEntity->objectId(), handleStr, pCname);
pEntity->close();
}
delete pBlockIterator;
pBlockTableRecord->close();
acutPrintf("\n");
}

Complex Entities
This section provides examples showing how to create and iterate through
complex entities.

Creating a Complex Entity


This example shows how to create an AcDb2dPolyline object and set some of
its properties—layer, color index, the closed parameter. It then creates four
vertex objects (AcDb2dPolylineVertex), sets their location, and appends
them to the polyline object. Finally, it closes all the open objects—vertices,
polyline, block table record, and block table. When the polyline object is
closed, AutoCAD adds the AcDbSequenceEnd object to it automatically.
void
createPolyline()
{
// Set four vertex locations for the pline.
//
AcGePoint3dArray ptArr;
ptArr.setLogicalLength(4);
for (int i = 0; i < 4; i++) {
ptArr[i].set((double)(i/2), (double)(i%2), 0.0);
}

// Dynamically allocate an AcDb2dPolyline object,


// given four vertex elements whose locations are supplied
// in ptArr. The polyline has no elevation, and is
// explicitly set as closed. The polyline is simple;

134 | Chapter 6 Entities


// that is, not curve fit or a spline. By default, the
// widths are all 0.0 and there are no bulge factors.
//
AcDb2dPolyline *pNewPline = new AcDb2dPolyline(
AcDb::k2dSimplePoly, ptArr, 0.0, Adesk::kTrue);
pNewPline->setColorIndex(3);

// Get a pointer to a Block Table object.


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

// Get a pointer to the MODEL_SPACE BlockTableRecord.


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

// Append the pline object to the database and


// obtain its object ID.
//
AcDbObjectId plineObjId;
pBlockTableRecord->appendAcDbEntity(plineObjId,
pNewPline);
pBlockTableRecord->close();

// Make the pline object reside on layer "0".


//
pNewPline->setLayer("0");
pNewPline->close();
}

Iterating through Vertices in a Polyline


The following example shows iterating through the vertices in a polyline
using a vertex iterator. It then prints the coordinates for each vertex.
// Accepts the object ID of an AcDb2dPolyline, opens it, and gets
// a vertex iterator. It then iterates through the vertices,
// printing out the vertex location.
//
void
iterate(AcDbObjectId plineId)
{
AcDb2dPolyline *pPline;
acdbOpenObject(pPline, plineId, AcDb::kForRead);

AcDbObjectIterator *pVertIter= pPline->vertexIterator();


pPline->close(); // Finished with the pline header.

Complex Entities | 135


AcDb2dVertex *pVertex;
AcGePoint3d location;
AcDbObjectId vertexObjId;

for (int vertexNumber = 0; !pVertIter->done();


vertexNumber++, pVertIter->step())
{
vertexObjId = pVertIter->objectId();
acdbOpenObject(pVertex, vertexObjId,
AcDb::kForRead);

location = pVertex->position();
pVertex->close();

acutPrintf("\nVertex #%d’s location is"


" : %0.3f, %0.3f, %0.3f", vertexNumber,
location[X], location[Y], location[Z]);
}
delete pVertIter;
}

Coordinate System Access


Entity functions retrieve and set coordinate values using World Coordinate
System values. The only exception to this rule is the AcDb2dPolylineVertex
class, described later in this section, which uses Entity Coordinate System
(ECS) values. For example, if you call the getCenter() function on a circle,
AutoCAD returns the X,Y center of the circle in world coordinates.

Entity Coordinate System


If you define your own entity, it may be useful to store its geometric con-
structs (points, angles, and vectors) in terms of its own relative coordinate
system. For example, arcs establish a coordinate system in which the Z axis
is perpendicular to the plane of the arc. An arc’s center point is returned in
world coordinates, but the start and end angles can only be interpreted with
respect to its ECS. In such cases, implement the getEcs() function to return
a matrix that is used to transform the entity from its Entity Coordinate Sys-
tem to the World Coordinate System. If an entity is not defined in terms of
its own Entity Coordinate System, then the getEcs() function returns the
identity matrix. (In other words, any time an entity’s getEcs() function
returns the identity matrix, you can assume the entity is defined in terms of
world coordinates.)

136 | Chapter 6 Entities


In AutoCAD, planar entities have an ECS; 3D entities do not. AutoCAD enti-
ties that can return a nonidentity matrix for their getEcs() function are:

■ Dimensions
■ Text
■ Circles
■ Arcs
■ 2D polylines
■ Block inserts
■ Points
■ Traces
■ Solids
■ Shapes
■ Attribute definitions
■ Attributes

AcDb2dPolylineVertex
An AcDb2dPolyline has as an elevation and a series of X,Y points of class
AcDb2dPolylineVertex. The position() and setPosition() functions of
AcDb2dPolylineVertex specify 3D locations in the ECS. The Z coordinate
passed in to the setPosition() function is stored in the entity and is
returned by the position() function, but is otherwise ignored. It does not
affect the polyline’s elevation.
The AcDb2dPolyline class provides the vertexPosition() function, which
returns a World Coordinate System value for the vertex passed in. The only
way to change the elevation of a polyline is using the
AcDb2dPolyline::setElevation() function.

Curve Functions
The abstract base class AcDbCurve provides a number of functions for operat-
ing on curves, including functions for projecting, extending, and offsetting
curves, as well as a set of functions for querying curve parameters. Curves can
be defined either in parameter space or in Cartesian coordinate space. A 3D
curve is a function of one parameter (f(t)), while a 3D surface is a function of
two parameters (f(u,v)). Conversion functions allow you to convert data from
its parameter representation to points in the Cartesian coordinate system.
Splines, for example, are best represented in parameter space. To split a spline
into three equal parts, you first find the parameters that correspond to the
points of the spline and then operate on the spline in parameter space.

Curve Functions | 137


Curves can be used as trim boundaries, extension boundaries, and as con-
struction objects for creating complex 3D entities.
You can project a curve onto a plane in a given direction, as shown in the
following example.
// Accepts an ellipse object ID, opens the ellipse, and uses
// its getOrthoProjectedCurve member function to create a
// new ellipse that is the result of a projection onto the
// plane with normal <1,1,1>. The resulting ellipse is
// added to the model space block Table Record.
//
void
projectEllipse(AcDbObjectId ellipseId)
{
AcDbEllipse *pEllipse;
acdbOpenObject(pEllipse, ellipseId, AcDb::kForRead);

// Now project the ellipse onto a plane with a


// normal of <1, 1, 1>.
//
AcDbEllipse *pProjectedCurve;
pEllipse->getOrthoProjectedCurve(AcGePlane(
AcGePoint3d::kOrigin, AcGeVector3d(1, 1, 1)),
(AcDbCurve*&)pProjectedCurve);
pEllipse->close();

AcDbObjectId newCurveId;
addToModelSpace(newCurveId, pProjectedCurve);
}

// Accepts an ellipse object ID, opens the ellipse, and uses


// its getOffsetCurves() member function to create a new
// ellipse that is offset 0.5 drawing units from the
// original ellipse.
//
void
offsetEllipse(AcDbObjectId ellipseId)
{
AcDbEllipse *pEllipse;
acdbOpenObject(pEllipse, ellipseId, AcDb::kForRead);

// Now generate an ellipse offset by 0.5 drawing units.


//
AcDbVoidPtrArray curves;
pEllipse->getOffsetCurves(0.5, curves);
pEllipse->close();

AcDbObjectId newCurveId;
addToModelSpace(newCurveId, (AcDbEntity*)curves[0]);
}

138 | Chapter 6 Entities


Associating Hyperlinks with Entities
ObjectARX allows you to associate hyperlinks with entities, by using the
classes AcDbHyperlink, AcDbHyperlinkCollection, and
AcDbEntityHyperlinkPE. A hyperlink can be a URL or a non-Web address
such as a local file. You can attach, view, edit, and list hyperlinks within your
application.
An overview of the hyperlink classes follows, but for complete information
on the classes and their methods, see the ObjectARX Reference.

AcDbHyperlink Class
An AcDbHyperlink object contains the hyperlink name (for example,
http://www.autodesk.com), a sublocation within that link, the hyperlink
description or friendly name (“Click here for Autodesk”), and a display string
for the hyperlink. For AutoCAD, a sublocation is a named view, while in a
spreadsheet application, for example, a sublocation might be a cell or group
of cells. The display string is usually the same as the hyperlink’s description.
If the description is null, the hyperlink’s name and sublocation are used
instead, in “name – sublocation” format.
Hyperlinks may also have nesting levels. Nesting level is only of interest
when dealing with hyperlink collections associated with an entity within a
block, or with collections associated with an INSERT entity.

AcDbHyperlinkCollection Class
This class is a collection of AcDbHyperlink objects, and has a variety of meth-
ods for adding and removing those objects. The AcDbHyperlinkCollection
deletes its contents when they are removed, and when the collection object
itself is deleted. Hyperlinks in the collection are numbered from zero.

AcDbEntityHyperlinkPE Class
The methods of the AcDbEntityHyperlinkPE class allow you to set, get, and
count the hyperlinks associated with an entity.

Associating Hyperlinks with Entities | 139


Hyperlink Example
The following function lists the hyperlinks associated with an entity and
allows new hyperlinks to be added in their place. (Error checking is not
shown.)
void AddHyperlink()
{
ads_name en;
ads_point pt;
AcDbEntity * pEnt;
AcDbObjectId pEntId;

// Prompt user to select entity.


acedEntSel("\nSelect an Entity: ", en, pt);

// Get Object id.


acdbGetObjectId(pEntId, en);

// Open object for write.


acdbOpenObject(pEnt, pEntId, AcDb::kForWrite);

// The hyperlink collection object is created inside


// of getHyperlinkCollection
// below. It is our responsibility to delete it.
AcDbHyperlinkCollection * pcHCL = NULL;

// Get the hyperlink collection associated with the entity.


ACRX_X_CALL(pEnt, AcDbEntityHyperlinkPE)->
getHyperlinkCollection(pEnt, pcHCL, false, true);

// If a hyperlink exists already, say so.


if (pcHCL->count() != 0)
{
AcDbHyperlink * pcHO;

acutPrintf("\nThe following hyperlink info already exists


on this entity:");

// Iterate through collection and print existing hyperlinks.


int i = 0;
for (i = 0; i < pcHCL->count(); i++)
{
// Get point to current hyperlink object.
pcHO = pcHCL->item(i);

acutPrintf("\nHyperlink name: %s", pcHO->name());


acutPrintf("\nHyperlink location: %s",
pcHO->subLocation());
acutPrintf("\nHyperlink description: %s",
pcHO->description());
}
acutPrintf("\n** All will be replaced.**");

140 | Chapter 6 Entities


// Remove existing hyperlinks from collection.
// RemoveAt will delete objects too.
for (i = pcHCL->count() - 1; i >= 0; i--)
{
pcHCL->removeAt(i);
}
}

// Get new hyperlinks for this entity.


for (;;)
{
acutPrintf("\nEnter null name, location, and description to
terminate input requests.");

// Prompt user for name and description.


char sName[100], sLocation[100], sDescription[100];
if (acedGetString(TRUE, "\nEnter hyperlink name: ", sName)
!= RTNORM)
acutPrintf("Invalid input\n");
if (acedGetString(TRUE, "\nEnter hyperlink location: ",
sLocation) != RTNORM)
acutPrintf("Invalid input\n");
if (acedGetString(TRUE, "\nEnter hyperlink description: ",
sDescription) != RTNORM)
acutPrintf("Invalid input\n");

// Add hyperlink or exit prompting.


if (strcmp(sName, "") || strcmp(sLocation, "") ||
strcmp(sDescription, ""))
pcHCL->addTail(sName, sDescription, sLocation);
else
break;
}

// Add these hyperlinks to the selected entity (opened above).


ACRX_X_CALL(pEnt, AcDbEntityHyperlinkPE)->
setHyperlinkCollection(pEnt, pcHCL);

// Delete the collection. The collection will delete all its


// contained hyperlink objects.
delete pcHCL;

// Close the object.


pEnt->close();
}

Associating Hyperlinks with Entities | 141


142
Container Objects

In This Chapter
7
This chapter describes the container objects used in ■ Comparison of Symbol Tables and
Dictionaries
AutoCAD database operations: symbol tables,
■ Symbol Tables

dictionaries, groups, and xrecords. As part of any draw- ■ Dictionaries


■ Layouts
ing, AutoCAD creates a fixed set of symbol tables and
■ Xrecords
the named object dictionary, which contains two other

dictionaries, the MLINE style and GROUP dictionaries.

The chapter examples demonstrate how to add entries

to symbol tables, dictionaries, and groups, and how to

query the contents of these containers using iterators.

They also show how to create and use your own

dictionaries and xrecords to manage application data

and objects. For a description of the extension

dictionary of an AcDbObject object, see chapter 5,

“Database Objects.”

143
Comparison of Symbol Tables and
Dictionaries
Symbol tables and dictionaries perform essentially the same function; they
contain entries that are database objects that can be looked up using a text
string key. You can add entries to these container objects, and you can use an
iterator to step through the entries and query their contents.
The AutoCAD database always contains a fixed set of nine symbol tables,
described in the following section. You cannot create or delete a symbol
table, but you can add or change the entries in a symbol table, which are
called records. Each symbol table contains only a particular type of object.
For example, the AcDbLayerTable contains only objects of type
AcDbLayerTableRecord. Symbol tables are defined in this manner mainly for
compatibility with AutoCAD Release 12 and previous releases of AutoCAD.
Dictionaries provide a similar mechanism for storing and retrieving objects
with associated name keys. The AutoCAD database creates the named object
dictionary whenever it creates a new drawing. The named object dictionary
can be viewed as the master “table of contents” for the nonentity object
structures in a drawing. This dictionary, by default, contains four
dictionaries: the GROUP dictionary, the MLINE style dictionary, the layout dic-
tionary, and the plot style name dictionary. You can create any number of
additional objects and add them to the named object dictionary. However,
the best practice is to add one object directly to the named object dictionary
and have that object in turn own the other objects associated with your
application. Typically, the owning object is a container class such as a
dictionary. Use your assigned four-letter Registered Developer Symbol for the
name of this class.
An AcDbDictionary object can contain any type of AcDbObject, including
other dictionaries. A dictionary object does not perform type checking of
entries. However, the MLINE style dictionary should contain only instances
of class AcDbMlineStyle, and the GROUP dictionary should contain only
instances of AcDbGroup. An application may require specific typing for entries
in a dictionary that it creates and maintains.

144 | Chapter 7 Container Objects


The class hierarchy for symbol tables, symbol table records, dictionaries, and
iterators is as follows.

AcDbSymbolTables AcDbSymbolTableRecord AcDbSymbolTableIterator


AcDbAbstractViewTable AcDbAbstractViewTableRecord AcDbAbstractViewTableIterator
AcDbViewportTable AcDbViewportTableRecord AcDbViewportTableIterator
AcDbViewTable AcDbViewTableRecord AcDbViewTableIterator
AcDbBlockTable AcDbBlockTableRecord AcDbBlockTableIterator
AcDbDimStyleTable AcDbDimStyleTableRecord AcDbDimStyleTableIterator
AcDbFontTable AcDbFontTableRecord AcDbFontTableIterator
AcDbLayerTable AcDbLayerTableRecord AcDbLayerTableIterator
AcDbLinetypeTable AcDbLinetypeTableRecord AcDbLinetypeTableIterator
AcDbRegAppTable AcDbRegAppTableRecord AcDbRegAppTableIterator
AcDbTextStyleTable AcDbTextStyleTableRecord AcDbTextStyleTableIterator
AcDbUCSTable AcDbUCSTableRecord AcDbUCSTableIterator

AcDbDictionary
AcDbDictionarywithDefault

An important difference between symbol tables and dictionaries is that sym-


bol table records cannot be erased directly by an ObjectARX application.
These records can be erased only with the PURGE command or selectively fil-
tered out with wblock operations. Objects owned by a dictionary can be
erased.

WARNING! Erasing dictionaries or dictionary entries (see “Essential Database


Objects” on page 22) probably will cause AutoCAD or other applications to fail.

Comparison of Symbol Tables and Dictionaries | 145


Another important difference is that symbol table records store their associ-
ated look-up name in a field in their class definition. Dictionaries, on the
other hand, store the name key as part of the dictionary, independent of the
object it is associated with, as shown in the following figure.

Symbol Table

Symbol table record


<name>
<other class-specific
members>

Dictionary

<name> Object
<class-specific fields>

146 | Chapter 7 Container Objects


Symbol Tables
Names used in symbol table records and in dictionaries must follow these
rules:

■ Names can be any length in ObjectARX, but symbol names entered by


users in AutoCAD are limited to 255 characters.
■ AutoCAD preserves the case of names but does not use the case in com-
parisons. For example, AutoCAD considers “Floor” to be the same symbol
as “FLOOR.”
■ Names can be composed of all characters allowed in Windows NT
filenames, except comma (,), backquote (‘), semi-colon (;), and
equal sign (=).

The AutoCAD database contains the following symbol tables (parentheses


indicate class name and AutoCAD command used for adding entries):

■ Block table (AcDbBlockTable; BLOCK)


■ Layer table (AcDbLayerTable; LAYER)
■ Text style table (AcDbTextStyleTable; STYLE)
■ Linetype table (AcDbLinetypeTable; LTYPE)
■ View table (AcDbViewTable; VIEW)
■ UCS table (AcDbUCSTable; UCS)
■ Viewport table (AcDbViewportTable; VPORT)
■ Registered applications table (AcDbRegAppTable)
■ Dimension styles table (AcDbDimStyleTable; DIMSTYLE)

Each table contains objects of a corresponding subclass of


AcDbSymbolTableRecord.

Each symbol table class provides a getAt() function for looking up the
record specified by name. The signatures for overloaded forms of the getAt()
function are as follows. (##BASE_NAME## stands for any of the nine symbol
table class types.)
Acad::ErrorStatus
AcDb##BASE_NAME##Table::getAt(const char* pEntryName,
AcDb::OpenMode mode,
AcDb##BASE_NAME##TableRecord*&
pRecord,
Adesk::Boolean openErasedRecord =
Adesk::kFalse) const;
or

Symbol Tables | 147


Acad::ErrorStatus
AcDb##BASE_NAME##Table::getAt(const char* pEntryName,
AcDbObjectId& recordId,
Adesk::Boolean getErasedRecord =
Adesk::kFalse) const;
This first version of this function returns a pointer to the opened record in
pRecord if a matching record is found and the open operation (with the
specified mode) succeeds. If openErasedRecord is kTrue, the function returns
the object even if it was erased. If openErasedRecord is kFalse, the function
returns a NULL pointer and an error status of eWasErased for erased objects.
The second version of the getAt() function returns the AcDbObjectId of the
record specified by name in the value recordId if a matching record is found.
If getErasedRecord is kTrue, the function returns the matching object even
if it has been erased. The object is not opened.
Once you have obtained a record and opened it, you can get and set different
member values. For the specific symbol table record class for a complete list
of the class member functions, see the ObjectARX Reference.
Other important functions provided by all symbol table classes are the has()
and add() functions. See the example in “Creating and Modifying a Layer
Table Record” on page 150. The signature for the has() function is
Adesk::Boolean
AcDb##BASE_NAME##Table::has(const char* pName) const;
The has() function returns kTrue if the table contains a record with a name
that matches pName.
The add() function has the following signatures:
Acad::ErrorStatus
AcDb##BASE_NAME##Table::add(AcDb##BASE_NAME##TableRecord*
pRecord);

Acad::ErrorStatus
AcDb##BASE_NAME##Table::add(AcDbObjectId& recordId,
AcDb##BASE_NAME##TableRecord*
pRecord);
This function adds the record pointed to by pRecord to both the database
containing the table and the table itself. If the additions succeed and the
argument pId is non-NULL, it is set to the AcDbObjectId of the record in the
database.

148 | Chapter 7 Container Objects


Block Table
Entities in the database typically belong to a block table record. The block
table contains three records by default, *MODEL_SPACE, *PAPER_SPACE, and
*PAPER_SPACE0, which correspond to the three initial drawing spaces that
can be edited directly by AutoCAD users. For examples of adding entities to
the model space block table record, see chapter 2, “Database Primer,” and
chapter 6, “Entities.”
The *PAPER_SPACE and *PAPER_SPACE0 records correspond to the two pre-
defined paper space layouts in AutoCAD. You can add, modify, and delete
paper space layouts.
New block table records are created when the user issues a BLOCK command
or an INSERT command to insert an external drawing. New block table
records are also created with the acdbEntMake() function. The BLOCK? com-
mand lists the contents of the block table, with the exception of the
*MODEL_SPACE and *PAPER_SPACE records. See chapter 6, “Entities,” for
examples of block table record and block reference creation. (A block refer-
ence is an entity that refers to a given block table record.)

Layer Table
The layer table contains one layer, layer 0, by default. A user adds layers to
this table with the LAYER command.

Layer Properties
The AcDbLayerTableRecord class contains member functions for specifying a
number of layer properties that affect the display of their associated entities.
All entities must refer to a valid layer table record. The AutoCAD User’s Guide
provides a detailed description of layer properties.
The following sections list the member functions for setting and querying
layer properties.
Frozen/Thawed
When a layer is frozen, graphics are not regenerated.
void AcDbLayerTableRecord::setIsFrozen(Adesk::Boolean);

Adesk::Boolean
AcDbLayerTableRecord::isFrozen() const;

Symbol Tables | 149


On/Off
When a layer is OFF, graphics are not displayed.
void AcDbLayerTableRecord::setIsOff(Adesk::Boolean);

Adesk::Boolean
AcDbLayerTableRecord::isOff() const;
Viewport
This setVPDFLT() function specifies whether the layer by default is visible or
invisible in new viewports.
void AcDbLayerTableRecord::setVPDFLT(Adesk::Boolean);

Adesk::Boolean
AcDbLayerTableRecord::VPDFLT() const;
Locked/Unlocked
Entities on a locked layer cannot be modified by an AutoCAD user or opened
for the write() function within a program.
void AcDbLayerTableRecord::setIsLocked(Adesk::Boolean);

Adesk::Boolean
AcDbLayerTableRecord::isLocked() const;
Color
The color set by the setColor() function is used when an entity’s color is
BYLAYER.
void AcDbLayerTableRecord::setColor(const AcCmColor &color);

AcCmColor
AcDbLayerTableRecord::color() const;
Linetype
The linetype set by the setLinetypeObjectId() function is used when an
entity’s linetype is BYLAYER.
void AcDbLayerTableRecord::setLinetypeObjectId(AcDbObjectId);

AcDbObjectId
AcDbLayerTableRecord::linetypeObjectId() const;

Creating and Modifying a Layer Table Record


The following example shows obtaining the layer table for the current data-
base and opening it for writing. It creates a new layer table record
(AcDbLayerTableRecord) and sets certain attributes of the layer (name, frozen
attribute, on/off, viewport, and locked). Then it creates a color class object
and sets the color of the layer to red.
To set the linetype for the layer, this example opens the linetype table for
reading and obtains the object ID of the linetype record for the desired line-

150 | Chapter 7 Container Objects


type (here, “DASHED”). Once it has the object ID for the linetype, it closes
the linetype table and sets the linetype for the new layer table record. This
example uses the add() function to add the layer table record to the layer
table. Finally, it closes the layer table record and the layer table itself.
void
addLayer()
{
AcDbLayerTable *pLayerTbl;
acdbHostApplicationServices()->workingDatabase()
->getSymbolTable(pLayerTbl, AcDb::kForWrite);
if (!pLayerTbl->has("ASDK_TESTLAYER")) {
AcDbLayerTableRecord *pLayerTblRcd
= new AcDbLayerTableRecord;
pLayerTblRcd->setName("ASDK_TESTLAYER");
pLayerTblRcd->setIsFrozen(0);// layer to THAWED
pLayerTblRcd->setIsOff(0); // layer to ON
pLayerTblRcd->setVPDFLT(0); // viewport default
pLayerTblRcd->setIsLocked(0);// un-locked

AcCmColor color;
color.setColorIndex(1); // set color to red
pLayerTblRcd->setColor(color);

// For linetype, we need to provide the object ID of


// the linetype record for the linetype we want to
// use. First, we need to get the object ID.
//
AcDbLinetypeTable *pLinetypeTbl;
AcDbObjectId ltId;
acdbHostApplicationServices()->workingDatabase()
->getSymbolTable(pLinetypeTbl, AcDb::kForRead);
if ((pLinetypeTbl->getAt("DASHED", ltId))
!= Acad::eOk)
{
acutPrintf("\nUnable to find DASHED"
" linetype. Using CONTINUOUS");

// CONTINUOUS is in every drawing, so use it.


//
pLinetypeTbl->getAt("CONTINUOUS", ltId);
}
pLinetypeTbl->close();

pLayerTblRcd->setLinetypeObjectId(ltId);
pLayerTbl->add(pLayerTblRcd);
pLayerTblRcd->close();
pLayerTbl->close();
} else {
pLayerTbl->close();
acutPrintf("\nlayer already exists");
}
}

Symbol Tables | 151


Iterators
Each symbol table has a corresponding iterator that you can create with the
AcDb##BASE_NAME##Table::newIterator() function.

Acad::ErrorStatus
AcDb##BASE_NAME##Table::newIterator(
AcDb##BASE_NAME##TableIterator*& pIterator,
Adesk::Boolean atBeginning = Adesk::kTrue,
Adesk::Boolean skipErased = Adesk::kTrue) const;
The newIterator() function creates an object that can be used to step
through the contents of the table and sets pIterator to point to the iterator
object. If atBeginning is kTrue, the iterator starts at the beginning of the
table; if kFalse, it starts at the end of the table. If the skipErased argument
is kTrue, the iterator is positioned initially at the first (or last) unerased
record; if kFalse, it is positioned at the first (or last) record, regardless of
whether it has been erased. For a description of the functions available for
each iterator class, see the ObjectARX Reference.
When you create a new iterator, you are also responsible for deleting it. A
symbol table should not be closed until all of the iterators it has constructed
have been deleted.
In addition to the symbol tables, the block table record has an iterator that
operates on the entities it owns. The AcDbBlockTableRecord class returns an
object of class AcDbBlockTableRecordIterator when you ask it for a new
iterator. This iterator enables you to step through the entities contained in
the block table record and to seek particular entities.

Iterating over Tables


The code in the following example creates an iterator that walks through the
symbol table records in the linetype table. It obtains each record, opens it for
read, obtains the linetype name, closes the record, and then prints the line-
type name. At the end, the program deletes the iterator.
void
iterateLinetypes()
{
AcDbLinetypeTable *pLinetypeTbl;
acdbHostApplicationServices()->workingDatabase()
->getSymbolTable(pLinetypeTbl, AcDb::kForRead);

// Create a new iterator that starts at table


// beginning and skips deleted.
//
AcDbLinetypeTableIterator *pLtIterator;
pLinetypeTbl->newIterator(pLtIterator);

152 | Chapter 7 Container Objects


// Walk the table, getting every table record and
// printing the linetype name.
//
AcDbLinetypeTableRecord *pLtTableRcd;
char *pLtName;
for (; !pLtIterator->done(); pLtIterator->step()) {
pLtIterator->getRecord(pLtTableRcd, AcDb::kForRead);
pLtTableRcd->getName(pLtName);
pLtTableRcd->close();
acutPrintf("\nLinetype name is: %s", pLtName);
free(pLtName);
}
delete pLtIterator;
pLinetypeTbl->close();
}

Dictionaries
To create a new dictionary, you need to create an instance of AcDbDictionary,
add it to the database, and register it with its owner object. Use the setAt()
function of AcDbDictionary to add objects to the dictionary and the data-
base. The signature of this function is
Acad::ErrorStatus
AcDbDictionary::setAt(const char* pSrchKey,
AcDbObject* pNewValue,
AcDbObjectId& retObjId);
The setAt() function adds a new entry specified by newValue to the
dictionary. If the entry already exists, it is replaced by the new value. The
name of the object is specified by srchKey. The object ID of the entry is
returned in retObjId.
When you add an entry to a dictionary, the dictionary automatically attaches
a reactor to the entry. If the object is erased, the dictionary is notified and
removes it from the dictionary.

Groups and the Group Dictionary


A group is a container object that maintains an ordered collection of database
entities. Groups can be thought of as named persistent selection sets. They
do not have an ownership link to the entities they contain.
When an entity is erased, it is automatically removed from the groups that
contain it. If an entity is unerased, it is automatically reinserted into the
group.

Dictionaries | 153
Use the AcDbGroup::newIterator() function to obtain an iterator and step
through the entities in the group. The AcDbGroup class also provides
functions for appending and prepending entities to the group, inserting enti-
ties at a particular index in the group, removing entities, and transferring
entities from one position in the group to another. See AcDbGroup in the
ObjectARX Reference.
You can also assign properties to all members of a group using the
setColor(), setLayer(), setLinetype(), setVisibility(), and
setHighlight() functions of the AcDbGroup class. These operations have the
same effect as opening each entity in the group and setting its property
directly.
Groups should always be stored in the GROUP dictionary, which can be
obtained as follows:
AcDbDictionary* pGrpDict =
acdbHostApplicationServices()->working Database()->
getGroupDictionary(pGroupDict, AcDb::kForWrite);
An alternative way to obtain the GROUP dictionary is to look up
“ACAD_GROUP” in the named object dictionary.
The following functions are part of an application that first prompts the user
to select some entities that are placed into a group called
“ASDK_GROUPTEST”. Then it calls the function removeAllButLines() to
iterate over the group and remove all the entities that are not lines. Finally,
it changes the remaining entities in the group to red.
void
groups()
{
AcDbGroup *pGroup = new AcDbGroup("grouptest");

AcDbDictionary *pGroupDict;
acdbHostApplicationServices()->workingDatabase()
->getGroupDictionary(pGroupDict, AcDb::kForWrite);

AcDbObjectId groupId;
pGroupDict->setAt("ASDK_GROUPTEST", pGroup, groupId);
pGroupDict->close();
pGroup->close();

makeGroup(groupId);
removeAllButLines(groupId);
}

// Prompts the user to select objects to add to the group,


// opens the group identified by "groupId" passed in as
// an argument, then adds the selected objects to the group.
//

154 | Chapter 7 Container Objects


void
makeGroup(AcDbObjectId groupId)
{
ads_name sset;
int err = acedSSGet(NULL, NULL, NULL, NULL, sset);

if (err != RTNORM) {
return;
}
AcDbGroup *pGroup;
acdbOpenObject(pGroup, groupId, AcDb::kForWrite);

// Traverse the selection set, exchanging each ads_name


// for an object ID, then adding the object to the group.
//
long i, length;
ads_name ename;
AcDbObjectId entId;
acedSSLength(sset, &length);
for (i = 0; i < length; i++) {
acedSSName(sset, i, ename);
acdbGetObjectId(entId, ename);
pGroup->append(entId);
}
pGroup->close();
acedSSFree(sset);
}

// Accepts an object ID of an AcDbGroup object, opens it,


// then iterates over the group, removing all entities that
// are not AcDbLines and changing all remaining entities in
// the group to color red.
//
void
removeAllButLines(AcDbObjectId groupId)
{
AcDbGroup *pGroup;

acdbOpenObject(pGroup, groupId, AcDb::kForWrite);


AcDbGroupIterator *pIter = pGroup->newIterator();
AcDbObject *pObj;
for (; !pIter->done(); pIter->next()) {
pIter->getObject(pObj, AcDb::kForRead);

// If it is not a line or descended from a line,


// close it and remove it from the group. Otherwise,
// just close it.
//

Dictionaries | 155
if (!pObj->isKindOf(AcDbLine::desc())) {
// AcDbGroup::remove() requires that the object
// to be removed be closed, so close it now.
//
pObj->close();
pGroup->remove(pIter->objectId());
} else {
pObj->close();
}
}

delete pIter;

// Now change the color of all the entities in the group


// to red (AutoCAD color index number 1).
//
pGroup->setColorIndex(1);
pGroup->close();
}

MLINE Style Dictionary


The MLINE style dictionary contains objects of class AcDbMlineStyle. As
shown in the following figure, objects of class AcDbMline each have an
associated MLINE style that specifies the properties of the multiline, such as
offset, color, and linetype.

Dictionary AcDbMlineStyle objects AcDbMline objects

<STANDARD>

<MYSTYLE>

AcDbMline::setStyle( )

<name>

Layout Dictionary
The layout dictionary is a default dictionary within the named object dictio-
nary that contains objects of class AcDbLayout. The AcDbLayout object stores
the characteristics of a paper space layout, including the plot settings. Each
AcDbLayout object also contains an object ID of an associated block table
record, which stores the entities associated with the layout.

156 | Chapter 7 Container Objects


Database

Other Symbol Named Object


Block Table
Tables Dictionary

Block Table Layout Other


Their Symbol Dictionary Dictionaries
Record
Table Records

Entity Layout

Creating a Dictionary
The following example creates a new dictionary (ASDK_DICT) and adds it to
the named object dictionary. Then it creates two new objects of the custom
class AsdkMyClass (derived from AcDbObject) and adds them to the dictio-
nary using the setAt() function.

NOTE You need to close the objects after adding them with the setAt()
function.

// This function creates two objects of class AsdkMyClass.


// It fills them in with the integers 1 and 2, and then adds
// them to the dictionary associated with the key ASDK_DICT. If this
// dictionary doesn’t exist, it is created and added to the named
// object dictionary.
//
void
createDictionary()
{
AcDbDictionary *pNamedobj;
acdbHostApplicationServices()->workingDatabase()->
getNamedObjectsDictionary(pNamedobj, AcDb::kForWrite);

// Check to see if the dictionary we want to create is


// already present. If not, create it and add
// it to the named object dictionary.
//

Dictionaries | 157
AcDbDictionary *pDict;
if (pNamedobj->getAt("ASDK_DICT", (AcDbObject*&) pDict,
AcDb::kForWrite) == Acad::eKeyNotFound)
{
pDict = new AcDbDictionary;
AcDbObjectId DictId;
pNamedobj->setAt("ASDK_DICT", pDict, DictId);
}

pNamedobj->close();
if (pDict) {
// Create new objects to add to the new dictionary,
// add them, then close them.
//
AsdkMyClass *pObj1 = new AsdkMyClass(1);
AsdkMyClass *pObj2 = new AsdkMyClass(2);

AcDbObjectId rId1, rId2;


pDict->setAt("OBJ1", pObj1, rId1);
pDict->setAt("OBJ2", pObj2, rId2);

pObj1->close();
pObj2->close();
pDict->close();
}
}

Iterating over Dictionary Entries


The iterator class for dictionaries is AcDbDictionaryIterator. The following
code excerpt obtains a dictionary (ASDK_DICT) from the named object dic-
tionary. It then uses a dictionary iterator to step through the dictionary
entries and print the value of the stored integer. Finally, it deletes the iterator
and closes the dictionary.
void
iterateDictionary()
{
AcDbDictionary *pNamedobj;
acdbHostApplicationServices()->workingDatabase()
->getNamedObjectsDictionary(pNamedobj, AcDb::kForRead);

// Get a pointer to the ASDK_DICT dictionary.


//
AcDbDictionary *pDict;
pNamedobj->getAt("ASDK_DICT", (AcDbObject*&)pDict,
AcDb::kForRead);
pNamedobj->close();

// Get an iterator for the ASDK_DICT dictionary.


//
AcDbDictionaryIterator* pDictIter = pDict->newIterator();
AsdkMyClass *pMyCl;
Adesk::Int16 val;

158 | Chapter 7 Container Objects


for (; !pDictIter->done(); pDictIter->next()) {
// Get the current record, open it for read, and
// print its data.
//
pDictIter->getObject((AcDbObject*&)pMyCl,
AcDb::kForRead);
pMyCl->getData(val);
pMyCl->close();
acutPrintf("\nintval is: %d", val);
}
delete pDictIter;
pDict->close();
}

Layouts
AutoCAD initially contains three layouts: a model space layout and two
paper space layouts. These layouts can be accessed by tabs at the bottom of
the drawing window in AutoCAD. The tabs are initially named Model,
Layout1, and Layout2.
The Model tab is the default tab and represents model space, in which you
generally create your drawing. The Layout1 and Layout2 tabs represent paper
space and are generally used for laying out your drawing for printing. The
paper space layouts display a paper image that shows the printable boundary
for the configured print device.
It is recommended that you use paper space layouts for preparing final draw-
ings for output, but printing can be performed from any layout, including
the model space layout. For more information on using layouts in AutoCAD,
see the AutoCAD User’s Guide.

ObjectARX Layout Classes


The main classes involved in creating and manipulating layouts are the fol-
lowing:

■ AcDbLayout
■ AcDbPlotSettings
■ AcDbPlotSettingsValidator
■ AcDbLayoutManager
■ AcApLayoutManager
■ AcDbLayoutManagerReactor

AcDbLayout, AcDbPlotSettings, and AcDbPlotSettingsValidator are used


to create and set attributes on layout objects. AcDbLayoutManager,
AcApLayoutManager, and AcDbLayoutManagerReactor are used to manipulate

Layouts | 159
layout objects and to perform other layout-related tasks. The following sec-
tions provide an overview of some of these classes. For more information, see
the ObjectARX Reference. For an example of using the ObjectARX layout
classes, see the lmgrtest.arx sample application in the ObjectARX samples
directory.

Layout Objects
Information about layouts is stored in instances of the AcDbLayout class. A
layout object contains the printing and plotting settings information needed
to print the desired portion of the drawing. For example, a layout object con-
tains the plot device, media size, plot area, and plot rotation, as well as sev-
eral other attributes that help define the area to be printed.
AcDbLayout objects are stored in the ACAD_LAYOUT dictionary within the
named object dictionary of the database. There is one AcDbLayout object per
paper space layout, as well as a single AcDbLayout for model space. Each
AcDbLayout object contains the object ID of its associated
AcDbBlockTableRecord. This makes it easy to find the block table record in
which the layout’s actual geometry resides. If an AcDbBlockTableRecord rep-
resents a layout, then it contains the object ID of its associated AcDbLayout
object.
Most of the plot information for layout objects is stored in
AcDbPlotSettings, the base class of AcDbLayout. You can create named plot
settings and use them to initialize other AcDbLayout objects. This allows you
to export and import plot settings from one layout to another. These named
plot settings are stored in instances of the AcDbPlotSettings class. There is
one AcDbPlotSettings object for each named plot setting and they are stored
in the ACAD_PLOTSETTINGS dictionary within the named object
dictionary.

NOTE There is no direct connection between AcDbLayout objects in the


ACAD_LAYOUT dictionary and AcDbPlotSettings objects in the
ACAD_PLOTSETTINGS dictionary.

The Layout Manager


You can manage AcDbLayout objects by using the AcApLayoutManager class.
The AcApLayoutManager class allows you to

■ Create layouts
■ Delete layouts
■ Rename layouts
■ Copy and clone layouts

160 | Chapter 7 Container Objects


■ Set the current layout
■ Find a particular layout
■ Set the plot characteristics of a layout

There is one instance of a layout manager per application. The layout man-
ager always operates on the current layout.

Xrecords
Xrecords enable you to add arbitrary, application-specific data. Because they
are an alternative to defining your own object class, they are especially useful
to AutoLISP programmers. An xrecord is an instance of class AcDbxrecord,
which is a subclass of AcDbObject. Xrecord state is defined as the contents of
a resbuf chain, which is a list of data groups, each of which in turn contains
a DXF group code plus associated data. The value of the group code defines
the associated data type. Group codes for xrecords are in the range of
1 through 369. The following section describes the available DXF group
codes.
There is no inherent size limit to the amount of data you can store in an
xrecord. Xrecords can be owned by any other object, including the extension
dictionary of any object, the named object dictionary, any other dictionary,
or other xrecords.
No notification is sent when an xrecord is modified. If an application needs
to know when an object owning an xrecord has been modified, the applica-
tion will need to send its own notification.
The AcDbXrecord class provides two member functions for setting and
obtaining resbuf chains, the setfromRbChain() and rbChain() functions:
Acad::ErrorStatus
AcDbXrecord::setFromRbChain(
resbuf& pRb,
AcDbDatabase* auxDb=NULL);
Acad::ErrorStatus
AcDbXrecord::rbChain(
resbuf** ppRb,
AcDbDatabase* auxDb=NULL) const;
The AcDbXrecord::setFromRbChain() function replaces the existing resbuf
chain with the chain passed in.

Xrecords | 161
DXF Group Codes for Xrecords
The following table lists the DXF group codes that can be used in xrecords.

DXF group code ranges for xrecords

From To Data Type

1 4 Text

6 9 Text

10 17 Point or vector (3 reals)

38 59 Real

60 79 16-bit integer

90 99 32-bit integer

102 102 Control string “{“ or “}”

140 149 real

170 179 16-bit integer

210 219 Real

270 279 16-bit integer

280 289 8-bit integer

300 309 Text

310 319 Binary chunk

320 329 Handle

330 339 Soft pointer ID

340 349 Hard pointer ID

350 359 Soft ownership ID

360 369 Hard ownership ID

For a description of hard and soft owners and pointers, see chapter 12,
“Deriving from AcDbObject.”

162 | Chapter 7 Container Objects


Examples
The following ObjectARX examples consist of two functions:
createXrecord() and listXrecord(). The first function adds a new xrecord
to a dictionary, adds the dictionary to the named object dictionary, and then
adds data to the xrecord. The listXrecord() function opens an xrecord,
obtains its data list, and sends the list to be printed. For the complete pro-
gram, see the samples directory.
void
createXrecord()
{
AcDbDictionary *pNamedobj, *pDict;
acdbHostApplicationServices()->workingDatabase()
->getNamedObjectsDictionary(pNamedobj, AcDb::kForWrite);

// Check to see if the dictionary we want to create is


// already present. If not, then create it and add
// it to the named object dictionary.
//
if (pNamedobj->getAt("ASDK_DICT", (AcDbObject*&) pDict,
AcDb::kForWrite) == Acad::eKeyNotFound)
{
pDict = new AcDbDictionary;
AcDbObjectId DictId;
pNamedobj->setAt("ASDK_DICT", pDict, DictId);
}
pNamedobj->close();

// Add a new xrecord to the ASDK_DICT dictionary.


//
AcDbXrecord *pXrec = new AcDbXrecord;
AcDbObjectId xrecObjId;
pDict->setAt("XREC1", pXrec, xrecObjId);
pDict->close();

// Create a resbuf list to add to the xrecord.


//
struct resbuf *pHead;
ads_point testpt = {1.0, 2.0, 0.0};
pHead = acutBuildList(AcDb::kDxfText,
"This is a test Xrecord list",
AcDb::kDxfXCoord, testpt,
AcDb::kDxfReal, 3.14159,
AcDb::kDxfAngle, 3.14159,
AcDb::kDxfColor, 1,
AcDb::kDxfInt16, 180,
0);

// Add the data list to the xrecord. Notice that this


// member function takes a reference to resbuf, NOT a
// pointer to resbuf, so you must dereference the
// pointer before sending it.
//

Xrecords | 163
pXrec->setFromRbChain(*pHead);
acutRelRb(pHead);
pXrec->close();
}

// Gets the xrecord associated with the key XREC1 and


// lists out its contents by passing the resbuf list to the
// function printList.
//
void
listXrecord()
{
AcDbDictionary *pNamedobj;
acdbHostApplicationServices()->workingDatabase()
->getNamedObjectsDictionary(pNamedobj, AcDb::kForRead);

// Get the dictionary object associated with the key ASDK_DICT.


//
AcDbDictionary *pDict;
pNamedobj->getAt("ASDK_DICT", (AcDbObject*&)pDict,
AcDb::kForRead);
pNamedobj->close();

// Get the xrecord associated with the key XREC1.


//
AcDbXrecord *pXrec;
pDict->getAt("XREC1", (AcDbObject*&) pXrec,
AcDb::kForRead);
pDict->close();

struct resbuf *pRbList;


pXrec->rbChain(&pRbList);
pXrec->close();

printList(pRbList);
acutRelRb(pRbList);
}

164 | Chapter 7 Container Objects


Part II
User Interfaces

165
166
MFC Topics

In This Chapter
8
The Microsoft Foundation Class (MFC) library allows ■ Introduction
■ Using MFC with ObjectARX
a developer to implement standard user interfaces
Applications

quickly. The ObjectARX environment provides a set of ■ ObjectARX Applications with


Dynamically Linked MFC
classes that a developer can use to create MFC-based ■ Built-In MFC User Interface
Support
user interfaces that behave and appear as the built-in
■ Using AdUi and AcUi with VC++
Autodesk user interfaces. This chapter describes AppWizard

how to use the MFC library as part of an

ObjectARX application.

167
Introduction
ObjectARX applications can be created to take advantage of the Microsoft
Foundation Class (MFC) library. This chapter discusses how to build your
ObjectARX applications to make use of MFC and how the AutoCAD built-in
MFC system can be used to create dialogs that behave and operate like
AutoCAD.

Using MFC with ObjectARX Applications


You have the choice of building ObjectARX applications with either a
dynamically linked MFC library or a statically linked MFC library. You also
have the choice of using a regular DLL or an extension DLL.

NOTE It is highly recommended to dynamically link your MFC ObjectARX


application AND make it an extension DLL, since it is the only method that allows
you to use the Autodesk AdUi and AcUi MFC base classes.

For complete information about MFC, see the Microsoft online help and
technical notes. In particular, see notes 11 and 33 for information about
using MFC as part of a DLL, which is an important concept for ObjectARX.

MFC and Modeless Dialog Boxes


Since AutoCAD attempts to take focus away from all of its child windows,
modeless dialogs have a special requirement. At regular intervals, the mode-
less dialog will get a WM_ACAD_KEEPFOCUS window message, which is defined
in adscodes.h as 1001. When your dialog gets this message, it must return
TRUE if it should keep focus. If the response to this message is FALSE (which
is also the default), then your dialog box will lose focus as soon as the user
moves the mouse pointer off the dialog box’s window.
You can do this with the dialog box’s message map, and an ON_MESSAGE()
declaration such as
BEGIN_MESSAGE_MAP(HelloDlg, CDialog)
ON_COMMAND(IDCLOSE, OnClose)
ON_COMMAND(IDC_DRAW_CIRCLE, OnDrawCircle)
ON_MESSAGE(WM_ACAD_KEEPFOCUS, onAcadKeepFocus)
END_MESSAGE_MAP()
In this example, the application’s dialog class is HelloDlg, which is derived
from CDialog. When you add this entry to the message map, you must also

168 | Chapter 8 MFC Topics


write a handler function for the message. Assume you have written a func-
tion called keepTheFocus(), which returns TRUE if your dialog wants to keep
the input focus and FALSE if the dialog is willing to yield the focus to
AutoCAD. An example message handler is provided here:
afx_msg LONG HelloDlg::onAcadKeepFocus(UINT, LONG)
{
return keepTheFocus() ? TRUE : FALSE;
}

ObjectARX Applications with Dynamically


Linked MFC
The preferred method for building an MFC-based ObjectARX application is
to use the dynamically linked MFC libraries.

Visual C++ Project Settings for Dynamically


Linked MFC
To build an ObjectARX application using the shared MFC library
1 Select the MFC AppWizard (DLL) option for the project.
2 Select Extension DLL using shared MFC DLL.
3 Go to the Project Settings dialog box and select the General tab.
4 Select Use MFC in a Shared DLL for the Microsoft Foundation Classes field.
5 Add an acrxEntryPoint function to the project’s CPP file. See the example at
the end of the chapter for a complete setup for an MFC project.

Debugging ObjectARX Applications with


Dynamic MFC
When debugging ObjectARX applications built with a dynamically linked
MFC library, link with the release version of C runtime and MFC libraries.
This allows use of the MFC or C runtime debugging facilities, but does not
allow stepping into the Microsoft MFC debugging source code.

ObjectARX Applications with Dynamically Linked MFC | 169


Resource Management
Resource management is an important consideration when designing an
ObjectARX application that uses an MFC library shared with AutoCAD and
other applications.
You must insert your module state (using CDynaLinkLibrary) into the chain
that MFC examines when it performs operations such as locating a resource.
However, it is strongly recommended that you explicitly manage your appli-
cation’s resources so that they will not conflict with other resources from
AutoCAD or other ObjectARX applications.

To explicitly set resources


1 Before taking any steps that would cause MFC to look for your resource, call
the AFX function AfxSetResourceHandle() to set the custom resource as the
system default.
2 Before setting the system resource to your resource, call
AfxGetResourceHandle() to get the current system resource.
3 Immediately after performing any functions that require the custom
resource, the system resource should be reset to the resource handle previ-
ously saved.

Calling AutoCAD API functions (or invoking AutoCAD commands) inside


the dialog command handler that needs AutoCAD’s resources, such as
acedGetFileD(), sets the resource back to AutoCAD before calling the func-
tions. Restore your application resource afterwards. (Use
acedGetAcadResourceInstance() to get AutoCAD’s resource handle.)

CAcExtensionModule Class
The ObjectARX SDK provides two simple C++ classes that can be used to
make resource management easier. The CAcExtensionModule class serves two
purposes—it provides a placeholder for an AFX_EXTENSION_MODULE structure
(normally used to initialize or terminate an MFC extension DLL) and tracks
two resource providers for the DLL. The resource providers are the module’s
resources (which are normally the DLL itself, but may be set to some other
module) and the default resources (normally the host application, but are
actually the provider currently active when AttachInstance() is called).
CAcExtensionModule tracks these to simplify switching MFC resource lookup
between the default and the module’s. A DLL should create one instance of
this class and provide the implementation for the class.

170 | Chapter 8 MFC Topics


CAcModuleResourceOverride Class
Use an instance of this class to switch between resource providers. When the
object is constructed, a new resource provider will get switched in. Upon
destruction, the original resource provider will be restored. The following
code provides an example:
void MyFunc ()
{
CAcModuleResourceOverride myResources;
}
Upon entry to this function the module’s resources will be selected. When
the function returns, the default resources will be restored. A resource over-
ride can be used in any of three ways:

■ Use the default constructor (no arguments) to switch to the module’s


resources. The default resources will be restored by the destructor. The
module/default resources are those maintained by the DLL’s
CAcExtensionModule.
■ Pass NULL (or 0) to the constructor. The DLL’s resources will be selected and
the resources that were in effect will be restored when the override object
is destroyed.
■ Pass a non-NULL handle to the constructor. The associated module’s
resources will be selected and the resources that were in effect will be
restored when the override object is destroyed.

There are two macros provided, called AC_DECLARE_EXTENSION_MODULE and


AC_IMPLEMENT_EXTENSION_MODULE, to help define and implement the classes
in your application.
The following code illustrates how to make use of the CAcExtensionModule
and CAcModuleResourceOverride classes in an ObjectARX application:
AC_IMPLEMENT_EXTENSION_MODULE(theArxDLL);
HINSTANCE _hdllInstance = NULL;
extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
// Remove this if you use lpReserved
UNREFERENCED_PARAMETER(lpReserved);
if (dwReason == DLL_PROCESS_ATTACH)
{
theArxDLL.AttachInstance(hInstance);
hdllInstance = hInstance;
}
else if (dwReason == DLL_PROCESS_DETACH)
{
theArxDLL.DetachInstance();
}
return 1; // ok
}

ObjectARX Applications with Dynamically Linked MFC | 171


Built-In MFC User Interface Support
ObjectARX has a set of MFC User Interface (UI) related classes that easily
allow you to provide a consistent UI. This means your UI can behave like and
have the appearance of the AutoCAD UI. It is highly recommended to make
use of these classes, since they allow your application to be more tightly inte-
grated with the AutoCAD UI. The Autodesk MFC system is divided into two
libraries. The first is called AdUi and is not AutoCAD-specific. The second is
called AcUi and contains AutoCAD-specific appearance and behavior.
AdUi is an MFC extension dynamic-link library used to extend some of the
UI-related classes of MFC. The library was developed for use with AutoCAD
and other Autodesk products and contains core functionality. The
companion library, AcUi, builds upon the AdUi framework and provides
AutoCAD- specific appearance and behavior. The AdUi and AcUi libraries
provide classes that extend those provided by MFC in ways that allow ARX
developers to use the same UI functionality found in AutoCAD. MFC devel-
opers can seamlessly use these classes. Listed below are the main areas of
added functionality provided by AdUi and AcUi.
To use AdUi in an MFC-based application, the project’s C++ source files must
include adui.h and the project should link adui15.lib (the adui15.dll import
library).
To use AcUi in an MFC-based AutoCAD application, the project’s C++ source
files must include adui.h, then acui.h, and the project should link acui15.lib
and adui15.lib. AutoCAD invokes the library’s initialization routine,
InitAcUiDLL(), which also handles the AdUi initialization (via an
InitAdUiDLL() call); therefore your application need not reinitialize AcUi or
AdUi.

WARNING! Although adui15.dll may be called from MFC-based applications


other than AutoCAD (or other Autodesk products), the library’s intended use is
by Autodesk and third parties expressly for the creation of software to work
exclusively with AutoCAD, or other Autodesk products. Use of this DLL for non-
AutoCAD, standalone products is not permitted under the AutoCAD license
agreement.

AdUi and AcUi provide classes that implement the following features:

■ Dialog resizing
■ Dialog data persistency
■ Tabbed dialogs

172 | Chapter 8 MFC Topics


■ Extensible tabbed dialogs
■ Context-sensitive help and F1 help
■ Dialog interaction with AutoCAD’s drawing editor
■ Bitmap buttons that are easy to use
■ Static bitmap buttons
■ Bitmap buttons that are drag and drop sites
■ Toolbar-style bitmap buttons
■ Owner-draw buttons that are easy to use
■ Dialog and control support for standard ToolTips
■ Dialog and control support for TextTips (which display truncated text)
■ Dialog and control support for DrawTips (owner-draw TextTips)
■ Custom messaging, including data validation
■ Combo boxes that display and allow the selection of many AutoCAD spe-
cific items
■ Docking control bar windows for use with AutoCAD
■ AutoCAD-specific bitmap buttons (stock Pick and Select buttons)
■ Specialized edit controls that can perform AutoCAD-specific data
validation
■ Custom messaging, including data validation

Class Hierarchy
The following are the supported classes in the AdUi and AcUi libraries. There
are classes present in the header files that are not shown and are for internal
use only and are not supported for use with ObjectARX.

CDialog CFileDialog CTabCtrl


CAdUiBaseDialog CAdUiFileDialog CAdUiTab
CAdUiDialog CAcUiFileDialog CAcUiTab
CAdUiTabMainDialog
CAcUiTabMainDialog
CAdUiTabExtensionManager
CAdUiTabChildDialog CHeaderCtrl
CAcUiTabChildDialog CAdUiHeaderCtrl
CAdUiAlertDialog
CAcUiDialog CWnd
CAdUiTipWindow
CControlBar CAdUiTextTip
CAdUiDockControlBar
CComboBox CAcUiDockControlBar
CAdUiComboBox CAdUiDrawTipText
CAcUiComboBox
CAcUiAngleComboBox CEdit
CAcUiMRUComboBox CAdUiEdit CButton
CAcUiArrowHeadComboBox CAcUiEdit CAdUiOwnerDrawButton
CAcUiColorComboBox CAcUiAngleEdit CAdUiBitmapButton
CAcUiLineWeightComboBox CAcUiNumericEdit CAcUiPickButton
CAcUiPlotStyleNamesComboBox CAcUiStringEdit CAcUiSelectButton
CAcUiPlotStyleTablesComboBox CAcUiSymbolEdit CAdUiBitmapStatic
CAcUiNumericComboBox CAdUiDropSite
CAcUiStringComboBox CAdUiToolButton
CAcUiSymbolComboBox
CListBox
CAdUiListBox
CAcUiListBox CListCtrl
CAcUiMRUListBox CAdUiListCtrl
CAcUiListCtrl

Built-In MFC User Interface Support | 173


AdUi Messaging
The AdUi library uses an internal messaging scheme to facilitate communi-
cation between objects. Typically this involves a container (such as a dialog)
responding to a notification from a contained window (such as a control).
Advanced applications may tailor the built-in system to their needs, or add
AdUi messaging support to other CWnd derived classes.

AdUi Tip Windows


AdUi provides three types of tip windows: ToolTips, TextTips, and DrawTips.
ToolTips represent stock Windows ToolTips, as provided by the Common
Controls DLL installed on the user’s system. TextTips are text-based tip win-
dows that pop up over a control, usually to reveal data that the user would
otherwise have to scroll into view. DrawTips are an extension of TextTips.
The control underneath the tip is usually responsible for painting the con-
tents of the tip (analogous to an owner-draw tip).
Most applications rarely involve these classes directly, since AdUi usually
handles all of the requirements. AdUi uses its internal messaging system to
negotiate between containers and controls and decide when and how to dis-
play a tip.

CAdUiTipWindow Class
CAdUiTipWindow is the basic AdUi tip window class. These objects handle
generic tip display and know when to automatically hide themselves (such
as detecting cursor movement, a brief time-out, or keyboard activity).

CAdUiTextTip Class
CAdUiTextTip specializes CAdUiTipWindow to display a TextTip.

CAdUiDrawTipText Class
CAdUiDrawTipText is used internally by the AdUi messaging system to inform
a control that a tip window needs repainting. The control has the option of
changing attributes of the tip window’s device context and drawing the text.

AdUi Dialog Classes


The AdUi dialog classes are usable in applications other than AutoCAD.

CAdUiBaseDialog Class
CAdUiBaseDialog provides basic support for tip windows (ToolTips and
TextTips) and the AdUi message handling system. It also supports context

174 | Chapter 8 MFC Topics


help and F1 help in dialogs. It is the common base class for all dialogs except
those based on the common file dialog.

CAdUiDialog Class
CAdUiDialog is a general purpose class that provides a set of member func-
tions allowing for resizable dialogs and data persistency.

CAdUiFileDialog
CAdUiFileDialog specializes CFileDialog much the same way as
CAdUiBaseDialog specializes CDialog. The class provides basic support for tip
windows (ToolTips and TextTips), context help and AdUi message handling
in a common file dialog. Unlike CAdUiBaseDialog, there is no built-in sup-
port for position and size persistency.

CAdUiTabMainDialog Class
CAdUiTabMainDialog represents the main container dialog in a tabbed dialog.
CAdUiTabMainDialog and CAdUiTabMainDialog are used in place of
CPropertySheet and CPropertyPage to construct tabbed dialogs.

CAdUiTabChildDialog Class
CAdUiTabChildDialog represents a tab in a tabbed dialog.
CAdUiTabMainDialog and CAdUiTabChildDialog are used in place of
CPropertySheet and CPropertyPage to construct tabbed dialogs. Each tab in
a tabbed dialog is a CAdUiTabChildDialog.

AcUi Dialog Classes


The AcUi dialog classes build upon the AdUi dialog classes and are usable
only with AutoCAD.

CAcUiDialog Class
CAcUiDialog is a general-purpose class that provides a set of member func-
tions allowing for resizable dialogs and data persistency in AutoCAD.

CAcUiTabMainDialog Class
CAcUiTabMainDialog represents the main container dialog in an AutoCAD
tabbed dialog. CAcUiTabMainDialog and CAcUiTabMainDialog are used in
place of CPropertySheet and CPropertyPage to construct tabbed dialogs in
AutoCAD.

CAcUiTabChildDialog Class
CAcUiTabChildDialog represents a tab in a tabbed dialog.
CAcUiTabMainDialog and CAcUiTabChildDialog are used in place of

Built-In MFC User Interface Support | 175


CPropertySheet and CPropertyPage to construct tabbed dialogs in
AutoCAD. Each tab in an AutoCAD tabbed dialog is a CAcUiTabChildDialog.

CAcUiAlertDialog Class
CAdUiAlertDialog represents an alert dialog with three buttons. One button
is the CANCEL button and the other two button labels are set by the pro-
grammer. It is a general-purpose alert dialog.

CAcUiFileDialog Class
CAcUiFileDialog provides an AutoCAD-specific derivation of
CAdUiFileDialog.

AdUi Classes Supporting Tab Extensibility


The following classes provide support for tab dialogs.

CAdUiTabExtensionManager Class
CAdUiDialogManager is a class that manages adding and removing tabs from
a tabbed dialog that is extensible. If a dialog is tab extensible, an instance of
this class is found in the CAdUiTabMainDialog.

CAdUiTab Class
CAdUiTab encapsulates the MFC CTabCtrl and adds functionality to it. One
of these objects is found in the main dialog object.

AdUi and AcUi Control Bar Classes


The following classes provide support for docking windows.

CAdUiDockControlBar Class
The CAdUiDockControlBar class, part of a docking system, adds extended
capabilities to the MFC CControlBar class. The main feature provided is the
resizing of the control bars when docked. More than one control bar can be
docked together, each of them being able to be resized individually using
splitters created by the docking system. CAdUiDockControlBar also comes
with a gripper bar and a close button when docked. Control bars’ state can
be switched from docked to undocked or vice versa, by double-clicking on
the gripper when docked, or the title bar when undocked, or by dragging
them with the mouse. The docking system handles the persistency of the
control bars, preserving their position and state across sessions. Finally,
CAdUiDockControlBar provides a default context menu to control the bar
behavior, with a possibility for the developer to customize this menu.

176 | Chapter 8 MFC Topics


CAcUiDockControlBar Class
The CAcUiDockControlBar class adds to the CAdUiDockControlBar class a
behavior common to AutoCAD dockable tools: when the user moves the
mouse cursor out of the control bar region, the focus is automatically given
back to AutoCAD.

AdUi and AcUi Edit Controls


The following classes provide specialized editing controls, including support
for specific types of data.

CAdUiEdit Class
CAdUiEdit is derived from the CEdit class to provide edit box controls. This
class provides support for tip windows for truncated text items (TextTips).
This class takes bit flags to add desired validation behavior, based on the
following types of input: Numeric, String, Angular, and Symbol names. Gen-
erally you should use one of the classes derived from the AutoCAD-specific
class CAcUiComboBox, which adds a specific data type validation and persis-
tency to the control. These are CAcUiStringEdit, CAcUiSymbolEdit,
CAcUiNumericEdit, and CAcUiAngleEdit.

CAcUiEdit Class
CAcUiEdit provides an AutoCAD-specific derivation of CAdUiEdit.

CAcUiAngleEdit Class
CAcUiAngleEdit is derived from CAcUiEdit and provides a specialized con-
structor to ensure that the AC_ES_ANGLE style bit is always set in the style
mask. Objects of this class are intended for use in editing angular/rotational
data specific to AutoCAD settings.

CAcUiNumericEdit Class
CAcUiNumericEdit is derived from CAcUiEdit and provides a specialized con-
structor to ensure that the AC_ES_NUMERIC style bit is always set in the style
mask. Objects of this class are intended for use in editing numeric data (such
as distance) specific to AutoCAD settings.

CAcUiStringEdit Class
CAcUiStringEdit is derived from CAcUiEdit and provides a specialized con-
structor to ensure that the AC_ES_STRING style bit is always set in the style
mask. Any input is acceptable.

Built-In MFC User Interface Support | 177


CAcUiSymbolEdit Class
CAcUiSymbolEdit is derived from CAcUiEdit and provides a specialized con-
structor to ensure that the AC_ES_SYMBOL style bit is always set in the style
mask. Objects of this class are intended for use in editing valid AutoCAD
symbol names.

CAdUiListBox Class
CAdUiListBox specializes the MFC CListBox to provide a control that sup-
ports AdUi messaging. The class can be used anywhere a CListBox can be
used. Since it provides the additional container-side support for AdUi regis-
tered messages, it is convenient to use CAdUiBaseDialog (or a derived class)
with the CAdUiListBox (or a derived class) controls.
CAdUiListBox provides features that allow the class to be used to subclass a
list box included in a combo box. When used in concert with a
CAdUiComboBox, the list box is able to track the combo box and, in the case of
an owner-draw control, either delegate drawing to the combo box or provide
its own drawing routines.

CAdUiListCtrl Class
CAdUiListCtrl is derived from CListCtrl class to provide list controls. This
class provides support for tip windows for truncated text items (TextTips).
TextTips will appear for truncated header items for list controls in a report
view, and for individual truncated text items in columns in the body of a list
control. Owner-drawn controls are supported.

CAdUiHeaderCtrl
CAdUiHeaderCtrl specializes CHeaderCtrl. Most often, CAdUiHeaderCtrl rep-
resents the subclassed header contained in a list control (CAdUiListCtrl).
You do not need to subclass the header control to get TextTip support for col-
umn headers in a list control (provided automatically in CAdUiListCtrl).

AdUi and AcUi Combo Box Controls


The following classes provide support for combo box controls.

CAdUiComboBox Class
CAdUiComboBox is derived from the CComboBox class to provide combo box
controls. This class provides support for tip windows for truncated text items
(TextTips), and data validation in the edit control. This class takes bit flags to
add desired validation behavior, based on the following types of input:
numeric, string, angular, and symbol names. Generally, you should use one
of the classes derived from the AutoCAD-specific class CAcUiComboBox, which

178 | Chapter 8 MFC Topics


adds a specific data type validation and persistency to the control. These are
CAcUiStringComboBox, CAcUiSymbolComboBox, CAcUiNumericComboBox, and
CAcUiAngleComboBox. Support for owner-drawn controls is also built in.

CAcUiAngleComboBox Class
The CAcUiAngleComboBox constructor automatically creates a
CAcUiAngleEdit to subclass the control’s edit box. This allows for validation
of angles specific to AutoCAD settings.

CAcUiNumericComboBox Class
The CAcUiAngleComboBox constructor automatically creates a
CAcUiNumericEdit to subclass the control’s edit box. This allows for valida-
tion of numbers specific to AutoCAD settings.

CAcUiStringComboBox Class
The CAcUiStringComboBox constructor automatically creates a
CAcUiStringEdit to subclass the control’s edit box. Any input is acceptable.

CAcUiSymbolComboBox Class
The CAcUiSymbolComboBox constructor automatically creates a
CAcUiSymbolEdit to subclass the control’s edit box. Valid AutoCAD symbol
names are acceptable input.

AcUi MRU Combo Boxes


AcUi extends combo box support to manage an MRU (most recently used)
list automatically within the control. The basic functionality is provided by
the class CAcUiMRUComboBox (derived from CAcUiComboBox). A companion
class, CAcUiMRUListBox, provides DrawTip support for the combo box’s
ComboLBox. This is necessary due to the MRU combo box implementation as
an owner-draw control.
Five specialized MRU combo box classes are also provided:
CAcUiArrowHeadComboBox, CAcUiColorComboBox, CAcUiLineWeightComboBox,
CAcUiPlotStyleTablesComboBox, and CAcUiPlotStyleNamesComboBox. These
provide standard user interfaces for managing dimensioning arrowheads,
color and lineweight selections, and plot style table and plot style names
selection.

CAcUiMRUComboBox Class
CAcUiMRUComboBox inherits CAcUiComboBox and serves as the base class for
owner-draw combo boxes that implement an MRU list. Each item in the list
can contain a small image followed by some text. Each item also tracks a

Built-In MFC User Interface Support | 179


unique value, referred to as cargo, and maintained as standard Windows®
ITEMDATA within the control. The class features built-in support for up to
two generic, optional items, referred to as Option1 and Option2. These
usually correspond to “ByLayer” and “ByBlock” and often have special signif-
icance. Two other items, Other1 and Other2, may also be enabled and appear
only when the list is dropped down. Selecting either of these items triggers a
special event within the control.

CAcUiArrowHeadComboBox Class
CAcUiArrowHeadComboBox specializes CAcUiMRUComboBox for dimensioning
arrowhead selection. The control displays bitmaps representing the standard
AutoCAD dimensioning arrowhead styles, which are always present in the
list. By default no optional or additional items are present or added. The
cargo associated with each item is the AutoCAD index for the associated
stock arrowhead. When MRU items are added to the list, they are automati-
cally assigned a unique cargo value (which will be greater than the AutoCAD
index for a user-defined arrowhead style).

CAcUiColorComboBox Class
CAcUiColorComboBox specializes CAcUiMRUComboBox for color selection. The
control displays color swatches representing selections from AutoCAD’s pal-
ette. The stock items always present in the control reflect color numbers 1
through 7. Both optional items are used; Option1 displays “ByLayer” and
Option2 displays “ByBlock”. MRU items display “Color nnn,” where nnn is
the associated color number. The cargo associated with each item indicates
an AutoCAD color number (such as 1 to 255), “ByBlock” relates to 0, and
“ByLayer” corresponds to 256. The Other1 item is enabled and triggers the
AutoCAD Color Selection dialog. If Other2 is enabled it displays as
“Windows...” and by default triggers the Windows Color Selection Common
dialog. If the user selects an item from either of these dialogs the selection
appears in the MRU list and becomes the current item in the control.

CAcUiLineWeightComboBox Class
CAcUiLineWeightComboBox specializes CAcUiMRUComboBox for lineweight
selection. The control displays a small preview of the lineweights AutoCAD
supports, ranging from 0.05mm to 2.11mm, and includes “None” and
optionally “Default”. Both metric and imperial values are displayed, depend-
ing on the setting of the LWUNITS system variable. Both optional items are
used; Option1 displays “ByLayer” and Option2 displays “ByBlock”. Each
item maintains cargo that corresponds to the item’s AcDb::kLnWtxxx value.

180 | Chapter 8 MFC Topics


CAcUiPlotStyleTablesComboBox Class
CAcUiPlotStyleTablesComboBox specializes CAcUiMRUComboBox for plot style
table selection. The control displays plot style table names according to the
current plot style mode (color-dependent mode or named plot styles). The
MRU functionality of the combo box is not used. A bitmap indicating an
embedded translation table is displayed in named plot style mode for those
tables that have an embedded translation table.

CAcUiPlotStyleNamesComboBox Class
CAcUiPlotStyleNamesComboBox specializes CAcUiMRUComboBox for plot style
name selection. The MRU functionality of the combo is not used, and
“ByLayer”, “ByBlock”, and “Other...” items can be conditionally displayed. If
present, the “Other...” item can trigger either the Assign Plot Style dialog or
the Set Current Plot Style dialog.

CAcUiMRUListBox Class
CAcUiMRUListBox derives from CAcUiListBox. It is used by CAcUiMRUComboBox
to subclass the control’s list box (ComboLBox) and provide DrawTip support.
Advanced applications that use specialized MRU combo boxes may need to
derive special MRU list boxes to display DrawTips correctly.

AdUi Button Classes


These controls are usable in applications other than AutoCAD.

CAdUiOwnerDrawButton Class
This class provides a basic owner-draw button. The class can be used any-
where a CButton can be used. When used in an AdUi-derived dialog (or a class
that supports AdUi messaging) CAdUiOwnerDrawButton automatically pro-
vides for the display of an AdUi tip window. The class also supports
drag and drop, Static and Tool Display, and PointedAt effects. In Tool Display
mode, the button appears flat and pops up when pointed at (such as when
the mouse moves over the button). Clicking the button makes it push down.
In Static Display mode, the button appears flat and behaves more like a static
control than a push button. The combination of enabling drag and drop and
Static Display is appropriate for creating sites that receive files via
drag and drop.

CAdUiBitmapButton Class
This class specializes CAdUiOwnerDrawButton to provide a button that dis-
plays a bitmap (the image is drawn transparently in the button). By default,
objects of this class automatically resize to fit the associated bitmap image.

Built-In MFC User Interface Support | 181


Unlike MFC’s CBitmapButton, only one bitmap is needed to define all of the
button states (MFC’s class requires four bitmaps).

CAdUiBitmapStatic Class
CAdUiBitmapStatic specializes CAdUiBitmapButton to provide a button that
enables Static Display by default. These controls act more like statics than
pushbuttons.

CAdUiDropSite Class
CAdUiDropSite specializes CAdUiBitmapStatic to provide a button that
enables drag and drop as well as Static Display. These controls can receive
files via drag and drop.

CAdUiToolButton Class
CAdUiToolButton specializes CAdUiBitmapButton to provide a button that
enables Tool Display by default. These controls appear more like toolbar but-
tons than regular pushbuttons.

AcUi Button Classes


These controls build upon the AdUi classes and are usable only with
AutoCAD.

CAcUiPickButton Class
CAcUiPickButton specializes CAcUiBitmapButton, which is a wrapper for the
class CAdUiBitmapButton. CAcUiPickButton provides a button that displays a
standard pick button bitmap.

CAcUiSelectButton Class
CAcUiSelectButton specializes CAcUiPickButton. It provides a button that
displays a standard selection button bitmap.

Dialog Data Persistency


CAcUiDialog and the CAcUiTab classes automatically inherit persistency. Per-
sistency, as defined by the dialogs and controls in AcUi15.dll, means that stor-
age for any and all user modal dialogs in AutoCAD derived from these classes
will store data with the current user profile, making it a virtual preference.
Your dialog should have a unique name because it will use a shared area of
the user profile registry space. Given that developers usually create their

182 | Chapter 8 MFC Topics


applications using their registered developer prefix, the following method is
recommended:
module-name:dialog-name
For example, if your ObjectARX application is named AsdkSample and you
have a dialog titled Coordinates, you would name it
AsdkSample:Coordinates.
There are two types of dialog data persistency: out-of-the-box and developer-
defined. Out-of-the-box persistency refers to dialog position, size, and list
view column sizes. Developer-defined refers to any data that a developer
chooses to store in the user profile either during the lifetime or dismissal of
the dialog and which may be retrieved across dialog invocations.

Using and Extending the AdUi Tab Dialog System


All tabbed dialogs that use CAdUiTabMainDialog and CAdUiTabChildDialog
can be easily made tab extensible. There is no limit for the number of tabs
that can be added to a tab-extensible dialog. If the main dialog is resizable,
added tabs can participate in that resizing using the same directives outlined
in the documentation on resizable dialogs. All dialogs in AutoCAD use scroll-
ing tabs as opposed to stacked tabs.
It is important for you to set the dirty bit for the extended tab using the
SetDirty() member function of CAdUiTabChildDialog when data needs to
be initialized or updated via DoDataExchange.

Constructing a Custom Tab Dialog That Is


Extensible
Construct your tabbed dialog using CAcUiTabMainDialog for the main dialog
frame and CAcUiTabChildDialog for each tab. In the OnInitDialog() or con-
structor of the CAcUiTabMainDialog immediately call SetDialogName() with
the published name of your extensible dialog. ObjectARX applications will
use this name to add tabs to your dialog. After you add your tabs with calls
to AddTab(), in OnInitDialog, call AddExtendedTabs(). Remember that your
tabbed dialog can have any number of added tabs in it, so do not assume a
fixed number of tabs elsewhere in the dialog’s code.

Built-In MFC User Interface Support | 183


For example
BOOL CPrefTabFrame::OnInitDialog()
// Dialog initialization for my tabbed dialog frame.
{
SetDialogName("Preferences");
CAcUiTabMainDialog::OnInitDialog();
...

// Add my tabs here.


m_tab.AddTab(0,IDS_FILES_TABNAME,IDD_FILES_TAB,&m_filesTab);
m_tab.AddTab(1,IDS_PERF_TABNAME,IDD_PERF_TAB,&m_performTab);
m_tab.AddTab(2,IDS_COMP_TABNAME,IDD_COMP_TAB,&m_compatTab);

// Add any extended tabs. This call is what makes this


// dialog tab extensible
AddExtendedTabs();
}

Extending the AutoCAD Built-In Tab Dialogs


Use Class Wizard or some other means to create your tab subclassed from
CDialog. In the properties for the dialog, change the style of the dialog to
“popup” and the border to “resizing”. Implement an override for
PostNcDestroy(). Replace all occurrences of CDialog with
CAcUiTabExtension in all source files for the dialog. In PostNcDestroy() for
the tab extension delete the tab object that has been allocated (see example
below).
In your AcRx::kInitAppMsg handler in acrxEntryPoint() add a call to
acedRegisterExtendedTab("MYAPPNAME.ARX", "DIALOGNAME"), where
MYAPPNAME is the base file name of your application and DIALOGNAME is the
published name of the extensible tabbed dialog you wish to add to.
Implement an AcRx::kInitDialogMsg handler in acrxEntryPoint() and add
the tab there. The (void*)appId argument to acrxEntryPoint() is a
CAcUiTabExtensionManager pointer. Use the member function
GetDialogName() for the CAcUiTabExtensionManager to get the name of the
dialog being initialized and, if the application wants to add to this dialog, call
the AddTab() member function of the CAcUiTabExtensionManager to add the
tab. One argument to this function is a pointer to a previously allocated
CAcUiTabExtension object. If the dialog is resizable and you want some of
your controls to resize, add that resizing code after the call to AddTab().

184 | Chapter 8 MFC Topics


For example
extern "C" AcRx::AppRetCode acrxEntryPoint(
AcRx::AppMsgCode msg, void* appId)
{
switch (msg) {
case AcRx::kInitAppMsg:
acrxDynamicLinker->unlockApplication(appId);
acrxDynamicLinker->registerAppMDIAware(appId);
initApp();
break;
case AcRx::kUnloadAppMsg:
unloadApp();
break;
case AcRx::kInitDialogMsg:
// A dialog is initializing that we are interested in adding
// tabs to.
addMyTabs((CAcUiTabExtensionManager*)pkt);
break;
default:
break;
}
return AcRx::kRetOK;
}
void initApp()
{
InitMFC();
// Do other initialization tasks here.
acedRegCmds->addCommand(
"MYARXAPP",
"MYARXAPP",
"MYARXAPP",
ACRX_CMD_MODAL,
&MyArxAppCreate);
// Here is where we register the fact that we want to add
// a tab to the PREFERENCES dialog.
acedRegisterExtendedTab("MYARXAPP.ARX", "PREFERENCES");
}
// CMyTab1 is subclassed from CAcUiTabExtension.
static CMyTab1* pTab1;
void addMyTabs(CAcUiTabExtensionManager* pXtabManager)
{
// Allocate an extended tab if it has not been done already
// and add it through the CAcUiTabExtensionManager.
pTab1 = new CMyTab1;
pXtabManager->AddTab(_hdllInstance, IDD_TAB1,
"My Tab1", pTab1);
// If the main dialog is resizable, add your control
// resizing directives here.
pTab1->StretchControlXY(IDC_EDIT1, 100, 100);
}

Built-In MFC User Interface Support | 185


Then for the CMyTab1 class implementation:
void CMyTab1::PostNcDestroy()
// Override to delete added tab.
{
delete pTab1;
pTab1 = NULL;
CAcUiTabExtension::PostNcDestroy();
}

Using AdUi and AcUi with VC++ AppWizard


Now that you have seen an overview for the AdUi and AcUi dialog support,
we will present an example of using these systems. The dialog we will create
will appear as follows. The source code for this example can be found in the
ObjectARX SDK docsamps\AsdkAcUiSample directory. This example will,
however, describe how to set up your project from the beginning.

Create the ObjectARX MFC Application


Skeleton
1 Create a new project in Microsoft Visual C++ using the Application Wizard.
Choose the MFC AppWizard (dll) project type. Assign the project a name (for
this sample we will use the name AsdkAcUiSample) and directory and click
OK. On the next screen, choose MFC Extension DLL, then click Finish. We
now have a basic MFC Extension DLL project.
2 We will now add the necessary code to support ObjectARX. Open the
AsdkAcUiSample.cpp file. Remove the AFX_EXTENSION_MODULE call and also the
DllMain function.
3 Add the following declaration:
AC_IMPLEMENT_EXTENSION_MODULE(theArxDLL);
4 Add the following code to set up the AutoCAD command and
acrxEntryPoint:
void dialogCreate()
{
acutPrintf("\nAcUi Dialog Sample");
}

186 | Chapter 8 MFC Topics


The following addCommand call uses the module resource instance from the
AC_IMPLEMENT_EXTENSION_MODULE macro:

static void initApp()


{
theArxDLL.AttachInstance();
CAcModuleResourceOverride resOverride;
acedRegCmds->addCommand(
"ASDK_ACUI_SAMPLE",
"ASDKACUISAMPLE",
"ACUISAMPLE",
ACRX_CMD_MODAL,
dialogCreate,
NULL,
-1,
theArxDLL.ModuleResourceInstance());
}
The following unloadApp() function is called when the application unloads.
At this time it is important to detach the resource instance:
static void unloadApp()
{
// Do other cleanup tasks here
acedRegCmds->removeGroup("ASDK_ACUI_SAMPLE");

theArxDLL.DetachInstance();

}
// Entry point
//
extern "C" AcRx::AppRetCode acrxEntryPoint(
AcRx::AppMsgCode msg, void* appId)
{
switch( msg )
{
case AcRx::kInitAppMsg:
acrxDynamicLinker->unlockApplication(appId);
acrxDynamicLinker->registerAppMDIAware(appId);
initApp();
break;
case AcRx::kUnloadAppMsg:
unloadApp();
break;
case AcRx::kInitDialogMsg:
break;
default:
break;
}
return AcRx::kRetOK;
}

Using AdUi and AcUi with VC++ AppWizard | 187


Create an AsdkAcUiSample.h header file and add the following lines to the
file:
#include "resource.h" // main symbols
#define PI 3.14159265359

// Forward declaration for the entry point function of


// our application
void testCreate();
Then add the following include files to AsdkAcUiSample.cpp:
#include "AsdkAcUiSample.h"
#include "AcExtensionModule.h"

You will also need to add the ObjectARX libraries to the project file, change
the .dll extension to .arx, and modify the .def file with the proper exports.
Then you should be able to compile and load the application.

Create the MFC Dialog Using App Studio


1 In Visual C++ App Studio add a dialog resource.
2 Create the following dialog box using the App Studio controls:

IDC_BUTTON_POINT

IDC_EDIT_XPT
IDC_EDIT_YPT
IDC_EDIT_ZPT
IDC_BUTTON_ANGLE
IDC_EDIT_ANGLE

IDC_COMBO_REGAPPS

IDC_LIST_BLOCKS

3 Make sure the resource IDs match this diagram or the remaining code will
not work.

188 | Chapter 8 MFC Topics


Create the Classes and Controls
1 Using ClassWizard, create the dialog class. If you start ClassWizard from the
dialog creation screen it will prompt you to create a new class. Click OK for
a new class and then give the dialog a name. For this example use
AsdkAcUiDialogSample.
2 Switch to the Member Variable tab.
3 For the IDC_BUTTON_ANGLE and IDC_BUTTON_POINT resources add CButton
controls called m_ctrlAngleButton and m_ctrlPickButton, respectively.
4 For the IDC_EDIT_ANGLE, IDC_EDIT_XPT, IDC_EDIT_YPT, and IDC_EDIT_ZPT
resources add CEdit controls called m_ctrlAngleEdit, m_ctrlXPtEdit,
m_ctrlYPtEdit, and m_ctrlZPtEdit, respectively.
5 For the IDC_LIST_BLOCKS resource add a CListBox control called
m_ctrlBlockList.
6 For the IDC_COMBO_REGAPPS resource add a CComboBox control called
m_ctrlRegAppComboBox. At this point your member variables dialog should
appear like

7 Now open the AsdkAcUiDialogSample.h header file and change the derivation
of the new dialog class. It should be derived from CAcUiDialog:
class AsdkAcUiDialogSample : public CAcUiDialog

Using AdUi and AcUi with VC++ AppWizard | 189


8 Now we will change the types to use the AcUi controls. Start by opening the
AsdkAcUiDialogSample.h file. Change the control list to be the following:
CAcUiSymbolComboBox m_ctrlRegAppComboBox;
CAcUiListBox m_ctrlBlockListBox;
CAcUiPickButton m_ctrlPickButton;
CAcUiPickButton m_ctrlAngleButton;
CAcUiAngleEdit m_ctrlAngleEdit;
CAcUiNumericEdit m_ctrlXPtEdit;
CAcUiNumericEdit m_ctrlYPtEdit;
CAcUiNumericEdit m_ctrlZPtEdit;
9 Also add a couple of member variables to track the point and angle values
and some helper functions. These should be added to the public section of
the class:
AcGePoint3d m_ptValue;
double m_dAngle;
void DisplayPoint();
bool ValidatePoint();
void DisplayAngle();
bool ValidateAngle();
void DisplayBlocks();
void DisplayRegApps();

Create the Handlers for the Dialog


1 Go back into ClassWizard and select the Message Maps tab.
2 Highlight the AsdkAcUiDialogSample object ID and add a function for
WM_INITDIALOG. Then choose edit code to take you into the
AsdkAcUiDialogSample.cpp source file.
3 Change the parent OnInitDialog to be CAcUiDialog:
CAcUiDialog::OnInitDialog();
4 Change the constructor to also initialize CAcUiDialog:
AsdkAcUiDialogSample::AsdkAcUiDialogSample
(CWnd* pParent /*=NULL*/)
: CAcUiDialog(AsdkAcUiDialogSample::IDD, pParent)
The next step is to add message handlers for the IDC_BUTTON_ANGLE,
IDC_BUTTON_POINT, IDC_COMBO_REGAPPS, IDC_EDIT_ANGLE, and IDC_OK
resources. Using ClassWizard, add handlers mapped as follows:

190 | Chapter 8 MFC Topics


Message handlers

Handler Function Resource ID Message

OnButtonAngle IDC_BUTTON_ANGLE BN_CLICKED

OnButtonPoint IDC_BUTTON_POINT BN_CLICKED

OnOk IDOK BN_CLICKED

OnKillfocusComboRegapps IDC_COMBO_REGAPPS CBN_KILLFOCUS

OnKillfocusEditAngle IDC_EDIT_ANGLE EN_KILLFOCUS

OnKillfocusEditXpt IDC_EDIT_XPOINT EN_KILLFOCUS

OnKillfocusEditYpt IDC_EDIT_YPOINT EN_KILLFOCUS

OnKillfocusEditZpt IDC_EDIT_ZPOINT EN_KILLFOCUS

Add Code to the Handlers


Once you have added the handlers, you are ready to add code to deal with
your dialog. This section summarizes what each handler does with a com-
plete listing.
1 First we add a few utility functions to convert, display, and validate the val-
ues. Notice we are using the CAcUiNumeric and CAcUiAngleEdit controls to
do this:
// Utility functions
void AsdkAcUiDialogSample::DisplayPoint()
{
m_ctrlXPtEdit.SetWindowText(m_strXPt);
m_ctrlXPtEdit.Convert();
m_ctrlYPtEdit.SetWindowText(m_strYPt);
m_ctrlYPtEdit.Convert();
m_ctrlZPtEdit.SetWindowText(m_strZPt);
m_ctrlZPtEdit.Convert();
}

bool AsdkAcUiDialogSample::ValidatePoint()
{
if (!m_ctrlXPtEdit.Validate())
return false;
if (!m_ctrlYPtEdit.Validate())
return false;
if (!m_ctrlZPtEdit.Validate())
return false;
return true;
}

Using AdUi and AcUi with VC++ AppWizard | 191


void AsdkAcUiDialogSample::DisplayAngle()
{
m_ctrlAngleEdit.SetWindowText(m_strAngle);
m_ctrlAngleEdit.Convert();
}

bool AsdkAcUiDialogSample::ValidateAngle()
{
if (!m_ctrlAngleEdit.Validate())
return false;

return true;
}
2 Now add some utility functions to iterate over two symbol tables and display
the names in the two different list boxes:
void AsdkAcUiDialogSample::DisplayBlocks()
{
AcDbBlockTable *pBlockTable;
acdbHostApplicationServices()->workingDatabase()
->getSymbolTable(pBlockTable, AcDb::kForRead);

// Iterate through the block table and display


// the names in the list box.
//
char *pName;
AcDbBlockTableIterator *pBTItr;
if (pBlockTable->newIterator(pBTItr) == Acad::eOk) {
while (!pBTItr->done()) {
AcDbBlockTableRecord *pRecord;
if (pBTItr->getRecord(pRecord, AcDb::kForRead)
== Acad::eOk) {
pRecord->getName(pName);
m_ctrlBlockListBox.InsertString(-1, pName);
pRecord->close();
}
pBTItr->step();
}
}
pBlockTable->close();
}

void AsdkAcUiDialogSample::DisplayRegApps()
{
AcDbRegAppTable *pRegAppTable;
acdbHostApplicationServices()->workingDatabase()
->getSymbolTable(pRegAppTable, AcDb::kForRead);

// Iterate through the reg app table and display the


// names in the list box.
//

192 | Chapter 8 MFC Topics


char *pName;
AcDbRegAppTableIterator *pItr;
if (pRegAppTable->newIterator(pItr) == Acad::eOk) {
while (!pItr->done()) {
AcDbRegAppTableRecord *pRecord;
if (pItr->getRecord(pRecord, AcDb::kForRead)
== Acad::eOk) {
pRecord->getName(pName);
m_ctrlRegAppComboBox.InsertString(-1, pName);
pRecord->close();
}
pItr->step();
}
}
pRegAppTable->close();
}
3 Add the declarations for the functions and variables to the class definition in
the header file:
void DisplayPoint();
bool ValidatePoint();
void DisplayAngle();
bool ValidateAngle();
void DisplayBlocks();
void DisplayRegApps();
CString m_strAngle;
CString m_strXPt;
CString m_strYPt;
CString m_strZPt;
4 Next are the button handlers for picking a point and angle using the
AutoCAD editor. Notice how the BeginEditorCommand(),
CompleteEditorCommand(), and CancelEditorCommand() functions are used
to hide the dialog, allow the call to acedGetPoint and acedGetAngle, and
finally either cancel or redisplay the dialog based on how the user picked:
// AsdkAcUiDialogSample message handlers
void AsdkAcUiDialogSample::OnButtonPoint()
{
// Hide the dialog and give control to the editor
//
BeginEditorCommand();
ads_point pt;

// Get a point
//

Using AdUi and AcUi with VC++ AppWizard | 193


if (acedGetPoint(NULL, "\nPick a point: ", pt) == RTNORM) {

// If the point is good, continue


//
CompleteEditorCommand();
m_strXPt.Format("%g", pt[X]);
m_strYPt.Format("%g", pt[Y]);
m_strZPt.Format("%g", pt[Z]);
DisplayPoint();
} else {

// otherwise cancel the command (including the dialog)


CancelEditorCommand();
}
}

void AsdkAcUiDialogSample::OnButtonAngle()
{
// Hide the dialog and give control to the editor
//
BeginEditorCommand();

// Set up the default point for picking an angle


// based on the m_strXPt, m_strYPt, and m_strZPt values
//
ads_point pt;
acdbDisToF(m_strXPt, -1, &pt[X]);
acdbDisToF(m_strYPt, -1, &pt[Y]);
acdbDisToF(m_strZPt, -1, &pt[Z]);

double angle;

// Get a point from the user


//
if (acedGetAngle(pt, "\nPick an angle: ", &angle) == RTNORM) {
// If we got an angle, go back to the dialog
//
CompleteEditorCommand();

// Convert the acquired radian value to degrees since the


// AcUi control can convert that to the other formats.
//
m_strAngle.Format("%g", angle*(180.0/PI));
DisplayAngle();
} else {
// otherwise cancel the command (including the dialog)
//
CancelEditorCommand();
}
}

194 | Chapter 8 MFC Topics


5 Now the edit box handlers are implemented. Basically we just want to con-
vert the values to the current Units settings:
void AsdkAcUiDialogSample::OnKillfocusEditAngle()
{
// Get and update text the user typed in.
//
m_ctrlAngleEdit.Convert();
m_ctrlAngleEdit.GetWindowText(m_strAngle);
}

void AsdkAcUiDialogSample::OnKillfocusEditXpt()
{
// Get and update text the user typed in.
//
m_ctrlXPtEdit.Convert();
m_ctrlXPtEdit.GetWindowText(m_strXPt);
}

void AsdkAcUiDialogSample::OnKillfocusEditYpt()
{
// Get and update text the user typed in.
//
m_ctrlYPtEdit.Convert();
m_ctrlYPtEdit.GetWindowText(m_strYPt);
}

void AsdkAcUiDialogSample::OnKillfocusEditZpt()
{
// Get and update text the user typed in.
//
m_ctrlZPtEdit.Convert();
m_ctrlZPtEdit.GetWindowText(m_strZPt);
}
6 The combo box handler allows the user to type in a string and then register
this as an application name. This doesn’t really make sense for an applica-
tion, but it shows the use of a combo box:
void AsdkAcUiDialogSample::OnKillfocusComboRegapps()
{
CString strFromEdit;
m_ctrlRegAppComboBox.GetWindowText(strFromEdit);
if (m_ctrlRegAppComboBox.FindString(-1, strFromEdit) == CB_ERR)
if (acdbRegApp(strFromEdit) == RTNORM)
m_ctrlRegAppComboBox.AddString(strFromEdit);
}

Using AdUi and AcUi with VC++ AppWizard | 195


7 To do some data validation, we handle this in the OnOk() handler. This, of
course, can be done at any time. Also notice that the OnOk() handler is
storing the data into the user profile (registry) using the SetDialogData()
function:
void AsdkAcUiDialogSample::OnOK()
{
if (!ValidatePoint()) {
AfxMessageBox("Sorry, Point out of desired range.");
m_ctrlXPtEdit.SetFocus();
return;
}

if (!ValidateAngle()) {
AfxMessageBox("Sorry, Angle out of desired range.”);
m_ctrlAngleEdit.SetFocus();
return;
}

// Store the data into the registry


//
SetDialogData("ANGLE", m_strAngle);
SetDialogData("POINTX", m_strXPt);
SetDialogData("POINTY", m_strYPt);
SetDialogData("POINTZ", m_strZPt);
CAcUiDialog::OnOK();
}
8 Finally, the OnInitDialog() function takes care of all the initialization,
including the resizing and data persistency requirements:
BOOL AsdkAcUiDialogSample::OnInitDialog()
{
// Set the dialog name for registry lookup and storage
//
SetDialogName("AsdkAcUiSample:AsdkAcUiDialog");
CAcUiDialog::OnInitDialog();

DLGCTLINFOdlgSizeInfo[]= {
{ IDC_STATIC_GROUP1, ELASTICX, 20 },
{ IDC_STATIC_GROUP1, ELASTICY, 100 },
{ IDC_EDIT_XPT,ELASTICX, 20 },
{ IDC_EDIT_YPT,ELASTICX, 20 },
{ IDC_EDIT_ZPT,ELASTICX, 20 },
{ IDC_EDIT_ANGLE, ELASTICX, 20 },
{ IDC_STATIC_GROUP2, MOVEX, 20 },
{ IDC_STATIC_GROUP2, ELASTICY, 100 },
{ IDC_STATIC_GROUP2, ELASTICX, 80 },
{ IDC_LIST_BLOCKS, MOVEX, 20 },
{ IDC_LIST_BLOCKS, ELASTICY, 100 },
{ IDC_STATIC_TEXT2,MOVEX, 20 },
{ IDC_STATIC_TEXT2,MOVEY, 100 },
{ IDC_LIST_BLOCKS, ELASTICX, 80 },
{ IDC_STATIC_TEXT2,ELASTICX, 80 },

196 | Chapter 8 MFC Topics


{ IDC_STATIC_GROUP3, MOVEY, 100 },
{ IDC_STATIC_GROUP3, ELASTICX, 20 },
{ IDC_COMBO_REGAPPS, MOVEY, 100 },
{ IDC_COMBO_REGAPPS, ELASTICX, 20 },
{ IDC_STATIC_TEXT3,MOVEY, 100 },
{ IDC_STATIC_TEXT3,ELASTICX, 20 },
{ IDOK,MOVEX, 100 },
{ IDCANCEL, MOVEX, 100 },
};

const DWORD numberofentries =


sizeof dlgSizeInfo / sizeof DLGCTLINFO;
SetControlProperty(dlgSizeInfo, numberofentries);

// Must be within a 100-unit cube centered about 0,0,0.


//
m_ctrlXPtEdit.SetRange(-50.0, 50.0);
m_ctrlYPtEdit.SetRange(-50.0, 50.0);
m_ctrlZPtEdit.SetRange(-50.0, 50.0);

// Must be between 0 and 90 degrees.


//
m_ctrlAngleEdit.SetRange(0.0, 90.0 /*(PI/2.0)*/);

// Assign a title for the dialog.


//
SetWindowText("AcUiDialog Sample");

// Load the default bitmaps.


//
m_ctrlPickButton.AutoLoad();
m_ctrlAngleButton.AutoLoad();

// Get and display the preserved data from the registry.


//
if (!GetDialogData("ANGLE", m_strAngle))
m_strAngle = "0.0";
if (!GetDialogData("POINTX", m_strXPt))
m_strXPt = "0.0";
if (!GetDialogData("POINTY", m_strYPt))
m_strYPt = "0.0";
if (!GetDialogData("POINTZ", m_strZPt))
m_strZPt = "0.0";

DisplayPoint();
DisplayAngle();
DisplayBlocks();
DisplayRegApps();

return TRUE; // return TRUE unless you set the focus to a control
}

Using AdUi and AcUi with VC++ AppWizard | 197


198
Selection Set, Entity, and
Symbol Table Functions

In This Chapter
9
The global functions described in this chapter handle ■ Selection Set and Entity Names
■ Handling Selection Sets
selection sets, drawing entities, and symbol tables. See
■ Entity Name and Data Functions
the AutoCAD Customization Guide for background ■ Symbol Table Access

information on these topics.

199
Selection Set and Entity Names
Most of the ObjectARX functions that handle selection sets and entities iden-
tify a set or entity by its name, which is a pair of longs assigned and main-
tained by AutoCAD. In ObjectARX, names of selection sets and entities have
the corresponding type ads_name.
Before it can manipulate a selection set or an entity, an ObjectARX applica-
tion must obtain the current name of the set or entity by calling one of the
library functions that returns a selection set or entity name.

NOTE Selection set and entity names are volatile; they apply only while you
are working on a drawing with AutoCAD, and they are lost when exiting from
AutoCAD or switching to another drawing.

For selection sets, which also apply only to the current session, the volatility
of names poses no problem, but for entities, which are saved in the drawing
database, it does. An application that must refer at different times to the same
entities in the same drawing (or drawings), can use entity handles, described
in “Entity Handles and Their Uses” on page 216.

Handling Selection Sets


The ObjectARX functions that handle selection sets are similar to those in
AutoLISP. The acedSSGet() function provides the most general means of cre-
ating a selection set. It creates a selection set in one of three ways:

■ Prompting the user to select objects.


■ Explicitly specifying the entities to select by using the PICKFIRST set or the
Crossing, Crossing Polygon, Fence, Last, Previous, Window, or Window
Polygon options (as in interactive AutoCAD use), or by specifying a single
point or a fence of points.
■ Filtering the current drawing database by specifying a list of attributes and
conditions that the selected entities must match. You can use filters with
any of the previous options.

int
acedSSGet (
const char *str,
const void *pt1,
const void *pt2,
const struct resbuf *entmask,
ads_name ss);

200 | Chapter 9 Selection Set, Entity, and Symbol Table Functions


The first argument to acedSSGet() is a string that describes which selection
options to use, as summarized in the following table.

Selection options for acedSSGet


Selection Code Description

NULL Single-point selection (if pt1 is specified)


or user selection (if pt1 is also NULL)

# Nongeometric (all, last, previous)

:$ Prompts supplied

. User pick

:? Other callbacks

A All

B Box

C Crossing

CP Crossing Polygon

:D Duplicates OK

:E Everything in aperture

F Fence

G Groups

I Implied

:K Keyword callbacks

L Last

M Multiple

P Previous

:S Force single object selection only

W Window

WP Window Polygon

X Extended search (search whole database)

Handling Selection Sets | 201


The next two arguments specify point values for the relevant options. (They
should be NULL if they don’t apply.) If the fourth argument, entmask, is not
NULL, it points to the list of entity field values used in filtering. The fifth argu-
ment, ss, identifies the selection set’s name.
The following code shows representative calls to acedSSGet(). As the
acutBuildList() call illustrates, for the polygon options “CP” and “WP” (but
not for “F”), acedSSGet() automatically closes the list of points. You don’t
need to build a list that specifies a final point identical to the first.
ads_point pt1, pt2, pt3, pt4;
struct resbuf *pointlist;
ads_name ssname;
pt1[X] = pt1[Y] = pt1[Z] = 0.0;
pt2[X] = pt2[Y] = 5.0; pt2[Z] = 0.0;

// Get the current PICKFIRST set, if there is one;


// otherwise, ask the user for a general entity selection.
acedSSGet(NULL, NULL, NULL, NULL, ssname);

// Get the current PICKFIRST set, if there is one.


acedSSGet("I", NULL, NULL, NULL, ssname);

// Selects the most recently selected objects.


acedSSGet("P", NULL, NULL, NULL, ssname);

// Selects the last entity added to the database.


acedSSGet("L", NULL, NULL, NULL, ssname);

// Selects entity passing through point (5,5).


acedSSGet(NULL, pt2, NULL, NULL, ssname);

// Selects entities inside the window from (0,0) to (5,5).


acedSSGet("W", pt1, pt2, NULL, ssname);

// Selects entities enclosed by the specified polygon.


pt3[X] = 10.0; pt3[Y] = 5.0; pt3[Z] = 0.0;
pt4[X] = 5.0; pt4[Y] = pt4[Z] = 0.0;
pointlist = acutBuildList(RTPOINT, pt1, RTPOINT, pt2,
RTPOINT, pt3, RTPOINT, pt4, 0);
acedSSGet("WP", pointlist, NULL, NULL, ssname);

// Selects entities crossing the box from (0,0) to (5,5).


acedSSGet("C", pt1, pt2, NULL, ssname);

202 | Chapter 9 Selection Set, Entity, and Symbol Table Functions


// Selects entities crossing the specified polygon.
acedSSGet("CP", pointlist, NULL, NULL, ssname);
acutRelRb(pointlist);

// Selects the entities crossed by the specified fence.


pt4[Y] = 15.0; pt4[Z] = 0.0;
pointlist = acutBuildList(RTPOINT, pt1, RTPOINT, pt2,
RTPOINT, pt3, RTPOINT, pt4, 0);
acedSSGet("F", pointlist, NULL, NULL, ssname);
acutRelRb(pointlist);
The complement of acedSSGet() is acedSSFree(), which releases a selection
set once the application has finished using it. The selection set is specified by
name. The following code fragment uses the ads_name declaration from the
previous example.
acedSSFree(ssname);

NOTE AutoCAD cannot have more than 128 selection sets open at once. This
limit includes the selection sets open in all concurrently running ObjectARX
and AutoLISP applications. The limit may be different on your system. If the limit
is reached, AutoCAD refuses to create more selection sets. Simultaneously
managing a large number of selection sets is not recommended. Instead, keep
a reasonable number of sets open at any given time, and call acedSSFree() to
free unused selection sets as soon as possible. Unlike AutoLISP, the ObjectARX
environment has no automatic garbage collection to free selection sets after they
have been used. An application should always free its open selection sets when
it receives a kUnloadDwgMsg, kEndMsg, or kQuitMsg message.

Selection Set Filter Lists


When the entmask argument specifies a list of entity field values,
acedSSGet() scans the selected entities and creates a selection set containing
the names of all main entities that match the specified criteria. For example,
using this mechanism, you can obtain a selection set that includes all entities
of a given type, on a given layer, or of a given color.
You can use a filter in conjunction with any of the selection options. The “X”
option says to create the selection set using only filtering; as in previous
AutoCAD versions, if you use the “X” option, acedSSGet() scans the entire
drawing database.

NOTE If only filtering is specified (“X”) but the entmask argument is NULL,
acedSSGet() selects all entities in the database.

Handling Selection Sets | 203


The entmask argument must be a result buffer list. Each buffer specifies a
property to check and a value that constitutes a match; the buffer’s restype
field is a DXF group code that indicates the kind of property to look for, and
its resval field specifies the value to match.
The following are some examples.
struct resbuf eb1, eb2, eb3;
char sbuf1[10], sbuf2[10]; // Buffers to hold strings
ads_name ssname1, ssname2;

eb1.restype = 0;// Entity name


strcpy(sbuf1, "CIRCLE");
eb1.resval.rstring = sbuf1;
eb1.rbnext = NULL; // No other properties

// Retrieve all circles.


acedSSGet("X", NULL, NULL, &eb1, ssname1);

eb2.restype = 8; // Layer name


strcpy(sbuf2, "FLOOR3");
eb2.resval.rstring = sbuf2;
eb2.rbnext = NULL; // No other properties

// Retrieve all entities on layer FLOOR3.


acedSSGet("X", NULL, NULL, &eb2, ssname2);

NOTE The resval specified in each buffer must be of the appropriate type.
For example, name types are strings (resval.rstring); elevation and thickness
are double-precision floating-point values (resval.rreal); color, attributes-
follow, and flag values are short integers (resval.rint); extrusion vectors are
three-dimensional points (resval.rpoint); and so forth.

If entmask specifies more than one property, an entity is included in the


selection set only if it matches all specified conditions, as shown in the fol-
lowing example:
eb3.restype = 62; // Entity color
eb3.resval.rint = 1; // Request red entities.
eb3.rbnext = NULL; // Last property in list

eb1.rbnext = &eb2; // Add the two properties


eb2.rbnext = &eb3; // to form a list.

// Retrieve all red circles on layer FLOOR3.


acedSSGet("X", NULL, NULL, &eb1, ssname1);
An entity is tested against all fields specified in the filtering list unless the list
contains relational or conditional operators, as described in “Relational
Tests” on page 207 and “Conditional Filtering” on page 208.

204 | Chapter 9 Selection Set, Entity, and Symbol Table Functions


The acedSSGet() function returns RTERROR if no entities in the database
match the specified filtering criteria.
The previous acedSSGet() examples use the “X” option, which scans the
entire drawing database. If filter lists are used in conjunction with the other
options (user selection, a window, and so forth), the filter is applied only to
the entities initially selected.
The following is an example of the filtering of user-selected entities.
eb1.restype = 0; // Entity type group
strcpy(sbuf1, "TEXT");
eb1.resval.rstring = sbuf1; // Entity type is text.
eb1.rbnext = NULL;

// Ask the user to generally select entities, but include


// only text entities in the selection set returned.
acedSSGet(NULL, NULL, NULL, &eb1, ssname1);
The next example demonstrates the filtering of the previous selection set.
eb1.restype = 0; // Entity type group
strcpy(sbuf1, "LINE");
eb1.resval.rstring = sbuf1; // Entity type is line.
eb1.rbnext = NULL;

// Select all the lines in the previously created selection set.


acedSSGet("P", NULL, NULL, &eb1, ssname1);
The final example shows the filtering of entities within a selection window.
eb1.restype = 8; // Layer
strcpy(sbuf1, "FLOOR9");
eb1.resval.rstring = sbuf1; // Layer name
eb1.rbnext = NULL;

// Select all the entities within the window that are also
// on the layer FLOOR9.
acedSSGet("W", pt1, pt2, &eb1, ssname1);

NOTE The meaning of certain group codes can differ from entity to entity, and
not all group codes are present in all entities. If a particular group code is speci-
fied in a filter, entities that do not contain that group code are excluded from
the selection sets that acedSSGet() returns.

Wild-Card Patterns in Filter Lists


Symbol names specified in filter lists can include wild-card patterns. The
wild-card patterns recognized by acedSSGet() are the same as those recog-
nized by the function acutWcMatch().

Handling Selection Sets | 205


The following sample code retrieves an anonymous block named *U2.
eb2.restype = 2; // Block name
strcpy(sbuf1, "’*U2"); // Note the reverse quote.
eb2.resval.rstring = sbuf1; // Anonymous block name
eb2.rbnext = NULL;

// Select Block Inserts of the anonymous block *U2.


acedSSGet("X", NULL, NULL, &eb2, ssname1);

Filtering for Extended Data


Extended data (xdata) are text strings, numeric values, 3D points, distances,
layer names, or other data attached to an object, typically by an external
application.
The size of extended data is 16K bytes.
You can retrieve extended data for a particular application by specifying its
name in a filter list, using the -3 group code. The acedSSGet() function
returns entities with extended data registered to the specified name;
acedSSGet() does not retrieve individual extended data items (with group
codes in the range 1000–2000).
The following sample code fragment selects all circles that have extended
data registered to the application whose ID is “APPNAME”.
eb1.restype = 0; // Entity type
strcpy(sbuf1, "CIRCLE");
eb1.resval.rstring = sbuf1; // Circle
eb1.rbnext = &eb2;

eb2.restype = -3; // Extended data


eb2.rbnext = &eb3;
eb3.restype = 1001;
strcpy(sbuf2, "APPNAME");
eb3.resval.rstring = sbuf2; // APPNAME application
eb3.rbnext = NULL;

// Select circles with XDATA registered to APPNAME.


acedSSGet("X", NULL, NULL, &eb1, ssname1);
If more than one application name is included in the list, acedSSGet()
includes an entity in the selection set only if it has extended data for all the
specified applications. For example, the following code selects circles with
extended data registered to “APP1” and “APP2”.
eb1.restype = 0; // Entity type
strcpy(sbuf1, "CIRCLE");
eb1.resval.rstring = sbuf1; // Circle
eb1.rbnext = &eb2;

eb2.restype = -3; // Extended data


eb2.rbnext = &eb3;

206 | Chapter 9 Selection Set, Entity, and Symbol Table Functions


eb3.restype = 1001;
strcpy(sbuf2, "APP1");
eb2.resval.rstring = sbuf2; // APP1 application
eb2.rbnext = &eb4;

eb4.restype = 1001; // Extended data


strcpy(sbuf3, "APP2");
eb4.resval.rstring = sbuf3; // APP2 application
eb4.rbnext = NULL;

// Select circles with XDATA registered to APP1 & APP2.


acedSSGet("X", NULL, NULL, &eb1, ssname1);
You can specify application names using wild-card strings, so you can search
for the data of multiple applications at one time. For example, the following
code selects all circles with extended data registered to “APP1” or “APP2” (or
both).
eb1.restype = 0; // Entity type
strcpy(sbuf1, "CIRCLE");
eb1.resval.rstring = sbuf1; // Circle
eb1.rbnext = &eb2;

eb2.restype = -3; // Extended data


eb2.rbnext = &eb3;
eb3.restype = 1001; // Extended data
strcpy(sbuf2, "APP1,APP2");
eb3.resval.rstring = sbuf2; // Application names
eb3.rbnext = NULL;

// Select circles with XDATA registered to APP1 or APP2.


acedSSGet("X", NULL, NULL, &eb1, ssname1);
The following string finds extended data of the same application.
strcpy(sbuf2, "APP[12]");

Relational Tests
Unless you specify otherwise, there is an implied “equals” test between the
entity and each item in the filter list. For numeric groups (integers, real
values, points, and vectors), you can specify other relations by including rela-
tional operators in the filter list. Relational operators are passed as a special
-4 group, whose value is a string that indicates the test to be applied to the
next group in the filter list.
The following sample code selects all circles whose radii are greater than or
equal to 2.0:
eb3.restype = 40; // Radius
eb3.resval.rreal = 2.0;
eb3.rbnext = NULL;

Handling Selection Sets | 207


eb2.restype = -4; // Filter operator
strcpy(sbuf1, ">=");
eb2.resval.rstring = sbuf1; // Greater than or equals
eb2.rbnext = &eb3;

eb1.restype = 0; // Entity type


strcpy(sbuf2, "CIRCLE");
eb1.resval.rstring = sbuf2; // Circle
eb1.rbnext = &eb2;

// Select circles whose radius is >= 2.0.


acedSSGet("X", NULL, NULL, &eb1, ssname1);

Conditional Filtering
The relational operators just described are binary operators. You can also test
groups by creating nested Boolean expressions that use conditional opera-
tors. The conditional operators are also specified by -4 groups, but they must
be paired.
The following sample code selects all circles in the drawing with a radius of
1.0 and all lines on the layer “ABC”.
eb1 = acutBuildList(-4, "<or",-4, "<and", RTDXF0,
"CIRCLE", 40, 1.0, -4, "and>", -4, "<and", RTDXF0,
"LINE", 8, "ABC", -4, "and>", -4, "or>", 0);

acedSSGet("X", NULL, NULL, &eb1, ssname1);


The conditional operators are not case sensitive; you can use lowercase
equivalents.

NOTE Conditional expressions that test for extended data using the -3 group
can contain only -3 groups. See “Filtering for Extended Data” on page 206.

To select all circles that have extended data registered to either “APP1” or
“APP2” but not both, you could use the following code.
eb1 = acutBuildList(-4, "<xor", -3, "APP1", -3, "APP2",
-4, "xor>", 0);

acedSSGet("X", NULL, NULL, &eb1, ssname1);

208 | Chapter 9 Selection Set, Entity, and Symbol Table Functions


Selection Set Manipulation
You can add entities to a selection set or remove them from it by calling the
functions acedSSAdd() and acedSSDel(), which are similar to the Add and
Remove options when AutoCAD interactively prompts the user to select
objects or remove objects.

NOTE The acedSSAdd() function can also be used to create a new selection
set, as shown in the following example. As with acedSSGet(), acedSSAdd()
creates a new selection set only if it returns RTNORM.

The following sample code fragment creates a selection set that includes the
first and last entities in the current drawing.
ads_name fname, lname; // Entity names
ads_name ourset; // Selection set name

// Get the first entity in the drawing.


if (acdbEntNext(NULL, fname) != RTNORM) {
acdbFail("No entities in drawing\n");
return BAD;
}

// Create a selection set that contains the first entity.


if (acedSSAdd(fname, NULL, ourset) != RTNORM) {
acdbFail("Unable to create selection set\n");
return BAD;
}

// Get the last entity in the drawing.


if (acdbEntLast(lname) != RTNORM) {
acdbFail("No entities in drawing\n");
return BAD;
}
// Add the last entity to the same selection set.
if (acedSSAdd(lname, ourset, ourset) != RTNORM) {
acdbFail("Unable to add entity to selection set\n");
return BAD;
}
The example runs correctly even if there is only one entity in the database
(in which case both acdbEntNext() and acdbEntLast() set their arguments
to the same entity name). If acedSSAdd() is passed the name of an entity that
is already in the selection set, it ignores the request and does not report an
error.
As the example also illustrates, the second and third arguments to
acedSSAdd() can be passed as the same selection set name. That is, if the call
is successful, the selection set named by both arguments contains an addi-

Handling Selection Sets | 209


tional member after acedSSAdd() returns (unless the specified entity was
already in the selection set).
The following call removes the entity with which the selection set was
created in the previous example.
acedSSDel(fname, ourset);
If there is more than one entity in the drawing (that is, if fname and lname
are not equal), the selection set ourset now contains only lname, the last
entity in the drawing.
The function acedSSLength() returns the number of entities in a selection
set, and acedSSMemb() tests whether a particular entity is a member of a selec-
tion set. Finally, the function acedSSName() returns the name of a particular
entity in a selection set, using an index into the set (entities in a selection set
are numbered from 0).

NOTE Because selection sets can be quite large, the len argument returned
by acedSSLength() must be declared as a long integer. The i argument used
as an index in calls to acedSSName() must also be a long integer. (In this con-
text, standard C compilers will correctly convert a plain integer.)

The following sample code shows a few calls to acedSSName().


ads_name sset, ent1, ent4, lastent;
long ilast;

// Create the selection set (by prompting the user).


acedSSGet(NULL, NULL, NULL, NULL, sset);

// Get the name of first entity in sset.


if (acedSSName(sset, 0L, ent1) != RTNORM)
return BAD;

// Get the name of the fourth entity in sset.


if (acedSSName(sset, 3L, ent4) != RTNORM) {
acdbFail("Need to select at least four entities\n");
return BAD;
}

// Find the index of the last entity in sset.


if (acedSSLength(sset, &ilast) != RTNORM)
return BAD;

// Get the name of the last entity in sset.


if (acedSSName(sset, ilast-1, lastent) != RTNORM)
return BAD;

210 | Chapter 9 Selection Set, Entity, and Symbol Table Functions


Transformation of Selection Sets
The function acedXformSS() transforms a selection set by applying a trans-
formation matrix (of type ads_matrix) to the entities in the set. This provides
an efficient alternative to invoking the ROTATE, SCALE, MIRROR, or MOVE
commands with acedCommand() (or acedCmd()) or to changing values in the
database with acdbEntMod(). The selection set can be obtained in any of the
usual ways. The matrix must do uniform scaling. That is, the elements in the
scaling vector SX SY SZ must all be equal; in matrix notation, M00 M11 M22.
If the scale vector is not uniform, acedXformSS() reports an error.
The following sample code gets a selection set by using a crossing box, and
then applies the following matrix to it.

0.5 0.0 0.0 20.0

0.0 0.5 0.0 5.0

0.0 0.0 0.5 0.0

0.0 0.0 0.0 1.0

Applying this matrix scales the entities by one-half (which moves them
toward the origin) and translates their location by (20.0,5.0).
int rc, i, j;
ads_point pt1, pt2;
ads_matrix matrix;
ads_name ssname;

// Initialize pt1 and pt2 here.

rc = acedSSGet("C", pt1, pt2, NULL, ssname);


if (rc == RTNORM) {
// Initialize to identity.
ident_init(matrix);
// Initialize scale factors.
matrix[0][0] = matrix[1][1] = matrix[2][2] = 0.5;

Handling Selection Sets | 211


// Initialize translation vector.
matrix[0][T] = 20.0;
matrix[1][T] = 5.0;

rc = acedXformSS(ssname, matrix);
}
When you invoke acedDragGen(), you must specify a similar function to let
users interactively control the effect of the transformation. The function’s
declaration must have the following form:
int scnf(ads_point pt, ads_matrix mt)
It should return RTNORM if it modified the matrix, RTNONE if it did not, or
RTERROR if it detects an error.

The acedDragGen() function calls the scnf function every time the user
moves the cursor. The scnf() function sets the new value of the matrix mt.
When scnf() returns with a status of RTNORM, acedDragGen() applies the new
matrix to the selection set. If there is no need to modify the matrix (for exam-
ple, if scnf() simply displays transient vectors with acedGrVecs()), scnf()
should return RTNONE. In this case, acedDragGen() ignores mt and doesn’t
transform the selection set.
In the following example, the function sets the matrix to simply move (trans-
late) the selection set without scaling or rotation.
int dragsample(usrpt, matrix)
ads_point usrpt
ads_matrix matrix;
{
ident_init(matrix); // Initialize to identity.
// Initialize translation vector.
matrix[0][T] = usrpt[X];
matrix[1][T] = usrpt[Y];
matrix[2][T] = usrpt[Z];

return RTNORM; // Matrix was modified.


}
Conversely, the following version of dragsample() scales the selection set in
the current XY plane but doesn’t move it.
int dragsample(usrpt, matrix)
ads_point usrpt
ads_matrix matrix;
{
ident_init(matrix); // Initialize to identity.
matrix[0][0] = userpt[X];
matrix[1][1] = userpt[Y];

return RTNORM; // Matrix was modified.


}

212 | Chapter 9 Selection Set, Entity, and Symbol Table Functions


A call to acedDragGen() that employs the transformation function looks like
this:
int rc;
ads_name ssname;
ads_point return_pt;

// Prompt the user for a general entity selection:


if (acedSSGet(NULL, NULL, NULL, NULL, ssname) == RTNORM)

rc = acedDragGen(ssname, // The new entities


"Scale the selected objects by dragging", // Prompt
0, // Display normal cursor (crosshairs)
dragsample, // Pointer to the transform function
return_pt); // Set to the specified location
More complex transformations can rotate entities, combine transformations
(as in the acedXformSS() example), and so forth.
Combining transformation matrices is known as matrix composition. The
following function composes two transformation matrices by returning their
product in resmat.
void xformcompose(ads_matrix xf1, ads_matrix xf2,
ads_matrix resmat)
{
int i, j, k;
ads_real sum;

for (i=0; i<=3; i++) {


for (j=0; j<=3; j++) {
sum = 0.0;
for (k=0; k<3; k++) {
sum += xf1[i,k] * xf2[k,j];
}
resmat[i,j] = sum;
}
}
}

Handling Selection Sets | 213


Entity Name and Data Functions
Entity-handling functions are organized into two categories: functions that
retrieve the name of a particular entity and functions that retrieve or modify
entity data.

Entity Name Functions


To operate on an entity, an ObjectARX application must obtain its name for
use in subsequent calls to the entity data functions or the selection set func-
tions. The functions acedEntSel(), acedNEntSelP(), and acedNEntSel()
return not only the entity’s name but additional information for the appli-
cation’s use. The entsel functions require AutoCAD users (or the applica-
tion) to select an entity by specifying a point on the graphics screen; all the
other entity name functions can retrieve an entity even if it is not visible on
the screen or is on a frozen layer. Like the acedGetxxx() functions, you can
have acedEntSel(), acedNEntSelP(), and acedNEntSel() return a keyword
instead of a point by preceding them with a call to acedInitGet().
If a call to acedEntSel(), acedNEntSelP(), or acedNEntSel() returns
RTERROR, and you want to know whether the user specified a point that had
no entity or whether the user pressed RETURN, you can inspect the value of
the ERRNO system variable. If the user specified an empty point, ERRNO
equals 7 (OL_ENTSELPICK). If the user pressed RETURN, ERRNO equals 52
(OL_ENTSELNULL). (You can use the symbolic names if your program includes
the header file ol_errno.h.)

NOTE You should inspect ERRNO immediately after acedEntSel(),


acedNEntSelP(), or acedNEntSel() returns. A subsequent ObjectARX call can
change the value of ERRNO.

The acdbEntNext() function retrieves entity names sequentially. If its first


argument is NULL, it returns the name of the first entity in the drawing data-
base; if its first argument is the name of an entity in the current drawing, it
returns the name of the succeeding entity.
The following sample code fragment illustrates how acedSSAdd() can be used
in conjunction with acdbEntNext() to create selection sets and to add mem-
bers to an existing set.
ads_name ss, e1, e2;

214 | Chapter 9 Selection Set, Entity, and Symbol Table Functions


// Set e1 to the name of first entity.
if (acdbEntNext(NULL, e1) != RTNORM) {
acdbFail("No entities in drawing\n");
return BAD;
}

// Set ss to a null selection set.


acedSSAdd(NULL, NULL, ss);

// Return the selection set ss with entity name e1 added.


if (acedSSAdd(e1, ss, ss) != RTNORM) {
acdbFail("Unable to add entity to selection set\n");
return BAD;
}

// Get the entity following e1.


if (acdbEntNext(e1, e2) != RTNORM) {
acdbFail("Not enough entities in drawing\n");
return BAD;
}

// Add e2 to selection set ss


if (acedSSAdd(e2, ss, ss) != RTNORM) {
acdbFail("Unable to add entity to selection set\n");
return BAD;
}
The following sample code fragment uses acdbEntNext() to “walk” through
the database, one entity at a time.
ads_name ent0, ent1;
struct resbuf *entdata;

if (acdbEntNext(NULL, ent0) != RTNORM) {


acdbFail("Drawing is empty\n");
return BAD;
}
do {
// Get entity’s definition data.
entdata = acdbEntGet(ent0);
if (entdata == NULL) {
acdbFail("Failed to get entity\n");
return BAD;
}
.
. // Process new entity.
.
if (acedUsrBrk() == TRUE) {
acdbFail("User break\n");
return BAD;
}
acutRelRb(entdata); // Release the list.
ads_name_set(ent0, ent1); // Bump the name.
} while (acdbEntNext(ent1, ent0) == RTNORM);

Entity Name and Data Functions | 215


NOTE You can also go through the database by “bumping” a single variable
in the acdbEntNext() call (such as acdbEntNext(ent0, ent0)), but if you do,
the value of the variable is no longer defined once the loop ends.

The acdbEntLast() function retrieves the name of the last entity in the
database. The last entity is the most recently created main entity, so
acdbEntLast() can be called to obtain the name of an entity that has just
been created by means of a call to acedCommand(), acedCmd(), or
acdbEntMake().

The acedEntSel() function prompts the AutoCAD user to select an entity by


specifying a point on the graphics screen; acedEntSel() returns both the
entity name and the value of the specified point. Some entity operations
require knowledge of the point by which the entity was selected. Examples
from the set of existing AutoCAD commands include BREAK, TRIM, EXTEND,
and OSNAP.

Entity Handles and Their Uses


The acdbHandEnt() function retrieves the name of an entity with a specific
handle. Like entity names, handles are unique within a drawing. Unlike
entity names, an entity’s handle is constant throughout its life. ObjectARX
applications that manipulate a specific database can use acdbHandEnt() to
obtain the current name of an entity they must use.
The following sample code fragment uses acdbHandEnt() to obtain an entity
name and to print it out.
char handle[17];
ads_name e1;

strcpy(handle, "5a2");

if (acdbHandEnt(handle, e1) != RTNORM)


acdbFail("No entity with that handle exists\n");
else
acutPrintf("%ld", e1[0]);
In one particular editing session, this code might print out 60004722. In
another editing session with the same drawing, it might print an entirely dif-
ferent number. But in both cases, the code is accessing the same entity.
The acdbHandEnt() function has an additional use: entities deleted from the
database (with acdbEntDel()) are not purged until you leave the current
drawing (by exiting AutoCAD or switching to another drawing). This means
that acdbHandEnt() can recover the names of deleted entities, which can
then be restored to the drawing by a second call to acdbEntDel().

216 | Chapter 9 Selection Set, Entity, and Symbol Table Functions


Entities in drawings cross-referenced with XREF Attach are not actually part
of the current drawing; their handles are unchanged and cannot be accessed
by acdbHandEnt(). However, when drawings are combined by means of
INSERT, INSERT *, XREF Bind (XBIND), or partial DXFIN, the handles of entities
in the incoming drawing are lost, and incoming entities are assigned new
handle values to ensure that each handle in the original drawing remains
unique.

NOTE Extended data can include entity handles to save relational structures
in a drawing. In some circumstances, these handles require translation or main-
tenance. See “Using Handles in Extended Data” on page 240.

Entity Context and Coordinate Transform Data


The acedNEntSelP() function is similar to acedEntSel(), except that it
passes two additional result arguments to facilitate the handling of entities
that are nested within block references.

NOTE Another difference between acedNEntSelP() and acedEntSel() is


that when the user responds to an acedNEntSelP() call by selecting a complex
entity, acedNEntSelP() returns the selected subentity and not the complex
entity’s header as acedEntSel() does. For example, when the user selects a
polyline, acedNEntSelP() returns a vertex subentity instead of the polyline
header. To retrieve the polyline header, the application must use acdbEntNext()
to walk forward to the Seqend subentity and obtain the name of the header from
the Seqend subentity’s -2 group. This is true also when the user selects attributes
in a nested block reference and when the pick point is specified in the
acedNEntSelP() call.

Coordinate Transformation
The first of the additional arguments returned by acedNEntSelP() is a 4x4
transformation matrix of type ads_matrix. This matrix is known as the
Model to World Transformation Matrix. It enables the application to trans-
form points in the entity’s definition data (and extended data, if that is
present) from the entity’s model coordinate system (MCS) into the World
Coordinate System (WCS). The MCS applies only to nested entities. The ori-
gin of the MCS is the insert point of the block, and its orientation is that of
the UCS that was in effect when the block was created.

Entity Name and Data Functions | 217


If the selected entity is not a nested entity, the transformation matrix is set
to the identity matrix. The transformation is expressed by the following
matrix multiplication:

X' M00 M01 M02 M03 X

Y' M10 M11 M12 M13 Y


=
Z' M20 M21 M22 M23 Z

1.0 0.0 0.0 0.0 1.0 1.0

X' = M00X + M01Y + M02Z + M03

Y' = M10X + M11Y + M12Z + M13

Z' = M20X + M21Y + M22Z + M23

The individual coordinates of a transformed point are obtained from the


equations where Mmn is the Model to World Transformation Matrix
coordinates, (X,Y,Z) is the entity definition data point expressed in MCS
coordinates, and (X’,Y’,Z’) is the resulting entity definition data point
expressed in WCS coordinates. See “Transformation Matrices” on page 535.

NOTE To transform a vector rather than a point, do not add the translation
vector [M03 M13 M23] (from the fourth column of the transformation matrix).

The following sample code defines a function, mcs2wcs(), that performs the
transformations described by the preceding equations. It takes the transfor-
mation matrix returned by acedNEntSelP() and a single point (presumably
from the definition data of a nested entity), and returns the translated point.
If the third argument to mcs2wcs(), is_pt, is set to 0 (FALSE), the last column
of the transformation matrix—the translation vector or displacement—is not
added to the result. This enables the function to translate a vector as well as
a point.
void mcs2wcs(xform, entpt, is_pt, worldpt)
ads_matrix xform;
ads_point entpt, worldpt;
int is_pt;

218 | Chapter 9 Selection Set, Entity, and Symbol Table Functions


{
int i, j;

worldpt[X] = worldpt[Y] = worldpt[Z] = 0.0;

for (i=X; i<=Z; i++)


for (j=X; j<=Z; j++)
worldpt[i] += xform[i][j] * entpt[j];

if (is_pt) // If it’s a point, add in the displacement


for (i=X; i<=Z; i++)
worldpt[i] += xform[i][T];
}
The following code fragment shows how mcs2wcs() might be used in con-
junction with acedNEntSelP() to translate point values into the current
WCS.
ads_name usrent, containent;
ads_point usrpt, defpt, wcspt;
ads_matrix matrix;
struct resbuf *containers, *data, *rb, *prevrb;

status = acedNEntSelP(NULL, usrent, usrpt, FALSE, matrix,


&containers);
if ((status != RTNORM) || (containers == NULL))
return BAD;
data = acdbEntGet(usrent);

// Extract a point (defpt) from the data obtained by calling


// acdbEntGet() for the selected kind of entity.
.
.
.
mcs2wcs(matrix, defpt, TRUE, wcspt);
The acedNEntSelP() function also allows the program to specify the pick
point. A pickflag argument determines whether or not acedNEntSelP() is
called interactively.
In the following example, the acedNEntSelP() call specifies its own point for
picking the entity and does not prompt the user. The pickflag argument is
TRUE to indicate that the call supplies its own point value (also, the prompt is
NULL).

ads_point ownpoint;

ownpoint[X] = 2.7; ownpoint[Y] = 1.5; ownpoint[Z] = 0.0;

status = acedNEntSelP(NULL, usrent, ownpt, TRUE, matrix,


&containers);
The acedNEntSel() function is provided for compatibility with existing
ObjectARX applications. New applications should be written using
acedNEntSelP().

Entity Name and Data Functions | 219


The Model to World Transformation Matrix returned by the call to
acedNEntSel() has the same purpose as that returned by acedNEntSelP(),
but it is a 4x3 matrix—passed as an array of four points—that uses the con-
vention that a point is a row rather than a column. The transformation is
described by the following matrix multiplication:

M M M
00 10 20
M M M
01 11 21
X' Y' Z' 1.0 = X Y Z 1.0
M M M
02 12 22
M M M
03 13 23

The equations for deriving the new coordinates are as follows:

X' = XM00 + YM01 + ZM02 + M03

Y' = XM10 + YM11 + ZM12 + M13

Z' = XM20 + YM21 + ZM22 + M23

Although the matrix format is different, the formulas are equivalent to those
for the ads_matrix type, and the only change required to adapt mcs2wcs()
for use with acedNEntSel() is to declare the matrix argument as an array of
four points.
void mcs2wcs(xform, entpt, is_pt, worldpt);
ads_point xform[4]; // 4x3 version
ads_point entpt, worldpt;
int is_pt;
The identity form of the 4x3 matrix is as follows:

100

010

001

000

220 | Chapter 9 Selection Set, Entity, and Symbol Table Functions


In addition to using a different matrix convention, acedNEntSel() doesn’t
let the program specify the pick point.
Context Data
The function acedNEntSelP() provides an argument for context data,
refstkres. (This is another feature not provided by acedEntSel().) The
refstkres argument is a pointer to a linked list of result buffers that contains
the names of the entity’s container blocks. The list is ordered from lowest to
highest. In other words, the first name in the list is the name of the block
containing the selected entity, and the last name in the list is the name of the
block that was directly inserted into the drawing. The following figure shows
the format of this list.

refstkres

RTENAME RTENAME
ename1 ename2

most deeply nested


block that contains
the selected entity
RTENAME
enameN

outermost (inserted)
block that contains
the selected entity

If the selected entity entres is not a nested entity, refstkres is a NULL


pointer. This is a convenient way to test whether or not the entity’s coordi-
nates need to be translated. (Because xformres is returned as the identity
matrix for entities that are not nested, applying it to the coordinates of such
entities does no harm but does cost some needless execution time.)
Using declarations from the previous acedNEntSelP() example, the name of
the block that immediately contains the user-selected entity can be found by
the following code (in the acedNEntSelP() call, the pickflag argument is
FALSE for interactive selection).

status = acedNEntSelP(NULL, usrent, usrpt, FALSE, matrix,


&containers);
if ((status != RTNORM) || (containers == NULL))
return BAD;

containent[0] = containers->resval.rlname[0];
containent[1] = containers->resval.rlname[1];

Entity Name and Data Functions | 221


The name of the outermost container (that is, the entity originally inserted
into the drawing) can be found by a sequence such as the following:
// Check that containers is not already NULL.

rb = containers;
while (rb != NULL) {
prevrb = rb;
rb = containers->rbnext;
}
// The result buffer pointed to by prevrb now contains the
// name of the outermost block.
In the following example, the current coordinate system is the WCS. Using
AutoCAD, create a block named SQUARE consisting of four lines.
Command: line
From point: 1,1
To point: 3,1
To point: 3,3
To point: 1,3
To point: c

Command: block
Block name (or ?): square
Insertion base point: 2,2
Select objects: Select the four lines you just drew
Select objects: ENTER

Then insert the block in a UCS rotated 45 degrees about the Z axis.
Command: ucs
Origin/ZAxis/3point/Entity/View/X/Y/Z/Prev/Restore/Save/Del/?/
<World>: z
Rotation angle about Z axis <0>: 45

Command: insert
Block name (or ?): square
Insertion point: 7,0
X scale factor <1> / Corner / XYZ: ENTER
Y scale factor (default=X): ENTER
Rotation angle: ENTER

222 | Chapter 9 Selection Set, Entity, and Symbol Table Functions


If an ObjectARX application calls acedNEntSelP() (or acedNEntSel()) and
the user selects the lower-left side of the square, these functions set the
entres argument to equal the name of the selected line. They set the pick
point (ptres) to (6.46616,-1.0606,0.0) or a nearby point value. They return
the transformation matrix (xformres) as shown in the following figure.
Finally, they set the list of container entities (refstkres) to point to a single
result buffer containing the entity name of the block SQUARE.

0.707107 -0.707107 0.0 4.94975 0.707107 0.707107 0.0

0.707107 0.707107 -0.0 4.94975 -0.707107 0.707107 0.0

0.0 0.0 1.0 0.0 0.0 -0.0 1.0

0.0 0.0 0.0 1.0 4.94975 4.94975 0.0

ads_nentselp() result ads_nentsel() result

Entity Data Functions


Some functions operate on entity data and can be used to modify the current
drawing database. The acdbEntDel() function deletes a specified entity. The
entity is not purged from the database until you leave the current drawing.
So if the application calls acdbEntDel() a second time during that session
and specifies the same entity, the entity is undeleted. (You can use
acdbHandEnt() to retrieve the names of deleted entities.)

NOTE Using acdbEntDel(), attributes and polyline vertices cannot be deleted


independently from their parent entities; acdbEntDel() operates only on main
entities. To delete an attribute or vertex, use acedCommand() or acedCmd() to
invoke the AutoCAD ATTEDIT or PEDIT commands, use acdbEntMod() to rede-
fine the entity without the unwanted subentities, or open the vertex or attribute
and use its erase() method to erase it.

The acdbEntGet() function returns the definition data of a specified entity.


The data is returned as a linked list of result buffers. The type of each item
(buffer) in the list is specified by a DXF group code. The first item in the list
contains the entity’s current name (restype == -1).

Entity Name and Data Functions | 223


An ObjectARX application could retrieve and print the definition data for an
entity by using the following two functions. (The printdxf() function does
not handle extended data.)
void getlast()
{
struct resbuf *ebuf, *eb;
ads_name ent1;

acdbEntLast(ent1);
ebuf = acdbEntGet(ent1);

eb = ebuf;

acutPrintf("\nResults of entgetting last entity\n");

// Print items in the list.


for (eb = ebuf; eb != NULL; eb = eb->rbnext)
printdxf(eb);

// Release the acdbEntGet() list.


acutRelRb(ebuf);
}

int printdxf(eb)
struct resbuf *eb;
{
int rt;

if (eb == NULL)
return RTNONE;

if ((eb->restype >= 0) && (eb->restype <= 9))


rt = RTSTR ;
else if ((eb->restype >= 10) && (eb->restype <= 19))
rt = RT3DPOINT;
else if ((eb->restype >= 38) && (eb->restype <= 59))
rt = RTREAL ;
else if ((eb->restype >= 60) && (eb->restype <= 79))
rt = RTSHORT ;
else if ((eb->restype >= 210) && (eb->restype <= 239))
rt = RT3DPOINT ;
else if (eb->restype < 0)

224 | Chapter 9 Selection Set, Entity, and Symbol Table Functions


// Entity name (or other sentinel)
rt = eb->restype;
else
rt = RTNONE;

switch (rt) {

case RTSHORT:
acutPrintf("(%d . %d)\n", eb->restype,
eb->resval.rint);
break;

case RTREAL:
acutPrintf("(%d . %0.3f)\n", eb->restype,
eb->resval.rreal);
break;

case RTSTR:
acutPrintf("(%d . \"%s\")\n", eb->restype,
eb->resval.rstring);
break;

case RT3DPOINT:
acutPrintf("(%d . %0.3f %0.3f %0.3f)\n",
eb->restype,
eb->resval.rpoint[X], eb->resval.rpoint[Y],
eb->resval.rpoint[Z]);
break;

case RTNONE:
acutPrintf("(%d . Unknown type)\n", eb->restype);
break;

case -1:
case -2:
// First block entity
acutPrintf("(%d . <Entity name: %8lx>)\n",
eb->restype, eb->resval.rlname[0]);
}

return eb->restype;
}
In the next example, the following (default) conditions apply to the current
drawing.

■ The current layer is 0


■ The current linetype is CONTINUOUS
■ The current elevation is 0
■ Entity handles are disabled

Entity Name and Data Functions | 225


Also, the user has drawn a line with the following sequence of commands:
Command: line
From point: 1,2
To point: 6,6
To point: ENTER

Then a call to getlast() would print the following (the name value will
vary).
Results from acdbEntGet() of last entity:
(-1 . <Entity name: 60000014>)
(0 . "LINE")
(8 . "0")
(10 1.0 2.0 0.0)
(11 6.0 6.0 0.0)
(210 0.0 0.0 1.0)

NOTE The printdxf() function prints the output in the format of an AutoLISP
association list, but the items are stored in a linked list of result buffers.

The result buffer at the start of the list (with a -1 sentinel code) contains the
name of the entity that this list represents. The acdbEntMod() function uses
it to identify the entity to be modified.
The codes for the components of the entity (stored in the restype field) are
those used by DXF. As with DXF, the entity header items are returned only if
they have values other than the default. Unlike DXF, optional entity defini-
tion fields are returned regardless of whether they equal their defaults. This
simplifies processing; an application can always assume that these fields are
present. Also unlike DXF, associated X, Y, and Z coordinates are returned as
a single point variable (resval.rpoint), not as separate X (10), Y (20), and Z
(30) groups. The restype value contains the group number of the X coordi-
nate (in the range 10–19).

226 | Chapter 9 Selection Set, Entity, and Symbol Table Functions


To find a group with a specific code, an application can traverse the list. The
entitem() function shown here searches a result buffer list for a group of a
specified type.
static struct resbuf *entitem(rchain, gcode)
struct resbuf *rchain;
int gcode;
{
while ((rchain != NULL) && (rchain->restype != gcode))
rchain = rchain->rbnext;

return rchain;
}
If the DXF group code specified by the gcode argument is not present in the
list (or if gcode is not a valid DXF group), entitem() “falls off the end” and
returns NULL. Note that entitem() is equivalent to the AutoLISP function
(assoc).

The acdbEntMod() function modifies an entity. It passes a list that has the
same format as a list returned by acdbEntGet(), but with some of the entity
group values (presumably) modified by the application. This function
complements acdbEntGet(); the primary means by which an ObjectARX
application updates the database is by retrieving an entity with
acdbEntGet(), modifying its entity list, and then passing the list back to the
database with acdbEntMod().

NOTE To restore the default value of an entity’s color or linetype, use


acdbEntMod() to set the color to 256, which is BYLAYER, or the linetype to
BYLAYER.

The following code fragment retrieves the definition data of the first entity
in the drawing, and changes its layer property to MYLAYER.
ads_name en;
struct resbuf *ed, *cb;
char *nl = "MYLAYER";

if (acdbEntNext(NULL, en) != RTNORM)


return BAD; // Error status

ed = acdbEntGet(en); // Retrieve entity data.

Entity Name and Data Functions | 227


for (cb = ed; cb != NULL; cb = cb->rbnext)
if (cb->restype == 8) { // DXF code for Layer
// Check to make sure string buffer is long enough.
if (strlen(cb->resval.rstring) < (strlen(nl)))
// Allocate a new string buffer.
cb->resval.rstring = realloc(cb->resval.rstring,
strlen(nl) + 1);

strcpy(cb->resval.rstring, nl);

if (acdbEntMod(ed) != RTNORM) {
acutRelRb(ed);
return BAD; // Error
}
break; // From the for loop
}
acutRelRb(ed); // Release result buffer.
Memory management is the responsibility of an ObjectARX application.
Code in the example ensures that the string buffer is the correct size, and it
releases the result buffer returned by acdbEntGet() (and passed to
acdbEntMod()) once the operation is completed, whether or not the call to
acdbEntMod() succeeds.

NOTE If you use acdbEntMod() to modify an entity in a block definition, this


affects all INSERT or XREF references to that block; also, entities in block defini-
tions cannot be deleted by acdbEntDel().

An application can also add an entity to the drawing database by calling the
acdbEntMake() function. Like acdbEntMod(), the argument to
acdbEntMake() is a result-buffer list whose format is similar to that of a list
returned by acdbEntGet(). (The acdbEntMake() call ignores the entity name
field [-1] if that is present.) The new entity is appended to the drawing data-
base (it becomes the last entity in the drawing). If the entity is a complex
entity (a polyline or block), it is not appended to the database until it is
complete.
The following sample code fragment creates a circle on the layer MYLAYER.
int status;
struct resbuf *entlist;
ads_point center = {5.0, 7.0, 0.0};
char *layer = "MYLAYER";

entlist = acutBuildList(RTDXF0, "CIRCLE",// Entity type


8, layer, // Layer name
10, center, // Center point
40, 1.0, // Radius
0 );

228 | Chapter 9 Selection Set, Entity, and Symbol Table Functions


if (entlist == NULL) {
acdbFail("Unable to create result buffer list\n");
return BAD;
}

status = acdbEntMake(entlist);
acutRelRb(entlist); // Release acdbEntMake buffer.

if (status == RTERROR) {
acdbFail("Unable to make circle entity\n");
return BAD;
}
Both acdbEntMod() and acdbEntMake() perform the same consistency checks
on the entity data passed to them as the AutoCAD DXFIN command performs
when reading DXF files. They fail if they cannot create valid drawing entities.

Complex Entities
A complex entity (a polyline or block) must be created by multiple calls to
acdbEntMake(), using a separate call for each subentity. When
acdbEntMake() first receives an initial component for a complex entity, it
creates a temporary file in which to gather the definition data (and extended
data, if present). Each subsequent acdbEntMake() call appends the new sub-
entity to the file. When the definition of the complex entity is complete (that
is, when acdbEntMake() receives an appropriate Seqend or Endblk subentity),
the entity is checked for consistency, and if valid, it is added to the drawing.
The file is deleted when the complex entity is complete or when its creation
is canceled.
The following example contains five calls to acdbEntMake() that create a sin-
gle complex entity, a polyline. The polyline has a linetype of DASHED and a
color of BLUE. It has three vertices located at coordinates (1,1,0), (4,6,0), and
(3,2,0). All other optional definition data assume default values.
int status;
struct resbuf *entlist, result;
ads_point newpt;

entlist = acutBuildList(
RTDXF0, "POLYLINE",// Entity type
62, 5, // Color (blue)
6, "dashed",// Linetype
66, 1, // Vertices follow.
0);

if (entlist == NULL) {
acdbFail("Unable to create result buffer list\n");
return BAD;
}

Entity Name and Data Functions | 229


status = acdbEntMake(entlist);
acutRelRb(entlist); // Release acdbEntMake() buffer.

if (status != RTNORM) {
acutPrintf ("%d",status);
acedGetVar ("ERRNO", &result);
acutPrintf ("ERRNO == %d, result.resval.rint);
acdbFail("Unable to start polyline\n");
return BAD;
}

newpt[X] = 1.0;
newpt[Y] = 1.0;
newpt[Z] = 0.0; // The polyline is planar

entlist = acutBuildList(
RTDXF0, "VERTEX", // Entity type
62, 5, // Color (blue)
6, "dashed", // Linetype
10, newpt, // Start point
0);

if (entlist == NULL) {
acdbFail("Unable to create result buffer list\n");
return BAD;
}

status = acdbEntMake(entlist);
acutRelRb(entlist); // Release acdbEntMake() buffer.

if (status != RTNORM) {
acdbFail("Unable to add polyline vertex\n");
return BAD;
}

newpt[X] = 4.0;
newpt[Y] = 6.0;

entlist = acutBuildList(
RTDXF0, "VERTEX", // Entity type
62, 5, // Color (blue)
6, "dashed", // Linetype
10, newpt, // Second point
0);

if (entlist == NULL) {
acdbFail("Unable to create result buffer list\n");
return BAD;
}

status = acdbEntMake(entlist);
acutRelRb(entlist); // Release acdbEntMake() buffer.

230 | Chapter 9 Selection Set, Entity, and Symbol Table Functions


if (status != RTNORM) {
acdbFail("Unable to add polyline vertex\n");
return BAD;
}

newpt[X] = 3.0;
newpt[Y] = 2.0;

entlist = acutBuildList(
RTDXF0, "VERTEX", // Entity type
62, 5, // Color (blue)
6, "dashed", // Linetype
10, newpt, // Third point
0);

if (entlist == NULL) {
acdbFail("Unable to create result buffer list\n");
return BAD;
}

status = acdbEntMake(entlist);
acutRelRb(entlist); // Release acdbEntMake() buffer.

if (status != RTNORM) {
acdbFail("Unable to add polyline vertex\n");
return BAD;
}

entlist = acutBuildList(
RTDXF0, "SEQEND", // Sequence end
62, 5, // Color (blue)
6, "dashed", // Linetype
0);

if (entlist == NULL) {
acdbFail("Unable to create result buffer list\n");
return BAD;
}

status = acdbEntMake(entlist);
acutRelRb(entlist); // Release acdbEntMake() buffer.

if (status != RTNORM) {
acdbFail("Unable to complete polyline\n");
return BAD;
}
Creating a block is similar, except that when acdbEntMake() successfully
creates the Endblk entity, it returns a value of RTKWORD. You can verify the
name of the new block by a call to acedGetInput().

Entity Name and Data Functions | 231


Anonymous Blocks
You can create anonymous blocks by calls to acdbEntMake(). To do so, you
must open the block with a name whose first character is * and a block type
flag (group 70) whose low-order bit is set to 1. AutoCAD assigns the new
anonymous block a name; characters in the name string that follow the * are
often ignored. You then create the anonymous block the way you would cre-
ate a regular block, except that it is more important to call acedGetInput().
Because the name is generated by AutoCAD, your program has no other way
of knowing the name of the new block.
The following code begins an anonymous block, ends it, and retrieves its
name.
int status;
struct resbuf *entlist;
ads_point basept;
char newblkname[20];

ads_point pnt1 = ( 0.0, 0.0, 0.0);


entlist = acutBuildList(
RTDXF0, "BLOCK",
2, "*ANON", // Only the ’*’ matters.
10, "1", // No other flags are set.
0 );

if (entlist == NULL) {
acdbFail("Unable to create result buffer list\n");
return BAD;
}

status = acdbEntMake(entlist);
acutRelRb(entlist); // Release acdbEntMake buffer.

if (status != RTNORM) {
acdbFail("Unable to start anonymous block\n");
return BAD;
}

// Add entities to the block by more acdbEntMake calls.


.
.
.
entlist = acutBuildList(RTDXF0, "ENDBLK", 0 );

if (entlist == NULL) {
acdbFail("Unable to create result buffer list\n");
return BAD;
}

232 | Chapter 9 Selection Set, Entity, and Symbol Table Functions


status = acdbEntMake(entlist);
acutRelRb(entlist); // Release acdbEntMake buffer.

if (status != RTKWORD) {
acdbFail("Unable to close anonymous block\n");
return BAD;
}
status = acedGetInput(newblkname);

if (status != RTNORM) {
acdbFail("Anonymous block not created\n");
return BAD;
}
To reference an anonymous block, create an insert entity with
acdbEntMake(). (You cannot pass an anonymous block to the INSERT
command.)
Continuing the previous example, the following code fragment inserts the
anonymous block at (0,0).
basept[X] = basept[Y] = basept[Z] = 0.0;

entlist = acutBuildList(
RTDXF0, "INSERT",
2, newblkname, // From acedGetInput
10, basept,
0 );

if (entlist == NULL) {
acdbFail("Unable to create result buffer list\n");
return BAD;
}

status = acdbEntMake(entlist);
acutRelRb(entlist); // Release acdbEntMake buffer.

if (status != RTNORM) {
acdbFail("Unable to insert anonymous block\n");
return BAD;
}

Entity Data Functions and Graphics Screen


Changes to the drawing made by the entity data functions are reflected on
the graphics screen, provided that the entity being deleted, undeleted, mod-
ified, or made is in an area and is on a layer that is currently visible. There is
one exception: when acdbEntMod() modifies a subentity, it does not update
the image of the entire (complex) entity. The reason should be clear. If, for
example, an application were to modify 100 vertices of a complex polyline
with 100 iterated calls to acdbEntMod(), the time required to recalculate and
redisplay the entire polyline as each vertex was changed would be unaccept-

Entity Name and Data Functions | 233


ably slow. Instead, an application can perform a series of subentity
modifications and then redisplay the entire entity with a single call to the
acdbEntUpd() function.

In the following example, the first entity in the current drawing is a polyline
with several vertices. The following code modifies the second vertex of the
polyline and then regenerates its screen image.
ads_name e1, e2;
struct resbuf *ed, *cb;

if (acdbEntNext(NULL, e1) != RTNORM) {


acutPrintf("\nNo entities found. Empty drawing.");
return BAD;
}

acdbEntNext(e1, e2);

if ((ed = acdbEntGet(e2)) != NULL) {


for (cb = ed; cb != NULL; cb = cb->rbnext)

if (cb->restype == 10) { // Start point DXF code


cb->resval.rpoint[X] = 1.0;// Change coordinates.
cb->resval.rpoint[Y] = 2.0;

if (acdbEntMod(ed) != RTNORM) { // Move vertex.


acutPrintf("\nBad vertex modification.");
acutRelRb(ed);
return BAD;
} else {
acdbEntUpd(e1); // Regen the polyline.
acutRelRb(ed);
return GOOD; // Indicate success.
}
}
acutRelRb(ed);
}
return BAD; // Indicate failure.
The argument to acdbEntUpd() can specify either a main entity or a suben-
tity; in either case, acdbEntUpd() regenerates the entire entity. Although its
primary use is for complex entities, acdbEntUpd() can regenerate any entity
in the current drawing.

NOTE If the modified entity is in a block definition, then the acdbEntUpd()


function is not sufficient. You must regenerate the drawing by invoking the
AutoCAD REGEN command (with acedCmd() or acedCommand()) to ensure that
all instances of the block references are updated.

234 | Chapter 9 Selection Set, Entity, and Symbol Table Functions


Notes on Extended Data
Several ObjectARX functions are provided to handle extended data. An
entity’s extended data follows the entity’s normal definition data. This is
illustrated by the next figure, which shows the scheme of a result-buffer list
for an entity containing extended data.
An entity’s extended data can be retrieved by calling acdbEntGetX(), which
is similar to acdbEntGet(). The acdbEntGetX() function retrieves an entity’s
normal definition data and the extended data for applications specified in
the acdbEntGetX() call.

NOTE When extended data is retrieved with acdbEntGetX(), the beginning


of extended data is indicated by a -3 sentinel code; the -3 sentinel is in a result
buffer that precedes the first 1001 group. The 1001 group contains the applica-
tion name of the first application retrieved, as shown in the figure.

normal entity definition data


(regular definition data buffers) extended data sentinel

head
-1 0 -3

ename entity type

NULL
1001 1001

"APPNAME1" "APPNAME2"

first registered application name second registered application name


first application's extended data second application's extended data

Organization of Extended Data


Extended data consists of one or more 1001 groups, each of which begins
with a unique application name. Application names are string values. The
extended data groups returned by acdbEntGetX() follow the definition data
in the order in which they are saved in the database.

Entity Name and Data Functions | 235


Within each application’s group, the contents, meaning, and organization of
the data are defined by the application; AutoCAD maintains the information
but doesn’t use it. Group codes for extended data are in the range 1000–1071,
as follows:
String 1000. Strings in extended data can be up to 255 bytes long
(with the 256th byte reserved for the null character).
Application 1001 (also a string value). Application names can be up to
name 31 bytes long (the 32nd byte is reserved for the null
character) and must adhere to the rules for symbol table
names (such as layer names). An application name can
contain letters, digits, and the special characters $ (dollar
sign), - (hyphen), and _ (underscore). It cannot contain
spaces. Letters in the name are converted to uppercase.
A group of extended data cannot consist of an application name with no
other data.

To delete extended data


1 Call acdbEntGet() to retrieve the entity.
2 Add to the end of the list returned by acdbEntGet() a resbuf with a restype
of -3.
3 Add to the end of the list another resbuf with a restype of 1001 and a
resval.rstring set to <appname>.

If you attempt to add a 1001 group but no other extended data to an existing
entity, the attempt is ignored. If you attempt to make an entity whose only
extended data group is a single 1001 group, the attempt fails.
Layer name 1003. Name of a layer associated with the extended data.
Database 1005. Handles of entities in the drawing database. Under
handle certain conditions, AutoCAD translates these.
3D point 1010. Three real values, contained in a point.
Real 1040. A real value.
Integer 1070. A 16-bit integer (signed or unsigned).
Long 1071. A 32-bit signed (long) integer. If the value that
appears in a 1071 group is a short integer or a real value,
it is converted to a long integer; if it is invalid (for
example, a string), it is converted to a long zero (0L).

236 | Chapter 9 Selection Set, Entity, and Symbol Table Functions


Control 1002. An extended data control string can be either “{” or
string “}”. These braces enable the application to organize its
data by subdividing it into lists. The left brace begins a list,
and the right brace terminates the most recent list. (Lists
can be nested.) When it reads the extended data,
AutoCAD checks to ensure that braces are balanced
correctly.
Binary data 1004. Binary data is organized into variable-length
chunks, which can be handled in ObjectARX with the
ads_binary structure. The maximum length of each
chunk is 127 bytes.
World space 1011. Unlike a simple 3D point, the world space
position coordinates are moved, scaled, rotated, and mirrored
along with the parent entity to which the extended data
belongs. The world space position is also stretched when
the STRETCH command is applied to the parent entity and
this point lies within the selection window.
World space 1012. A 3D point that is scaled, rotated, or mirrored along
displacement with the parent, but not stretched or moved.
World 1013. Also a 3D point that is rotated, or mirrored along
direction with the parent, but not scaled, stretched, or moved. The
world direction is a normalized displacement that always
has a unit length.
Distance 1041. A real value that is scaled along with the parent
entity.
Scale factor 1042. Also a real value that is scaled along with the parent.

NOTE If a 1001 group appears within a list, it is treated as a string and does
not begin a new application group.

Registering an Application
Application names are saved with the extended data of each entity that uses
them and in the APPID table. An application must register the name or names
it uses. In ObjectARX, this is done by a call to acdbRegApp(). The
acdbRegApp() function specifies a string to use as an application name. It
returns RTNORM if it can successfully add the name to APPID; otherwise, it
returns RTERROR. A result of RTERROR usually indicates that the name is
already in the symbol table. This is not an actual error condition but a

Entity Name and Data Functions | 237


normally expected return value, because the application name needs to be
registered only once per drawing.
To register itself, an application should first check that its name is not already
in the APPID table, because acdbRegApp() needs to be called only once per
drawing. If the name is not there, the application must register it; otherwise,
it can go ahead and use the data.
The following sample code fragment shows the typical use of acdbRegApp().
#define APPNAME "Local_Operation_App_3-2"

struct resbuf *rbp;


static char *local_appname = APPNAME;
// The static declaration prevents a copy being made of the string
// every time it’s referenced.
.
.
.
if ((rbp = acdbTblSearch("APPID", local_appname, 0)) == NULL) {
if (acdbRegApp(APPNAME) != RTNORM) { // Some other
// problem
acutPrintf("Can’t register XDATA for %s.",
local_appname);
return BAD;
}
} else {
acutRelRb(rbp);
}

Retrieving Extended Data


An application can obtain registered extended data by calling the
acdbEntGetX() function, which is similar to acdbEntGet(). While
acdbEntGet() returns only definition data, acdbEntGetX() returns both the
definition data and the extended data for the applications it requests. It
requires an additional argument, apps, that specifies the application names
(this differs from AutoLISP, in which the (entget) function has been
extended to accept an optional argument that specifies application names).
The names passed to acdbEntGetX() must correspond to applications regis-
tered by a previous call to acdbRegApp(); they can also contain wild-card
characters. If the apps argument is a NULL pointer, the call to acdbEntGetX()
is identical to an acdbEntGet() call.
The following sample code fragment shows a typical sequence for retrieving
extended data for two specified applications. Note that the apps argument
passes application names in linked result buffers.
static struct resbuf appname2 = {NULL, RTSTR},
appname1 = {&appname2, RTSTR},
*working_ent;

238 | Chapter 9 Selection Set, Entity, and Symbol Table Functions


strsave(appname1.rstring, "MY_APP_1");
strsave(appname2.rstring, "SOMETHING_ELSE");
.
.
.

// Only extended data from "MY_APP_1" and


// "SOMETHING_ELSE" are retrieved:
working_ent = acdbEntGetX(&work_ent_addr, &appname1);
if (working_ent == NULL) {
// Gracefully handle this failure.
.
.
.
}

// Update working entity groups.

status = acdbEntMod(working_ent);
// Only extended data from registered applications still in the
// working_ent list are modified.
As the sample code shows, extended data retrieved by the acdbEntGetX()
function can be modified by a subsequent call to acdbEntMod(), just as
acdbEntMod() is used to modify normal definition data. (Extended data can
also be created by defining it in the entity list passed to acdbEntMake().)
Returning the extended data of only specifically requested applications
protects one application from damaging the data of another application. It
also controls the amount of memory that an application uses, and simplifies
the extended data processing that an application performs.

NOTE Because the strings passed with apps can include wild-card characters,
an application name of “*” will cause acdbEntGetX() to return all extended
data attached to an entity.

Managing Extended Data Memory Use


Extended data is limited to 16 kilobytes per entity. Because the extended data
of an entity can be created and maintained by multiple applications, this can
lead to problems when the size of the extended data approaches its limit.
ObjectARX provides two functions, acdbXdSize() and acdbXdRoom(), to
assist in managing the memory that extended data occupies. When
acdbXdSize() is passed a result-buffer list of extended data, it returns the
amount of memory (in bytes) that the data will occupy; when acdbXdRoom()
is passed the name of an entity, it returns the remaining number of free bytes
that can still be appended to the entity.

Entity Name and Data Functions | 239


The acdbXdSize() function must read an extended data list, which can be
large. Consequently, this function can be slow, so it is recommended that
you don’t call it frequently. A better approach is to use it (in conjunction
with acdbXdRoom()) in an error handler. If a call to acdbEntMod() fails, you
can use acdbXdSize() and acdbXdRoom() to find out whether the call failed
because the entity ran out of extended data, and then take appropriate action
if that is the reason for the failure.

Using Handles in Extended Data


Extended data can contain handles (group 1005) to save relational structures
within a drawing. One entity can reference another by saving the other
entity’s handle in its extended data. The handle can be retrieved later and
passed to acdbHandEnt() to obtain the other entity. Because more than one
entity can reference another, extended data handles are not necessarily
unique; the AUDIT command does require that handles in extended data are
either NULL or valid entity handles (within the current drawing). The best
way to ensure that extended entity handles are valid is to obtain a referenced
entity’s handle directly from its definition data, by means of acdbEntGet().
(The handle value is in group 5 or 105.)
To reference entities in other drawings (for example, entities that are
attached by means of an xref), you can avoid protests from AUDIT by using
extended entity strings (group 1000) rather than handles (group 1005),
because the handles of cross-referenced entities either are not valid in the
current drawing or conflict with valid handles. However, if an XREF Attach
changes to an XREF Bind or is combined with the current drawing in some
other way, it is up to the application to revise entity references accordingly.

NOTE When drawings are combined by means of INSERT, INSERT *, XREF


Bind (XBIND), or partial DXFIN, handles are translated so that they become valid
in the current drawing. (If the incoming drawing did not employ handles, new
ones are assigned.) Extended entity handles that refer to incoming entities are
also translated when these commands are invoked.

When an entity is placed in a block definition (by means of the BLOCK com-
mand), the entity within the block is assigned new handles. (If the original
entity is restored with OOPS, it retains its original handles.) The value of any
extended data handles remains unchanged. When a block is exploded (with
EXPLODE), extended data handles are translated, in a manner similar to the
way they are translated when drawings are combined. If the extended data
handle refers to an entity not within the block, it is unchanged; but if the
extended data handle refers to an entity within the block, it is assigned the
value of the new (exploded) entity’s handle.

240 | Chapter 9 Selection Set, Entity, and Symbol Table Functions


Xrecord Objects
The xrecord object is a built-in object class with a DXF name of “XRECORD”,
which stores and manages arbitrary data streams, represented externally as a
result-buffer list composed of DXF groups with “normal object” groups (that
is, non-xdata group codes), ranging from 1 through 369.

WARNING! The xrecord object is designed in a way that will not offend
earlier versions of AutoCAD; however, the xrecord object will disappear when
creating a DXF file from a pre-Release 13c4 level of AutoCAD.

Xrecord objects are generic objects intended for use by ObjectARX and
AutoLISP applications. This class allows applications to create and store
arbitrary object structures of arbitrary result-buffer lists of non-graphical
information completely separate from entities. The root owner for all appli-
cation-defined objects is either the named object dictionary, which accepts
any AcDbObject type as an entry, including AcDbXrecord, or the extension
dictionary of any object.
Applications are expected to use unique entry names in the named object
dictionary. The logic of using a named object dictionary or extension dictio-
nary entry name is similar to that of a REGAPP name. In fact, REGAPP names
are perfect for use as entry names when appending application-defined
objects to the database or a particular object.
The use of xrecord objects represents a substantial streamlining with respect
to the current practice of assigning xdata to entities. Because an xrecord
object does not need to be linked with an entity, you no longer need to create
dummy entities (dummy entities were often used to provide more room for
xdata), or entities on frozen layers.
Applications are now able to do the following:

■ Protect information from indiscriminate purging or thawing of layers,


which is always a threat to nongraphical information stored in xdata.
■ Utilize the new object ownership/pointer reference fields (330–369) to
maintain internal database object references. Arbitrary handle values are
completely exempt from the object ID translation mechanics. This is
opposed to 1005 xdata groups, which are translated in some cases but not
in others.
■ Remain unaffected by the 16K per object xdata capacity limit. This object
can also be used instead of xdata on specific entities and objects, if one so
wishes, with the understanding that no matter where you store xrecord
objects, they have no built-in size limit, other than the limit of 2 GB
imposed by signed 32-bit integer range.

Entity Name and Data Functions | 241


In the case of object-specific state, xrecord objects are well suited for stor-
ing larger amounts of stored information, while xdata is better suited for
smaller amounts of data.

When building up a hierarchy of xrecord objects (adding ownership or


pointer reference to an object), that object must already exist in the database,
and, thus, have a legitimate entity name. Because acdbEntMake() does not
return an entity name, and acdbEntLast() only recognizes graphical objects,
you must use acdbEntMakeX() if you are referencing nongraphical objects.
The acdbEntMakeX() function returns the entity name of the object added to
the database (either graphical or nongraphical). The initial Release 13 imple-
mentation of acdbEntMake() only supported objects whose class dictated its
specific owner-container object in the current drawing (such as symbol table
entries, all supplied Release 13 entity types, and dictionary objects), and reg-
istered the new object with its owner. These functions will continue to do
this for the same set of built-in object classes, including entities. For xrecords
and all custom classes, these functions will add the object to the database,
leaving it up to the application to establish its ownership links back up to the
named object dictionary. The acdbEntMakeX() function appends the object
to the database for all object types, including those that come with
AutoCAD. So, even when using this function on existing entity types, your
program is responsible for setting up ownership.

Symbol Table Access


The acdbTblNext() function sequentially scans symbol table entries, and the
acdbTblSearch() function retrieves specific entries. Table names are speci-
fied by strings. The valid names are “LAYER”, “LTYPE”, “VIEW”, “STYLE”,
“BLOCK”, “UCS”, “VPORT”, and “APPID”. Both of these functions return entries
as result-buffer lists with DXF group codes.
The first call to acdbTblNext() returns the first entry in the specified table.
Subsequent calls that specify the same table return successive entries unless
the second argument to acdbTblNext() (rewind) is nonzero, in which case
acdbTblNext() returns the first entry again.

In the following example, the function getblock() retrieves the first block
(if any) in the current drawing, and calls the printdxf() function to display
that block’s contents in a list format.
void getblock()
{
struct resbuf *bl, *rb;

242 | Chapter 9 Selection Set, Entity, and Symbol Table Functions


bl = acdbTblNext("BLOCK", 1); // First entry

acutPrintf("\nResults from getblock():\n");

// Print items in the list as "assoc" items.


for (rb = bl; rb != NULL; rb = rb->rbnext)
printdxf(rb);

// Release the acdbTblNext list.


acutRelRb(bl);
}
Entries retrieved from the BLOCK table contain a -2 group that contains the
name of the first entity in the block definition. In a drawing with a single
block named BOX, a call to getblock() prints the following (the name value
varies from session to session):
Results from getblock():
(0 . "BLOCK")
(2 . "BOX")
(70 . 0)
(10 9.0 2.0 0.0)
(-2 . <Entity name: 40000126>)

The first argument to acdbTblSearch() is a string that names a table, but the
second argument is a string that names a particular symbol in the table. If the
symbol is found, acdbTblSearch() returns its data. This function has a third
argument, setnext, that can be used to coordinate operations with
acdbTblNext(). If setnext is zero, the acdbTblSearch() call has no effect on
acdbTblNext(), but if setnext is nonzero, the next call to acdbTblNext()
returns the table entry that follows the entry found by acdbTblSearch().
The setnext option is especially useful when dealing with the VPORT symbol
table, because all viewports in a particular viewport configuration have the
same name (such as *ACTIVE).
Keep in mind that if the VPORT symbol table is accessed when TILEMODE is
off, changes have no visible effect until TILEMODE is turned back on.
(TILEMODE is set either by the SETVAR command or by entering its name
directly.) Do not confuse the VPORT symbol table with viewport entities.

Symbol Table Access | 243


To find and process each viewport in the configuration named 4VIEW, you
might use the following code:
struct resbuf *v, *rb;

v = acdbTblSearch("VPORT", "4VIEW", 1);

while (v != NULL} {
for (rb = v; rb != NULL; rb = rb->rbnext)
if (rb->restype == 2)
if (strcmp(rb->resval.rstring, "4VIEW") == 0) {
.// Process the VPORT entry
.
.
acutRelRb(v);
// Get the next table entry.
v = acdbTblNext("VPORT", 0);
} else {
acutRelRb(v);
v = NULL; // Break out of the while loop.
break; // Break out of the for loop.
}
}

244 | Chapter 9 Selection Set, Entity, and Symbol Table Functions


Global Functions for
Interacting with AutoCAD

In This Chapter
10
The global functions described in this chapter allow ■ AutoCAD Queries and
Commands
your application to communicate with AutoCAD. This
■ Getting User Input

chapter discusses functions for registering commands ■ Conversions


■ Character Type Handling
with AutoCAD, handling user input, handling data
■ Coordinate System
conversions, and setting up external devices such as Transformations
■ Display Control
the tablet.
■ Tablet Calibration
■ Wild-Card Matching

245
AutoCAD Queries and Commands
The functions described in this section access AutoCAD commands and
services.

General Access
The most general of the functions that access AutoCAD are acedCommand()
and acedCmd(). Like the (command) function in AutoLISP, these functions
send commands and other input directly to the AutoCAD Command
prompt.
int
acedCommand(int rtype, ...);
int
acedCmd(struct resbuf *rbp);
Unlike most other AutoCAD interaction functions, acedCommand() has a
variable-length argument list: arguments to acedCommand() are treated as
pairs except for RTLE and RTLB, which are needed to pass a pick point. The
first of each argument pair identifies the result type of the argument that fol-
lows, and the second contains the actual data. The final argument in the list
is a single argument whose value is either 0 or RTNONE. Typically, the first
argument to acedCommand() is the type code RTSTR, and the second data argu-
ment is a string that is the name of the command to invoke. Succeeding
argument pairs specify options or data that the specified command requires.
The type codes in the acedCommand() argument list are result types.
The data arguments must correspond to the data types and values expected
by that command’s prompt sequence. These can be strings, real values,
integers, points, entity names, or selection set names. Data such as angles,
distances, and points can be passed either as strings (as the user might enter
them) or as the values themselves (that is, as integer, real, or point values).
An empty string (“”) is equivalent to entering a space on the keyboard.
Because of the type identifiers, the acedCommand() argument list is not the
same as the argument list for the AutoLISP (command) routine. Be aware of
this if you convert an AutoLISP routine into an ObjectARX application.
There are restrictions on the commands that acedCommand() can invoke,
which are comparable to the restrictions on the AutoLISP (command)
function.

246 | Chapter 10 Global Functions for Interacting with AutoCAD


NOTE The acedCommand() and acedCmd() functions can invoke the AutoCAD
SAVE or SAVEAS command. When they do so, AutoLISP issues a kSaveMsg
message to all other ObjectARX applications currently loaded, but not to the
application that invoked SAVE. The comparable code is sent when these func-
tions invoke NEW, OPEN, END, or QUIT from an application.

The following sample function shows a few calls to acedCommand().


int docmd()
{
ads_point p1;
ads_real rad;

if (acedCommand(RTSTR, "circle", RTSTR, "0,0", RTSTR,


"3,3", 0) != RTNORM)
return BAD;
if (acedCommand(RTSTR, "setvar", RTSTR, "thickness",
RTSHORT, 1, 0) != RTNORM)
return BAD;

p1[X] = 1.0; p1[Y] = 1.0; p1[Z] = 3.0;


rad = 4.5;

if (acedCommand(RTSTR, "circle", RT3DPOINT, p1, RTREAL,


rad, 0) != RTNORM)
return BAD;

return GOOD;
}
Provided that AutoCAD is at the Command prompt when this function is
called, AutoCAD performs the following actions:
1 Draws a circle that passes through (3.0,3.0) and whose center is at (0.0,0.0).
2 Changes the current thickness to 1.0. Note that the first call to
acedCommand() passes the points as strings, while the second passes a short
integer. Either method is possible.
3 Draws another (extruded) circle whose center is at (1.0,1.0,3.0) and whose
radius is 4.5. This last call to acedCommand() uses a 3D point and a real
(double-precision floating-point) value. Note that points are passed by refer-
ence, because ads_point is an array type.

Using acedCmd()
The acedCmd() function is equivalent to acedCommand() but passes values to
AutoCAD in the form of a result-buffer list. This is useful in situations where
complex logic is involved in constructing a list of AutoCAD commands. The
acutBuildList() function is useful for constructing command lists.

AutoCAD Queries and Commands | 247


The acedCmd() function also has the advantage that the command list can be
modified at runtime rather than be fixed at compile time. Its disadvantage is
that it takes slightly longer to execute. For more information, see the
ObjectARX Reference.
The following sample code fragment causes AutoCAD to perform a REDRAW
on the current graphics screen (or viewport).
struct resbuf *cmdlist;

cmdlist = acutBuildList(RTSTR, "redraw", 0);


if (cmdlist == NULL) {
acdbFail("Couldn’t create list\n");
return BAD;
}

acedCmd(cmdlist);
acutRelRb(cmdlist);

Pausing for User Input


If an AutoCAD command is in progress and AutoCAD encounters the PAUSE
symbol as an argument to acedCommand() or acedCmd(), the command is sus-
pended to allow direct user input, including dragging. The PAUSE symbol
consists of a string that contains a single backslash. This is similar to the
backslash pause mechanism provided for menus.
The following call to acedCommand() invokes the ZOOM command and then
uses the PAUSE symbol so that the user can select one of the ZOOM options.
result = acedCommand(RTSTR, "Zoom", RTSTR, PAUSE, RTNONE);
The following call starts the CIRCLE command, sets the center point as (5,5),
and then pauses to let the user drag the circle’s radius on the screen. When
the user specifies the chosen point (or enters the chosen radius), the function
resumes, drawing a line from (5,5) to (7,5).
result = acedCommand(RTSTR, "circle", RTSTR, "5,5",
RTSTR, PAUSE, RTSTR, "line", RTSTR, "5,5", RTSTR,
"7,5", RTSTR, "", 0);

Passing Pick Points to AutoCAD Commands


Some AutoCAD commands (such as TRIM, EXTEND, and FILLET) require users
to specify a pick point as well as the entity. To pass such pairs of entity and
point data by means of acedCommand(), you must specify the name of the
entity first and enclose the pair in the RTLB and RTLE result type codes.
The following sample code fragment creates a circle centered at (5,5) and a
line that extends from (1,5) to (8,5); it assumes that the circle and line are
created in an empty drawing. It then uses a pick point with the TRIM com-

248 | Chapter 10 Global Functions for Interacting with AutoCAD


mand to trim the line at the circle’s edge. The acdbEntNext() function finds
the next entity in the drawing, and the acdbEntLast() function finds the last
entity in the drawing.
ads_point p1;
ads_name first, last;

acedCommand(RTSTR, "Circle", RTSTR, "5,5", RTSTR, "2",


0);
acedCommand(RTSTR, "Line", RTSTR, "1,5", RTSTR, "8,5",
RTSTR, "", 0);
acdbEntNext(NULL, first); // Get circle.
acdbEntLast(last); // Get line.
// Set pick point.
p1[X] = 2.0;
p1[Y] = 5.0;
p1[Z] = 0.0;
acedCommand(RTSTR, "Trim", RTENAME, first, RTSTR, "",
RTLB, RTENAME, last, RTPOINT, p1, RTLE,
RTSTR, "", 0);

System Variables
A pair of functions, acedGetVar() and acedSetVar(), enable ObjectARX
applications to inspect and change the value of AutoCAD system variables.
These functions use a string to specify the variable name (in either uppercase
or lowercase), and a (single) result buffer for the type and value of the vari-
able. A result buffer is required in this case because the AutoCAD system
variables come in a variety of types: integers, real values, strings, 2D points,
and 3D points.
The following sample code fragment ensures that subsequent FILLET
commands use a radius of at least 1.
struct resbuf rb, rb1;

acedGetVar("FILLETRAD", &rb);

rb1.restype = RTREAL;
rb1.resval.rreal = 1.0;
if (rb.resval.rreal < 1.0)
if (acedSetVar("FILLETRAD", &rb1) != RTNORM)
return BAD; // Setvar failed.
In this example, the result buffer is allocated as an automatic variable when
it is declared in the application. The application does not have to explicitly
manage the buffer’s memory use as it does with dynamically allocated
buffers.

AutoCAD Queries and Commands | 249


If the AutoCAD system variable is a string type, acedGetVar() allocates space
for the string. The application is responsible for freeing this space. You can
do this by calling the standard C library function free(), as shown in the fol-
lowing example:
acedGetVar("TEXTSTYLE", &rb);
if (rb.resval.rstring != NULL)
// Release memory acquired for string:
free(rb.resval.rstring);

AutoLISP Symbols
The functions acedGetSym() and acedPutSym() let ObjectARX applications
inspect and change the value of AutoLISP variables.
In the first example, the user enters the following AutoLISP expressions:
Command: (setq testboole t)
T
Command: (setq teststr “HELLO, WORLD”)
“HELLO, WORLD”
Command: (setq sset1 (ssget))
<Selection set: 1>

Then the following sample code shows how acedGetSym() retrieves the new
values of the symbols.
struct resbuf *rb;
int rc;
long sslen;

rc = acedGetSym("testboole", &rb);
if (rc == RTNORM && rb->restype == RTT)
acutPrintf("TESTBOOLE is TRUE\n");
acutRelRb(rb);

rc = acedGetSym("teststr", &rb);
if (rc == RTNORM && rb->restype == RTSTR)
acutPrintf("TESTSTR is %s\n", rb->resval.rstring);
acutRelRb(rb);

rc = acedGetSym("sset1", &rb);
if (rc == RTNORM && rb->restype == RTPICKS) {
rc = acedSSLength(rb->resval.rlname, &sslen);
acutPrintf("SSET1 contains %lu entities\n", sslen);
}
acutRelRb(rb);
Conversely, acedPutSym() can create or change the binding of AutoLISP
symbols, as follows:
ads_point pt1;
pt1[X] = pt1[Y] = 1.4; pt1[Z] = 10.9923;

250 | Chapter 10 Global Functions for Interacting with AutoCAD


rb = acutBuildList(RTSTR, "GREETINGS", 0);
rc = acedPutSym("teststr", rb);
acedPrompt("TESTSTR has been reset\n");
acutRelRb(rb);

rb = acutBuildList(RTLB, RTSHORT, -1,


RTSTR, "The combinations of the world",
RTSTR, "are unstable by nature.", RTSHORT, 100,
RT3DPOINT, pt1,
RTLB, RTSTR, "He jests at scars",
RTSTR, "that never felt a wound.", RTLE, RTLE, 0);

rc = acedPutSym("longlist", rb);
acedPrompt("LONGLIST has been created\n");
acutRelRb(rb);
To set an AutoLISP variable to nil, make the following assignment and func-
tion call:
rb->restype = RTNIL;
acedPutSym("var1", rb);
Users can retrieve these new values. (As shown in the example, your program
should notify users of any changes.)
TESTSTR has been reset.
LONGLIST has been created.
Command: !teststr
(“GREETINGS”)
Command: !longlist
((-1 “The combinations of the world” “are unstable by nature.” 100 (1.4 1.4
10.9923) (“He jests at scars” “that never felt a wound.”)))

File Search
The acedFindFile() function enables an application to search for a file of a
particular name. The application can specify the directory to search, or it can
use the current AutoCAD library path.
In the following sample code fragment, acedFindFile() searches for the
requested file name according to the AutoCAD library path.
char *refname = "refc.dwg";
char fullpath[100];
.
.
.
if (acedFindFile(refname, fullpath) != RTNORM) {
acutPrintf("Could not find file %s.\n", refname);
return BAD;

AutoCAD Queries and Commands | 251


If the call to acedFindFile() is successful, the fullpath argument is set to a
fully qualified path name string, such as the following:
/home/work/ref/refc.dwg
You can also prompt users to enter a file name by means of the standard
AutoCAD file dialog box. To display the file dialog box, call acedGetFileD().
The following sample code fragment uses the file dialog box to prompt users
for the name of an ObjectARX application.
struct resbuf *result;
int rc, flags;

if (result = acutNewRb(RTSTR) == NULL) {


acdbFail("Unable to allocate buffer\n");
return BAD;
}

result->resval.rstring=NULL;

flags = 2; // Disable the "Type it" button.

rc = acedGetFileD("Get ObjectARX Application", // Title


"/home/work/ref/myapp", // Default pathname
NULL, // The default extension: NULL means "*".
flags, // The control flags
result); // The path selected by the user.
if (rc == RTNORM)
rc = acedArxLoad(result->resval.rstring);

Object Snap
The acedOsnap() function finds a point by using one of the AutoCAD Object
Snap modes. The snap modes are specified in a string argument.
In the following example, the call to acedOsnap() looks for the midpoint of
a line near pt1.
acedOsnap(pt1, "midp", pt2);
The following call looks for either the midpoint or endpoint of a line, or the
center of an arc or circle—whichever is nearest pt1.
acedOsnap(pt1, "midp,endp,center", pt2);
The third argument (pt2 in the examples) is set to the snap point if one is
found. The acedOsnap() function returns RTNORM if a point is found.

NOTE The APERTURE system variable determines the allowable proximity of a


selected point to an entity when using Object Snap.

252 | Chapter 10 Global Functions for Interacting with AutoCAD


Viewport Descriptors
The function acedVports(), like the AutoLISP function (vports), gets a list
of descriptors of the current viewports and their locations.
The following sample code gets the current viewport configuration and
passes it back to AutoLISP for display.
struct resbuf *rb;
int rc;

rc = acedVports(&rb);
acedRetList(rb);
acutRelRb(rb);
For example, given a single-viewport configuration with TILEMODE turned
on, the preceding code may return the list shown in the following figure.

rb NULL
RTSHORT RTPOINT RTPOINT
1 0.0 30.0
0.0 30.0

Similarly, if four equal-sized viewports are located in the four corners of the
screen and TILEMODE is turned on, the preceding code may return the con-
figuration shown in the next figure.

rb
RTSHORT RTPOINT RTPOINT RTSHORT RTPOINT RTPOINT
5 0.5 1.0 2 0.5 1.0
0.0 0.5 0.5 1.0

NULL
RTSHORT RTPOINT RTPOINT RTSHORT RTPOINT RTPOINT
3 0.0 0.5 4 0.0 0.5
0.5 1.0 0.0 0.5

The current viewport’s descriptor is always first in the list. In the list shown
in the preceding figure, viewport number 5 is the current viewport.

Geometric Utilities
One group of functions enables applications to obtain geometric informa-
tion. The acutDistance() function finds the distance between two points,
acutAngle() finds the angle between a line and the X axis of the current UCS
(in the XY plane), and acutPolar() finds a point by means of polar coordi-
nates (relative to an initial point). Unlike most ObjectARX functions, these

AutoCAD Queries and Commands | 253


functions do not return a status value. The acdbInters() function finds the
intersection of two lines; it returns RTNORM if it finds a point that matches the
specification.

NOTE Unlike acedOsnap(), the functions in this group simply calculate the
point, line, or angle values, and do not actually query the current drawing.

The following sample code fragment shows some simple calls to the geomet-
ric utility functions.
ads_point pt1, pt2;
ads_point base, endpt;
ads_real rads, length;
.
. // Initialize pt1 and pt2.
.
// Return the angle in the XY plane of the current UCS, in radians.
rads = acutAngle(pt1, pt2);
// Return distance in 3D space.
length = acutDistance(pt1, pt2);
base[X] = 1.0; base[Y] = 7.0; base[Z] = 0.0;
acutPolar(base, rads, length, endpt);
The call to acutPolar() sets endpt to a point that is the same distance from
(1,7) as pt1 is from pt2, and that is at the same angle from the X axis as the
angle between pt1 and pt2.

The Text Box Utility Function


The function acedTextBox() finds the diagonal coordinates of a box that
encloses a text entity. The function takes an argument, ent, that must specify
a text definition or a string group in the form of a result-buffer list. The
acedTextBox() function sets its p1 argument to the minimum XY coordi-
nates of the box and its p2 argument to the maximum XY coordinates.
If the text is horizontal and is not rotated, p1 (the bottom-left corner) and p2
(the top-right corner) describe the bounding box of the text. The coordinates
are expressed in the Entity Coordinate System (ECS) of ent with the origin
(0,0) at the left endpoint of the baseline. (The origin is not the bottom-left
corner if the text contains letters with descenders, such as g and p.) For exam-
ple, the following figure shows the results of applying acedTextBox() to a
text entity with a height of 1.0. The figure also shows the baseline and origin
of the text.

254 | Chapter 10 Global Functions for Interacting with AutoCAD


pt2
top right: (5.5, 1.0)

origin: (0,0) baseline

pt1

bottom left: (0,-0.333333)

The next figure shows the point values that acedTextBox() returns for sam-
ples of vertical and aligned text. In both samples, the height of the letters was
entered as 1.0. (For the rotated text, this height is scaled to fit the alignment
points.)

pt2 = 1.0, 0.0


origin (0,0)

pt2 = 9.21954,1.38293

(10,3)

(1,1)
pt1 pt1 = 0,0 alignment points entered where text was created
= -0.5,-20.0

Note that with vertical text styles, the points are still returned in left-to-right,
bottom-to-top order, so the first point list contains negative offsets from the
text origin.
The acedTextBox() function can also measure strings in attdef and attrib
entities. For an attdef, acedTextBox() measures the tag string (group 2); for
an attrib entity, it measures the current value (group 1).
The following function, which uses some entity handling functions, prompts
the user to select a text entity, and then draws a bounding box around the
text from the coordinates returned by acedTextBox().

NOTE The sample tbox() function works correctly only if you are currently in
the World Coordinate System (WCS). If you are not, the code should convert the
ECS points retrieved from the entity into the UCS coordinates used by
acedCommand(). See “Coordinate System Transformations” on page 271.

AutoCAD Queries and Commands | 255


int tbox()
{
ads_name tname;
struct resbuf *textent, *tent;
ads_point origin, lowleft, upright, p1, p2, p3, p4;
ads_real rotatn;
char rotatstr[15];
if (acedEntSel("\nSelect text: ", tname, p1) != RTNORM) {
acdbFail("No Text entity selected\n");
return BAD;
}
textent = acdbEntGet(tname);
if (textent == NULL) {
acdbFail("Couldn’t retrieve Text entity\n");
return BAD;
}

tent = entitem(textent, 10);


origin[X] = tent->resval.rpoint[X]; //ECS coordinates
origin[Y] = tent->resval.rpoint[Y];

tent = entitem(textent, 50);


rotatn = tent->resval.rreal;
// acdbAngToS() converts from radians to degrees.
if (acdbAngToS(rotatn, 0, 8, rotatstr) != RTNORM) {
acdbFail("Couldn’t retrieve or convert angle\n");
acutRelRb(textent);
return BAD;
}

if (acedTextBox(textent, lowleft, upright) != RTNORM) {


acdbFail("Couldn’t retrieve text box
coordinates\n");
acutRelRb(textent);
return BAD;
}
acutRelRb(textent);

// If not currently in the WCS, at this point add


// acedTrans() calls to convert the coordinates
// retrieved from acedTextBox().

p1[X] = origin[X] + lowleft[X]; // UCS coordinates


p1[Y] = origin[Y] + lowleft[Y];

p2[X] = origin[X] + upright[X];


p2[Y] = origin[Y] + lowleft[Y];

p3[X] = origin[X] + upright[X];


p3[Y] = origin[Y] + upright[Y];

p4[X] = origin[X] + lowleft[X];


p4[Y] = origin[Y] + upright[Y];

256 | Chapter 10 Global Functions for Interacting with AutoCAD


if (acedCommand(RTSTR, "pline", RTPOINT, p1,
RTPOINT, p2, RTPOINT, p3,RTPOINT, p4, RTSTR, "c",
0) != RTNORM) {
acdbFail("Problem creating polyline\n");
return BAD;
}
if (acedCommand(RTSTR, "rotate", RTSTR, "L", RTSTR, "",
RTPOINT, origin, RTSTR, rotatstr, 0) != RTNORM) {
acdbFail("Problem rotating polyline\n");
return BAD;
}
return GOOD;
}
The preceding example “cheats” by using the AutoCAD ROTATE command to
cause the rotation. A more direct way to do this is to incorporate the rotation
into the calculation of the box points, as follows:
ads_real srot, crot;

tent = entitem(textent, 50);


rotatn = tent->resval.rreal;
srot = sin(rotatn);
crot = cos(rotatn);
.
.
.
p1[X] = origin[X] + (lowleft[X]*crot - lowleft[Y]*srot);
p1[Y] = origin[Y] + (lowleft[X]*srot + lowleft[Y]*crot);

p2[X] = origin[X] + (upright[X]*crot - lowleft[Y]*srot);


p2[Y] = origin[Y] + (upright[X]*srot + lowleft[Y]*crot);

p3[X] = origin[X] + (upright[X]*crot - upright[Y]*srot);


p3[Y] = origin[Y] + (upright[X]*srot + upright[Y]*crot);

p4[X] = origin[X] + (lowleft[X]*crot - upright[Y]*srot);


p4[Y] = origin[Y] + (lowleft[X]*srot + upright[Y]*crot);

AutoCAD Queries and Commands | 257


Getting User Input
Several global functions enable an ObjectARX application to request data
interactively from the AutoCAD user.

User-Input Functions
The user-input or acedGetxxx() functions pause for the user to enter data of
the indicated type, and return the value in a result argument. The application
can specify an optional prompt to display before the function pauses.

NOTE Several functions have similar names but are not part of the user-input
group: acedGetFunCode(), acedGetArgs(), acedGetVar(), and
acedGetInput().

The following functions behave like user-input functions: acedEntSel(),


acedNEntSelP(), acedNEntSel(), and acedDragGen().

The following table briefly describes the user-input functions.

User-input function summary

Function Name Description

acedGetInt Gets an integer value

acedGetReal Gets a real value

acedGetDist Gets a distance

acedGetAngle Gets an angle (oriented to 0 degrees as specified by the


ANGBASE variable)

acedGetOrient Gets an angle (oriented to 0 degrees at the right)

acedGetPoint Gets a point

acedGetCorner Gets the corner of a rectangle

acedGetKword Gets a keyword (see the description of keywords later in this


section)

acedGetString Gets a string

258 | Chapter 10 Global Functions for Interacting with AutoCAD


With some user-input functions such as acedGetString(), the user enters a
value on the AutoCAD prompt line. With others such as acedGetDist(), the
user either enters a response on the prompt line or specifies the value by
selecting points on the graphics screen.
If the screen is used to specify a value, AutoCAD displays rubber-band lines,
which are subject to application control. A prior call to acedInitGet() can
cause AutoCAD to highlight the rubber-band line (or box).
The acedGetKword() function retrieves a keyword. Keywords are also string
values, but they contain no white space, can be abbreviated, and must be set
up before the acedGetKword() call by a call to acedInitGet(). All user-input
functions (except acedGetString()) can accept keyword values in addition
to the values they normally return, provided acedInitGet() has been called
to set up the keywords. User-input functions that accept keywords can also
accept arbitrary text (with no spaces).

NOTE You can also use acedInitGet() to enable acedEntSel(),


acedNEntSelP(), and acedNEntSel() to accept keyword input. The
acedDragGen() function also recognizes keywords.

The AutoCAD user cannot respond to a user-input function by entering an


AutoLISP expression.
The user-input functions take advantage of the error-checking capability of
AutoCAD. Trivial errors (such as entering only a single number in response
to acedGetPoint()) are trapped by AutoCAD and are not returned by the
user-input function. The application needs only to check for the conditions
shown in the following table.

Return values for user-input functions

Code Description

RTNORM User entered a valid value

RTERROR The function call failed

RTCAN User entered ESC

RTNONE User entered only ENTER

RTREJ AutoCAD rejected the request as invalid

RTKWORD User entered a keyword or arbitrary text

Getting User Input | 259


The RTCAN case enables the user to cancel the application’s request by press-
ing ESC . This helps the application conform to the style of built-in AutoCAD
commands, which always allow user cancellation. The return values RTNONE
and RTKWORD are governed by the function acedInitGet(): a user-input func-
tion returns RTNONE or RTKWORD only if these values have been explicitly
enabled by a prior acedInitGet() call.

Control of User-Input Function Conditions


The function acedInitGet() has two arguments: val and kwl. The val argu-
ment specifies one or more control bits that enable or disable certain input
values to the following acedGetxxx() call. The kwl (for keyword list) argu-
ment can specify the keywords that the functions acedGetxxx(),
acedEntSel(), acedNEntSelP(), acedNEntSel(), or acedDragGen()
recognize.

NOTE The control bits and keywords established by acedInitGet() apply


only to the next user-input function call. They are discarded immediately after-
ward. The application doesn’t have to call acedInitGet() a second time to clear
any special conditions.

Input Options for User-Input Functions


The following table summarizes the control bits that can be specified by the
val argument. To set more than one condition at a time, add the values
together to create a val value between 0 and 127. If val is set to zero, none
of the control conditions apply to the next user-input function call.

NOTE Future versions of AutoCAD or ObjectARX may define additional


acedInitGet() control bits, so you should avoid setting any bits that are not
shown in the table or described in this section.

Input options set by acedInitGet()

Code Bit Value Description

RSG_NONULL 1 Disallow null input

RSG_NOZERO 2 Disallow zero values

RSG_NONEG 4 Disallow negative values

260 | Chapter 10 Global Functions for Interacting with AutoCAD


Input options set by acedInitGet() (continued)

Code Bit Value Description

RSG_NOLIM 8 Do not check drawing limits, even if


LIMCHECK is on

RSG_DASH 32 Use dashed lines when drawing rubber-


band line or box

RSG_2D 64 Ignore Z coordinate of 3D points


(acedGetDist() only)

RSG_OTHER 128 Allow arbitrary input—whatever the user


enters

The following program excerpt shows the use of acedInitGet() to set up a


call to the acedGetInt() function.
int age;
acedInitGet(RSG_NONULL | RSG_NOZERO | RSG_NONEG, NULL);
acedGetInt("How old are you? ", &age);
This sequence asks the user’s age. AutoCAD automatically displays an error
message and repeats the prompt if the user tries to enter a negative or zero
value, press ENTER only, or enter a keyword. (AutoCAD itself rejects attempts
to enter a value that is not an integer.)
The RSG_OTHER option lets the next user-input function call accept arbitrary
input. If RSG_OTHER is set and the user enters an unrecognized value, the
acedGetxxx() function returns RTKWORD, and the input can be retrieved by a
call to acedGetInput(). Because spaces end user input just as ENTER does, the
arbitrary input never contains a space. The RSG_OTHER option has the lowest
priority of all the options listed in the preceding table; if the acedInitGet()
call has disallowed negative numbers with RSG_NONEG, for example, AutoCAD
still rejects these.
The following code allows arbitrary input (the error checking is minimal).
int age, rc;
char userstring[511];

acedInitGet(RSG_NONULL | RSG_NOZERO | RSG_NONEG | RSG_OTHER,


"Mine Yours");
if ((rc = acedGetInt("How old are you? ", &age))
== RTKWORD)
// Keyword or arbitrary input
acedGetInput(userstring);
}

Getting User Input | 261


In this example, acedGetInt() returns the values shown in the following
table, depending on the user’s input.

Arbitrary user input

User Input Result

41 acedGetInt() returns RTNORM and sets age to 41

m acedGetInt() returns RTKWORD, and acedGetInput() returns


“Mine”

y acedGetInt() returns RTKWORD, and acedGetInput() returns


“Yours”

twenty acedGetInt() returns RTKWORD, and acedGetInput() returns


“twenty”

what??? acedGetInt() returns RTKWORD, and acedGetInput() returns


“what???”

-10 AutoCAD rejects this input and redisplays the prompt, as


RSG_NONEG is set (other bit codes take precedence over
RSG_OTHER)

-34.5 acedGetInt() returns RTKWORD, and acedGetInput() returns


“-34.5”
AutoCAD doesn’t reject this value, because it expects an integer,
not a real value (if this were an acedGetReal() call, AutoCAD
would accept the negative integer as arbitrary input but would
reject the negative real value)

NOTE The acedDragGen() function indicates arbitrary input (if this has been
enabled by a prior acedInitGet() call) by returning RTSTR instead of RTKWORD.

Keyword Specifications
The optional kwl argument specifies a list of keywords that will be recognized
by the next user-input (acedGetxxx()) function call. The keyword value that
the user enters can be retrieved by a subsequent call to acedGetInput(). (The
keyword value will be available if the user-input function was
acedGetKword().) The meanings of the keywords and the action to perform
for each is the responsibility of the ObjectARX application.
The acedGetInput() function always returns the keyword as it appears in the
kwl argument, with the same capitalization (but not with the optional char-
acters, if those are specified after a comma). Regardless of how the user enters

262 | Chapter 10 Global Functions for Interacting with AutoCAD


a keyword, the application has to do only one string comparison to identify
it, as demonstrated in the following example. The code segment that follows
shows a call to acedGetReal() preceded by a call to acedInitGet() that
specifies two keywords. The application checks for these keywords and sets
the input value accordingly.
int stat;
ads_real x, pi = 3.14159265;
char kw[20];

// Null input is not allowed.


acedInitGet(RSG_NONULL, "Pi Two-pi");

if ((stat = acedGetReal("Pi/Two-pi/<number>: ", &x)) < 0) {


if (stat == RTKWORD && acedGetInput(kw) == RTNORM) {
if (strcmp(kw, "Pi") == 0) {
x = pi;
stat = RTNORM;
} else if (strcmp(kw, "Two-pi") == 0) {
x = pi * 2;
stat = RTNORM;
}
}
}
if (stat != RTNORM)
acutPrintf("Error on acedGetReal() input.\n");
else
acutPrintf("You entered %f\n", x);
The call to acedInitGet() prevents null input and specifies two keywords:
“Pi” and “Two-pi”. When acedGetReal() is called, the user responds to the
prompt Pi/Two-pi/<number> by entering either a real value (stored in the
local variable x) or one of the keywords. If the user enters a keyword,
acedGetReal() returns RTKWORD. The application retrieves the keyword by
calling acedGetInput() (note that it checks the error status of this function),
and then sets the value of x to pi or 2pi, depending on which keyword was
entered. In this example, the user can enter either p to select pi or t to
select 2pi.

Graphically Dragging Selection Sets


The function acedDragGen() prompts the user to drag a group of selected
objects, as shown in the following example:
int rc;
ads_name ssname;
ads_point return_pt;

// Prompt the user for a general entity selection.

Getting User Input | 263


if (acedSSGet(NULL, NULL, NULL, NULL, ssname) == RTNORM)
// The newly selected entities
rc = acedDragGen(ssname,
"Drag selected objects", // Prompt
0, // Display normal cursor (crosshairs)
dragsample, // Transformation function
return_pt); // Set to the specified location.
The fourth argument points to a function that does the entity transforma-
tion. See “Transformation of Selection Sets” on page 211 for examples of
dragsample() and acedDragGen().

User Breaks
The user-input functions and the acedCommand(), acedCmd(), acedEntSel(),
acedNEntSelP(), acedNEntSel(), acedDragGen(), and acedSSGet() func-
tions return RTCAN if the AutoCAD user responds by pressing ESC . An external
function should treat this response as a cancel request and return immedi-
ately. ObjectARX also provides a function, acedUsrBrk(), that explicitly
checks whether the user pressed ESC . This function enables ObjectARX appli-
cations to check for a user interrupt.
An application doesn’t need to call acedUsrBrk() unless it performs lengthy
computation between interactions with the user. The function acedUsrBrk()
should never be used as a substitute for checking the value returned by user-
input functions that can return RTCAN.
In some cases, an application will want to ignore the user’s cancellation
request. If this is the case, it should call acedUsrBrk() to clear the request;
otherwise, the ESC will still be outstanding and will cause the next user-input
call to fail. (If an application ignores the ESC , it should print a message to tell
the user it is doing so.) Whenever an ObjectARX application is invoked, the
ESC condition is automatically cleared.

For example, the following code fragment fails if the user enters ESC at the
prompt.
int test()
{
int i;

while (!acedUsrBrk()) {
acedGetInt("\nInput integer:", &i); // WRONG
.
.
.
}
}

264 | Chapter 10 Global Functions for Interacting with AutoCAD


The slightly modified code fragment that follows correctly handles an input
of ESC without calling acedUsrBrk().
int test()
{