Arx Autocad
Arx Autocad
May, 2001
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, 3DSurfer, ActiveShapes, ActiveShapes (logo), Actrix, 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, AutoSurf, AutoVision, Biped, bringing information down to earth, CAD Overlay, Character Studio, Design Companion, Design Your World, Design Your World (logo), 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, ObjectARX, Office Series, Opus, PeopleTracker, Physique, Planix, Powered with Autodesk Technology, Powered with Autodesk Technology (logo), RadioRay, Rastation, Softdesk, Softdesk (logo), Solution 3000, Tech Talk, Texture Universe, The AEC Authority, The Auto Architect, TinkerTech, VISION, 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, 3ds max, ACAD, 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 Learning Assistance, AutoCAD LT Learning Assistance, AutoCAD Simulator, AutoCAD SQL Extension, AutoCAD SQL Interface, Autodesk Animator Clips, Autodesk Animator Theatre, Autodesk Device Interface, Autodesk Inventor, Autodesk PhotoEDIT, Autodesk Software Developer's Kit, Autodesk Streamline, Autodesk View DwgX, AutoFlix, AutoPAD, AutoSnap, AutoTrack, Built with ObjectARX (logo), ClearScale, Colour Warper, Combustion, Concept Studio, Content Explorer, cornerStone Toolkit, Dancing Baby (image), Design 2000 (logo), DesignCenter, Design Doctor, Designer's Toolkit, DesignProf, DesignServer, DWG Linking, DWG Unplugged, DXF, Extending the Design Team, FLI, FLIC, GDX Driver, Generic 3D, gmax, Heads-up Design, Home Series, i-drop, Kinetix (logo), Lightscape, ObjectDBX, onscreen onair online, Ooga-Chaka, Photo Landscape, Photoscape, Plasma, Plugs and Sockets, PolarSnap, Pro Landscape, QuickCAD, Reactor, Real-Time Roto, Render Queue, SchoolBox, Simply Smarter Diagramming, SketchTools, Sparks, Suddenly Everything Clicks, Supportdesk, The Dancing Baby, Transform Ideas Into Reality, Visual LISP, Visual Syllabus, VIZable, Volo, and Where Design Connects.
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
Part I
Chapter 1
Using ObjectARX .
Overview of ObjectARX.
.
. .
.
.
.
.
.
.
.
.
. . . . . . . . . . . . . . . . . .
.
.
. . . . . . . . . . . . . . . . . .
.
.
. . . . . . . . . . . . . . . . . .
.
.
. . . . . . . . . . . . . . . . . .
.
.
. . . . . . . . . . . . . . . . . .
.
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. 1
.
. . . . . . . . . . . . . . . . . .
3
.4 .4 .4 .5 .5 .5 .5 .5 .6 .6 .6 .7 .8 .9 .9 10 11 12
The ObjectARX Programming Environment . Accessing the AutoCAD Database. . . Interacting with the AutoCAD Editor . Creating User Interfaces with MFC . . Supporting MDI . . . . . . . . Creating Custom Classes . . . . . Building Complex Applications . . . Interacting with Other Environments . The ObjectARX Documentation Set . . . . Using the ObjectARX Developers Guide ObjectARX Logo Program . . . . . . . ObjectARX Class Libraries . . . . . . . AcRx Library . . . . . . . . . Runtime Type Identification . . AcEd Library . . . . . . . . . AcDb Library . . . . . . . . . AcGi Library . . . . . . . . . AcGe Library . . . . . . . . .
iii
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
14 14 14 14
Chapter 2
Database Primer .
.
. . . . . . . . . .
.
. . . . . . . . . .
.
. . . . . . . . . .
.
. . . . . . . . . .
.
. . . . . . . . . .
.
. . . . . . . . . .
.
. . . . . . . . . . . . . . . . . . . .
. 17
18 19 19 20 20 23 23 24 25 26
AutoCAD Database Overview . . . . . . Multiple Databases . . . . . . . Obtaining Object IDs . . . . . . Essential Database Objects . . . . . . . Creating Objects in AutoCAD . . . . . . Creating Objects in ObjectARX . . . . . Creating Entities . . . . . . . . Creating a New Layer . . . . . . Opening and Closing ObjectARX Objects Adding a Group to the Group Dictionary
Chapter 3
.
. . . . . . . . . . . . . . . . . .
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. 27
28 29 29 33 34 35 36 37 38 38 40 40 41 41 42 42 42 42
Creating an ObjectARX Application . . . . . . . . . Creating Custom Classes . . . . . . . . . . Responding to AutoCAD Messages . . . . . . . Sequence of Events in an ObjectARX Application . Implementing an Entry Point for AutoCAD . . . . Initializing an ObjectARX Application . . . . . . Preparing for Unloading . . . . . . . . . . Example Application . . . . . . . . . . . . . Registering New Commands . . . . . . . . . . . Command Stack . . . . . . . . . . . . . Lookup Order . . . . . . . . . . . . . . Global versus Local Command Names . . . . . . Transparent versus Modal Commands . . . . . . Loading an ObjectARX Application . . . . . . . . . The Library Search Path . . . . . . . . . . . Listing Loaded ObjectARX Applications. . . . . . Unloading an ObjectARX Application . . . . . . . . Unlocking Applications . . . . . . . . . . .
iv
Contents
Demand Loading . . . . . . . . . . . . . . . AutoCAD, the Windows System Registry, and ObjectARX Applications . . . . . . . . . . . Modification of the Registry at ObjectARX Application Installation . . . . . . . . . . . Creating AutoCAD Subkeys and Values . . . . Creating ObjectARX Application Keys and Values . Removing System Registry Information . . . . The DEMANDLOAD System Variable . . . . . . . Demand Loading on Detection of Custom Objects . . . Demand Loading on Command. . . . . . . . . Demand Loading on AutoCAD Startup . . . . . . Managing Applications with the System Registry . . . ARX Command . . . . . . . . . . . . . . . . ?List Applications . . . . . . . . . . . . Load . . . . . . . . . . . . . . . . . Unload. . . . . . . . . . . . . . . . . Commands . . . . . . . . . . . . . . . Options . . . . . . . . . . . . . . . . Running ObjectARX Applications from AutoLISP . . . . . Error Handling . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. 43 . 44 . 45 . 46 . 47 . 48 . 48 . 49 . 50 . 50 . 51 . 51 . 51 . 52 . 52 . 52 . 52 . 53 . 54
Chapter 4
Database Operations .
.
. . . . . . . . . . . . . . . . .
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.
. . . . . . . . . . . . . . . . .
. 59
. 60 . 60 . 61 . 61 . 63 . 63 . 63 . 64 . 64 . 64 . 65 . 66 . 66 . 66 . 66 . 67 . 67
Initial Database . . . . . . . . . . . . . . Creating and Populating a Database . . . . . . . Saving a Database . . . . . . . . . . . . . Setting the Default File Format . . . . . . . Global Save Functions . . . . . . . . . . The wblock Operation. . . . . . . . . . . . Creating a New Database from an Existing Database Creating a New Database with Entities. . . . . Copying a Named Block . . . . . . . Copying an Array of Entities . . . . . . Inserting a Database . . . . . . . . . . . . Setting Current Database Values . . . . . . . . Database Color Value . . . . . . . . . . Database Linetype Value . . . . . . . . . Database Linetype Scale Value . . . . . . . Database Layer Value . . . . . . . . . . Example of Database Operations . . . . . . . .
Contents
Long Transactions . . . . . . . . . . . . . . . . Class and Function Overview . . . . . . . . . . . AcDbLongTransaction Class . . . . . . . . . AcDbLongTransWorkSetIterator Class . . . . . . AcApLongTransactionReactor Class . . . . . . . AcApLongTransactionManager Class . . . . . . . AcDbDatabase::wblockCloneObjects() Function . . . Long Transaction Example . . . . . . . . . . . . External References . . . . . . . . . . . . . . . . External Reference Pre- and Post-Processing . . . . . . AcDbDatabase::xrefBlockId() Function . . . . . . AcDbDatabase::restoreOriginalXrefSymbols() Function . AcDbDatabase::restoreForwardingXrefSymbols() Function File Locking and Consistency Checks . . . . . . . . Indexes and Filters . . . . . . . . . . . . . . . . AcDbIndexFilterManager Namespace . . . . . . . . AcDbIndex Class . . . . . . . . . . . . . . . AcDbFilter Class . . . . . . . . . . . . . . . AcDbFilteredBlockIterator Class . . . . . . . . . . AcDbCompositeFilteredBlockIterator Class. . . . . . . Drawing Summary Information . . . . . . . . . . . . AcDbDatabaseSummaryInfo Class . . . . . . . . . AcDbSummaryInfoReactor Class . . . . . . . . . . AcDbSummaryInfoManager Class . . . . . . . . . Global Summary Information Functions . . . . . . . Last Saved by Autodesk Software . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
69 69 70 70 70 70 70 71 74 75 76 76 76 76 77 78 78 78 78 78 79 79 80 80 80 80
Chapter 5
Database Objects
.
. . . . . . . . . .
.
. . . . . . . . . .
.
. . . . . . . . . .
.
. . . . . . . . . .
.
. . . . . . . . . .
.
. . . . . . . . . .
.
. . . . . . . . . .
.
. . . . . . . . . .
.
. . . . . . . . . . . . . . . . . . . .
. 81
82 84 85 85 86 88 89 91 93 94
Opening and Closing Database Objects. Deleting Objects . . . . . . . . Database Ownership of Objects . . . Adding Object-Specific Data . . . . Extended Data . . . . . . . Extension Dictionary . . . . ObjectARX Example . . . Global Function Example . Erasing Objects . . . . . . . . Object Filing . . . . . . . . .
vi
Contents
Chapter 6
Entities
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. 97
. 98 . 98 100 100 101 102 102 103 103 103 104 105 106 106 108 109 109 110 110 111 115 122 124 124 125 126 128 131 133 133 134 135 135 136 136 138 138 138 138 139
Entities Defined . . . . . . . . . . . . . . . . Entity Ownership . . . . . . . . . . . . . . . Common Entity Properties . . . . . . . . . . . . Entity Color . . . . . . . . . . . . . . . Entity Linetype . . . . . . . . . . . . . . Entity Linetype Scale . . . . . . . . . . . . Linetype Scale Specified Per Entity . . . . . . Regenerating a Drawing . . . . . . . . . Entity Visibility . . . . . . . . . . . . . . Entity Layer . . . . . . . . . . . . . . . Common Entity Functions . . . . . . . . . . . . Object Snap Points . . . . . . . . . . . . . Transform Functions . . . . . . . . . . . . Intersecting for Points . . . . . . . . . . . . GS Markers and Subentities . . . . . . . . . . Subentity Path . . . . . . . . . . . . Simple Highlighting Example . . . . . . . Selecting an Entity . . . . . . . . . Converting GS Markers to Subentity Paths. . Highlighting the Subentity . . . . . . Highlighting Nested Block References . . . . . Exploding Entities . . . . . . . . . . . . . Creating Instances of AutoCAD Entities . . . . . . . . Creating a Simple Entity . . . . . . . . . . . Creating a Simple Block Table Record . . . . . . . Creating a Block Table Record with Attribute Definitions . Creating a Block Reference with Attributes . . . . . Iterating through a Block Table Record . . . . . . Complex Entities . . . . . . . . . . . . . . . Creating a Complex Entity . . . . . . . . . . Iterating through Vertices in a Polyline . . . . . . Coordinate System Access . . . . . . . . . . . . Entity Coordinate System . . . . . . . . . . . AcDb2dVertex . . . . . . . . . . . . . . Curve Functions . . . . . . . . . . . . . . . Associating Hyperlinks with Entities . . . . . . . . . AcDbHyperlink Class . . . . . . . . . . . . AcDbHyperlinkCollection Class . . . . . . . . . AcDbEntityHyperlinkPE Class . . . . . . . . . Hyperlink Example . . . . . . . . . . . . .
Contents
vii
Chapter 7
Container Objects
.
. . . . . . . . . . . . . . . . . . . . . . . . . . .
.
. . . . . . . . . . . . . . . . . . . . . . . . . . .
.
. . . . . . . . . . . . . . . . . . . . . . . . . . .
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
141
142 144 146 147 147 147 147 147 148 148 148 148 150 150 152 152 155 155 156 157 157 158 158 159 159 160 161
Comparison of Symbol Tables and Dictionaries . . . Symbol Tables. . . . . . . . . . . . . . Block Table . . . . . . . . . . . . . Layer Table . . . . . . . . . . . . . Layer Properties . . . . . . . . . Frozen/Thawed . . . . . . . . On/Off . . . . . . . . . . Viewport . . . . . . . . . . Locked/Unlocked . . . . . . . Color . . . . . . . . . . . Linetype . . . . . . . . . . Creating and Modifying a Layer Table Record Iterators . . . . . . . . . . . . . . Iterating over Tables . . . . . . . . Dictionaries . . . . . . . . . . . . . . Groups and the Group Dictionary . . . . . MLINE Style Dictionary . . . . . . . . . Layout Dictionary . . . . . . . . . . Creating a Dictionary . . . . . . . . . Iterating over Dictionary Entries . . . . . . Layouts . . . . . . . . . . . . . . . . ObjectARX Layout Classes . . . . . . . . Layout Objects . . . . . . . . . . The Layout Manager . . . . . . . . Xrecords . . . . . . . . . . . . . . . DXF Group Codes for Xrecords . . . . . . Examples . . . . . . . . . . . . .
Part II
Chapter 8
User Interfaces .
MFC Topics . . .
.
.
.
.
.
. .
.
.
.
.
.
.
.
.
.
. .
.
.
.
.
. . . . . . . .
.
.
. . . . . . . . . . . . . . . .
165
167
168 168 169 169 169 169 170 170
Using MFC with ObjectARX Applications . . . . . . . MFC and Modeless Dialog Boxes . . . . . . . . ObjectARX Applications with Dynamically Linked MFC . . Visual C++ Project Settings for Dynamically Linked MFC Debugging ObjectARX Applications with Dynamic MFC Resource Management . . . . . . . . . . . CAcExtensionModule Class. . . . . . . . CAcModuleResourceOverride Class . . . . .
viii
Contents
Built-In MFC User Interface Support . . . . . . . Class Hierarchy . . . . . . . . . . . . AdUi Messaging . . . . . . . . . . . . AdUi Tip Windows . . . . . . . . . . . AdUi Dialog Classes . . . . . . . . . . AcUi Dialog Classes. . . . . . . . . . . AdUi Classes Supporting Tab Extensibility . . . AdUi and AcUi Control Bar Classes . . . . . . AdUi and AcUi Edit Controls. . . . . . . . AdUi and AcUi Combo Box Controls . . . . . AcUi MRU Combo Boxes . . . . . . . . . AdUi Button Classes . . . . . . . . . . AcUi Button Classes . . . . . . . . . . Dialog Data Persistency . . . . . . . . . Using and Extending the AdUi Tab Dialog System . Constructing a Custom Tab Dialog that is Extensible Extending the AutoCAD Built-in Tab Dialogs . . Using AdUi and AcUi with VC++ AppWizard . . . . Create the ObjectARX MFC Application Skeleton . Create the MFC Dialog Using App Studio . . . . Create the Classes and Controls . . . . . . . Create the Handlers for the Dialog . . . . . . Add Code to the Handlers . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . .
171 173 173 174 174 175 176 176 177 179 180 182 183 184 184 185 185 187 187 189 190 192 192
Chapter 9
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.
. . . . . . . . . . . . . . . . . . .
. 201
202 202 205 208 208 210 210 211 213 216 216 218 219 220 224 226 231 234 236
Selection Set and Entity Names . . . . . . . . . . Handling Selection Sets . . . . . . . . . . . . Selection Set Filter Lists . . . . . . . . . . Wild-Card Patterns in Filter Lists . . . . . Filtering for Extended Data . . . . . . . Relational Tests . . . . . . . . . . . Conditional Filtering . . . . . . . . . Selection Set Manipulation . . . . . . . . . Transformation of Selection Sets . . . . . . . Entity Name and Data Functions . . . . . . . . . Entity Name Functions . . . . . . . . . . Entity Handles and their Uses . . . . . . Entity Context and Coordinate Transform Data . Coordinate Transformation . . . . . Context Data . . . . . . . . . . Entity Data Functions . . . . . . . . . . . Complex Entities . . . . . . . . . . Anonymous Blocks . . . . . . . . . . Entity Data Functions and Graphics Screen . . . .
Contents
ix
Notes on Extended Data . . . . . . Organization of Extended Data . . Registering an Application . . . . Retrieving Extended Data . . . . Managing Extended Data Memory Use Using Handles in Extended Data . . Xrecord Objects . . . . . . . . . Symbol Table Access. . . . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
Chapter 10
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
247
248 248 249 250 250 251 252 253 254 255 256 257 260 260 262 263 265 266 267 268 269 269 272 273 274 276 276 278 278 279 281
Contents
Part III
Chapter 11
.
.
. . . . . . . . . .
.
.
. . . . .
.
.
. . . . .
.
.
. . . . .
.
.
. . . . .
.
.
. . . . .
.
.
. . . . . . . . . .
283
. 285
. . . . . 286 287 288 289 291
Chapter 12
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. 293
294 294 294 295 295 296 296 296 298 299 300 301 301 301 301 301 302 303 304 304 306 306 307 310 312 313 314 315 315 315 315 316
Overriding AcDbObject Virtual Functions . . . . . . . . AcDbObject: Essential Functions to Override. . . . . . AcDbObject: Functions Often Overridden . . . . . . AcDbObject: Functions Sometimes Overridden . . . . . AcDbObject: Functions Rarely Overridden . . . . . . AcRxObject: Functions Rarely Overridden . . . . . . AcDbEntity: Functions to Override . . . . . . . . . AcDbCurve: Functions to Override . . . . . . . . . Implementing Member Functions . . . . . . . . . . . Filing Objects to DWG and DXF Files . . . . . . . . . . dwgOut() Function . . . . . . . . . . . . . . dwgIn() Function . . . . . . . . . . . . . . dxfOut() Function . . . . . . . . . . . . . . dxfIn() Function . . . . . . . . . . . . . . Error Checking . . . . . . . . . . . . . . . Implementing the DWG Filing Functions. . . . . . . Sample Code for dwgOutFields(). . . . . . . . Sample Code for dwgInFields() . . . . . . . . Implementing the DXF Filing Functions . . . . . . . DXF Group Code Ranges . . . . . . . . . . Order Dependence . . . . . . . . . . . . Sample Code for dxfOutFields() . . . . . . . . Sample Code for dxfInFields() with Order Independence Sample Code for dxfInFields() with Order Dependence Object References . . . . . . . . . . . . . . . . Ownership References . . . . . . . . . . . . . . . Uses of Ownership . . . . . . . . . . . . . . Types of Ownership . . . . . . . . . . . . . Hard Ownership . . . . . . . . . . . . . Soft Ownership . . . . . . . . . . . . . Building an Ownership Hierarchy . . . . . . . . . ObjectARX Example . . . . . . . . . . .
Contents
xi
Pointer References . . . . . . . . . Hard Pointers . . . . . . . . . Soft Pointers . . . . . . . . . Long Transaction Issues for Custom Objects . Purge . . . . . . . . . . . . . Undo and Redo . . . . . . . . . . Automatic Undo . . . . . . . . Partial Undo . . . . . . . . . Recording State . . . . . . Restoring State . . . . . . . Redo . . . . . . . . . . . . subErase, subOpen, subClose, and subCancel . Example of a Custom Object Class . . . . Header File . . . . . . . . . . Source File . . . . . . . . . . Object Version Support . . . . . . . . Class Versioning . . . . . . . . Class Versioning Example . . . Using Class Versioning . . . . Implementing Class Versioning . Class Renaming . . . . . . . . Class Data or Xdata Version Numbers .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
323 323 323 324 326 327 327 328 328 329 330 331 341 341 342 346 346 347 348 349 350 350
Chapter 13
.
. . . . . . . . . . . . . . .
.
. . . . . . . . . . . . . . .
.
. . . . . . . . . . . . . . .
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
353
354 354 355 356 357 357 359 360 362 365 366 368 373 373 374
Deriving Custom Entities . . . . . . . . . . AcDbEntity Functions to Override . . . . . AcDbEntity Functions Usually Overridden . . . AcDbEntity Functions Rarely Overridden . . . Overriding Common Entity Functions . . . . . . Overriding worldDraw() and viewportDraw() . . Overriding saveAs() . . . . . . . . . . Implementing the Object Snap Point Function . Implementing the Grip Point Functions . . . Implementing the Stretch Point Functions . . . Transformation Functions . . . . . . . . Intersecting with Other Entities . . . . . . Intersecting a Custom Entity with Another Entity Exploding an Entity . . . . . . . . . . Extending Entity Functionality . . . . . . . .
xii
Contents
Using AcEdJig . . . . . . . . . . . . . . . . . Deriving a New Class from AcEdJig . . . . . . . . . General Steps for Using AcEdJig . . . . . . . . . . Setting Up Parameters for the Drag Sequence . . . . . Drag Loop. . . . . . . . . . . . . . . . . Implementing the sampler(), update(), and entity() Functions Keyword List . . . . . . . . . . . . . . Display Prompt . . . . . . . . . . . . . Cursor Types . . . . . . . . . . . . . . User Input Controls . . . . . . . . . . . . Adding the Entity to the Database . . . . . . . . . Sample Code . . . . . . . . . . . . . . . .
. . . . . . . . . . . .
374 375 375 376 376 378 378 378 378 379 381 381
Part IV
Chapter 14
Specialized Topics .
Proxy Objects . . .
.
. .
.
.
.
.
. . . . . .
.
.
. . . . . . . . . . . .
.
.
. . . . . .
.
.
. . . . . .
.
.
. . . . . .
.
.
. . . . . .
.
.
. . . . . .
.
.
. . . . . . . . . . . .
387
. 389
. . . . . . 390 390 391 392 392 394
Proxy Objects Defined. . . . . Proxy Object Life Cycle . . . . User Encounters with Proxy Objects Displaying Proxy Entities . . . . Editing Proxy Entities . . . . . Unloading an Application . . .
Chapter 15
Notification .
.
. . . . . . . . . . . . .
.
. . . . . . . . . . . . .
.
. . . . . . . . . . . . . . . . . . . . . . . . . .
.
. . . . . . . . . . . . .
. 395
396 396 397 398 400 400 400 401 404 404 406 412 414
Notification Overview . . . . . . . . . . . Reactor Classes . . . . . . . . . . . Types of Object Reactors . . . . . . . . Using Reactors . . . . . . . . . . . . . AcDbObject and Database Notification Events . Custom Notifications . . . . . . . . . Using an Editor Reactor . . . . . . . . Using a Database Reactor . . . . . . . . Using an Object Reactor . . . . . . . . Obtaining the ID of the Object Reactor . Example: Building in Object Dependencies Immediate versus Commit-Time Events . Notification Use Guidelines . . . . . . . . .
Contents
xiii
Chapter 16
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
415
416 416 416 417 417 418 422 423 423 423 424 425 426 426 426 427 427 428 428 429 430 430 431 431 431 432 434 435 435 435 436 437 438 439
MDI Overview . . . . . . . . . . . . . . . . . Document Execution Contexts . . . . . . . . . . Data Instances . . . . . . . . . . . . . . . . Document Locking . . . . . . . . . . . . . . Document Management Classes . . . . . . . . . . MDI Terminology . . . . . . . . . . . . . . . . SDI System Variable . . . . . . . . . . . . . . . . Levels of Compatibility . . . . . . . . . . . . . . . SDI-Only Level . . . . . . . . . . . . . . . MDI-Aware Level . . . . . . . . . . . . . . . Per-Document Data . . . . . . . . . . . . Explicit Document Locking . . . . . . . . . . AutoLISP Commands. . . . . . . . . . . . Registering as MDI-Aware . . . . . . . . . . MDI-Capable Level . . . . . . . . . . . . . . MDI-Enhanced Level. . . . . . . . . . . . . . Interacting with Multiple Documents . . . . . . . . . . Accessing the Current Document and Its Related Objects . . Accessing Databases Associated with Noncurrent Documents . Setting the Current Document without Activating It . . . Document Event Notification. . . . . . . . . . . . . Application-Specific Document Objects . . . . . . . . . Nonreentrant Commands . . . . . . . . . . . . . . Making a Command Nonreentrant . . . . . . . . . Nonreentrant AutoCAD Commands . . . . . . . . . Multi-Document Commands . . . . . . . . . . . . . Disabling Document Switching . . . . . . . . . . . . Application Execution Context . . . . . . . . . . . . Code Invoked under the Application Execution Context . . Code Differences under the Application Execution Context . Other Application Execution Context Considerations . . . Database Undo and Transaction Management Facilities . . . . Document-Independent Databases . . . . . . . . . . . An MDI-Aware Example Application . . . . . . . . . .
xiv
Contents
Chapter 17
Transaction Management
. 447
448 449 449 450 451 452 452 453 453 453 454 455
Overview of Transaction Management . . . . . . . . . . Transaction Manager . . . . . . . . . . . . . . . . Nesting Transactions . . . . . . . . . . . . . . . . Transaction Boundaries . . . . . . . . . . . . . . . Obtaining Pointers to Objects in a Transaction . . . . . . . . Newly Created Objects and Transactions . . . . . . . . . . Commit-Time Guidelines . . . . . . . . . . . . . . Undo and Transactions . . . . . . . . . . . . . . . Mixing the Transaction Model with the Open and Close Mechanism . Transactions and Graphics Generation . . . . . . . . . . Transaction Reactors . . . . . . . . . . . . . . . . Example of Nested Transactions . . . . . . . . . . . .
Chapter 18
Deep Cloning
. 465
466 467 467 467 467 468 468 469 471 474 474 475 475 476 477 478 478 482 486 496 497 498
Deep Clone Basics . . . . . . . . . . . . . . . . . Using clone() versus deepClone() . . . . . . . . . . Key Concepts of Cloning . . . . . . . . . . . . . Cloning and Filing . . . . . . . . . . . . . Cloning and Ownership . . . . . . . . . . . Cloning and ID Map . . . . . . . . . . . . Cloning and Translating . . . . . . . . . . . Typical Deep Clone Operation . . . . . . . . . . . Cloning Objects from Different Owners . . . . . . . . Implementing deepClone() for Custom Classes. . . . . . . . AutoCAD Commands That Use Deep Clone and Wblock Clone. Cloning Phase . . . . . . . . . . . . . . . . Translation Phase . . . . . . . . . . . . . . . Translation Phase: Case 1 . . . . . . . . . . . Translation Phase: Case 2 . . . . . . . . . . . Translation Phase: Case 3 . . . . . . . . . . . Named Object Dictionary . . . . . . . . . . . . . Overriding the deepClone() Function . . . . . . . . . Overriding the wblockClone() Function . . . . . . . . Using appendAcDbEntity() During Cloning . . . . . . . deepClone() . . . . . . . . . . . . . . . wblockClone() . . . . . . . . . . . . . .
Contents
xv
Handling Hard References to AcDbEntities During wblockClone() . . . . . . . . . Handling Hard References to AcDbEntities During wblockClone(): CASE 1 . . . . . Handling Hard References to AcDbEntities During wblockClone(): CASE 2 . . . . . Insert . . . . . . . . . . . . . . . . Editor Reactor Notification Functions . . . . . .
. . . . .
. . . . .
Chapter 19
Protocol Extension .
.
. . . . . . . . . . . . . . . . . .
509
510 510 510 511 513 513 513 514 514
Protocol Extension Defined . . . . . . . . . . . . Implementing Protocol Extension . . . . . . . . . . Declaring and Defining Protocol Extension Classes . . . Registering Protocol Extension Classes . . . . . . . Default Class for Protocol Extension . . . . . . . . Unloading the Application . . . . . . . . . . . Using Protocol Extension Functionality in an Application . Protocol Extension for the MATCH Command . . . . . . Protocol Extension Example . . . . . . . . . . . .
Chapter 20
.
. . . . . . . . . . . . . . . . . . . .
519
520 520 520 521 521 521 522 522 524 525 526 530 530 531 531 532 532 533 536 537
Common Characteristics of ObjectARX Library Functions. . . . ObjectARX Global Function Calls Compared to AutoLISP Calls Argument Lists in AutoLISP and C . . . . . . . Memory Considerations . . . . . . . . . . . Memory Management . . . . . . . . . Function Return Values versus Function Results . . . . . External Functions . . . . . . . . . . . . . . Defining External Functions . . . . . . . . . Evaluating External Functions . . . . . . . . . Error Handling . . . . . . . . . . . . . . . Communication between Applications . . . . . . . . Handling Errors from Invoked Functions . . . . . Handling External Applications . . . . . . . . . . Variables, Types, and Values Defined in ObjectARX . . . . . . General Types and Definitions . . . . . . . . . . Real Numbers . . . . . . . . . . . . . . Points . . . . . . . . . . . . . . . . Transformation Matrices . . . . . . . . . . Entity and Selection Set Names . . . . . . . . Useful Values . . . . . . . . . . . . . . . .
xvi
Contents
Result Buffers and Type Codes . . . . . . Result-Buffer Lists . . . . . . . . struct resbuf . . . . . . . . . . Result Type Codes Defined by ObjectARX . DXF Group Codes . . . . . . . . ObjectARX Function Result Type Codes . . . User-Input Control Bit Codes . . . . . . Lists and Other Dynamically Allocated Data. . . . Result-Buffer Memory Management . . . . List Creation and Deletion . . . . . AutoLISP Lists . . . . . . . . . Entity Lists with DXF Codes in ObjectARX Command and Function Invocation Lists . Extended Data Exclusive Data Types . . . . . . Text String Globalization Issues . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
538 538 538 539 540 542 544 544 546 547 549 551 552 552 553
Chapter 21
. 555
556 556 556 557 557 559 560 565 565 566 568 572 573 573 573 574 574 575 575
Custom Object Snap Modes . . . . . . . . . . . . . . Creating and Registering a Custom Object Snap Mode . . . Using the Custom Object Snap Manager . . . . . . Creating Custom Object Snap Modes . . . . . . . Creating Protocol Extension Classes . . . . . . . . . Creating a Custom Glyph . . . . . . . . . . . . . Custom Object Snap Mode Example . . . . . . . . . Input Point Management. . . . . . . . . . . . . . . Input Point Manager . . . . . . . . . . . . . . Input Context Events . . . . . . . . . . . . . . Input Context Events Example . . . . . . . . . Bypassed User Input Contexts . . . . . . . . . . . Input Point Filters and Monitors . . . . . . . . . . Input Point Filtering . . . . . . . . . . . . Input Point Monitoring . . . . . . . . . . . Guidelines for Using Input Point Filtering and Monitoring Filter Chaining . . . . . . . . . . . . . . Retrying . . . . . . . . . . . . . . . . Input Point Filter and Monitor Example . . . . . .
Chapter 22
Application Configuration
.
. . . .
.
. . . .
.
. . . .
.
. . . .
.
. . . .
.
. . . .
.
. . . .
.
. . . . . . . .
.
. . . .
. 583
584 584 585 586
Contents
xvii
Chapter 23
Object Enablers .
.
. . . . . .
.
. . . . . .
.
. . . . . .
.
. . . . . .
.
. . . . . . . . . . . .
589
590 590 591 591 592 594
Developing an Object Enabler . . . . . . . Using Live Enabler Technology . . . . . . . Building register.dbx to Populate the Registry . Creating a Self-Extracting Package . . . . Testing Your Object Enabler . . . . . . Uploading to the ADN Website . . . . .
Chapter 24
DesignXML APIs .
.
. . . . . . .
595
596 596 597 597 598 598 599
Overview of DesignXML . . . . . . . . . . . . . . Prerequisite Knowledge . . . . . . . . . . . . . Capabilities of ObjectARX XML APIs . . . . . . . . Providing XML Support for Custom Objects . . . . . . . . Guidelines for Authoring XML Schemas . . . . . . . Sample Schema. . . . . . . . . . . . Writing an XML Object Enabler . . . . . . . . . . Declaring Protocol Extension Classes Derived from AcDbXmlObjectProtocol . . . . . . Defining an ObjectDBX Application for Your XML Object Enabler . . . . . . . . . . . . Sample Application Interface for XML Object Enabler . . . . . . . . . . Implementing xmlOut() . . . . . . . . . . . Sample Implementation of xmlOut() . . . . . Implementing xmlIn() . . . . . . . . . . . Sample Implementation of xmlIn() . . . . . Reading and Writing DesignXML Files from Your Application . .
Part V
Chapter 25
611
613
. 614 . 614 . 615 . 615 . 621
xviii
Contents
AutoCAD ActiveX Automation Implementation . . . . . . The Relationship between AcDbObjects and Automation Objects . . . . . . . . . . . . . IAcadBaseObject . . . . . . . . . . . . . SetObjectId() . . . . . . . . . . . . GetObjectId() . . . . . . . . . . . . Clone() . . . . . . . . . . . . . . GetClassID() . . . . . . . . . . . . NullObjectId(). . . . . . . . . . . . OnModified() . . . . . . . . . . . . AcAxOleLinkManager . . . . . . . . . . . Creating the COM Object . . . . . . . . . . . . Obtaining a CLSID for a Custom Class . . . . . . Using CoCreateInstance . . . . . . . . . . Using COM to Add Custom Objects to the Database . Implementation of Automation Objects . . . . . ATL Templates . . . . . . . . . . . . . Interacting with AutoCAD . . . . . . . . . . . . . Document Locking . . . . . . . . . . . . . . . . Creating a Registry File . . . . . . . . . . . . . . Exposing Automation Functionality . . . . . . . . . . Setting Up an ATL Project File . . . . . . . . . . Writing a COM Wrapper . . . . . . . . . . . . Exposing ObjectARX Functions through ActiveX Automation . . . . . . . . . . Adding a Custom Object or Entity to an Object Model. Building and Registering a COM DLL . . . . . . . . Object Property Manager API . . . . . . . . . . . . AutoCAD COM Implementation . . . . . . . . . Static OPM COM Interfaces . . . . . . . . . . . . . ICategorizeProperties Interface . . . . . . . . . . IPerPropertyBrowsing Interface . . . . . . . . . . IOPMPropertyExtension Interface . . . . . . . . . IOPMPropertyExpander Interface . . . . . . . . . Implementing Static OPM Interfaces . . . . . . . . . . Dynamic Properties and OPM . . . . . . . . . . . . IDynamicProperty . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
628 628 629 630 630 630 630 630 630 630 632 632 632 633 633 634 635 636 637 639 640 642 642 644 649 651 651 652 653 653 653 654 654 659 660
Contents
xix
Chapter 26
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
663
664 664 664 664 665 665 665 665 665 666 667 667 668 670 671 671 674
AutoCAD DesignCenter API . . . . . . . . . . . . IAcDcContentBrowser Interface . . . . . . . . . IAcDcContentView Interface . . . . . . . . . . IAcDcContentFinderSite Interface . . . . . . . . IAcDcContentFinder Interface. . . . . . . . . . IAcPostDrop Interface . . . . . . . . . . . . Registry Requirements for an AutoCAD DesignCenter Component Applications Key . . . . . . . . . . . . . . Application Name . . . . . . . . . . . . Extensions Key . . . . . . . . . . . . . . Extension Name . . . . . . . . . . . . CLASSID Registration . . . . . . . . . . . . Implementing the Interfaces for AutoCAD DesignCenter . . . Customizing AutoCAD DesignCenter . . . . . . . . . Create an ActiveX Template Library Project . . . . . Add Registry Support and a New ATL COM Object . . . Add Code to Support the New ATL COM Object . . . .
Chapter 27
.
. . . .
.
. . . .
.
. . . .
.
. . . .
.
. . . .
.
. . . .
.
. . . .
.
. . . .
.
. . . . . . . .
683
684 684 684 685
Overview of the eTransmit Interfaces . Extracting the eTransmit IDL . . . . Using the eTransmit Interfaces . . . eTransmit Example . . . . .
Part VI
Chapter 28
ObjectARX Libraries .
The ObjectDBX Libraries .
.
. .
.
.
.
.
. . . . . . . . . . .
.
.
. . . . . . . . . . . . . . . . . . . . . .
.
.
. . . . . . . . . . .
.
.
. . . . . . . . . . .
.
.
. . . . . . . . . . .
.
.
. . . . . . . . . . .
.
.
. . . . . . . . . . .
689
.
. . . . . . . . . . . . . . . . . . . . . .
691
692 692 692 692 693 693 693 693 693 694 694
Overview of ObjectDBX . . . . . Host Applications. . . . . . ObjectDBX Libraries . . . . . User Interface and Database Access Using ObjectDBX . . . . . . . Getting Started with ObjectDBX . C Runtime Libraries . . . Multithreading. . . . . AcDbDatabase . . . . . ObjectDBX Library Changes . . The Application Services Class. .
xx
Contents
Differences between ObjectDBX and ObjectARX . . . . . . AcEditorReactor Class . . . . . . . . . . . . . AcGi API . . . . . . . . . . . . . . . . . Localization and XMX Files . . . . . . . . . . . . . Transaction Management . . . . . . . . . . . . . AcTransaction and AcTransactionReactor Classes . . . . AcTransactionManager and AcDbTransactionManager Classes Creating a Viewer . . . . . . . . . . . . . . . . Viewer Components . . . . . . . . . . . . . AcGi . . . . . . . . . . . . . . . . . . AcGix . . . . . . . . . . . . . . . . . . AcGix Differences from AutoCAD Viewing . . . . . . TrueType Font Elaboration . . . . . . . . . SimpleView . . . . . . . . . . . . . . . . Using the Database Mutex. . . . . . . . . . WhipView . . . . . . . . . . . . . . . . ViewAcDb. . . . . . . . . . . . . . . . . Basic Viewer Operation . . . . . . . . . . . . Graphical Entity Selection . . . . . . . . . . Configuration Suggestions . . . . . . . . . . . Demand Loading . . . . . . . . . . . . . . . . Installing the ObjectDBX Libraries . . . . . . . . . . . Use COMMONFILES . . . . . . . . . . . . . Install by Version and as SHAREDFILE . . . . . . . . Ensure the Files are on the Path . . . . . . . . . . For All Operating Systems . . . . . . . . . . Windows NT . . . . . . . . . . . . . . Windows 95 and Windows 98 . . . . . . . . Ensure Smart Pathing Updates . . . . . . . . . . Tips and Techniques . . . . . . . . . . . . . . . ACAD_OBJID_INLINE_INTERNAL . . . . . . . . . AcDbDatabase Notes . . . . . . . . . . . . . Always Instantiate an AcDbDatabase . . . . . . Always Have a Current Database. . . . . . . . Delete All AcDbDatabases at Application Exit . . . AcDbDatabase::insert(). . . . . . . . . . . . . Finding the Active Viewports in Model Space . . . . . Details About Viewports . . . . . . . . . . . . Always Test Your Drawings in AutoCAD . . . . . . . Using DWG Files from Earlier Releases. . . . . . . . Extended Entity Data . . . . . . . . . . . . . Raster Images. . . . . . . . . . . . . . . . Known Limitations. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
695 696 696 696 698 698 698 699 699 699 700 701 701 702 703 704 704 705 705 705 706 707 707 707 708 708 708 708 709 710 710 711 711 712 713 713 713 714 715 715 716 717 717
Contents
xxi
Chapter 29
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
719
720 722 723 724 725 726 726 727 727 728 729 731 732 734 735 739 740 740 742 744 744 746 746 747 747 747 748 748 749 750 754 755 759 759 760
AcGi Overview . . . . . . . . . . . . . . . . . The setAttributes Function . . . . . . . . . . . . The worldDraw() Function . . . . . . . . . . . . The viewportDraw() Function . . . . . . . . . . . Viewport Regeneration Type . . . . . . . . . . . Setting Entity Traits . . . . . . . . . . . . . . . . Subentity Traits . . . . . . . . . . . . . . . Fill Type . . . . . . . . . . . . . . . . GS Markers . . . . . . . . . . . . . . . Useful AcGi Constants . . . . . . . . . . . . . Example of Using AcGi . . . . . . . . . . . . . Primitives . . . . . . . . . . . . . . . . . . . Mesh . . . . . . . . . . . . . . . . . . . Visibility. . . . . . . . . . . . . . . . Shell . . . . . . . . . . . . . . . . . . . Arc . . . . . . . . . . . . . . . . . . . Polyline . . . . . . . . . . . . . . . . . . Text . . . . . . . . . . . . . . . . . . . Associating an AcDbTextStyleTableRecord with an AcGiTextStyle . . . . . . . . . . Using Drawables in your Object . . . . . . . . . . . . Tessellation . . . . . . . . . . . . . . . . . . Isolines . . . . . . . . . . . . . . . . . . . . Transformations . . . . . . . . . . . . . . . . . Model Coordinate System . . . . . . . . . . . . World Coordinate System . . . . . . . . . . . . Eye Coordinate System . . . . . . . . . . . . . Display Coordinate System. . . . . . . . . . . . Transformation Examples . . . . . . . . . . . . Example 1: Coordinate Systems . . . . . . . . Example 2: Determining Hidden Lines for an Object for Standard Display . . . . . . . . . Example 3: Obtaining the Window Coordinates . . . Example 4: Calculating the Circle to Draw . . . . . Using Clip Boundaries in AcGi . . . . . . . . . . . . Background . . . . . . . . . . . . . . . . Clip Boundary Example . . . . . . . . . . . . .
xxii
Contents
Chapter 30
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
.
. . . . . . . . . . . . . .
. 761
762 764 765 766 768 769 769 770 771 772 774 780 781 782
Overview of the AcGe Library . . . . . . . . . . Global Data and Functions . . . . . . . . . Tolerances . . . . . . . . . . . . . . Using Basic Geometry Types . . . . . . . . . . . Using the Line and Plane Classes . . . . . . . . . Parametric Geometry . . . . . . . . . . . . . Curves . . . . . . . . . . . . . . . . Characteristics . . . . . . . . . . . Degeneracy . . . . . . . . . . . . Surfaces . . . . . . . . . . . . . . . Special Evaluation Classes . . . . . . . . . . . Tips for Efficient Use of Curve and Surface Evaluators . Persistent AcGe Entities . . . . . . . . . . . . AcGe Persistency Examples . . . . . . . . .
Chapter 31
.
. . . . . . . . . . . . . . . . . . . .
. 787
788 789 790 791 792 793 793 794 794 794 794 794 794 795 797 797 798 798 798 799
Overview of the AcBr Library . . . . . . . . . . . . Domain . . . . . . . . . . . . . . . . . . . Limitations . . . . . . . . . . . . . . . . . . Class Hierarchy . . . . . . . . . . . . . . . . . Topological Objects . . . . . . . . . . . . . . . Using Topological Objects in your Program . . . . . . Using Solid Objects . . . . . . . . . . . . Using Specific Subentities . . . . . . . . . . Using the Geometry of a Face, Edge, or Vertex . . . Using the Mesh Data of a Brep, Complex, Shell, or Face Using Topological Traversers in your Program . . . . . Global Searches . . . . . . . . . . . . . Hierarchical (Local) Searches . . . . . . . . . From Topological Traversers to Objects . . . . . . . From Mesh Traversers to Mesh Objects . . . . . . . AcBr Class Descriptions . . . . . . . . . . . . . . Entity Classes . . . . . . . . . . . . . . . Containment Classes . . . . . . . . . . . . . Mesh Classes . . . . . . . . . . . . . . . . Traverser Classes. . . . . . . . . . . . . . .
Contents
xxiii
Enumerated Types . . . . . . . . . . Error Return Codes . . . . . . . . Validation Level . . . . . . . . . ShellType . . . . . . . . . . . LoopType . . . . . . . . . . . Mesh Element Shape Control . . . . . Building an Application . . . . . . . . Sample Application Using the AcBr Library
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
Index
803
xxiv
Contents
Part 1
Using ObjectARX
ObjectARX Application Basics 27 Database Operations Database Objects 81 Entities 97 141 Container Objects 59
Overview of ObjectARX
1
In this chapter
I The ObjectARX Programming
ObjectARX the AutoCAD Runtime Extension , programming environment, includes C++ libraries that 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. An ObjectARX application is a dynamic link library (DLL) that shares the address space of AutoCAD and makes direct function calls to AutoCAD. You can add new classes to the ObjectARX program environment and export them for use by other programs. You can also extend the ObjectARX protocol by adding functions at runtime to existing AutoCAD classes. This section provides an overview of the ObjectARX class libraries and gives information for getting started with ObjectARX. The ObjectARX Developers Guide assumes that you are familiar with C++, object-oriented programming, and AutoCAD.
Environment
I The ObjectARX
Documentation Set
I ObjectARX Logo Program I ObjectARX Class Libraries I Getting Started
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.
Chapter 1
Overview of ObjectARX
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.
Notification Transaction management Deep cloning Reference editing Protocol extension Proxy object support
ObjectARX Reference. A programmers reference that provides detailed information on each class and function in the ObjectARX API. ObjectARX Developers Guide. The same content as the printed ObjectARX Developers Guide in online format. Whats New in ObjectARX. Lists the APIs that have been added to ObjectARX for AutoCAD 2002. ObjectARX Readme. Describes last-minute changes and additions to ObjectARX.
Chapter 1
Overview of ObjectARX
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 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 register
your Registered Developer Symbol (RDS) with Autodesk, in addition to the other logo requirements.
The following table lists the libraries required to link ObjectARX applications. 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 AcRx AcEd AcDb Required Libraries acad.lib, rxapi.lib, acrx15.lib acad.lib, rxapi.lib, acedapi.lib, acrx15.lib acad.lib, rxapi.lib, acdb15.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 linking and for runtime class registration and identification. The base class of this library is AcRxObject, which provides the following facilities:
I I I I
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.
Chapter 1
Overview of ObjectARX
AcRxClass AcRxDictionary AcRxDynamicLinker AcRxEvent AcEditor AcRxService AcRxKernal AcDbServices AcEdServices AcadAppInfo
AcEd Library
The AcEd library provides classes for defining and registering new AutoCAD commands that operate in the same manner as built-in AutoCAD commands. 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, ending, or canceling a command.
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:
I
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. A named object dictionary (of class AcDbDictionary), which provides the table of contents for an AutoCAD drawing. Initially, this table of contents 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.
10
Chapter 1
Overview of ObjectARX
AcDbDictionary AcDbDictionaryWithDefault AcDbFilter AcDbLayerFilter AcDbSpatialFilter AcDbGroup AcDbIDBuffer AcDbIndex AcDbLayerIndex AcDbSpatialIndex AcDbLongTransaction AcDbMlineStyle AcDbPlaceholder AcDbPlotSettings AcDbLayout AcDbProxyObject AcDbXrecord AcDbEntity
AcDbSymbolTable AcDbAbstractViewTable AcDbViewportTable AcDbViewTable AcDbBlockTable AcDbDimStyleTable AcDbFontTable AcDbLayerTable AcDbLinetypeTable AcDbRegAppTable AcDbTextStyleTable AcDbUCSTable
AcDbSymbolTableRecord AcDbAbstractViewTableRecord AcDbViewportTableRecord AcDbViewTableRecord AcDbBlockTableRecord AcDbDimStyleTableRecord AcDbFontTableRecord AcDbLayerTableRecord AcDbLinetypeTableRecord AcDbRegAppTableRecord AcDbTextStyleTableRecord AcDbUCSTableRecord
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 produce different graphical representations for each viewport.
11
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 geometric 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 beginning 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.
12
Chapter 1
Overview of ObjectARX
AcGeBoundBlock2d AcGeClipBoundary2d AcGeCurve2d AcGeCircArc2d AcGeCompositeCurve2d AcGeEllipArc2d AcGeExternalCurve2d AcGeLinearEnt2d AcGeLine2d AcGeLineSeg2d AcGeRay2d AcGeOffsetCurve2d AcGeSplineEnt2d AcGeCubicSplineCurve2d AcGeNurbCurve2d AcGePolyline2d AcGeCurveCurveInt2d AcGePointEnt2d AcGePointOnCurve2d AcGePosition2d AcGeCurveBoundary AcGe AcGeContext AcGeDwgIO AcGeDxfIO AcGeFileIO AcGeFiler AcGeInterval AcGeKnotVector AcGeLibVersion AcGeMatrix2d AcGeMatrix3d AcGePoint2d AcAxPoint2d AcGePoint3d AcAxPoint3d AcGeScale2d AcGeScale3d AcGeTol AcGeVector2d AcGeVector3d
AcGeBoundBlock3d AcGeCurve3d AcGeCircArc3de AcGeCompositeCurve3d AcGeEllipArc3e AcGeExternalCurve3d AcGeLinearEnt3d AcGeLine3d AcGeLineSeg3d AcGeRay3d AcGeMatrix3d AcGeOffsetCurve3d AcGeSplineEnt3d AcGeCubicSplineCurve3d AcGeNurbCurve3d AcGePolyline3d AcGeAugPolyline3d AcGeCurveCurveInt3d AcGeCurveSurfInt AcGePointEnt3d AcGePointOnCurve3d AcGePointOnSurface AcGePosition3d AcGeSurfSurfInt AcGeSurface AcGeCone AcGeCylinder AcGeExternalBoundedSurface AcGeExternalSurface AcGeNurbSurface AcGeOffsetSurface AcGePlanarEnt AcGeBoundedPlanet AcGePlane AcGeSphere AcGeTorus
The AcGe library provides several different coordinate systems. For more information, see chapter 30, Using the Geometry Library. The sample programs in this documentation illustrate numerous common uses of AcGe classes.
13
Getting Started
The following topics discuss the system requirements for ObjectARX and provide installation instructions.
System Requirements
Developing applications with ObjectARX requires the following software and hardware:
I I I I
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.
14
Chapter 1
Overview of ObjectARX
classmap docs
The classmap directory contains an AutoCAD drawing illustrating the ObjectARX class hierarchy. The docs directory contains online help files for ObjectARX developers, including the ObjectARX Developers Guide, the ObjectARX Reference, Whats New in ObjectARX, and the ObjectARX Readme file. The docsamps directory contains subdirectories for each of the programs from which examples were extracted for the ObjectARX Developers Guide. Each subdirectory contains the full set of source code for the application and an explanatory Readme file. The inc directory contains the ObjectARX header files. The lib directory contains the ObjectARX library files. 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. 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. The utils directory contains subdirectories for applications that are extensions to ObjectARX, including brep for boundary representation. Each application directory includes inc, lib, and sample subdirectories.
docsamps
samples
utils
15
16
Database Primer
2
In this chapter
I AutoCAD Database Overview I Essential Database Objects I Creating Objects in AutoCAD I Creating Objects in
The AutoCAD database stores the objects and entities that make up an AutoCAD drawing. This section discusses the key elements of the database: entities, symbol tables, and the named object dictionary. This section 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.
ObjectARX
17
Entity
18
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.
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.
19
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.
loaded.
AcDbDatabase(Adesk::Boolean buildDefaultDrawing = Adesk::kTrue);
20
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
When you first invoke AutoCAD and the database is in its default state, entities are added to model space, the main space in AutoCAD, which is used for model geometry and graphics. Paper space is intended to support documentation 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 entityhere, AcDbCircleand adds it to the model space block table record.
Paper Space Block Table
21
AutoCAD creates a new layer table record to hold the layer and then adds it to the layer table.
Paper Space Block Table
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
22
Chapter 2
Database Primer
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.
23
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; }
24
Chapter 2
Database Primer
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();
New instances of an object are considered to be open for write. Some functions, such as the AcDbBlockTable::getAt() function, obtain an object ID and open the object at the same time. An object cant 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.
25
26
Chapter 2
Database Primer
3
In this chapter
I Creating an ObjectARX
This section describes how to write and run an ObjectARX application. It lists the messages passed by AutoCAD to the ObjectARX application and shows how the application typically responds to those messages. This section also discusses the registration of new commands, how to load and unload an application, AutoCADs demand loading feature, and error handling.
Application
I Example Application I Registering New Commands I Loading an ObjectARX
Application
I Unloading an ObjectARX
Application
I Demand Loading I ARX Command I Running ObjectARX
27
28
Chapter 3
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 kInitAppMsg Description Sent when the ObjectARX application is loaded to open communications between AutoCAD and the application. 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. 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. Sent when AutoCAD is saving the drawing because a SAVE, SAVEAS, NEW, or OPEN command is entered. Sent (in the reverse order of load time) when the user quits a drawing session.
kUnloadAppMsg
kLoadDwgMsg
kSaveMsg
kUnloadDwgMsg
29
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 kInvkSubrMsg kEndMsg Description Sent to invoke functions registered using acedDefun(). 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. 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. Sent when AutoCAD returns from the configuration program, and used only for a change to the display driver.
kQuitMsg
kCfgMsg
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 kDependencyMsg Description Sent when the ObjectARX application has registered an AcRxService object and the dependency count on that service changes from 0 to 1. Sent when the ObjectARX application has registered an AcRxService object and the dependency count on that service changes from 1 to 0.
kNoDependencyMsg
30
Chapter 3
The next table lists the messages that an application needs to respond to if it is using ActiveX Automation. See chapter 25, COM, ActiveX Automation, and the Object Property Manager. Messages only responded to by applications that use ActiveX Automation
Message kOleUnloadAppMsg Description 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 kInitAppMsg Recommended Actions Do register services, classes, AcEd commands and reactors, and AcRxDynamicLinker reactors. Initialize applications 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. Dont 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. Dont 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.
31
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(). Dont 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.
32
Chapter 3
Time
Start AutoCAD
Open drawing 1
kLoadDwgMsg ads_defun"c:TEST1"
kInvkSubr
kSaveMsg
Quit
If an application is loaded when a drawing is already open, the kInitAppMsg and kLoadDwgMsg messages are sent in succession. When an ObjectARX application is unloaded while an edit session is in progress, the kUnloadDwg and kUnloadApp messages are sent in succession.
33
Represents the message sent from the ObjectARX kernel to the application. Holds packet data values. 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.
34
Chapter 3
35
5 Register commands with the AutoCAD command mechanism. Use acedRegCmds->addCommand() to make AutoCAD aware of the commands that your application defines. For more information, see Registering New Commands on page 38.
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.
36
Chapter 3
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 341.)
// 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
37
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 applications can add a command of the same name, because the group name makes the commands unambiguous.
38
Chapter 3
cmdGroupName ASCII representation of the group to add the command to. If the group doesnt 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 40). 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 41). 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.
39
hResourceHandle Input resource handle to be made current when the command is executed cmdPtrRet Input pointer to pointer to be filled in with address of AcEdCommand object for the command being added
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.
40
Chapter 3
I I I I I
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. 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.
41
I I I
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
42
Chapter 3
application 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 36. 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;
Demand Loading
Demand loading is a feature of AutoCAD that automatically attempts to load an ObjectARX application that is not resident in AutoCAD. ObjectARX applications can be designed for loading by AutoCAD under one or more of the following circumstances:
I I I
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 applications commands When AutoCAD is started
Demand Loading
43
Autodesk recommends developing ObjectARX applications that take advantage of AutoCADs demand-loading feature because demand loading provides the following benefits:
I I I
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.
44
Chapter 3
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 identification 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 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.
Demand Loading
45
AutoCAD Map), the installation program must add the appropriate information to the section of the registry for each version of AutoCAD. The installation process for ObjectARX applications must therefore include:
I
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 applications installation program 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.
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 application will be loaded, using one or more logical ORs of the following hex values listed with their associated meanings:
46
Chapter 3
Load the application upon detection of proxy object. Load the application upon AutoCAD startup. Load the application upon invocation of a command. Load the application upon request by the user or another application. Do not load the application.
The RegistryPathWhereLoaderIsSpecified value must identify the registry path for the applications 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 application is loaded, and confirm the presence of that information on subsequent loads.
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.
Demand Loading
47
The value in the Groups key may be used to uniquely identify an ObjectARX applications command groups and therefore the commands as well.
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.
48
Chapter 3
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, lets 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 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 applications 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
Demand Loading
49
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 isnt a class implementation available, custom objects are treated as proxies and the load continues.
In this example, the developers 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.
NOTE Invoking the command using Group prefix will also disambiguate the
command. In other words, GROUP_NAME.COMMAND_NAME will demand load the command associated with the specified command group. The ObjectARX application must also include the appropriate calls to the acedRegCmds macro for demand loading on command to work.
50
Chapter 3
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 callers responsibility to release the space allocated for the returned strings.
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.
ARX Command
51
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 42 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
I
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 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 embedded 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
52
Chapter 3
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 Constructions commands are named with construction terminology, while most of XYZ Interiors commands are named with interior decorating terminology, 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 following 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 command. 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.
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.
53
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 symbols 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, youll always want to check return status and take appropriate 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); 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; }
54
Chapter 3
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)); } es = pCirc->close(); if (es != Acad::eOk) { acrx_abort("\nApp X failed to" " close circle entity. Error: %d", acadErrorStatusText(es)); } return es; }
Error Handling
55
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; } 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; }
56
Chapter 3
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; }
Error Handling
57
58
Database Operations
4
In this chapter
I Initial Database I Creating and Populating a
This section describes basic database protocol including how to create a database, how to read in a drawing file, and how to save the database. The wblock and insert operations are also described here. For more detailed information on the deepClone and
wblock
Database
I Saving a Database I The wblock Operation I Inserting a Database I Setting Current Database
Values
I Example of Database
Operations
I Long Transactions I External References I Indexes and Filters I Drawing Summary Information I Last Saved by Autodesk
Software
59
Initial Database
When an AutoCAD session begins, the database contains the following elements:
I
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 applications 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.)
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
Saving a Database
To save a database, use the AcDbDatabase::saveAs() function:
Acad::ErrorStatus AcDbDatabase::saveAs(char* fileName);
Saving a Database
61
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 different 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 current 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:
I I
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.
Both functions accept a database pointer and a filename, and write out the drawing in AutoCAD Release 13 or Release 14 DWG format, respectively.
This function creates a new database from the invoked database (this). Any unreferenced symbols in the input database are omitted in the new database (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.
63
The recordId argument represents a block table record in the input database. The entities in this block table record are copied into the new databases model-space block table record. The insert base of the new database is the block table records origin.
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.
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
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 entitys 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 regenerated (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;
67
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 feature 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 transaction check out:
I I I
From a normal block within the same drawing From an external reference (xref) of the drawing From an unrelated, temporary database
class
I AcDbLongTransWorkSetIterator I AcApLongTransactionReactor I
I AcDbDatabase::wblockCloneObjects()
Long Transactions
69
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
object. Like all other database-resident objects, its destruction is handled by the database.
AcDbLongTransWorkSetIterator Class
AcDbLongTransWorkSetIterator provides read-only access to the objects in
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 COPY EXPLODE BLOCK INSERT/BIND XRESOLVE INSERT insert() WBLOCK deepCloneObjects() wblockObjects() wblockObjects() wblockObjects() wblockObjects() kDcCopy kDcExplode kDcBlock kDcXrefInsert kDcSymTableMerge kDcInsert kDcInsertCopy kDcWblock kDcObjects kDcObjects kDcObjects kDcObjects kDcObjects DuplicateRecordCloning kDrcNotApplicable kDrcNotApplicable kDrcNotApplicable kDrcIgnore kDrcXrefMangleName kDrcIgnore kDrcIgnore kDrcNotApplicable kDrcNotApplicable kDrcIgnore kDrcReplace kDrcMangleName kDrcUnmangleName
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
I I I I I I I I I
For information on the AutoCAD XREF command, see the AutoCAD Users 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 contains the name of the external drawing and points to the entities of the model space of the externally referenced drawing. The xref database also contains 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:
I I I I I I
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 additional 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).
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 restoration. 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, execute any xref commands, or provide user prompts while the xref database is in its restored condition. The functions are
I AcDbDatabase::xrefBlockId() I AcDbDatabase::restoreOriginalXrefSymbols() I 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 database 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 database 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 modifications, 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.
76
Chapter 4
Database Operations
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
namespace
I AcDbFilteredBlockIterator
I AcDbCompositeFilteredBlockIterator
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()
78
Chapter 4
Database Operations
method. It then orders the composition of the AcDbFilteredBlockIterator objects based on the AcDbFilteredBlockIterator::estimatedHits() and AcDbFilteredBlockIterator::buffersForComposition() methods. The collection 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.
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
I I I I I I I I
Title Subject Author Keywords Comments Last saved by Revision number Hyperlink base
79
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.
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 reactors, with methods to add and remove reactors, and to send notification that the summary information has changed.
80
Chapter 4
Database Operations
Database Objects
5
In this chapter
I Opening and Closing Database
This section describes topics that relate to all AutoCAD database objects, including entities, symbol table records, and dictionaries. Major concepts included are opening and closing objects, managing objects in memory, object ownership, and extending an object using xdata or the objects extension dictionary. Other common operations on objects, such as filing and erasing, are also discussed.
Objects
I Deleting Objects I Database Ownership of
Objects
I Adding Object-Specific Data I Erasing Objects I Object Filing
81
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
AcDbDatabase ObjectID
open object
close object
C++ Pointer
82
Chapter 5
Database Objects
You can also open an object and then request its handle:
AcDbObject* pObject; AcDbHandle handle; pObject->getAcDbHandle(handle);
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; }
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.
83
I 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 openedForRead eAtMaxReaders (if readCount = 256; otherwise succeeds) eWasOpenForWrite eWasOpenForNotify (Succeeds) eWasOpenForUndo kForWrite eWasOpenForRead kForNotify (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 notifyfor example, when you are receiving notificationand you want to open it for write, you can use upgradeFromNotify() to upgrade its open status to write. Then you would use downgradeToNotify() to downgrade its status to notify. For more information about how to manage complex sequences of opening and closing objects, see Transaction Manager on page 449.
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 cannot delete it; AutoCAD manages the deletion of all database-resident objects.
84
Chapter 5
Database Objects
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:
I I I I
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.
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)
85
Extended Data
Extended data (xdata) is created by applications written with ObjectARXor AutoLISPand can be added to any object. Xdata consists of a linked list of resbufs used by the application. (AutoCAD maintains the information but doesnt 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 containing a copy of the xdata for an object:
virtual resbuf* AcDbObject::xData(const char* regappName = NULL) const;
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 objects 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. // void printXdata() { // Select and open an object. // AcDbObject *pObj; if ((pObj = selectObject(AcDb::kForRead)) == NULL) { return; }
86
Chapter 5
Database Objects
// 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. // 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;
87
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); }
Extension Dictionary
Every object can have an extension dictionary, which can contain an arbitrary 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.
88
Chapter 5
Database Objects
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(); // 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);
89
// 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(); // Get the xrecord's data list and then close the xrecord. // struct resbuf *pRbList; pXrec->rbChain(&pRbList); pXrec->close(); printList(pRbList); acutRelRb(pRbList); }
90
Chapter 5
Database Objects
91
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. // int listXrecord() { struct resbuf *pXrec, *pEnt, *pTemp; ads_point dummy; ads_name ename, extDict = {0L, 0L};
92
Chapter 5
Database Objects
// 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; }
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:
Erasing Objects
93
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.
Object Filing
Object filing refers to the conversion process between an objects 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 serializing. Filing an object in is the process of turning a sequence of data back into an object, sometimes called deserializing.
94
Chapter 5
Database Objects
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, youll 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
6
In this chapter
I Entities Defined I Entity Ownership I Common Entity Properties I Common Entity Functions I Creating Instances of
This section describes entitiesdatabase objects with a graphical representation. It lists the properties and operations all entities have in common. Examples show how to create blocks, inserts, and complex entities, and how to select and highlight subentities.
AutoCAD Entities
I Complex Entities I Coordinate System Access I Curve Functions I 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:
I AcDb2dPolyline, I AcDb3dPolyline, I I I I
which owns AcDb2dPolylineVertex objects 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 133.
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.
98
Chapter 6
Entities
AcDbDatabase
AcDbBlockTable
AcDbBlockTableRecord
AcDbBlockBegin
AcDbEntity
AcDbBlockEnd
AcDbSequenceEnd
Entity Ownership
99
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 databases current value for that property is used. See chapter 4, Database Operations for a description of the member functions used for setting and getting the current 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 correct 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 following table: Colors 1 to 7
Color Number 1 2 3 Color Name Red Yellow Green
100
Chapter 6
Entities
Colors 1 to 7 (continued)
Color Number 4 5 6 7 Color Name Cyan Blue Magenta 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. Specifies BYLAYER. Entities assume the color of the entitys associated layer. 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 databases current color index.
256 257
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 linetype 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 databases 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 entitys target database.
101
Special linetype entries are as follows: CONTINUOUS BYLAYER BYBLOCK Default linetype, which is automatically created in the linetype symbol table Linetype value of the entitys layer Linetype value of the entitys surrounding block definitions current block reference
If a linetype value is specified for an entity, the current database default linetype 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 object ID for the symbol table record specifying the linetype:
AcDbObjectId AcDbEntity::linetypeId() const;
102
Chapter 6
Entities
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 Users 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 dont 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 entitys 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);
103
This function returns the object ID for the current layer (an object of type AcDbLayerTableRecord):
AcDbObjectId AcDbEntity::layerId() const;
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 transformation 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
104
Chapter 6
Entities
I getSubentPathsAtGsMarker() I I I
returns the subentity paths that correspond to the given GS marker (see GS Markers and Subentities) 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 Subentities on page 108)
105
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 122).
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
106
Chapter 6
Entities
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.
A B
line3
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 signatures 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 entitys intersectWith() function has implemented the use of GS markers, then
107
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 polygon, passing in the GS marker for that line eliminates the need to test the other five lines of the polygon.
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
108
Chapter 6
Entities
form the endpoints of this line are returned. If you ask for the edges associated with this marker, one entitythe lineis 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 AcDbSubentId } mObjectIds; 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 contains 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 subentity 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)};
109
3 Once you have the path to the selected subentity, youre ready to call the highlight() function, passing in the correct subentity path. Selecting an Entity For selection, youll 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 youre interested in (vertex, edge, or face). In the example code in Highlighting the Subentity, the first call to this function specifies kEdgeSubentType because youre going to highlight the corresponding edge. The second call to the getSubentPathsAtGsMarker() function specifies kFaceSubentType because youre 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.
110
Chapter 6
Entities
Highlighting the Subentity Once youve 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 subentities 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).
not generally expected to be overridden. However, if it is overridden, any new implementation of this function must call AcDbEntity::highlight() to perform 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. // void highlightTest() { AcDbObjectId objId; int marker; if (getObjectAndGsMarker(objId, marker) != Acad::eOk) return; highlightEdge(objId, marker); highlightFaces(objId, marker); highlightAll(objId); }
111
// 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); // 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; }
112
Chapter 6
Entities
// 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); // 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(); }
113
// 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(); } // 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);
114
Chapter 6
Entities
// 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; }
115
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). Weve 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; }
116
Chapter 6
Entities
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; } 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);
117
postToDb(box1); postToDb(box2); postToDb(box3); // 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;
118
Chapter 6
Entities
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 ads_name short AcGePoint3d AcGeVector3d sel_method; subname; marker; pickpnt; 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];
119
// 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);
120
Chapter 6
Entities
// 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);
121
// 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", 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 elements. The specific behavior depends on the class. For example, boxes can be exploded into regions, then lines. Polylines can be exploded into line segments. 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
Entity AcDb3dSolid AcDbBody By Itself Regions, bodies Regions, bodies Nonuniform Scaling (when in a block) NA; cant be exploded NA
122
Chapter 6
Entities
123
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 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):
I I I
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.
124
Chapter 6
Entities
AcDbBlockTableRecord *pBlockTableRecord; pBlockTable->getAt(ACDB_MODEL_SPACE, pBlockTableRecord, AcDb::kForWrite); pBlockTable->close(); AcDbObjectId lineId; pBlockTableRecord->appendAcDbEntity(lineId, pLine); pBlockTableRecord->close(); pLine->close(); return lineId; }
125
entity and adds it to the new block table record. It creates two attribute definition 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);
126
Chapter 6
Entities
// 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); // 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());
127
// 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); // Append the second attribute definition to the block. // pBlockRecord->appendAcDbEntity(entityId, pAttdef2); pAttdef->close(); pAttdef2->close(); pBlockRecord->close(); pBlockTable->close(); return; }
128
Chapter 6
Entities
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 corresponding 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);
129
// 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. // AcDbBlockTableRecordIterator *pIterator; pBlockDef->newIterator(pIterator); AcDbEntity *pEnt; AcDbAttributeDefinition *pAttdef; for (pIterator->start(); !pIterator->done(); pIterator->step()) {
130
Chapter 6
Entities
// 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(); }
131
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); 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();
132
Chapter 6
Entities
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.
Complex Entities
133
// 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(); }
134
Chapter 6
Entities
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; }
135
I I I I I I I I I
Arcs 2D polylines Block inserts Points Traces Solids Shapes Attribute definitions Attributes
AcDb2dVertex
An AcDb2dPolyline has as an elevation and a series of X,Y points of class AcDb2dVertex. The position() and setPosition() functions of AcDb2dVertex 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 polylines 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 operating 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. Curves can be used as trim boundaries, extension boundaries, and as construction objects for creating complex 3D entities.
136
Chapter 6
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]); }
Curve Functions
137
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 hyperlinks description. If the description is null, the hyperlinks 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 methods 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.
138
Chapter 6
Entities
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.**");
139
// 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(); }
140
Chapter 6
Entities
Container Objects
7
In this chapter
I Comparison of Symbol Tables
This section describes the container objects used in AutoCAD database operations: symbol tables, dictionaries, groups, and xrecords. As part of any drawing, AutoCAD creates a fixed set of symbol tables and the named object dictionary, which contains two other dictionaries, the MLINE style and GROUP dictionaries. The section 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.
and Dictionaries
I Symbol Tables I Dictionaries I Layouts I Xrecords
141
142
Chapter 7
Container Objects
The class hierarchy for symbol tables, symbol table records, dictionaries, and iterators is as follows.
AcDbObject
AcDbSymbolTable AcDbAbstractViewTable AcDbViewportTable AcDbViewTable AcDbBlockTable AcDbDimStyleTable AcDbLayerTable AcDbLinetypeTable AcDbRegAppTable AcDbTextStyleTable AcDbUCSTable
AcDbSymbolTableRecord AcDbAbstractViewTableRecord AcDbViewportTableRecord AcDbViewTableRecord AcDbBlockTableRecord AcDbDimStyleTableRecord AcDbLayerTableRecord AcDbLinetypeTableRecord AcDbRegAppTableRecord AcDbTextStyleTableRecord AcDbUCSTableRecord
AcDbSymbolTableIterator AcDbAbstractViewTableIterator AcDbViewportTableIterator AcDbViewTableIterator AcDbBlockTableIterator AcDbDimStyleTableIterator AcDbLayerTableIterator AcDbLinetypeTableIterator AcDbRegAppTableIterator AcDbTextStyleTableIterator AcDbUCSTableIterator AcDbDictionary AcDbDictionarywithDefault
An important difference between symbol tables and dictionaries is that symbol table records cannot be erased directly by an ObjectARXapplication. These records can be erased only with the PURGE command or selectively filtered out with wblock operations. Objects owned by a dictionary can be erased.
143
Another important difference is that symbol table records store their associated 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>
Symbol Tables
Names used in symbol table records and in dictionaries must follow these rules:
I I
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 comparisons. For example, AutoCAD considers Floor to be the same symbol as FLOOR.
144
Chapter 7
Container Objects
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):
I I I I I I I I I
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 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
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.
Symbol Tables
145
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 148. 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.
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 predefined 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
146
Chapter 7
Container Objects
records are also created with the acdbEntMake() function. The BLOCK? command 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 reference 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 Users 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;
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;
Symbol Tables
147
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 entitys 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 entitys linetype is BYLAYER.
void AcDbLayerTableRecord::setLinetypeObjectId(AcDbObjectId); AcDbObjectId AcDbLayerTableRecord::linetypeObjectId() const;
148
Chapter 7
Container Objects
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
149
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.
150
Chapter 7
Container Objects
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); // 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(); }
Symbol Tables
151
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 database. 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.
same effect as opening each entity in the group and setting its property directly.
152
Chapter 7
Container Objects
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. // 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);
Dictionaries
153
// 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 If it is not a line or descended from a line, close it and remove it from the group. Otherwise, just close it.
(!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(); }
154
Chapter 7
Container Objects
<MYSTYLE>
AcDbMline::setStyle( )
<name>
Layout Dictionary
The layout dictionary is a default dictionary within the named object dictionary 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.
Database
Block Table
Entity
Layout
Dictionaries
155
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 dictionary 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. // 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(); } }
156
Chapter 7
Container Objects
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.
Layouts
157
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 drawings 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 Users Guide.
AcDbLayout, AcDbPlotSettings, and AcDbPlotSettingsValidator are used to create and set attributes on layout objects. AcDbLayoutManager, AcApLayoutManager, and AcDbLayoutManagerReactor are used to manipulate
layout objects and to perform other layout-related tasks. The following sections provide an overview of some of these classes. For more information, see the ObjectARX Reference.
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 contains the plot device, media size, plot area, and plot rotation, as well as several 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 layouts actual geometry resides. If an AcDbBlockTableRecord represents a layout, then it contains the object ID of its associated AcDbLayout object.
158
Chapter 7
Container Objects
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.
Create layouts Delete layouts Rename layouts Copy and clone layouts 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 manager 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 AutoLISPprogrammers. 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.
Xrecords
159
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 application 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.
160
Chapter 7
Container Objects
For a description of hard and soft owners and pointers, see chapter 12, Deriving from AcDbObject.
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 program, see the samples directory.
Xrecords
161
void createXrecord() { AcDbDictionary *pNamedobj, *pDict; acdbHostApplicationServices()->workingDatabase() ->getNamedObjectsDictionary(pNamedobj, AcDb::kForWrite); // // // // if { 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); 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. (pNamedobj->getAt("ASDK_DICT", (AcDbObject*&) pDict, AcDb::kForWrite) == Acad::eKeyNotFound)
162
Chapter 7
Container Objects
// 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. // 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); }
163
164
Part 2
User Interfaces
MFC Topics 167 Selection Set, Entity, and Symbol Table Functions 201 Global Functions for Interacting with AutoCAD 247
165
166
MFC Topics
8
In this chapter
I Using MFC with ObjectARX
The Microsoft Foundation Class (MFC) library allows a developer to implement standard user interfaces quickly. ObjectARX applications can be created to take advantage of the MFC library. The ObjectARX environment provides a set of classes that a developer can use to create MFC-based user interfaces that behave and appear as the built-in Autodesk user interfaces. This section discusses how to use the MFC library as part of an ObjectARX application and how the AutoCAD built-in MFC system can be used to create dialogs that behave and operate like AutoCAD.
Applications
I ObjectARX Applications with
Support
I Using AdUi and AcUi with
VC++ AppWizard
167
NOTE Linking to the static MFC library is not supported because some of the
ObjectARX libraries (acedapi.lib, for example) link with the dynamic MFC library. A DLL cannot link to both the static and dynamic MFC libraries; linker warnings will result. 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.
In this example, the applications dialog class is HelloDlg, which is derived from CDialog. When you add this entry to the message map, you must also write a handler function for the message. Assume you have written a function 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; }
168
Chapter 8
MFC Topics
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 CDynLinkLibrary) 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 applications resources so that they will not conflict with other resources from AutoCAD or other ObjectARX applications.
169
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 previously saved. Calling AutoCAD API functions (or invoking AutoCAD commands) inside the dialog command handler that needs AutoCADs resources, such as acedGetFileD(), sets the resource back to AutoCAD before calling the functions. Restore your application resource afterwards. (Use acedGetAcadResourceInstance() to get AutoCADs 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 purposesit 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 modules 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 modules. A DLL should create one instance of this class and provide the implementation for the class.
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 modules resources will be selected. When the function returns, the default resources will be restored. A resource override can be selected in any of the following ways:
170
Chapter 8
MFC Topics
Use the default constructor (no arguments), or pass NULL (or 0) to the constructor. The DLLs resources will be selected. The default resources will be restored when the CAcModuleResourceOverride destructor is called. The DLL and default resource handles are tracked by the DLLs CAcExtensionModule. Pass a non-NULL handle to the constructor. The resources of the module associated with the given handle will be selected. The default resources will be restored when the CAcModuleResourceOverride destructor is called.
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 }
171
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 developers 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 projects 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 projects C++ source files must include adui.h, then acui.h, and the project should link acui15.lib and adui15.lib. AutoCAD invokes the librarys initialization routine, InitAcUiDLL(), which also handles the AdUi initialization (via an InitAdUiDLL() call); therefore your application need not reinitialize AcUi or AdUi.
Dialog resizing Dialog data persistency Tabbed dialogs Extensible tabbed dialogs Context-sensitive help and F1 help Dialog interaction with AutoCADs 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)
172
Chapter 8
MFC Topics
I I I I I
Combo boxes that display and allow the selection of many AutoCAD specific 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.
CHeaderCtrl CAdUiHeaderCtrl
CComboBox CAdUiComboBox CAcUiComboBox CAcUiAngleComboBox CAcUiMRUComboBox CAcUiArrowHeadComboBox CAcUiColorComboBox CAcUiLineWeightComboBox CAcUiPlotStyleNamesComboBox CAcUiPlotStyleTablesComboBox CAcUiNumericComboBox CAcUiStringComboBox CAcUiSymbolComboBox
AdUi Messaging
The AdUi library uses an internal messaging scheme to facilitate communication 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.
173
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
messaging system to inform a control that a tip window needs repainting. The control has the option of changing attributes of the tip windows device context and drawing the text.
windows (ToolTips and TextTips) and the AdUi message handling system. It also supports context help and F1 help in dialogs. It is the common base class for all dialogs except those based on the common file dialog.
174
Chapter 8
MFC Topics
CAdUiDialog Class
CAdUiDialog is a general purpose class that provides a
set of member functions allowing for resizable dialogs and data persistency. CAdUiFileDialog Class
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 support 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
set of member functions allowing for resizable dialogs and data persistency in AutoCAD. CAcUiTabMainDialog Class
CAcUiTabMainDialog represents the main container
175
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 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 programmer. It is a general-purpose alert dialog. CAcUiFileDialog Class
CAcUiFileDialog provides an AutoCAD-specific derivation of CAdUiFileDialog.
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.
176
Chapter 8
MFC Topics
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. 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.
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. Generally you should use one of the classes derived from the AutoCAD-specific class CAcUiComboBox, which adds a specific data type validation and persistency to the control. These are CAcUiStringEdit, CAcUiSymbolEdit, CAcUiNumericEdit, and CAcUiAngleEdit. CAcUiEdit Class
CAcUiEdit provides an AutoCAD-specific derivation of CAdUiEdit.
177
CAcUiAngleEdit Class
CAcUiAngleEdit is derived from CAcUiEdit and
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
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
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 supports AdUi messaging. The class can be used anywhere a CListBox can be used. Since it provides the additional container-side support for AdUi registered 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.
178
Chapter 8
MFC Topics
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 Class
CAdUiHeaderCtrl specializes CHeaderCtrl. Most often, CAdUiHeaderCtrl represents the subclassed header contained in a list control (CAdUiListCtrl). You do not
need to subclass the header control to get TextTip support for column headers in a list control (provided automatically in CAdUiListCtrl).
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 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 controls edit box. This allows for validation of angles specific to AutoCAD settings.
179
CAcUiNumericComboBox Class The CAcUiAngleComboBox constructor automatically creates a CAcUiNumericEdit to subclass the controls edit box. This allows for validation of numbers specific to AutoCAD settings. CAcUiStringComboBox Class The CAcUiStringComboBox constructor automatically creates a CAcUiStringEdit to subclass the controls edit box. Any input is acceptable. CAcUiSymbolComboBox Class The CAcUiSymbolComboBox constructor automatically creates a CAcUiSymbolEdit to subclass the controls edit box. Valid AutoCAD symbol names are acceptable input.
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 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
180
Chapter 8
MFC Topics
ByBlock and often have special significance. 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 automatically assigned a unique cargo value (which will be greater than the AutoCAD index for a userdefined arrowhead style). CAcUiColorComboBox Class
CAcUiColorComboBox specializes CAcUiMRUComboBox for
color selection. The control displays color swatches representing selections from AutoCADs palette. 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
181
includes None and optionally Default. Both metric and imperial values are displayed, depending 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 items AcDb::kLnWtxxx value. 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 controls 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.
182
Chapter 8
MFC Topics
AdUi messaging) CAdUiOwnerDrawButton automatically provides 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 displays a bitmap (the image is drawn transparently in the button). By default, objects of this class automatically resize to fit the associated bitmap image. Unlike MFCs CBitmapButton, only one bitmap is needed to define all of the button states (MFCs 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 buttons than regular pushbuttons.
183
CAcUiPickButton Class
CAcUiPickButton specializes CAcUiBitmapButton, which is a wrapper for the class CAdUiBitmapButton. CAcUiPickButton provides a button that displays a
sistency, as defined by the dialogs and controls in AcUi15.dll, means that storage 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 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. For more information, see CAdUiDialog::SetDialogName(). There are two types of dialog data persistency: out-of-the-box and developerdefined. 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.
184
Chapter 8
MFC Topics
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.
185
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(). 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"); }
186
Chapter 8
MFC Topics
// 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); } Then for the CMyTab1 class implementation: void CMyTab1::PostNcDestroy() // Override to delete added tab. { delete pTab1; pTab1 = NULL; CAcUiTabExtension::PostNcDestroy(); }
4 Add the following code to set up the AutoCAD command and acrxEntryPoint:
187
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) {
188
Chapter 8
MFC Topics
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; }
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();
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.
189
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.
190
Chapter 8
MFC Topics
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
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 CAcUiListBox CAcUiPickButton CAcUiPickButton CAcUiAngleEdit CAcUiNumericEdit CAcUiNumericEdit CAcUiNumericEdit m_ctrlRegAppComboBox; m_ctrlBlockListBox; m_ctrlPickButton; m_ctrlAngleButton; m_ctrlAngleEdit; m_ctrlXPtEdit; m_ctrlYPtEdit; 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();
191
5 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: Message handlers
Handler Function OnButtonAngle OnButtonPoint OnOk OnKillfocusComboRegapps OnKillfocusEditAngle OnKillfocusEditXpt OnKillfocusEditYpt OnKillfocusEditZpt Resource ID IDC_BUTTON_ANGLE IDC_BUTTON_POINT IDOK IDC_COMBO_REGAPPS IDC_EDIT_ANGLE IDC_EDIT_XPOINT IDC_EDIT_YPOINT IDC_EDIT_ZPOINT Message BN_CLICKED BN_CLICKED BN_CLICKED CBN_KILLFOCUS EN_KILLFOCUS EN_KILLFOCUS EN_KILLFOCUS EN_KILLFOCUS
192
Chapter 8
MFC Topics
1 First we add a few utility functions to convert, display, and validate the values. 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; } 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);
193
// 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. // 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(); }
194
Chapter 8
MFC Topics
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 // 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();
195
// 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(); } }
5 Now the edit box handlers are implemented. Basically we just want to convert 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); }
196
Chapter 8
MFC Topics
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 doesnt really make sense for an application, 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); }
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(); }
197
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 }, { { { { { { { { }; 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"); 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 },
198
Chapter 8
MFC Topics
// 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 }
199
200
9
In this chapter
I Selection Set and Entity Names I Handling Selection Sets I Entity Name and Data
The global functions described in this section handle selection sets, drawing entities, and symbol tables. See the AutoCAD Customization Guide for background information on these topics.
Functions
I Symbol Table Access
201
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 218.
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.
202
Chapter 9
int acedSSGet ( const char *str, const void *pt1, const void *pt2, const struct resbuf *entmask, ads_name ss);
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 NULL Description 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 All Box Crossing Crossing Polygon Duplicates OK Everything in aperture Fence Groups Implied Keyword callbacks Last Multiple
# :$ . :? A B C CP :D :E F G I :K L M
203
The next two arguments specify point values for the relevant options. (They should be NULL if they dont apply.) If the fourth argument, entmask, is not NULL, it points to the list of entity field values used in filtering. The fifth argument, ss, identifies the selection sets 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 dont 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);
204
Chapter 9
// 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); // 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.
205
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.
The entmask argument must be a result buffer list. Each buffer specifies a property to check and a value that constitutes a match; the buffers 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, attributesfollow, and flag values are short integers (resval.rint); extrusion vectors are three-dimensional points (resval.rpoint); and so forth.
206
Chapter 9
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 following 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 inRelational Tests on page 227 and Conditional Filtering on page 228. 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);
207
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 specified in a filter, entities that do not contain that group code are excluded from the selection sets that acedSSGet() returns.
208
Chapter 9
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; 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;
209
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);
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 relational 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; 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 operators. The conditional operators are also specified by -4 groups, but they must be paired.
210
Chapter 9
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 208. 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);
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; }
211
// 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 additional 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 selection 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 context, standard C compilers will correctly convert a plain integer.)
212
Chapter 9
213
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; // 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 functions 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 example, if scnf() simply displays transient vectors with acedGrVecs()), scnf() should return RTNONE. In this case, acedDragGen() ignores mt and doesnt transform the selection set.
214
Chapter 9
In the following example, the function sets the matrix to simply move (translate) 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 doesnt 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. }
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.
215
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; } } }
216
Chapter 9
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 database; 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 members to an existing set.
ads_name ss, e1, e2; // 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; }
217
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);
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.
218
Chapter 9
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 different 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(). 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 maintenance. See Using Handles in Extended Data on page 242.
219
X Y Z 1.0
M10 M11 M12 M13 M20 M21 M22 M23 0.0 0.0 0.0 1.0
X' = M00X + M01Y + M02Z + M03 Y' = M10X + M11Y + M12Z + M13 Z' = M20X + M21Y + M22Z + M23
220
Chapter 9
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 551.
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 transformation 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 matrixthe translation vector or displacementis 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; { 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]; }
221
The following code fragment shows how mcs2wcs() might be used in conjunction 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(). 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 matrixpassed as an array of four pointsthat uses the convention that a point is a row rather than a column. The transformation is described by the following matrix multiplication:
222
Chapter 9
X Y Z 1.0
M M M 01 11 21 M M M 02 12 22 M M M 03 13 23
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;
223
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 entitys 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 ename1 most deeply nested block that contains the selected entity RTENAME enameN outermost (inserted) block that contains the selected entity RTENAME ename2
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 entitys coordinates 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];
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:
224
Chapter 9
// 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 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.
225
Finally, they set the list of container entities (refstkres) to point to a single result buffer containing the entity name of the block SQUARE.
226
Chapter 9
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) // 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;
227
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.
I I I I
The current layer is 0 The current linetype is CONTINUOUS The current elevation is 0 Entity handles are disabled
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.
228
Chapter 9
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 definition 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 coordinate (in the range 1019). 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().
BYLAYER.
229
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. 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.
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.
230
Chapter 9
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 ); 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 subentity 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 single 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;
231
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; } 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;
232
Chapter 9
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. 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; }
233
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().
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 create 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; }
234
Chapter 9
// 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; } 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; }
235
236
Chapter 9
The argument to acdbEntUpd() can specify either a main entity or a subentity; 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.
237
Application name
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 Database handle 3D point 1003. Name of a layer associated with the extended data. 1005. Handles of entities in the drawing database. Under certain conditions, AutoCAD translates these. 1010. Three real values, contained in a point.
238
Chapter 9
1040. A real value. 1070. A 16-bit integer (signed or unsigned). 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). 1002. An extended data control string can be either { or }. 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. 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. 1011. Unlike a simple 3D point, the world space 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. 1012. A 3D point that is scaled, rotated, or mirrored along with the parent, but not stretched or moved. 1013. Also a 3D point that is rotated, or mirrored along with the parent, but not scaled, stretched, or moved. The world direction is a normalized displacement that always has a unit length. 1041. A real value that is scaled along with the parent entity. 1042. Also a real value that is scaled along with the parent.
Control string
Binary data
NOTE If a 1001 group appears within a list, it is treated as a string and does
not begin a new application group.
239
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 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); }
240
Chapter 9
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;
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.
241
242
Chapter 9
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 application-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 dictionary 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.
243
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:
I I
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 (330369) 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. In the case of object-specific state, xrecord objects are well suited for storing 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 implementation 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 registered 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.
244
Chapter 9
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>)
245
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. 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. } }
246
Chapter 9
10
In this chapter
I AutoCAD Queries and
Commands
I Getting User Input I Conversions I Character Type Handling I Coordinate System
Transformations
I Display Control I Tablet Calibration I Wild-Card Matching
247
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 follows, 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 argument 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 commands 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.
248
Chapter 10
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 functions 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 reference, 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.
249
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);
The following call starts the CIRCLE command, sets the center point as (5,5), and then pauses to let the user drag the circles 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);
250
Chapter 10
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 variable. 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 buffers memory use as it does with dynamically allocated buffers. 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
251
do this by calling the standard C library function free(), as shown in the following 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);
252
Chapter 10
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; 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 function 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.
253
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;
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);
254
Chapter 10
The following call looks for either the midpoint or endpoint of a line, or the center of an arc or circlewhichever 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.
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 RTSHORT 1 RTPOINT 0.0 0.0 NULL RTPOINT 30.0 30.0
255
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 configuration shown in the next figure.
rb RTSHORT 5 RTPOINT 0.5 0.0 RTPOINT 1.0 0.5 RTSHORT 2 RTPOINT 0.5 0.5 RTPOINT 1.0 1.0
NULL RTSHORT 3 RTPOINT 0.0 0.5 RTPOINT 0.5 1.0 RTSHORT 4 RTPOINT 0.0 0.0 RTPOINT 0.5 0.5
The current viewports 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 information. 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 coordinates (relative to an initial point). Unlike most ObjectARX functions, these 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 geometric utility functions.
ads_point pt1, pt2; ads_point base, endpt; ads_real rads, length; . . // Initialize pt1 and pt2. .
256
Chapter 10
// 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.
baseline
The next figure shows the point values that acedTextBox() returns for samples of vertical and aligned text. In both samples, the height of the letters
257
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 = -0.5,-20.0 pt1 = 0,0 alignment points entered where text was created
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 274.
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; }
258
Chapter 10
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]; 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; }
259
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 = rotatn srot = crot = entitem(textent, 50); = tent->resval.rreal; sin(rotatn); 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);
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().
260
Chapter 10
The following table briefly describes the user-input functions. User-input function summary
Function Name acedGetInt acedGetReal acedGetDist acedGetAngle Description Gets an integer value Gets a real value Gets a distance Gets an angle (oriented to 0 degrees as specified by the ANGBASE variable) Gets an angle (oriented to 0 degrees at the right) Gets a point Gets the corner of a rectangle Gets a keyword (see the description of keywords later in this section) Gets a string
acedGetString
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).
261
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 RTNORM RTERROR RTCAN RTNONE RTREJ RTKWORD Description User entered a valid value The function call failed User entered ESC User entered only ENTER AutoCAD rejected the request as invalid User entered a keyword or arbitrary text
The RTCAN case enables the user to cancel the applications request by pressing 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 function returns RTNONE or RTKWORD only if these values have been explicitly enabled by a prior acedInitGet() call.
262
Chapter 10
RSG_DASH
32
RSG_2D
64
RSG_OTHER
128
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);
263
This sequence asks the users 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); }
In this example, acedGetInt() returns the values shown in the following table, depending on the users input. Arbitrary user input
User Input 41 m Result acedGetInt() returns RTNORM and sets age to 41 acedGetInt() returns RTKWORD, and acedGetInput() returns Mine acedGetInt() returns RTKWORD, and acedGetInput() returns Yours acedGetInt() returns RTKWORD, and acedGetInput() returns twenty acedGetInt() returns RTKWORD, and acedGetInput() returns what???
twenty
what???
264
Chapter 10
-34.5
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 characters, if those are specified after a comma). Regardless of how the user enters 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");
265
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.
The fourth argument points to a function that does the entity transformation. See Transformation of Selection Sets for examples of dragsample() and acedDragGen().
266
Chapter 10
User Breaks
The user-input functions and the acedCommand(), acedCmd(), acedEntSel(), acedNEntSelP(), acedNEntSel(), acedDragGen(), and acedSSGet() functions return RTCAN if the AutoCAD user responds by pressing ESC . An external function should treat this response as a cancel request and return immediately. ObjectARX also provides a function, acedUsrBrk(), that explicitly checks whether the user pressed ESC . This function enables ObjectARX applications to check for a user interrupt. An application doesnt 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 userinput functions that can return RTCAN. In some cases, an application will want to ignore the users 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 . . . } }
The slightly modified code fragment that follows correctly handles an input of ESC without calling acedUsrBrk().
int test() { int i; for (;;) { if (acedGetInt("\nInput integer:", &i) != RTNORM) break; ... } }
267
The following sample changes the loop condition. This construction also works correctly.
int test() { int i; while (acedGetInt("\nInput integer:", &i) == RTNORM) { ... } }
A valid place to use acedUsrBrk() is in a lengthy operation. For example, code that steps through every entity in the drawing database can be time consuming and should call acedUsrBrk().
268
Chapter 10
The following example shows the scheme of a function called when the application receives a kInvkSubrMsg request. It returns a real value to AutoLISP.
int dofun() { ads_real x // Check the arguments and input conditions here. // Calculate the value of x. acedRetReal(x); return GOOD; }
NOTE An external function can make more than one call to value-return
functions upon a single kInvkSubrMsg request, but the AutoLISP function returns only the value passed it by the last value-return function invoked.
Conversions
The functions described in this section are utilities for converting data types and units.
String Conversions
The functions acdbRToS() and acdbAngToS() convert values used in AutoCAD to string values that can be used in output or as textual data. The acdbRToS() function converts a real value, and acdbAngToS() converts an angle. The format of the result string is controlled by the value of AutoCAD system variables: the units and precision are specified by LUNITS and LUPREC for real (linear) values and by AUNITS and AUPREC for angular values. For both functions, the DIMZIN dimensioning variable controls how leading and trailing zeros are written to the result string. The complementary functions acdbDisToF() and acdbAngToF() convert strings back into real (distance) values or angles. If passed a string generated by acdbRToS() or acdbAngToS(), acdbDisToF() and acdbAngToF() (respectively) are guaranteed to return a valid value.
Conversions
269
For example, the following fragment shows calls to acdbRToS(). (Error checking is not shown but should be included in applications.)
ads_real x = 17.5; char fmtval[12]; //Precision is the 3rd argument: 4 places in the first // call, 2 places in the others. acdbRToS(x, 1, 4, fmtval); // Mode 1 = scientific acutPrintf("Value formatted as %s\n", fmtval); acdbRToS(x, 2, 2, fmtval); // Mode 2 = decimal acutPrintf("Value formatted as %s\n", fmtval); acdbRToS(x, 3, 2, fmtval); // Mode 3 = engineering acutPrintf("Value formatted as %s\n", fmtval); acdbRToS(x, 4, 2, fmtval); // Mode 4 = architectural acutPrintf("Value formatted as %s\n", fmtval); acdbRToS(x, 5, 2, fmtval); // Mode 5 = fractional acutPrintf("Value formatted as %s\n", fmtval);
These calls (assuming that the DIMZIN variable equals 0) display the following values on the AutoCAD text screen. Value formatted as 1.7500E+01 Value formatted as 17.50 Value formatted as 1-5.50 Value formatted as 1-5 1/2 Value formatted as 17 1/2 When the UNITMODE system variable is set to 1, which specifies that units are displayed as entered, the string returned by acdbRToS() differs for engineering (mode equals 3), architectural (mode equals 4), and fractional (mode equals 5) units. For example, the first two lines of the preceding sample output would be the same, but the last three lines would appear as follows: Value formatted as 15.50 Value formatted as 15-1/2 Value formatted as 17-1/2
270
Chapter 10
The acdbDisToF() function complements acdbRToS(), so the following calls, which use the strings generated in the previous examples, all set result to the same value, 17.5. (Again, the examples do not show error checking.)
acdbDisToF("1.7500E+01", 1, &result); // 1 = scientific acdbDisToF("17.50", 2, &result); // 2 = decimal // Note the backslashes. Needed for inches. acdbDisToF("1'-5.50\"", 3, &result); // 3 = engineering acdbDisToF("1'-5 1/2\"", 4, &result); // 4 = architectural acdbDisToF("17 1/2", 5, &result); // 5 = fractional
The following fragment shows calls to acdbAngToS() that are similar to the previous acdbRToS() examples.
ads_real ang = 3.14159; char fmtval[12]; // Precision is the 3rd argument: 0 places in the first // call, 4 places in the next 3, 2 in the last. acdbAngToS(ang, 0, 0, fmtval); // Mode 0 = degrees acutPrintf("Angle formatted as %s\n", fmtval); acdbAngToS(ang, 1, 4, fmtval); // Mode 1 = deg/min/sec acutPrintf("Angle formatted as %s\n", fmtval); acdbAngToS(ang, 2, 4, fmtval); // Mode 2 = grads acutPrintf("Angle formatted as %s\n", fmtval); acdbAngToS(ang, 3, 4, fmtval); // Mode 3 = radians acutPrintf("Angle formatted as %s\n", fmtval); acdbAngToS(ang, 4, 2, fmtval); // Mode 4 = surveyor's acutPrintf("Angle formatted as %s\n", fmtval);
These calls (still assuming that DIMZIN equals 0) display the following values on the AutoCAD text screen. Angle formatted as 180 Angle formatted as 180d00 Angle formatted as 200.0000g Angle formatted as 3.1416r Angle formatted as W
Conversions
271
UNITMODE equals 0, the string returned can include spaces (for example, N 45d E); if UNITMODE equals 1, the string contains no spaces (for example, N45dE). The acdbAngToF() function complements acdbAngToS(), so the following calls all set the result argument to the same value, 3.14159. (This is rounded up to 3.1416 in the example that uses radians.)
acdbAngToF("180", 0, &result); // 0 = degrees acdbAngToF("180d0'0\"", 1, &result); // 1 = deg/min/sec acdbAngToF("200.0000g", 2, &result); // 2 = grads acdbAngToF("3.1416r", 3, &result); // 3 = radians acdbAngToF("W", 4, &result); // 4 = surveyor's
NOTE When you have a string that specifies an angle in degrees, minutes, and
seconds, you must use a backslash (\) to escape the seconds symbol () so that it doesnt appear to be the end of the string. The second of the preceding acdbAngToF() examples demonstrates this.
Real-World Units
The file acad.unt defines a variety of conversions between real-world units such as miles/kilometers, Fahrenheit/Celsius, and so on. The function acutCvUnit() takes a value expressed in one system of units and returns the equivalent value in another system. The two systems of units are specified by strings that must match one of the definitions in acad.unt. If the current drawing units are engineering or architectural (feet and inches), the following fragment converts a user-specified distance into meters.
ads_real eng_len, metric_len; char *prmpt = "Select a distance: "; if (acedGetDist(NULL, prmpt, &eng_len) != RTNORM) return BAD; acutCvUnit(eng_len, "inches", "meters", &metric_len);
The acutCvUnit() function will not convert incompatible units, such as inches into years.
272
Chapter 10
The following code fragment takes a character (the value in this example is arbitrary) and converts it to uppercase. The acutToUpper() function has no effect if the character is already uppercase.
int cc = 0x24; cc = acutToUpper(cc);
273
An integer code (restype == RTSHORT) that specifies the WCS, current UCS, or current DCS (of either the current viewport or paper space). An entity name (restype == RTENAME), as returned by one of the entity name or selection set functions. This specifies the ECS of the named entity. For planar entities, the ECS can differ from the WCS. If the ECS does not differ, conversion between ECS and WCS is an identity operation. A 3D extrusion vector (restype == RT3DPOINT), which is another method of specifying an entitys ECS. Extrusion vectors are always represented in world coordinates; an extrusion vector of (0,0,1) specifies the WCS itself.
The following are descriptions of the AutoCAD coordinate systems that can be specified by the from and to arguments. WCS World Coordinate System. The reference coordinate system. All other coordinate systems are defined relative to the WCS, which never changes. Values measured relative to the WCS are stable across changes to other coordinate systems. User Coordinate System. The working coordinate system. All points passed to AutoCAD commands, including those returned from AutoLISP routines and external functions, are points in the current UCS (unless the user precedes them with a * at the Command prompt). If you want your application to send coordinates in the WCS, ECS, or DCS to AutoCAD
UCS
274
Chapter 10
commands, you must first convert them to the UCS by calling acedTrans(). ECS Entity Coordinate System. Point values returned by acdbEntGet() are expressed in this coordinate system relative to the entity itself. Such points are useless until they are converted into the WCS, current UCS, or current DCS, according to the intended use of the entity. Conversely, points must be translated into an ECS before they are written to the database by means of acdbEntMod() or acdbEntMake(). Display Coordinate System. The coordinate system into which objects are transformed before they are displayed. The origin of the DCS is the point stored in the AutoCAD TARGET system variable, and its Z axis is the viewing direction. In other words, a viewport is always a plan view of its DCS. These coordinates can be used to determine where something appears to the AutoCAD user. When the from and to integer codes are 2 and 3, in either order, 2 indicates the DCS for the current model space viewport, and 3 indicates the DCS for paper space (PSDCS). When the 2 code is used with an integer code other than 3 (or another means of specifying the coordinate system), it is assumed to indicate the DCS of the current space (paper space or model space), and the other argument is assumed to indicate a coordinate system in the current space. PSDCS Paper Space DCS. This coordinate system can be transformed only to or from the DCS of the currently active model space viewport. This is essentially a 2D transformation, where the X and Y coordinates are always scaled and are offset if the disp argument is 0. The Z coordinate is scaled but is never translated; it can be used to find the scale factor between the two coordinate systems. The PSDCS (integer code 2) can be transformed only into the current model space viewport: if the from argument equals 3, the to argument must equal 2, and vice versa.
DCS
275
The following example translates a point from the WCS into the current UCS.
ads_point pt, result; struct resbuf fromrb, torb; pt[X] = 1.0; pt[Y] = 2.0; pt[Z] = 3.0; fromrb.restype = RTSHORT; fromrb.resval.rint = 0; // WCS torb.restype = RTSHORT; torb.resval.rint = 1; // UCS // disp == 0 indicates that pt is a point: acedTrans(pt, &fromrb, &torb, FALSE, result);
If the current UCS is rotated 90 degrees counterclockwise around the world Z axis, the call to acedTrans() sets the result to the point (2.0,-1.0,3.0). However, if acedTrans() is called as shown in the following example, the result is (-2.0,1.0,3.0).
acedTrans(pt, &torb, &fromrb, FALSE, result);
Display Control
ObjectARX has several functions for controlling the AutoCAD display, including both text and graphics screens.
Interactive Output
The basic output functions are acedPrompt(), which displays a message on the AutoCAD prompt line, and acutPrintf(), which displays text on the text screen. The acutPrintf() functions calling sequence is equivalent to the standard C library function printf(). It is provided as a separate function, because on some platforms the standard C printf() causes the output message to mangle the AutoCAD graphics screen. (Remember that the acdbFail() function also displays messages on the text screen.) The size of a string displayed by acedPrompt() should not exceed the length of the graphics screens prompt line; typically this is no more than 80 characters. The size of a string displayed by acutPrintf() must not exceed 132 characters, because this is the size of the string buffer used by the acutPrintf() function (133 bytes, with the last byte reserved for the null character).
276
Chapter 10
The acedMenuCmd() function provides control of the display of the graphics screen menu. The acedMenuCmd() function activates one of the submenus of the current menu. It takes a string argument, str, that consists of two parts, separated by an equal sign, in the form:
"section=submenu"
where section indicates the menu section and submenu indicates which submenu to activate within that section. For example, the following function call causes the OSNAP submenu defined in the current menu file to appear on the screen.
acedMenuCmd("S=OSNAP");
In a similar way, the following function call assigns the submenu MY-BUTTONS to the BUTTONS menu, and activates it.
acedMenuCmd("B=MY-BUTTONS");
In Release 12 and earlier versions of AutoCAD, you could assign any kind of menu to any other. For example, you could assign a SCREEN menu to a POP menu. With Release 13 and later versions of AutoCAD, you can assign menus to other menus on the Windows platform only if they are of the same type. A POP menu can be assigned only to another POP menu, and a SCREEN menu to another SCREEN menu. You can specify the menu in detail, because Windows loads partial menus. Calling acedMenuCmd() and passing P1=test.numeric assigns POP menu 12 to POP menu 2, assuming that the following menu file definitions exist.
***MENUGROUP=test ***POP12 **NUMERIC [Numeric Menu] [First item] [Second item]
The following call shows how to activate a drop-down menu and then display it.
acedMenuCmd("P1=NUMERIC");
The call to acedMenuCmd() assigns the submenu NUMERIC to drop-down menu 1 (in the upper-left corner of the graphics screen). See the AutoCAD Customization Guide for more information on custom menus.
Display Control
277
278
Chapter 10
The following sequence reverses damage to the graphics screen display caused by incorrect calls to acedGrText(), acedGrDraw(), or acedGrVecs().
acedGrText(-3, NULL, 0); acedRedraw(NULL, 0);
The arguments to acedGrText() have the following meanings: -3 restores standard text, NULL == no new text, and 0 == no highlighting. The arguments to acedRedraw() have the following meanings: NULL == all entities, and 0 == entire viewport.
Tablet Calibration
AutoCAD users with a digitizing tablet can calibrate the tablet by using the TABLET command. With the acedTablet() function, applications can manage calibrations by setting them directly and by saving calibration settings for future use. The function takes two arguments, list and result, each of which is a result-buffer list. The first result buffer in the first list is an integer code that must be 0 to retrieve the current calibration (in result), or 1 to set the calibration according to the remaining buffers in list. Calibrations are expressed as four 3D points (in addition to the code). The first three of these pointsrow1, row2, and row3are the three rows of the tablets transformation matrix. The fourth point is a vector, direction, that is normal to the plane of the tablets surface (expressed in WCS).
NOTE The TABMODE system variable controls whether Tablet mode is set to
On (1) or Off (0). You can control it by using acedSetVar(). The following code sequence retrieves the current tablet calibration, and saves it in calibr2. In this example, the user has used the TABLET command to calibrate the matrix, and Tablet mode is on.
struct resbuf *calibr1, *calibr2; struct resbuf varbuf, rb; // Retrieve the current calibration. calibr1 = acutBuildList(RTSHORT, 0, RTNONE); if (acedTablet(calibr1, &calibr2) != RTNORM) { acdbFail("Calibration not obtainable\n"); return BAD; }
Tablet Calibration
279
The code returned in the result argument, calibr2 in the example, is automatically set to 1. To reset the calibration to the values retrieved by the preceding example, you could use the following code:
if (acedTablet(calibr2, &calibr1) != RTNORM) { acdbFail("Couldn't reset calibration\n"); return BAD; } rb.restype = RTSHORT; rb.resval.rint = 1; acedSetVar("TABMODE", &rb); acedGetVar("TABMODE" &varbuf); if (varbuf.resval.rint == 0) { acdbFail("Couldn't set TABMODE\n"); return BAD; }
In this example, calibr1 now contains the result of the calibration. Because this is presumably identical to calibr2 (which was initialized by acedTablet()), you dont necessarily need this result. When you set a calibration, you can specify a NULL result, which causes acedTablet() to set the calibration silently.
if (acedTablet(calibr2, NULL) != RTNORM) { . . . }
The transformation matrix passed as row1, row2, and row3 is a 3x3 transformation matrix meant to transform a 2D point. The 2D point is expressed as a column vector in homogeneous coordinates (by appending 1.0 as the third element), so the transformation looks like this:
The calculation of a point is similar to the 3D case. AutoCAD transforms the point by using the following formulas:
X' = M00X + M01Y + M02 Y' = M10X + M11Y + M12 D' = M20X + M21Y + 1.0
To turn the resulting vector back into a 2D point, the first two components are divided by the third, the scale factor D' , yielding the point (X'/D',Y'/D') .
280
Chapter 10
For a projective transformation, which is the most general case, acedTablet() does the full calculation. But for affine and orthogonal transformations, M20 and M21 are both 0, so D' would be 1.0. The calculation of and the division are omitted; the resulting 2D point is simply (X',Y') . An affine transformation is a special, uniform case of a projective transformation. An orthogonal transformation is a special case of an affine transformation: not only are M20 and M21 0, but M00 = M11 and M10 = -M01 .
NOTE When you set a calibration, the result does not equal the list argument
if the direction in the list was not normalized; AutoCAD normalizes the direction vector before it returns it. Also, it ensures that the third element in the third column (row3[Z]) is equal to 1. This situation should not arise if you set the calibration using values retrieved from AutoCAD by means of acedTablet(). However, it can happen if your program calculates the transformation itself.
Wild-Card Matching
The acutWcMatch() function enables applications to compare a string to a wild-card pattern. This facility can be used when building a selection set (in conjunction with acedSSGet()) and when retrieving extended entity data by application name (in conjunction with acdbEntGetX()). The acutWcMatch() function compares a single string to a pattern, and returns RTNORM if the string matches the pattern, and RTERROR if it does not. The wild-card patterns are similar to the regular expressions used by many system and application programs. In the pattern, alphabetic characters and numerals are treated literally; brackets can be used to specify optional characters or a range of letters or digits; a question mark (?) matches a single character, and an asterisk (*) matches a sequence of characters; certain other special characters have meanings within the pattern. For a complete table of characters used in wild-card strings, see the description of acutWcMatch(). In the following examples, a string variable called matchme has been declared and initialized. The following call checks whether matchme begins with the five characters allof.
if (acutWcMatch(matchme, "allof*") == RTNORM) { . . . }
Wild-Card Matching
281
The following call illustrates the use of brackets in the pattern. In this case, acutWcMatch() returns RTNORM if matchme equals STR1, STR2, STR3, or STR8.
if (acutWcMatch(matchme, "STR[1-38]") == RTNORM) { . . . }
The pattern string can specify multiple patterns, separated by commas. The following call returns RTNORM if matchme equals ABC, if it begins with XYZ, or if it ends with 123.
if (acutWcMatch(matchme, "ABC,XYZ*,*123") == RTNORM) { . . . }
The acutWcMatchEx() function is similar to acutWcMatch(), but it has an additional argument to allow it to ignore case.
bool acutWcMatchEx( const char * string, const char * pattern, bool ignoreCase);
282
Chapter 10
Part 3
Defining New Classes
Deriving a Custom ObjectARX Class 285 Deriving from AcDbObject 293 Deriving from AcDbEntity 353
283
284
11
In this chapter
I Custom Class Derivation I Runtime Class Identification I Class Declaration Macro I Class Implementation Macros I Class Initialization Function
This section describes how to orrganize your code and use the ObjectARX macros to simplify the task of deriving a custom ObjectARX class. The macros allow a custom class to participate in the AcRxObject runtime type identification mechanism. If you do not need to distinguish your custom class at runtime, you can use standard C++ derivation style to create the new class.
285
AcDbEntity AcDbCurve AcDbObjectReactor AcDbDatabaseReactor AcDbEntityReactor All AcDbXxxDimension classes AcTransactionReactor AcEdJig AcEditorReactor
286
Chapter 11
I I I I I I I I
Classes not appearing in either of the preceding lists can theoretically be derived from, although doing so is not explicitly supported.
a static member function that returns the class descriptor object of a particular (known) class. cast(), a static member function that returns an object of the specified type, or NULL if the object is not of the required class (or a derived class). isKindOf() returns whether an object belongs to the specified class (or a derived class). isA() returns the class descriptor object of an object whose class is unknown.
When you want to know what class an object is, use AcRxObject::isA(). This function returns the class descriptor object (an instance of AcRxClass) for a database object. Its signature is
AcRxClass* isA() const;
287
When you already know what class an object is, you can use the desc() function to obtain the class descriptor object:
static AcRxClass* desc();
The following example looks for instances of AcDbEllipse or any class derived from it, using isKindOf() and the AcDbEllipse::desc() static member function:
AcDbEntity* curEntity = somehowGetAndOpenAnEntity(); if (curEntity->isKindOf(AcDbEllipse::desc())) { // Got some kind of AcDbEllipse instance. }
This example shows another way of looking for instances of AcDbEllipse, or any class derived from it, using the AcDbEllipse::cast() static member function:
AcDbEllipse* ellipseEntity = AcDbEllipse::cast(curEntity); if (ellipseEntity != NULL) { // Got some kind of AcDbEllipse instance. }
The following example looks for instances of AcDbEllipse, but not instances of classes derived from AcDbEllipse, using isA() and AcDbEllipse::desc():
if (curEntity->isA() == AcDbEllipse::desc()) { // Got an AcDbEllipse, no more, no less.
For AsdkPoly, the following line expands to a single long line of code.
ACRX_DECLARE_MEMBERS(AsdkPoly);
When reformatted to multiple lines for clarity, the line looks like this:
288
Chapter 11
virtual AcRxClass* isA() const; static AcRxClass* gpDesc; static AcRxClass* desc(); static AsdkPoly* cast(const AcRxObject* inPtr) { return ((inPtr == 0) || !inPtr->isKindOf(AsdkPoly::desc())) ? 0 : (AsdkPoly*)inPtr; }; static void rxInit();
The static rxInit() function and the static gpDesc pointer declared by this macro are used to implement the isA(), desc(), and cast() functions.
Use for abstract classes and any other classes that should not be instantiated.
I ACRX_CONS_DEFINE_MEMBERS(CLASS_NAME, PARENT_CLASS, VERNO)
Use for transient classes that can be instantiated but are not written to file.
I ACRX_DXF_DEFINE_MEMBERS(CLASS_NAME, PARENT_CLASS, DWG_VERSION,\
Use for classes that can be written to, or read from, DWG and DXF files. Each of these macros defines the following:
I I I I
Class descriptor object Class initialization function (see Class Initialization Function) A desc() function for this class A virtual isA() function (inherited from AcRxObject) that this custom class will override
For AsdkPoly, the following line expands to a very long single line of code:
ACRX_DXF_DEFINE_MEMBERS(AsdkPoly, AcDbCurve, AcDb::kDHL_CURRENT,\ AcDb::kMReleaseCurrent, 0, POLYGON, /*MSG0*/"AutoCAD");
289
When reformatted to multiple lines for clarity, the line looks like this:
AcRxClass* AsdkPoly::desc() { if (AsdkPoly::gpDesc != 0) return AsdkPoly::gpDesc; return AsdkPoly::gpDesc = (AcRxClass*)((AcRxDictionary*)acrxSysRegistry()-> at("ClassDictionary"))->at("AsdkPoly"); } AcRxClass* AsdkPoly::isA() const { return AsdkPoly::desc(); } AcRxClass* AsdkPoly::gpDesc = 0; static AcRxObject * makeAsdkPoly() { return new AsdkPoly(); } void AsdkPoly::rxInit() { if (AsdkPoly::gpDesc != 0) return; AsdkPoly::gpDesc = newAcRxClass("AsdkPoly", "AsdkCurve", AcDb::kDHL_CURRENT, AcDb::kMReleaseCurrent, 0, &makeAsdkPoly, "POLYGON", "\"AutoCAD\""); };
When expanded, the semicolon (;) at the end of the macro call line moves to just after the closing brace (}) for a function definition. Therefore, this semicolon is not required for this macro call line. If you want to write your own rxInit() function, use the ACRX_DEFINE_MEMBERS() macro by itself, which defines desc(), cast(), and isA() for your class but does not define the rxInit() function. This macro also does not create the associated AcRxClass object, which is the responsibility of the rxInit() function.
290
Chapter 11
Registers the custom class Creates the class descriptor object Places the class descriptor object in the class dictionary
291
292
12
In this chapter
I Overriding AcDbObject Virtual
Functions
the four types of object references (hard and soft owners, and hard and soft pointers), and the undo and redo operations. This chapter also discusses mechanisms for object versioning. The descriptions in this chapter assume you are familiar with the material described in chapter 5, Database Objects, and chapter 11,Deriving a Custom ObjectARX Class.
I Implementing Member
Functions
I Filing Objects to DWG and
DXF Files
I Object References I Ownership References I Pointer References I Long Transaction Issues for
Custom Objects
I Purge I Undo and Redo I subErase, subOpen, subClose,
and subCancel
I Example of a Custom Object
Class
I Object Version Support
293
294
Chapter 12
295
virtual void modifyUndone(const AcDbObject* dbObj); virtual void modifiedXData(const AcDbObject* dbObj); virtual void unappended(const AcDbObject* dbObj); virtual void objectClosed(const AcDbObjectId objId); virtual void modifiedGraphics(const AcDbEntity* dbEnt);
296
Chapter 12
virtual Acad::ErrorStatus getPlane(AcGePlane&, AcDb::Planarity&) const; virtual Acad::ErrorStatus getStartParam(double&) const; virtual Acad::ErrorStatus getEndParam(double&) const; virtual Acad::ErrorStatus getStartPoint(AcGePoint3d&) const; virtual Acad::ErrorStatus getEndPoint(AcGePoint3d&) const; virtual Acad::ErrorStatus getPointAtParam(double, AcGePoint3d&) const; virtual Acad::ErrorStatus getParamAtPoint(const AcGePoint3d&, double&)const; virtual Acad::ErrorStatus getDistAtParam(double param, double& dist) const; virtual Acad::ErrorStatus getParamAtDist(double dist, double& param) const; virtual Acad::ErrorStatus getDistAtPoint(const AcGePoint3d&, double&) const; virtual Acad::ErrorStatus getPointAtDist(double, AcGePoint3d&) const; virtual Acad::ErrorStatus getFirstDeriv( double param, AcGeVector3d& firstDeriv) const; virtual Acad::ErrorStatus getFirstDeriv( const AcGePoint3d&, AcGeVector3d& firstDeriv) const; virtual Acad::ErrorStatus getSecondDeriv( double param, AcGeVector3d& secDeriv) const; virtual Acad::ErrorStatus getSecondDeriv( const AcGePoint3d&, AcGeVector3d& secDeriv) const; virtual Acad::ErrorStatus getClosestPointTo( const AcGePoint3d& givenPnt, AcGePoint3d& pointOnCurve, Adesk::Boolean extend = Adesk::kFalse) const;
297
virtual Acad::ErrorStatus getClosestPointTo( const AcGePoint3d& givenPnt, const AcGeVector3d& normal, AcGePoint3d& pointOnCurve, Adesk::Boolean extend = Adesk::kFalse) const; virtual Acad::ErrorStatus getOrthoProjectedCurve( const AcGePlane&, AcDbCurve*& projCrv) const; virtual Acad::ErrorStatus getProjectedCurve( const AcGePlane&, const AcGeVector3d& projDir, AcDbCurve*& projCrv) const; virtual Acad::ErrorStatus getOffsetCurves( double offsetDist, AcDbVoidPtrArray& offsetCurves) const; virtual Acad::ErrorStatus getSpline(AcDbSpline*& spline) const; virtual Acad::ErrorStatus getSplitCurves( const AcGeDoubleArray& params, AcDbVoidPtrArray& curveSegments) const; virtual Acad::ErrorStatus getSplitCurves( const AcGePoint3dArray& points, AcDbVoidPtrArray& curveSegments) const; virtual Acad::ErrorStatus extend(double newParam); virtual Acad::ErrorStatus extend( Adesk::Boolean extendStart, const AcGePoint3d& toPoint); virtual Acad::ErrorStatus getArea(double&) const;
298
Chapter 12
of the modification that is occurring in the member function. (See Undo and Redo on page 327.) Even if you dont desire undo recording, it is essential to call
assertWriteEnabled(kFalse, kFalse);
This call marks the object for incremental save. Failure to follow this instruction can result in corrupted drawings. The following table shows the three possible states for opening an object (read, write, notify) and indicates which assert calls succeed for each state. If the object is not open in one of the allowed states for the assert function call, the function does not return. AutoCAD exits, and the user is prompted to save the drawing.
Object open for Read Write Notify
assertReadEnabled()
returns
returns
returns
assertWriteEnabled()
aborts
returns
aborts
assertNotifyEnabled()
returns
returns
returns
299
Acad::ErrorStatus AcDbObject::dxfOut( AcDbDxfFiler* filer, Adesk::Boolean allXdFlag, Adesk::uchar* regAppTable) const); Acad::ErrorStatus AcDbObject::dxfIn(AcDbDxfFiler* filer);
Each function takes a pointer to a filer as its primary argument. An AcDbObject writes data to and reads data from a filer. The FilerType enum allows you to check the filer type. Filer types are
I kFileFiler I kCopyFiler I kUndoFiler I kBagFiler I I I I I
The dwgOut() and dwgIn() functions in turn call dwgOutFields() and dwgInFields(), respectively, and the DXF filing functions call an analogous set of functions for DXF. If you are deriving a custom class from AcDbObject, you will need to override the following virtual functions, which are used for persistent storage of objects as well as for copying and undo operations:
I I I I
dwgOut() Function
The dwgOut() function, which calls dwgOutFields(), is invoked by the following commands and conditions:
I SAVE I I I I I I
I SAVEAS
(uses kFileFiler) (uses kFileFiler) WBLOCK (uses kWblockCloneFiler and kIdXlateFiler) INSERT, XREF (use kDeepCloneFiler and kIdXlateFiler) COPY (uses same filers as INSERT; a copy requires writing out an objects state and then reading it back in to an object of the same class) PURGE (uses a kPurgeFiler) Any time an object is paged out (uses a kPageFiler) Any time an object is modified (for undo recording; uses a kUndoFiler)
300
Chapter 12
dwgIn() Function
The dwgIn() function, which calls dwgInFields(), is invoked by the following commands and conditions:
I OPEN I I I
I UNDO
(uses a kFileFiler) (uses a kUndoFiler) INSERT, COPY, XREF (use a kDeepCloneFiler and a kIdXlateFiler) WBLOCK (uses kWblockCloneFiler and kIdXlateFiler) Any time an object is paged in (uses a kPageFiler)
dxfOut() Function
The dxfOut() function, which calls dxfOutFields(), is invoked by the following commands and functions:
I I I I
dxfIn() Function
The dxfIn() function, which calls dxfInFields(), is invoked by the following commands and functions:
I OPEN I INSERT I acdbEntMod()
or acdbEntMake()
Error Checking
When you are writing to a filer, you do not need to perform intermediate error checking. Once an error condition is encountered, the filer returns the same error status to all write requests until the status is cleared by the filer. Every filer class has a getFilerStatus() function that returns the filer status. When you are reading in a file, you may want to check the filer status if you rely on success or failure for your next step.
301
The next thing your derived class must do is to call the same function (for example, dwgOutFields()) on the parent class. This process is referred to as super messaging. The following is an example:
AcDbDerivedClass::dwgOutFields( ... ); { assertReadEnabled() myParent::dwgOutFields(); // Perform class-specific operations after super-messaging. }
If you forget to call the corresponding message of the parent class, youll receive a runtime error. After super-messaging, you write or read fields. You may improve performance by checking the filer type. For example, if the filer type is kIdXlateFiler and your class doesnt define any reference connections, you can simply return. With DWG files, you need to write and read calls in the same order. If calls are mismatched, derived classes will be confused. If you have any variablesized data, put the count first.
NOTE If your class has integer data members, you need to use the read and
write functions that explicitly state the integer size (for example, writeInt32).
302
Chapter 12
303
switch (version) { case 1: { AcGePoint3d center; filer->readPoint3d(¢er); AcGePoint3d startPoint; filer->readPoint3d(&startPoint); filer->readInt32(&mNumSides); filer->readVector3d(&mPlaneNormal); acutDelString(mpName); filer->readString(&mpName); filer->readHardPointerId(&mTextStyle); //convert data from old format acdbWcs2Ecs(asDblArray(center),asDblArray(center), asDblArray(mPlaneNormal),Adesk::kFalse); mCenter.set(center.x,center.y); mElevation = center.z; acdbWcs2Ecs(asDblArray(startPoint),asDblArray(startPoint), asDblArray(mPlaneNormal),Adesk::kFalse); mStartPoint.set(startPoint.x,startPoint.y); assert(mElevation == startPoint.z); break; } case 2: filer->readPoint2d(&mCenter); filer->readPoint2d(&mStartPoint); filer->readInt32(&mNumSides); filer->readVector3d(&mPlaneNormal); acutDelString(mpName); filer->readString(&mpName); filer->readHardPointerId(&mTextStyle); filer->readDouble(&mElevation); break; default: assert(false); } return filer->filerStatus(); }
304
Chapter 12
your own DXF representation, the first data group you write out and read in must be a subclass data marker. This marker consists of a 100 group code followed by a string that is the current class name. Then, you select group codes from the following table that correspond to the data types of each data field you are writing out. DXF group code ranges for object representation
From 1 6 10 38 60 90 100 102 140 170 210 270 280 300 310 320 330 340 350 360 To 4 9 17 59 79 99 100 102 149 179 219 279 289 309 319 329 339 349 359 369 Data Type Text Text Point or vector (3 reals) Real 16-bit integer 32-bit integer Subclass data marker Text Real 16-bit integer 3 reals 16-bit integer 8-bit integer Text Binary chunk Handle Soft pointer ID Hard pointer ID Soft owner ID Hard owner ID
305
An object ID translates to an rlname. For example, an AcDbObjectId corresponds to an ads_name, which is represented in the resval union as rlname.
Order Dependence
With DXF, at the class authors discretion, data groups can be presented in arbitrary order, or optionally omitted. Some classes support order independence of data groups, while others do not. If you allow order independence, then your dxfInFields() function must use a switch statement to choose an action based on the group code value. Order independence is usually appropriate for objects with a fixed and predictable set of fields. Objects with variable-length arrays or structures tend to be order-dependent when they are filed out and in.
306
Chapter 12
307
case AcDb::kDxfXCoord + 1: if (version == 1) sp3d = asPnt3d(rb.resval.rpoint); else sp2d = asPnt2d(rb.resval.rpoint); fieldsFlags |= 0x2; break; case AcDb::kDxfInt32: numSides = rb.resval.rlong; fieldsFlags |= 0x4; break; case AcDb::kDxfNormalX: planeNormal = asVec3d(rb.resval.rpoint); fieldsFlags |= 0x8; break; case AcDb::kDxfText: acutUpdString(rb.resval.rstring,pName); fieldsFlags |= 0x11; break; case AcDb::kDxfHardPointerId: acdbGetObjectId(textStyle, rb.resval.rlname); fieldsFlags |= 0x12; break; case AcDb::kDxfReal: if (version == 2) { fieldsFlags |= 0x10; elevation = rb.resval.rreal; break; } //fall through intentional default: // An unrecognized group. Push it back so that // the subclass can read it again. filer->pushBackItem(); es = Acad::eEndOfFile; break; } } // // // // // if At this point, the es variable must contain eEndOfFile, either from readResBuf() or from pushbackBackItem(). If not, it indicates that an error happened and we should return immediately. (es != Acad::eEndOfFile) return Acad::eInvalidResBuf;
308
Chapter 12
// Now check to be sure all necessary group codes were // present. // // Mandatory fields: // - center // - start point // - normal // - number of sides // - elevation (if version > 1) short required[] = {AcDb::kDxfXCoord, AcDb::kDxfXCoord+1, AcDb::kDxfInt32, AcDb::kDxfNormalX, AcDb::kDxfReal}; for (short i = 0; i < (version>1?4:3); i++) { if (!fieldsFlags & 0x1) { filer->setError(Acad::eMissingDxfField, "\nMissing DXF group code: %d", 2, required[i]); return Acad::eMissingDxfField; } else fieldsFlags >>= 1; } mPlaneNormal = planeNormal; mNumSides = numSides; mTextStyle = textStyle; setName(pName); acutDelString(pName); if (version==1) { //convert data from old format acdbWcs2Ecs(asDblArray(cen3d),asDblArray(cen3d), asDblArray(planeNormal),Adesk::kFalse); mCenter.set(cen3d.x,cen3d.y); mElevation = cen3d.z; acdbWcs2Ecs(asDblArray(sp3d),asDblArray(sp3d), asDblArray(planeNormal),Adesk::kFalse); mStartPoint.set(sp3d.x,sp3d.y); assert(mElevation == sp3d.z); } else { mCenter = cen2d; mStartPoint = sp2d; mElevation = elevation; } return es; }
The complete code for the AsdkPoly application-defined class can be found in the samples directory.
309
310
Chapter 12
filer->readItem(&rb); if (rb.restype != AcDb::kDxfText) throw AcDb::kDxfText; setName(rb.resval.rstring); filer->readItem(&rb); if (rb.restype != kDxfHardPointerId) throw AcDb::kDxfHardPointerId; acdbGetObjectId(mTextStyle, rb.resval.rlname); // Convert data from old format. acdbWcs2Ecs(asDblArray(cent),asDblArray(cent), asDblArray(mPlaneNormal),Adesk::kFalse); mCenter.set(cent.x,cent.y); mElevation = cent.z; acdbWcs2Ecs(asDblArray(sp),asDblArray(sp), asDblArray(mPlaneNormal),Adesk::kFalse); mStartPoint.set(sp.x,sp.y); assert(mElevation == sp.z); } else if (version == 2) { filer->readItem(&rb); if (rb.restype != AcDb::kDxfXCoord) throw AcDb::kDxfXCoord; mCenter = asPnt2d(rb.resval.rpoint); filer->readItem(&rb); if (rb.restype != AcDb::kDxfXCoord + 1) throw AcDb::kDxfXCoord + 1; mStartPoint = asPnt2d(rb.resval.rpoint); filer->readItem(&rb); if (rb.restype != AcDb::kDxfInt32) throw AcDb::kDxfInt32 mNumSides = rb.resval.rlong; filer->readItem(&rb); if (rb.restype != AcDb::kDxfNormalX) throw AcDb::kDxfNormalX; mPlaneNormal = asVec3d(rb.resval.rpoint); filer->readItem(&rb); if (rb.restype != AcDb::kDxfText) throw AcDb::kDxfText setName(rb.resval.rstring); filer->readItem(&rb); if (rb.restype != AcDb::kDxfHardPointerId) throw AcDb::kDxfHardPointerId; acdbGetObjectId(mTextStyle, rb.resval.rlname);
311
filer->readItem(&rb); if (rb.restype != AcDb::kDxfReal) throw AcDb::kDxfReal; mElevation = rb.resval.rreal; } else assert(false); } catch (AcDb::DxfCode code) { filer->pushBackItem(); filer->setError(Acad::eInvalidDxfCode, "\nError: expected group code %d", code); return filer->filerStatus(); } }
Object References
An object reference can be either hard or soft, and it can be either an ownership reference or a pointer reference. The hard or soft distinction indicates whether the referenced object is essential to the existence of the object that refers to it. A hard reference indicates that an object depends on the referenced object for its survival. A soft reference indicates that an object has some kind of relationship to the referenced object, but it is not an essential one. An ownership reference dictates how objects are filed. If one object owns another, then whenever the first object is filed out, it takes the owned object with it. Because an object can have only one owner, ownership references are used for nonredundant writing out of the database. In contrast, pointer references are used to express any arbitrary reference between AcDb objects. Pointer references are used for complete (redundant) writing out of the database. For example, in the following figure, the double lines indicate ownership references. If you follow the double lines, you touch every object in this small database only once. If you also follow the single lines, which represent pointer references, you touch some objects more than once, because multiple objects can point to the same object. To obtain the full definition of the
312
Chapter 12
AcDbLine object, you would need to follow all the hard references, both
ownership and pointer (that is, both the single and double solid lines).
Database
Block Table
Linetype
Layer
Dashed
(myLayer)
AcDbLine
Ownership References
If you are creating your own ownership hierarchy, you need to set up the connection between the owner and the owned object. An object cannot have multiple owners. To create an ownership connection 1 Specify that the owner owns the object. 2 Specify that the object belongs to the owner. The AcDbObject protocol always specifies the link from the owner to the owned object and the backward link from the object to its owner.
Ownership References
313
The following code illustrates setting up the two-way ownership link between an owner and its contents:
// Uses the OwnerDemo class defined in the next example // (see "ObjectARX Example," below). // // Sets pOwner to be the owner of pOwned. // void makeOwner(OwnerDemo* pOwner, AcDbObject* pOwned) { // First let pOwner know it is the owner. This // establishes ownership for filing persistence. // pOwner->setIdData(pOwned->ojectId()); // Now set up the backpointer so that the owned // object knows who its owner is. // pOwned->setOwnerId(pOwner->objectId()); }
Most commonly used container class members establish the two-way link automatically. For example, the following function call sets the block table record as the owner of the entity, and also adds the entity to the block table records list of owned entities.
blockTableRecord->appendAcDbEntity( ...);
Similarly, the AcDbDictionary::setAt() function and the AcDbSymbolTable::add() function set up two-way links between the owner and its objects in one step. If you are directly manipulating objects using entmod() or entmake() in AutoLISP, you first add the owned object to the database using entmake(), then associate its ads_name or entity name with the appropriate DXF group code in the owner object representation.
Uses of Ownership
When an object is written to a DXF or DWG file, all objects owned by this object are also written out. The deep clone operation also recursively copies every object owned by the cloned object. See chapter 18, Deep Cloning. A hard ownership relationship protects the owned object from purge.
314
Chapter 12
Types of Ownership
Owners can be either hard or soft owners of their objects.
Hard Ownership
The following are three examples of hard ownership:
I I I
A database object is a hard owner of its extension dictionary. The block table is a hard owner of the model space and paper space block table records (but not the other block table records). Extension dictionaries are hard owners of their elements.
Soft Ownership
A soft ownership ID (of type AcDbSoftOwnershipId) does not protect the owned object from purge. The following are examples of soft ownership:
I
In most cases, symbol tables are soft owners of their elements (exceptions include the block *MODEL_SPACE, *PAPER_SPACE, *PAPER_SPACE0, and layer 0; for these elements, the symbol table maintains a hard reference). Dictionaries are soft owners of their entries (but you can flag a dictionary to be a hard owner of its entries).
Ownership References
315
ObjectARX Example
// Class declarations // class AsdkOwnerDemo : public AcDbObject // This is a custom object class to demonstrate what is // necessary to create ownership trees. // // To keep it simple, this class has two data members: a // simple integer to represent normal data, and a hard // ownership ID data member to hold the object ID of an owned // object. // // Get and set functions are provided for both data members. // { public: ACRX_DECLARE_MEMBERS(AsdkOwnerDemo); AsdkOwnerDemo(): mIntval(0) {}; AsdkOwnerDemo(const Adesk::Int16& val): mIntval(val) {}; Adesk::Int16 intData(); Acad::ErrorStatus setIntData(const Adesk::Int16&); AcDbHardOwnershipId idData(); Acad::ErrorStatus setIdData(const AcDbHardOwnershipId&); Acad::ErrorStatus dwgInFields (AcDbDwgFiler*); Acad::ErrorStatus dwgOutFields(AcDbDwgFiler*) const; Acad::ErrorStatus dxfInFields (AcDbDxfFiler*); Acad::ErrorStatus dxfOutFields(AcDbDxfFiler*) const; private: Adesk::Int16 mIntval; AcDbHardOwnershipId mObjId; }; ACRX_DXF_DEFINE_MEMBERS(AsdkOwnerDemo, AcDbObject, AcDb::kDHL_CURRENT, AcDb::kMReleaseCurrent, 0, ASDKOWNERDEMO, OWNERSHIP); // Gets the value of the integer data member. // Adesk::Int16 AsdkOwnerDemo::intData() { assertReadEnabled(); return mIntval; }
316
Chapter 12
// Sets the value of the integer data member. // Acad::ErrorStatus AsdkOwnerDemo::setIntData(const Adesk::Int16& val) { assertWriteEnabled(); mIntval = val; return Acad::eOk; } // Returns a copy of the ownership ID data member. // AcDbHardOwnershipId AsdkOwnerDemo::idData() { assertReadEnabled(); return mObjId; } // Sets the value of the ownership ID data member. // Acad::ErrorStatus AsdkOwnerDemo::setIdData(const AcDbHardOwnershipId& ownedId) { if (ownedId.asOldId() == 0L) { return Acad::eInvalidInput; } assertWriteEnabled(); mObjId = ownedId; // Now set the backpointer. A transaction is used for // opening the object, so if the object is already // open it won't prevent this setting from taking place. // AcDbObject *pObj; AcTransaction *pTrans = actrTransactionManager->startTransaction(); pTrans->getObject(pObj, ownedId, AcDb::kForWrite); pObj->setOwnerId(objectId()); actrTransactionManager->endTransaction(); return Acad::eOk; } // Files data in from a DWG file. // Acad::ErrorStatus AsdkOwnerDemo::dwgInFields(AcDbDwgFiler* filer) { assertWriteEnabled(); AcDbObject::dwgInFields(filer);
Ownership References
317
// // // // if
For wblock filing we wrote out our owner as a hard pointer Id so now we need to read it in to keep things in sync. (filer->filerType() == AcDb::kWblockCloneFiler) { AcDbHardPointerId id; filer->readItem(&id);
} filer->readItem(&mIntval); filer->readItem(&mObjId); return filer->filerStatus(); } // Files data out to a DWG file. // Acad::ErrorStatus AsdkOwnerDemo::dwgOutFields(AcDbDwgFiler* filer) const { assertReadEnabled(); AcDbObject::dwgOutFields(filer); // // // // // // if Since objects of this class will be in the Named Objects Dictionary tree and may be hard referenced by some other object, to support wblock we need to file out our owner as a hard pointer Id so that it will be added to the list of objects to be wblocked (filer->filerType() == AcDb::kWblockCloneFiler) filer->writeHardPointerId((AcDbHardPointerId)ownerId());
filer->writeItem(mIntval); filer->writeItem(mObjId); return filer->filerStatus(); } // Files data in from a DXF file. // Acad::ErrorStatus AsdkOwnerDemo::dxfInFields(AcDbDxfFiler* filer) { assertWriteEnabled(); Acad::ErrorStatus es; if ((es = AcDbObject::dxfInFields(filer)) != Acad::eOk) { return es; } // Check if we're at the right subclass data marker. // if (!filer->atSubclassData("AsdkOwnerDemo")) { return Acad::eBadDxfSequence; }
318
Chapter 12
struct resbuf inbuf; while (es == Acad::eOk) { if ((es = filer->readItem(&inbuf)) == Acad::eOk) { if (inbuf.restype == AcDb::kDxfInt16) { mIntval = inbuf.resval.rint; } else if (inbuf.restype == AcDb::kDxfHardOwnershipId) { acdbGetObjectId(mObjId, inbuf.resval.rlname); } } } return filer->filerStatus(); } // Files data out to a DXF file. // Acad::ErrorStatus AsdkOwnerDemo::dxfOutFields(AcDbDxfFiler* filer) const { assertReadEnabled(); AcDbObject::dxfOutFields(filer); filer->writeItem(AcDb::kDxfSubclass, "AsdkOwnerDemo"); filer->writeItem(AcDb::kDxfInt16, mIntval); // Null object IDs are invalid: don't write them out. // if (mObjId.asOldId() != 0L) { filer->writeItem(AcDb::kDxfHardOwnershipId, mObjId); } return filer->filerStatus(); } // Creates an AsdkOwnerDemo object (pObjC) and adds data to // it. Then, AsdkOwnerDemo pObjC is created and set to be // the owner of pObjC. Next, AsdkOwnerDemo pObjA is created // and set to own pObjB. Finally, pObjA is added to a // dictionary in the named object dictionary. Technically, // we could just add pObjA to the named object dictionary // itself, but that's not appropriate because it would clutter // up the named object dictionary. // void createObjs() { AcDbObjectId objIdA, objIdB, objIdC; AcDbDictionary *pNamedobj; AcDbDictionary *pDict = NULL; AcDbDatabase *pCurDwg = acdbHostApplicationServices()->workingDatabase();
Ownership References
319
// Create object C with a dummy integer data value of 3. // AsdkOwnerDemo *pObjC = new AsdkOwnerDemo(3); // Append object C to database without setting an owner. // pCurDwg->addAcDbObject(objIdC, pObjC); pObjC->close(); // Create object B with a dummy integer data value of 2. // AsdkOwnerDemo *pObjB = new AsdkOwnerDemo(2); // Append object B to the database without setting an owner. // pCurDwg->addAcDbObject(objIdB, pObjB); // Now set up ownership for object C. The // AsdkOwnerDemo::setIdData() function takes the // objectId parameter and copies it into the // AcDbHardOwnershipId data member. This places the // object ID in a position to be filed out/in via the // dwgInFields/dwgOutFields/dxfInFields/dxfOutFields // member functions. This constitutes primary // "ownership." The AsdkOwnerDemo::setIdData() function // also calls each owned object's setOwnerId() member // function to set the backpointer and establish the // full two-way ownership link. // pObjB->setIdData(objIdC); pObjB->close(); // Create object A with a dummy integer data value of 1. // AsdkOwnerDemo *pObjA = new AsdkOwnerDemo(1); // Next, add objA to a dictionary in the named object // dictionary. This will establish ownership for objA, // set the ownership backlink, and add it to the // database. // pCurDwg->getNamedObjectsDictionary(pNamedobj, AcDb::kForWrite); // // // // if { pDict = new AcDbDictionary; AcDbObjectId DictId; pNamedobj->setAt("ASDK_DICT", pDict, DictId); } Get a pointer to the ASDK_DICT dictionary. If it doesn't exist, then create it and add it to the named object dictionary. (pNamedobj->getAt("ASDK_DICT", (AcDbObject*&) pDict, AcDb::kForWrite) == Acad::eKeyNotFound)
320
Chapter 12
pNamedobj->close(); // Add object A to the ASDK_DICT dictionary. // pDict->setAt("OBJA", pObjA, objIdA); pDict->close(); // Now set up ownership for object B. // pObjA->setIdData(objIdB); pObjA->close(); } // The list tree function runs through all objects in the // ASDK_DICT dictionary, follows their ownership trees, and // lists out information on all objects in the tree. // void listTree() { AcDbDictionary *pNamedobj; AcDbDictionary *pDict; acdbHostApplicationServices()->workingDatabase() ->getNamedObjectsDictionary(pNamedobj, AcDb::kForWrite); // Get a pointer to the ASDK_DICT dictionary. // pNamedobj->getAt("ASDK_DICT",(AcDbObject*&)pDict, AcDb::kForRead); pNamedobj->close(); // Run through the entries and list their backpointers. // AcDbDictionaryIterator *pDictItr = pDict->newIterator(); for (; !pDictItr->done(); pDictItr->next()) { printOut(pDictItr->objectId()); } pDict->close(); } // Recursively walks down an ownership tree of AsdkOwnerDemo // class objects, printing out information on each one. // void printOut(AcDbObjectId id) { AsdkOwnerDemo *pDemo; acdbOpenObject((AcDbObject*&)pDemo, id, AcDb::kForRead); acutPrintf("\nIntdata: %d ObjId: %ld Backpointer:" " %ld OwnedObj: %ld", pDemo->intData(), (pDemo->objectId()).asOldId(), (pDemo->ownerId()).asOldId(), (pDemo->idData()).asOldId());
Ownership References
321
// Recursive tree walk // if ((pDemo->idData()).asOldId() != 0L) { printOut(pDemo->idData()); } pDemo->close(); } // The initialization function is called from acrxEntryPoint() // during kInitAppMsg case. This function is used to add commands // to the command stack and to add classes to the ACRX class // hierarchy. // void initApp() { acedRegCmds->addCommand("ASDK_OWNERSHIP_COMMANDS", "ASDK_CREATE", "CREATE",ACRX_CMD_MODAL, createObjs); acedRegCmds->addCommand("ASDK_OWNERSHIP_COMMANDS", "ASDK_LISTREE", "LISTREE",ACRX_CMD_MODAL, listTree); AsdkOwnerDemo::rxInit(); acrxBuildClassHierarchy(); } // The clean up function is called from acrxEntryPoint() during the // kUnloadAppMsg case. This function removes this application's // command set from the command stack and the // AsdkOwnerDemo class from the ARX runtime class tree. // void unloadApp() { acedRegCmds->removeGroup("ASDK_OWNERSHIP_COMMANDS"); // Remove the AsdkOwnerDemo class from the ACRX runtime // class hierarchy. If this is done while the database is // still active, it should cause all objects of class // AsdkOwnerDemo to be turned into proxies. // deleteAcRxClass(AsdkOwnerDemo::desc()); } // ObjectARX entry point // AcRx::AppRetCode acrxEntryPoint(AcRx::AppMsgCode msg, void* appId) { switch (msg) { case AcRx::kInitAppMsg: acrxDynamicLinker->unlockApplication(appId); acrxDynamicLinker->registerAppMDIAware(appId); initApp(); break;
322
Chapter 12
Pointer References
Your custom class may also contain hard or soft pointer references to other objects in the database. A pointer is a one-way link (that is, there is no information in the referenced object that indicates the source of the pointer). An object can point to, or be pointed to by, any number of other objects.
Hard Pointers
A hard pointer reference protects an object from purge. For example, an entity contains a hard pointer reference to a layer. Therefore, you cant purge a layer that is pointed to by one or more entities. When a new database is written out from an existing one (for example, in a WBLOCK operation), all hard pointers are copied into the new database. Other examples of hard pointer references
I I I I
A leader entity contains a hard pointer reference to a dimension style. A text entity contains a hard pointer reference to a text style. A dimension entity contains a hard pointer reference to a dimension style. An mline entity has a hard pointer reference to an mline style.
Soft Pointers
A soft pointer is simply a pointer to an object. It does not protect the referenced object from purge. Examples of soft pointer references
I I
Xdata references are soft pointers. Persistent reactors are soft pointers.
If you use a soft pointer to refer to an object, you should check that the object still exists before you open it.
Pointer References
323
324
Chapter 12
boundary objects to the list of objects to be checked out. This is how it achieves transitive closure. If this did not happen, the LTM would find the hatchs soft pointer IDs to its boundaries. If it found that a boundary object so referenced was missing from the cloning, the long transaction would be aborted. If it did not do this, even if no changes were made to the checked out hatch, the original hatch would lose its associativity on check in. Sometimes, there are known references that do not need to be resolved. One situation would be an object that keeps track of all the entities that use it. For example, block table records keep a list of all the block references that use them. It is correct to only check out one of the references, so you must let the long transaction mechanism know that the rest of the references do not need to be cloned. There are several ways this can be done. Here are some examples:
I
If the application knows about which objects are referenced but will not be clonedat beginWblockObjects(), beginDeepClone(), or beginCheckOut() notificationthey can add the object ID of the referenced object to the IdMap for the cloning. The recommended approach is to set the value to NULL, and the idPair as not cloned. For example
idMap.assign(idPair(id, AcDbObjectId::kNull, kFalse);
If the object needs to be cloned later, the idPair will be changed accordingly.
I I
The above mapping can also be done from within the objects wblockClone() method, if that has already been overridden. If the reference is a data member of the object, which is filed out using dwgOutFields(), then it may be possible to avoid the long transaction validity test. The test is done by filing out the IDs using a kIdFiler type of filer. To avoid the test, do not file out the IDs that do not need to be cloned, during this type of filing. However, do not hold any ownership IDs out of this filing, or other features that use this filer, like partial save and load, may not properly handle your objects. The only safe IDs to withhold from this filer are AcDbSoftPointerId and AcDbHardPointerId objects.
325
If the ID is actually in a persistent reactor, it is possible to find it using the reactor iterator. Heres an example of how a dictionary object finds and adds its ID to the IdMap during beginWblockClone() notification.
beginWblockClone(..., AcDbIdMapping& idMap) { ... AcDbDictionaryIterator* pIter = pDict->newIterator(); AcDbObject* pObj; for ( ; !pIter->done(); pIter->next()) { acdbOpenObject(pObj, pIter->objectId(), kForRead); AcDbVoidPtrArray* pReactors = pObj->reactors(); void* pReactor; AcDbObjectId rId; MyReactor* pMyReactor; if (pReactors) { for (int i = 0; i < pReactors->length(); i++) { pReactor = pReactors->at(i); if (acdbIsPersistentReactor(pReactor)) { rId = acdbPersistentReactorObjectId(pReactor); if (acdbOpenObject(pMyReactor, rId, kForRead) == eOk) { pMyReactor->close(); AcDbIdPair idPair(rId, AcDbObjectId::kNull, kFalse); idMap.assign(idPair); } } } } pObj->close(); } delete pIter; pDict->close(); }
Purge
The purge mechanism allows you to erase unused objects in the database. If an object has a hard owner or pointer reference, it cannot be purged. The purge() function of AcDbDatabase is invoked on the set of objects specified in the ID array:
AcDbDatabase::purge(AcDbObjectIdArray &idArray);
The purge() function returns in the same ID array the IDs of the objects that can be purged (that is, that have no hard references to them). Once you have this array of object IDs, you are responsible for erasing the objects.
326
Chapter 12
When a drawing is loaded, AutoCAD goes through the database and purges unreferenced anonymous blocks and nested xref blocks. These blocks are erased when the drawing file is closed. If you create any anonymous blocks between the open and close of a drawing, they will be purged without your knowledge unless you protect them by calling the standalone function acdbSetReferenced(). This purging occurs even if the objects have hard references to them.
Automatic Undo
The assertWriteEnabled() function has the following signature:
void assertWriteEnabled( Adesk::Boolean autoUndo = Adesk::kTrue, Adesk::Boolean recordModified = Adesk::kTrue);
When a modification function calls assertWriteEnabled(), it first checks the value of the recordModified parameter. If recordModified is kFalse, no undo recording is performed. If recordModified is kTrue, it next checks the
327
be performed. If autoUndo is kTrue (the default), the full object state is automatically written to the objects undo filer. If you specify kFalse for autoUndo, no information is recorded. AutoCAD assumes that your modification function will take care of recording the changed object state to the objects undo filer. Even if you plan to implement a partial undo mechanism for your class, you can rely on automatic undo in the first stages of development.
Partial Undo
It is up to the implementor of a new class to decide whether to implement a partial undo mechanism for certain modification functions of the class. If only a small portion of an objects state is typically modified in a particular member function, using partial undo can yield substantial performance benefits. However, if your object state is small (512 bytes or less), it is probably not worth the effort to implement your own partial undo recording and restoring scheme. If your modification function records a partial object state, you must implement the applyPartialUndo() function for your class so that the data can also be restored selectively. See Restoring State on page 329.
Recording State
To record only part of an objects state, specify kFalse for the autoUndo parameter, and then use the undoFiler::writeItem() function (or another writexxx() function) to save the relevant information in the undo file. The setNumSides() function of AsdkPoly is a typical example of a modification function. Because assertWriteEnabled() specifies kFalse for autoUndo, the class assumes the responsibility of recording relevant parts of the objects state. First, the modification function must record the class descriptor object so that derived classes can check and let this class process its partial undo data if necessary.
undoFiler()->writeItem((long)AsdkPoly::desc());
328
Chapter 12
Then the modification function needs to indicate the type of action, followed by the data. In this example, the type of operation is kSetNumSides and the data is mNumSides.
Acad::ErrorStatus AsdkPoly::setNumSides(int numSides) { assertWriteEnabled(Adesk::kFalse, Adesk::kTrue); if (numSides<3) return Acad::eInvalidInput; if (mNumSides == numSides) return Acad::eOk; // There are situations under which AutoCAD doesn't // want to do undo recording, so it won't create an // undo filer. Check for the existence of the filer // before starting to write into it. // AcDbDwgFiler *pFiler = NULL; if ((pFiler = undoFiler()) != NULL) { undoFiler()->writeItem((long)AsdkPoly::desc()); undoFiler()->writeItem((Adesk::Int16)kSetNumSides); undoFiler()->writeItem((Adesk::Int32)mNumSides); } mNumSides = numSides; return Acad::eOk; }
Once an object has performed an auto undo operation, which records its full state, additional requests for auto undo are ignored.
Restoring State
If you specified kFalse for autoUndo, the objects applyPartialUndo() function is called when the UNDO command is invoked. The applyPartialUndo() function is a virtual function on AcDbObject. Derived classes can implement this function to interpret the class-specific information stored by the undo filer and read it in. The applyPartialUndo() function must ensure that your class performed the modification. If not, it must super-message, as shown in the following example. If you are implementing a partial undo mechanism, be sure to call the following function so that no recording happens by default.
assertWriteEnabled(kFalse, kFalse);
329
Redo
When the undo operation undoes your work, it also records the current state in preparation for a redo operation. This recording for redo requires no further work on your part, because it uses the same filing mechanism as the undo operation, calling the objects dwgOutFields() function to record the objects state. If you implement a partial undo for your modification function, you are responsible for recording for redo in your undo operation. This is usually accomplished by calling the appropriate set() functions. When your set() function is called, assertWriteEnabled() is invoked, which records the data for undo.
330
Chapter 12
To override a subsidiary function 1 Validate your surroundings. For example, if your object has a hard pointer reference to another object and your object is being unerased, you can check that the object you refer to still exists. If there are problems, immediately return an appropriate error status and dont pass the message up, because your bad error status will effectively kill the operation. 2 If everything is OK, then invoke the Your Parent::subErase() function. Examine its result. If it does not return eOK, then return. 3 If everything is OK, then perform your actions. It is best not to change any state in a subsidiary function. If you must change state, then try to change it after invoking the parent class implementation of the same function (in case an error code is returned). If you must change state before invoking the parent class function, then be prepared to reverse it if the parent class returns a bad status. The following example shows the implementation of the subErase() function that is called when an object is erased. The subErase() function checks for hard pointer references to other objects and erases them as well.
class AsdkEllipse : public AcDbEllipse // This class extends AcDbEllipse by adding in functionality // to store a dynamic array of hard pointer object IDs. // // The subErase() member function has been overridden and // implemented, so when an object of this class is // erased, the objects pointed to by the hard pointer IDs // stored within the object will also be erased. // { public: ACRX_DECLARE_MEMBERS(AsdkEllipse); AsdkEllipse() {};
331
AsdkEllipse(const AcDbObjectIdArray& ellipses) : mEllipseIds(ellipses) {}; AsdkEllipse(const AcGePoint3d& center, const AcGeVector3d& unitNormal, const AcGeVector3d& majorAxis, double radiusRatio, double startAngle = 0.0, double endAngle = 6.28318530717958647692); AsdkEllipse(const AcDbObjectIdArray& ellipses, const AcGePoint3d& center, const AcGeVector3d& unitNormal, const AcGeVector3d& majorAxis, double radiusRatio, double startAngle = 0.0, double endAngle = 6.28318530717958647692); AcDbObjectId ellipseId(unsigned short which); Acad::ErrorStatus setEllipseId( const AcDbObjectId& objId, unsigned short which); Acad::ErrorStatus setEllipseIds( const AcDbObjectIdArray& Ids); Acad::ErrorStatus appendId(const AcDbObjectId& objId); Acad::ErrorStatus appendIds( const AcDbObjectIdArray& objIds); inline Adesk::Boolean removeId( const AcDbObjectId& objId); // AcDbObject overrides. // virtual Acad::ErrorStatus subErase( Adesk::Boolean pErasing); virtual Acad::ErrorStatus dwgInFields( AcDbDwgFiler* filer); virtual Acad::ErrorStatus dwgOutFields( AcDbDwgFiler* filer) const; virtual Acad::ErrorStatus dxfInFields( AcDbDxfFiler* filer); virtual Acad::ErrorStatus dxfOutFields( AcDbDxfFiler* filer) const; virtual Acad::ErrorStatus wblockClone( AcRxObject* pOwnerObject, AcDbObject*& pClonedObject, AcDbIdMapping& idMap, Adesk::Boolean isPrimary = Adesk::kTrue) const; // AcDbEntity overrides. // virtual void list() const; private: AcDbObjectIdArray mEllipseIds; static int mInFlux; // == 1 when first object's // subErase is erasing. };
332
Chapter 12
ACRX_DXF_DEFINE_MEMBERS(AsdkEllipse, AcDbEllipse, AcDb::kDHL_CURRENT, AcDb::kMReleaseCurrent, 0,\ ASDKELLIPSE, REFERENC); // Static class data member definition. // int AsdkEllipse::mInFlux = Adesk::kFalse;
AsdkEllipse::AsdkEllipse(const AcGePoint3d& const AcGeVector3d& unitNormal, const AcGeVector3d& majorAxis, double radiusRatio, double startAngle, double endAngle) :
center,
AsdkEllipse::AsdkEllipse(const AcDbObjectIdArray& const AcGePoint3d& center, const AcGeVector3d& unitNormal, const AcGeVector3d& majorAxis, double radiusRatio, double startAngle, double endAngle) :
AcDbObjectId AsdkEllipse::ellipseId(unsigned short which) { assertReadEnabled(); if (which > mEllipseIds.length()) return AcDbObjectId::kNull; return mEllipseIds[which]; } Acad::ErrorStatus AsdkEllipse::setEllipseId(const AcDbObjectId& objId, unsigned short which) { assertWriteEnabled(); if (which > mEllipseIds.length()) return Acad::eInvalidIndex; mEllipseIds[which] = objId; return Acad::eOk; }
333
Acad::ErrorStatus AsdkEllipse::setEllipseIds(const AcDbObjectIdArray& objIds) { assertWriteEnabled(); if (objIds.length() == 0) return Acad::eNullObjectId; mEllipseIds = objIds; return Acad::eOk; } Acad::ErrorStatus AsdkEllipse::appendId(const AcDbObjectId& objId) { assertWriteEnabled(); if (objId == AcDbObjectId::kNull) return Acad::eNullObjectId; mEllipseIds.append(objId); return Acad::eOk; } Acad::ErrorStatus AsdkEllipse::appendIds(const AcDbObjectIdArray& objIds) { assertWriteEnabled(); if (objIds.length() == 0) return Acad::eNullObjectId; mEllipseIds.append(objIds); return Acad::eOk; } inline Adesk::Boolean AsdkEllipse::removeId(const AcDbObjectId& objId) { assertWriteEnabled(); return mEllipseIds.remove(objId); } // This implementation of subErase opens and erases all // objects that this entity has hard pointer references // to. The effect is that when one AsdkEllipse is erased, // all the others it has hard pointers to also erase as // a "group". // Acad::ErrorStatus AsdkEllipse::subErase(Adesk::Boolean pErasing) { Acad::ErrorStatus es = AcDbEllipse::subErase(pErasing); if (es != Acad::eOk) return es;
334
Chapter 12
if (mInFlux == Adesk::kFalse) { mInFlux = Adesk::kTrue; AsdkEllipse *pEllipse; int es; for (int i = 0; i < mEllipseIds.length(); i++) { es = acdbOpenObject(pEllipse, mEllipseIds[i], AcDb::kForWrite, Adesk::kTrue); if (es != Acad::eOk) continue; pEllipse->erase(pErasing); pEllipse->close(); } mInFlux = Adesk::kFalse; } return Acad::eOk; } Acad::ErrorStatus AsdkEllipse::dwgInFields(AcDbDwgFiler* filer) { assertWriteEnabled(); AcDbEllipse::dwgInFields(filer); mEllipseIds.setLogicalLength(0); int idCount; filer->readInt32((long*)&idCount); AcDbHardPointerId objId; for (int i = 0; i < idCount; i++) { filer->readItem(&objId); mEllipseIds.append(objId); } return filer->filerStatus(); } Acad::ErrorStatus AsdkEllipse::dwgOutFields(AcDbDwgFiler* filer) const { assertReadEnabled(); AcDbEllipse::dwgOutFields(filer); filer->writeInt32(mEllipseIds.length()); for (int i = 0; i < mEllipseIds.length(); i++) { filer->writeHardPointerId(mEllipseIds[i]); } return filer->filerStatus(); }
335
Acad::ErrorStatus AsdkEllipse::dxfInFields(AcDbDxfFiler* filer) { assertWriteEnabled(); Acad::ErrorStatus es = AcDbEllipse::dxfInFields(filer); if (es != Acad::eOk) { return es; } // Check to see if we're at the right subclass data // marker. // if (!filer->atSubclassData("AsdkEllipse")) { return Acad::eBadDxfSequence; } struct resbuf inbuf; AcDbObjectId objId; int idCount; filer->readItem(&inbuf); if (inbuf.restype == AcDb::kDxfInt32) { idCount = inbuf.resval.rint; } else { filer->pushBackItem(); filer->setError(Acad::eInvalidDxfCode, "\nError: expected group code %d", AcDb::kDxfInt32); return filer->filerStatus(); } for (int i = 0; i < idCount; i++) { es = filer->readItem(&inbuf); if (es != Acad::eOk) { filer->setError(Acad::eMissingDxfField, "\nError: expected more group code %d's", AcDb::kDxfHardPointerId); return filer->filerStatus(); } if (inbuf.restype == AcDb::kDxfHardPointerId) { acdbGetObjectId(objId, inbuf.resval.rlname); mEllipseIds.append(objId); } else { filer->pushBackItem(); filer->setError(Acad::eInvalidDxfCode, "\nError: expected group code %d", AcDb::kDxfHardPointerId); return filer->filerStatus(); } } return filer->filerStatus(); }
336
Chapter 12
Acad::ErrorStatus AsdkEllipse::dxfOutFields(AcDbDxfFiler* filer) const { assertReadEnabled(); AcDbEllipse::dxfOutFields(filer); filer->writeItem(AcDb::kDxfSubclass, "AsdkEllipse"); filer->writeInt32(AcDb::kDxfInt32, mEllipseIds.length()); for (int i = 0; i < mEllipseIds.length(); i++) { filer->writeObjectId(AcDb::kDxfHardPointerId, mEllipseIds[i]); } return filer->filerStatus(); }
void AsdkEllipse::list() const { assertReadEnabled(); AcDbEllipse::list(); acutPrintf("\nClass:\t%s", isA()->name()); for (int i = 0; i < mEllipseIds.length(); i++) { acutPrintf("\nReferenceId[%d]:\t%ld", i, (mEllipseIds[i]).asOldId()); } }
Acad::ErrorStatus AsdkEllipse::wblockClone(AcRxObject* pOwnerObject, AcDbObject*& pClonedObject, AcDbIdMapping& idMap, Adesk::Boolean isPrimary) const { assertReadEnabled(); static AcDbObjectId btr, pspace = AcDbObjectId::kNull; AcTransaction *pTrans = NULL; pClonedObject = NULL; if (pspace == AcDbObjectId::kNull) { AcDbBlockTable *pTable; database()->getSymbolTable(pTable, AcDb::kForRead); pTable->getAt(ACDB_PAPER_SPACE, pspace); pTable->close(); } if ( idMap.deepCloneContext() == AcDb::kDcXrefBind && ownerId() == pspace) return Acad::eOk;
337
// Have we already done this entity ? // AcDbIdPair idPair(objectId(), (AcDbObjectId)NULL, Adesk::kTrue); if (idMap.compute(idPair) == TRUE && idPair.value() != NULL) { pClonedObject = NULL; return Acad::eOk; } AcDbBlockTableRecord *pBTR = AcDbBlockTableRecord::cast(pOwnerObject); if (pBTR != NULL) { if (isPrimary == Adesk::kTrue) btr = pBTR->objectId(); else btr = AcDbObjectId::kNull; } else if (btr != AcDbObjectId::kNull) { pTrans = actrTransactionManager->startTransaction(); pTrans->getObject((AcDbObject*&)pBTR, btr, AcDb::kForWrite); pOwnerObject = pBTR; } Acad::ErrorStatus es = AcDbEllipse::wblockClone(pOwnerObject, pClonedObject, idMap, btr != AcDbObjectId::kNull); if (pTrans) actrTransactionManager->endTransaction(); acutPrintf("\nWblockClone error status: %s", acadErrorStatusText(es) ); return Acad::eOk; }
void createEllipses() { const ellipseCount = 10; AsdkEllipse *pEllipse; pEllipse = new AsdkEllipse(AcGePoint3d(4.0, 4.0, 0.0), AcGeVector3d(0.0, 0.0, 1.0), AcGeVector3d(2.0, 0.0, 0.0), 0.5); AcDbVoidPtrArray ellipses; ellipses.append(pEllipse);
338
Chapter 12
// Now use the getTransformedCopy() function with a // scaling matrix (in X & Y only) to create new // AsdkEllipses, each 0.5 units larger than the last in // the X & Y direction, but identical in the Z // direction. This would be similar to the // getOffsetCurves() function, but that function // returns AcDbSpline entities instead of AcDbEllipses. // double j = 1.1; AcGeMatrix3d scale; for (int i = 0; i < ellipseCount; i++, j += 0.1) { scale.setToScaling(j, pEllipse->center()); scale.entry[2][2] = 1.0; // Z scaling == 1 // // // // // // // // // getTransformed copy uses this->clone() to create a new object, which the ent pointer is assigned to point to. Therefore, ent should NOT point to an existing entity or there will be a memory leak! Since this->clone() is used, the AsdkEllipse class must override this member function to be sure that an AsdkEllipse is created instead of just an AcDbEllipse. //
AsdkEllipse *pNextEllipse; ((AsdkEllipse*)ellipses[0])->getTransformedCopy( scale, (AcDbEntity*&)pNextEllipse); ellipses.append(pNextEllipse); } AcDbBlockTable *pBlockTable; acdbHostApplicationServices()->workingDatabase() ->getSymbolTable(pBlockTable, AcDb::kForRead); AcDbBlockTableRecord *pBlockTableRecord; pBlockTable->getAt(ACDB_MODEL_SPACE, pBlockTableRecord, AcDb::kForWrite); pBlockTable->close(); AcDbObjectIdArray ellipseIds; AcDbObjectId tempId; for (i = 0; i < ellipses.length(); i++) { pBlockTableRecord->appendAcDbEntity(tempId, (AsdkEllipse*)ellipses[i]); ellipseIds.append(tempId); } pBlockTableRecord->close();
339
// Set up the hard pointers and close the ellipses. // for (i = 0; i < ellipses.length(); i++) { // Add in all the IDs. // ((AsdkEllipse*)ellipses[i]) ->setEllipseIds(ellipseIds); // Now remove the object ID of the "*this" ellipse // so it doesn't reference itself. // ((AsdkEllipse*)ellipses[i])->removeId( ((AsdkEllipse*)ellipses[i])->objectId()); ((AsdkEllipse*)ellipses[i])->close(); } } void initApp() { acedRegCmds->addCommand("ASDK_ELLIPSES", "ASDK_ELLIPSES", "ELLIPSES", ACRX_CMD_MODAL, createEllipses); AsdkEllipse::rxInit(); acrxBuildClassHierarchy(); } void unloadApp() { acedRegCmds->removeGroup("ASDK_ELLIPSES"); // Remove the AsdkEllipse class from the ACRX runtime // class hierarchy. If this is done while the database is // still active, it should cause all objects of class // AsdkEllipse to be turned into proxies. // deleteAcRxClass(AsdkEllipse::desc()); } 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(); } return AcRx::kRetOK; }
340
Chapter 12
Header File
The following code shows the class declaration for the new class AsdkMyClass derived from AcDbObject.
class AsdkMyClass : public AcDbObject // // This class demonstrates custom objects. // // To keep it simple, this class has a single integer data // member. Get and set functions are provided for this // data member. // { public: ACRX_DECLARE_MEMBERS(AsdkMyClass); AsdkMyClass(): mIntval(0) {}; AsdkMyClass(const Adesk::Int16& val): mIntval(val) {}; Acad::ErrorStatus getData (Adesk::Int16&); Acad::ErrorStatus setData (Adesk::Int16); virtual Acad::ErrorStatus dwgInFields (AcDbDwgFiler*); virtual Acad::ErrorStatus dwgOutFields(AcDbDwgFiler*) const; virtual Acad::ErrorStatus dxfInFields (AcDbDxfFiler*); virtual Acad::ErrorStatus dxfOutFields(AcDbDxfFiler*) const; private: Adesk::Int16 mIntval; };
341
Source File
The following code shows the implementation for the new class AsdkMyClass:
ACRX_DXF_DEFINE_MEMBERS(AsdkMyClass, AcDbObject, AcDb::kDHL_CURRENT, AcDb::kMReleaseCurrent, 0, ASDKMYCLASS, SAMP2); // Gets the value of the integer data member. // Acad::ErrorStatus AsdkMyClass::getData(Adesk::Int16& val) { // Tells AutoCAD a read operation is taking place. // assertReadEnabled(); val = mIntval; return Acad::eOk; } // Sets the value of the integer data member. // Acad::ErrorStatus AsdkMyClass::setData(Adesk::Int16 val) { // Triggers openedForModify notification. // assertWriteEnabled(); mIntval = val; return Acad::eOk; } // Files data in from a DWG file. // Acad::ErrorStatus AsdkMyClass::dwgInFields(AcDbDwgFiler* pFiler) { assertWriteEnabled(); AcDbObject::dwgInFields(pFiler); // For wblock filing we wrote out our owner as a hard // pointer ID so now we need to read it in to keep things // in sync. // if (pFiler->filerType() == AcDb::kWblockCloneFiler) { AcDbHardPointerId id; pFiler->readItem(&id); } pFiler->readItem(&mIntval); return pFiler->filerStatus(); }
342
Chapter 12
// Files data out to a DWG file. // Acad::ErrorStatus AsdkMyClass::dwgOutFields(AcDbDwgFiler* pFiler) const { assertReadEnabled(); AcDbObject::dwgOutFields(pFiler); // Since objects of this class will be in the Named // Objects Dictionary tree and may be hard referenced // by some other object, to support wblock we need to // file out our owner as a hard pointer ID so that it // will be added to the list of objects to be wblocked. // if (pFiler->filerType() == AcDb::kWblockCloneFiler) pFiler->writeHardPointerId((AcDbHardPointerId)ownerId()); pFiler->writeItem(mIntval); return pFiler->filerStatus(); } // Files data in from a DXF file. // Acad::ErrorStatus AsdkMyClass::dxfInFields(AcDbDxfFiler* pFiler) { assertWriteEnabled(); Acad::ErrorStatus es; if ((es = AcDbObject::dxfInFields(pFiler)) != Acad::eOk) { return es; } // Check if we're at the right subclass getData marker. // if (!pFiler->atSubclassData("AsdkMyClass")) { return Acad::eBadDxfSequence; } struct resbuf inbuf; while (es == Acad::eOk) { if ((es = pFiler->readItem(&inbuf)) == Acad::eOk) { if (inbuf.restype == AcDb::kDxfInt16) { mIntval = inbuf.resval.rint; } } } return pFiler->filerStatus(); }
343
// Files data out to a DXF file. // Acad::ErrorStatus AsdkMyClass::dxfOutFields(AcDbDxfFiler* pFiler) const { assertReadEnabled(); AcDbObject::dxfOutFields(pFiler); pFiler->writeItem(AcDb::kDxfSubclass, "AsdkMyClass"); pFiler->writeItem(AcDb::kDxfInt16, mIntval); return pFiler->filerStatus(); } // 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. // 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(); } }
344
Chapter 12
// Opens the dictionary associated with the key ASDK_DICT // and iterates through all its entries, printing out the // integer data value in each entry. // 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; 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(); } // 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(); }
345
// 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()); }
Renaming the class for each new version. Maintaining a version number as the first data member of the class. Maintaining the version number as extended data (xdata) or in an extension dictionary.
For most situations, these mechanisms have been superseded by the class versioning system. If the custom class might have its data format changed within the same AutoCAD maintenance release version, then both the class versioning system and some other mechanism, such as version numbering, would need to be used. The version numbering would tell the custom class which version its data was written in and the class version system values would tell the base classes what version their data was written in. The earlier mechanisms are described below (following the description of class versioning).
Class Versioning
Beginning with AutoCAD 2000, every custom class must provide a drawing and maintenance version number. The drawing version number value corresponds to the release of AutoCAD that was current when the class was created. The maintenance version number value can be set to whatever is appropriate for your class. For ObjectARX built-in classes, the maintenance value will be set to zero every time the drawing version changes. The version
346
Chapter 12
values are defined in the acdb.h header file. The ACRX_DXF_DEFINE_MEMBERS macro was modified in AutoCAD 2000 to take two new arguments, DWG_VERSION and MAINTENANCE_VERSION:
#define ACRX_DXF_DEFINE_MEMBERS(CLASS_NAME,PARENT_CLASS,\ DWG_VERSION,MAINTENANCE_VERSION,PROXY_FLAGS,DXF_NAME,APP)
These two arguments specify the version when the class was introduced. You must provide these arguments; there are no defaults. They become data members of the AcRxClass, but they are not persistent, that is, they are not stored in the class section of DWG and DXF files.
changed. When the drawing is saved by Release 14, the data is dumped back to the DWG file as it was read in. The result is a Release 14 DWG file that has an instance of AcDbDictionaryWithDefault that is missing the mTreatElementsAsHard data. When reading in this drawing with AutoCAD 2000 or later, AutoCAD (specifically, AcDbDictionary::dwgInFields()) looks for that data member because the filer indicates that the drawing is a Release 14 version and the mTreatElementsAsHard data member should be present in an R14 drawing dictionary object. However, the data is not present, the sequence is lost, and the drawing is corrupt. This is not specific to AcDbDictionaryWithDefault. New classes introduced by ObjectARX or third parties can suffer from this problem, especially if one of their superclasses has changed data.
347
In the filer methods, instead of calling filer->dwgVersion(), call self()->getObjectSaveVersion(filer, ...) to let the object indicate which version to use to dump the data out. Similarly, call that method in dwgInFields() and dxfInFields() to find out which version the data is coming back in. Because not all the objects need to override the filer version, the ones that do specify their intent by setting a bit on the object. This is normally done in the constructor of the class. The bit is used as a quick check to determine if its necessary to override the filer version. Methods related to this have been added to AcDbObject:
348
Chapter 12
There is also a new AcDbObject method to get the birth version of the object:
Acad::ErrorStatus getObjectBirthVersion( AcDb::AcDbDwgVersion& ver, AcDb::MaintenanceReleaseVersion& maintVer);
This method returns the two version numbers stored with the AcRxClass of this object, which are specified while registering the class using the ACRX_DXF_DEFINE_MEMBERS macro.
349
Class Renaming
Renaming classes for each new version is the simplest method of avoiding versioning conflicts, as it does not involve implementing new data elements or functions to detect and respond to different class version numbers. However, the logistics of dealing with new and old class names can be complicated.
When the application encounters an outdated version of an object in a file, it should be able to update the object to the current version. Updating an old object involves adding any new data members and member functions, as well as changing the version number. When an older version of the application encounters a newer version of an object (that is, when the revision number of an object is greater than the revision number of the application), the custom classs dxfInFields() and dwgInFields() functions should immediately return the error code eMakeMeProxy to AutoCAD. AutoCAD will then create a proxy object for the drawing session, and write the original object to file when the drawing is saved.
Object versioning with a data-member version number is illustrated in the following code fragments from \objectarx\samples\polysamp\poly.cpp in the ObjectARX SDK.
350
Chapter 12
// Object Version #define VERSION 1 ... Acad::ErrorStatus AsdkPoly::dwgInFields(AcDbDwgFiler* filer) { ... // Object Version - must always be the first item Adesk::Int16 version; filer->readItem(&version); if (version > VERSION) return Acad::eMakeMeProxy; ... }
Acad::ErrorStatus AsdkPoly::dwgOutFields(AcDbDwgFiler* filer) const { ... // Object Version - must always be the first item Adesk::Int16 version = VERSION; filer->writeItem(version); ... } Acad::ErrorStatus AsdkPoly::dxfInFields(AcDbDxfFiler* filer) { ... // Object Version case AcDb::kDxfInt16: Adesk::Int16 version; version = rb.resval.rint; if (version > VERSION) return Acad::eMakeMeProxy; break; ... } Acad::ErrorStatus AsdkPoly::dxfOutFields(AcDbDxfFiler* filer) const { ... // Object Version Adesk::Int16 version = VERSION; filer->writeItem(AcDb::kDxfInt16, version); ... }
351
352
13
In this chapter
I Deriving Custom Entities I Overriding Common Entity
virtual methods provided by the AcDbEntity class. Overriding common entity operations, such as object snap points, grip points, and stretch points, is also discussed in this section. The material in this section assumes you are familiar with the material presented in chapter 6, Entities; chapter 11, Deriving a Custom ObjectARX Class; and chapter 12, Deriving from AcDbObject.
Functions
I Extending Entity Functionality I Using AcEdJig
353
To create a custom entity 1 Derive a custom class from AcDbEntity. 2 Override all of the necessary AcDbObject functions. See chapter 12, Deriving from AcDbObject. 3 Override the required AcDbEntity functions. This will be discussed in the following sections. 4 Override other functions as needed to support your custom functionality. 5 If you want to support the MATCHPROP command, implement AcDbMatchProperties as a protocol extension. 6 If you want to create a custom drag sequence for your entity, implement your own version of AcEdJig. The following sections discuss these topics in more detail.
354
Chapter 13
355
virtual Acad::ErrorStatus getSubentPathsAtGsMarker( AcDb::SubentType type, int gsMark, const AcGePoint3d& pickPoint, const AcGeMatrix3d& viewXform, int& numPaths, AcDbFullSubentPath* & subentPaths, int numInserts = 0, AcDbObjectId* entAndInsertStack = NULL) const; virtual Acad::ErrorStatus applyPartialUndo( AcDbDwgFiler* undoFiler, AcRxClass* classObj); virtual void subSetDatabaseDefaults( AcDbDatabase* pDb); virtual void saveAs( AcGiWorldDraw* mode, AcDb::SaveType st);
356
Chapter 13
virtual Acad::ErrorStatus unhighlight( const AcDbFullSubentPath& subId = kNullSubent) const; virtual AcDbEntity* subentPtr( const AcDbFullSubentPath& id) const; virtual Adesk::Boolean saveImagesByDefault() const; virtual void setAttributes( AcGiSubEntityTraits* pTraits);
Whenever AutoCAD needs to regenerate the graphics to display an entity, the worldDraw() and viewportDraw() functions are called in the following manner:
if (!entity->worldDraw(pWd)) for (each relevant viewport) entity->viewportDraw(pVd);
The worldDraw() function builds the portion of the entitys graphical representation that can be specified independently of any particular modelspace view or paper-space viewport contexts. The viewportDraw() function
357
then builds the view-dependent portion of the entitys graphics. If any of the entitys graphics are view-dependent, the worldDraw() function must return kFalse and the viewportDraw() function must be implemented. Conversely, if the entity has no view-dependent graphics, then the worldDraw() function must return kTrue, and the custom entity does not implement the viewportDraw() function. The AcDbEntity::worldDraw() function takes a pointer to an AcGiWorldDraw object. AcGiWorldDraw is a container class for the AcGi geometry and traits objects. Specifically, AcGiWorldDraw contains two other objects:
I AcGiWorldGeometry I AcGiSubEntityTraits
The AcGiWorldGeometry object can be accessed from within the worldDraw() function by using the AcGiWorldDraw::geometry() function, and the AcGiSubEntityTraits object can be accessed by using the AcGiWorldDraw::subEntityTraits() function. The AcGiWorldGeometry object writes vectors to AutoCADs refresh memory using its set of drawing primitives. A primitive is the lowest-level instruction used to draw graphical entities. The world geometry object has the following functions for drawing primitives in world coordinates:
I I I I I I I I I
Circle Circular arc Polyline Polygon Mesh Shell Text Xline Ray
The AcGiSubEntityTraits object sets graphical attribute values using its set of traits functions:
I I I I I
The AcDbEntity::viewportDraw() function takes a pointer to an AcGiViewportDraw object and builds the view-specific representation of an
358
Chapter 13
entity. The viewport draw object is also a container object for other objects, which include the following:
I AcGiViewportGeometry I AcGiSubEntityTraits I AcGiViewport
The viewport geometry object provides the same list of primitives as the world geometry object and adds to it the following primitives, which use eyeand display-space coordinates to draw polylines and polygons:
I I I I
The viewport subentity traits object is the same as that used by the world draw object (AcGiSubEntityTraits). The viewport object provides functions for querying the viewports transformation matrices and viewing parameters.
Overriding saveAs()
You should override saveAs() if you want to save an alternate graphical representation for saving proxy entity graphics, Release 12 DWG files, or both. If your custom entity doesnt override the AcDbEntity::saveAs() function, AutoCAD will leverage your worldDraw() function to support proxy entity graphics or Release 12 DWG files. The AcDbEntity::saveAs() function merely calls the worldDraw() function.
virtual void AcDbEntity::saveAs( AcGiWorldDraw *pWd, AcDb::SaveType saveType);
The saveType parameter is used when you want to build unique, alternate graphical representations for both kinds of saving; it indicates for which
359
purpose saveAs() was called. The saveType parameter has either of the following values:
I kR13Save
I kR12Save indicates that saveAs() was called for saving to Release 12 DWG
files. From within saveAs(), you may want to call the worldDraw() function for one value of saveType and make direct AcGiWorldGeometry and AcGiSubEntityTraits calls for the other value, or you may not want to call the worldDraw() function at all. In either case, before calling saveAs(), AutoCAD first replaces AcGiWorldDraws geometry and traits objects with special subclasses of AcGiWorldGeometry and AcGiSubEntityTraits. These subclassess geometric primitive and property traits functions cache the data in the appropriate format rather than performing a display. After calling saveAs(), AutoCAD writes the cached data to disk. Neither kind of saving permits preserving any view-dependent graphics. The
viewportDraw() function is not called as part of either of the save operations. Your custom entity may rely on its viewportDraw() function for its graphics, so its worldDraw() function alone would not produce an appropriate image. In that case, youll need to override saveAs() to produce reasonable graphics
for Release 12 and proxy objects. For more information on proxy graphics data, see chapter 14, Proxy Objects. In Release 12 DWG files, information about the original entity is not saved in the file. However, the first Release 12 entity will have the same handle as the original entity, and any additional Release 12 entities will have the original entity handle placed in their xdata. (Look under the application name ACAD, following the string data member R13OBJECT.) This feature is provided so that you can group the Release 12 entities into a block.
360
Chapter 13
361
AcGeLineSeg3d lnsg(vertexArray[startIndex], vertexArray[startIndex + 1]); AcGePoint3d pt; AcGeLine3d line, perpLine; AcGeVector3d vec; AcGeVector3d viewDir(viewXform(Z, 0), viewXform(Z, 1), viewXform(Z, 2)); switch (osnapMode) { case AcDb::kOsModeEnd: snapPoints.append(vertexArray[startIndex]); snapPoints.append(vertexArray[startIndex + 1]); break; case AcDb::kOsModeMid: pt.set( ((vertexArray[startIndex])[X] + (vertexArray[startIndex + 1])[X]) * 0.5, ((vertexArray[startIndex])[Y] + (vertexArray[startIndex + 1])[Y]) * 0.5, ((vertexArray[startIndex])[Z] + (vertexArray[startIndex + 1])[Z]) * 0.5); snapPoints.append(pt); break; case AcDb::kOsModeNear: pt = lnsg.projClosestPointTo(pickPoint, viewDir); snapPoints.append(pt); break; case AcDb::kOsModePerp: // Create a semi-infinite line and find a point on it. // vec = vertexArray[startIndex + 1] - vertexArray[startIndex]; vec.normalize(); line.set(vertexArray[startIndex], vec); pt = line.closestPointTo(lastPoint); snapPoints.append(pt); break; case AcDb::kOsModeCen: snapPoints.append(center); break; default: return Acad::eOk; } return es; }
362
Chapter 13
The signatures for the getGripPoints() and moveGripPointsAt() functions for AcDbEntity are
virtual Acad::ErrorStatus AcDbEntity::getGripPoints( AcGePoint3dArray& gripPoints, AcDbIntArray& osnapModes, AcDbIntArray& geomIds) const; virtual Acad::ErrorStatus AcDbEntity::moveGripPointsAt( const AcDbIntArray& indices, const AcGeVector3d& offset);
The osnapModes and geomIds arguments of the getGripPoints() function are not currently used. Stretch mode in grip editing allows you to stretch an object by moving selected grips to new locations. AutoCAD calls the moveGripPointsAt() function when the user is in stretch mode. For certain entities, however, some grips move the object rather than stretching it. These grips include grips on text objects, blocks, midpoints of lines, centers of circles, centers of ellipses, and point objects. In these cases, the moveGripPointsAt() function calls transformBy().
function. When the user is in grip move, rotate, scale, or mirror modes, AutoCAD calls the transformBy() function, described in chapter 6, Entities. If you want the user to be able to edit your entity using grips, youll need to override the getGripPoints() and moveGripPointsAt() functions. The entity defines its grip points and how to interpret the user-supplied offset. The following excerpt shows how the custom AsdkPoly class implements these functions. The object defined by this class has a grip point at each vertex and a grip point at its center. These grip points are returned by the getGripPoints() function. If the user selects a grip point when in grip stretch mode, AutoCAD invokes the moveGripPointsAt() function passing in an array of the indexes for the selected grip points and a 3D vector specifying how much the user moved the pointing device. If the user has selected a vertex grip point, the polygon is stretched uniformly by the specified offset. If the user picked the center grip point, the polygon is simply
363
translated by an amount equal to the offset (this value is passed to thetransformBy() function, as shown here).
Acad::ErrorStatus AsdkPoly::getGripPoints( AcGePoint3dArray& gripPoints, AcDbIntArray& osnapModes, AcDbIntArray& geomIds) const { assertReadEnabled(); Acad::ErrorStatus es; if ((es = getVertices3d(gripPoints)) != Acad::eOk) { return es; } // Remove the duplicate point at the start/end and add // center as the last point. // gripPoints.removeAt(gripPoints.length() - 1); AcGePoint3d center; getCenter(center); gripPoints.append(center); return es; } Acad::ErrorStatus AsdkPoly::moveGripPointsAt( const AcDbIntArray& indices, const AcGeVector3d& offset) { if (indices.length()== 0 || offset.isZeroLength()) return Acad::eOk; //that's easy :-) if (mDragDataFlags & kCloneMeForDraggingCalled) { mDragDataFlags &= kUseDragCache; // We need to make sure that all the poly's drag data members // are in sync with the true data members. // //mDragCenter = mCenter; //mDragStartPoint = mStartPoint; } else // Only if we're not dragging do we want to make an undo // recording and check if the object's open for write. // assertWriteEnabled(); //if theres more than one hot vertex or there's one and it is //the center then simply transform. if (indices.length()>1 || indices[0] == mNumSides) return transformBy(AcGeMatrix3d::translation(offset)); AcGeVector3d off(offset);
364
Chapter 13
// Calculate the offset vector of the startpoint // from the offset vector on a vertex. double rotateBy = 2.0 * 3.14159265358979323846 / mNumSides * indices[0]; AcGePoint3d cent; getCenter(cent); off.transformBy(AcGeMatrix3d::rotation(rotateBy, normal(),cent)); acdbWcs2Ecs(asDblArray(off),asDblArray(off), asDblArray(normal()),Adesk::kTrue); if (mDragDataFlags & kUseDragCache){ mDragStartPoint = mStartPoint + AcGeVector2d(off.x,off.y); mDragElevation = mElevation + off.z; } else{ mStartPoint = mStartPoint + AcGeVector2d(off.x,off.y); mElevation = mElevation + off.z; } return Acad::eOk; }
You are not required to override the getStretchPoints() and moveStretchPointsAt() functions of AcDbEntity, because they default to the getGripPoints() and transformBy() functions. The custom AsdkPoly class overrides these functions as shown in the example in this section. The getStretchPoints() function returns the vertices of the polygon, but not the center. The moveStretchPointsAt() function checks whether all the stretch points have been selected. If they have, it invokes the transformBy() function. Otherwise, it invokes the moveGripPointsAt() function.
365
Acad::ErrorStatus AsdkPoly::getStretchPoints( AcGePoint3dArray& stretchPoints) const { assertReadEnabled(); Acad::ErrorStatus es; if ((es = getVertices3d(stretchPoints)) != Acad::eOk) { return es; } // Remove the duplicate point at the start and end. // stretchPoints.removeAt(stretchPoints.length() - 1); return es; } Acad::ErrorStatus AsdkPoly::moveStretchPointsAt( const AcDbIntArray& indices, const AcGeVector3d& offset) { return moveGripPointsAt(indices, offset); }
Transformation Functions
The AcDbEntity class offers two transformation functions. The transformBy() function applies a matrix to an entity. The getTransformedCopy() function enables an entity to return a copy of itself with the transformation applied to it. If an entity is uniformly scaled and orthogonal, the default implementation of the AcDbEntity::getTransformedCopy() function clones the entity and then invokes the transformBy() function on the cloned entity. (Use the AcGeMatrix3d::isUniScaledOrtho() function to determine if the input matrix is uniformly scaled and orthogonal.) The custom AsdkPoly class overrides both the transformBy() function and the getTransformedCopy() function. When AsdkPoly is nonuniformly scaled, it becomes a polyline.
Acad::ErrorStatus AsdkPoly::transformBy(const AcGeMatrix3d& xform) { // If we're dragging, we aren't really going to change our // data, so we don't want to make an undo recording nor do // we really care if the object's open for write. //
366
Chapter 13
if (mDragDataFlags & kCloneMeForDraggingCalled) { mDragDataFlags &= kUseDragCache; mDragPlaneNormal = mPlaneNormal; mDragElevation = mElevation; AcGeMatrix2d xform2d(xform.convertToLocal(mDragPlaneNormal, mDragElevation)); mDragCenter = xform2d * center(); mDragStartPoint = xform2d * startPoint(); mDragPlaneNormal.normalize(); } else { assertWriteEnabled(); AcGeMatrix2d xform2d(xform.convertToLocal(mPlaneNormal, mElevation)); mCenter.transformBy(xform2d); mStartPoint.transformBy(xform2d); mPlaneNormal.normalize(); } return Acad::eOk; } Acad::ErrorStatus AsdkPoly::getTransformedCopy( const AcGeMatrix3d& mat, AcDbEntity*& ent) const { assertReadEnabled(); Acad::ErrorStatus es = Acad::eOk; AcGePoint3dArray vertexArray; if ((es = getVertices3d(vertexArray)) != Acad::eOk) { return es; } for (int i = 0; i < vertexArray.length(); i++) { vertexArray[i].transformBy(mat); } AcDbSpline *pSpline = NULL; if ((es = rx_makeSpline(vertexArray, pSpline)) != Acad::eOk) { return es; } assert(pSpline != NULL); pSpline->setPropertiesFrom(this); ent = pSpline; return es; }
367
The first form of the intersectWith() function tests for simple intersection of two entities. The second form calculates the intersection on a projection plane. However, both functions return the intersection points on the entity itself. To use the projection plane form of the intersectWith() function 1 Project your entity and the argument entity onto the plane. 2 Test the entities for intersection on the projection plane. 3 Project the intersection points back onto the entity and return them. The custom AsdkPoly class overrides both forms of the intersectWith() function.
Acad::ErrorStatus AsdkPoly::intersectWith( const AcDbEntity* ent, AcDb::Intersect intType, AcGePoint3dArray& points, int /*thisGsMarker*/, int /*otherGsMarker*/) const { assertReadEnabled(); Acad::ErrorStatus es = Acad::eOk; if (ent == NULL) return Acad::eNullEntityPointer;
368
Chapter 13
// // // // // // // // if
The idea is to intersect each side of the polygon with the given entity and return all the points. For non-R12-entities with intersection methods defined, we call that method for each of the sides of the polygon. For R12-entities, we use the locally defined intersectors, since their protocols are not implemented. (ent->isKindOf(AcDbLine::desc())) { if ((es = intLine(this, AcDbLine::cast(ent), intType, NULL, points)) != Acad::eOk) { return es; } else if (ent->isKindOf(AcDbArc::desc())) { if ((es = intArc(this, AcDbArc::cast(ent), intType, NULL, points)) != Acad::eOk) { return es; } else if (ent->isKindOf(AcDbCircle::desc())) { if ((es = intCircle(this, AcDbCircle::cast(ent), intType, NULL, points)) != Acad::eOk) { return es; } else if (ent->isKindOf(AcDb2dPolyline::desc())) { if ((es = intPline(this, AcDb2dPolyline::cast(ent), intType, NULL, points)) != Acad::eOk) { return es; } else if (ent->isKindOf(AcDb3dPolyline::desc())) { if ((es = intPline(this, AcDb3dPolyline::cast(ent), intType, NULL, points)) != Acad::eOk) { return es; } else { AcGePoint3dArray vertexArray; if ((es = getVertices3d(vertexArray)) != Acad::eOk) { return es; } if (intType == AcDb::kExtendArg || intType == AcDb::kExtendBoth) { intType = AcDb::kExtendThis; } AcDbLine *pAcadLine;
369
for (int i = 0; i < vertexArray.length() - 1; i++) { pAcadLine = new AcDbLine(); pAcadLine->setStartPoint(vertexArray[i]); pAcadLine->setEndPoint(vertexArray[i + 1]); pAcadLine->setNormal(normal()); if ((es = ent->intersectWith(pAcadLine, intType, points)) != Acad::eOk) { delete pAcadLine; return es; } delete pAcadLine; } } return es; } Acad::ErrorStatus AsdkPoly::intersectWith( const AcDbEntity* ent, AcDb::Intersect intType, const AcGePlane& projPlane, AcGePoint3dArray& points, int /*thisGsMarker*/, int /*otherGsMarker*/) const { assertReadEnabled(); Acad::ErrorStatus es = Acad::eOk; if (ent == NULL) return Acad::eNullEntityPointer; // // // // // // // // if The idea is to intersect each side of the polygon with the given entity and return all the points. For non-R12-entities, with intersection methods defined, we call that method for each of the sides of the polygon. For R12-entities, we use the locally defined intersectors, since their protocols are not implemented.
(ent->isKindOf(AcDbLine::desc())) { if ((es = intLine(this, AcDbLine::cast(ent), intType, &projPlane, points)) != Acad::eOk) { return es; } } else if (ent->isKindOf(AcDbArc::desc())) { if ((es = intArc(this, AcDbArc::cast(ent), intType, &projPlane, points)) != Acad::eOk) { return es; }
370
Chapter 13
} else if (ent->isKindOf(AcDbCircle::desc())) { if ((es = intCircle(this, AcDbCircle::cast(ent), intType, &projPlane, points)) != Acad::eOk) { return es; } } else if (ent->isKindOf(AcDb2dPolyline::desc())) { if ((es = intPline(this, AcDb2dPolyline::cast(ent), intType, &projPlane, points)) != Acad::eOk) { return es; } } else if (ent->isKindOf(AcDb3dPolyline::desc())) { if ((es = intPline(this, AcDb3dPolyline::cast(ent), intType, &projPlane, points)) != Acad::eOk) { return es; } } else { AcGePoint3dArray vertexArray; if ((es = getVertices3d(vertexArray)) != Acad::eOk) { return es; } if (intType == AcDb::kExtendArg || intType == AcDb::kExtendBoth) { intType = AcDb::kExtendThis; } AcDbLine *pAcadLine; for (int i = 0; i < vertexArray.length() - 1; i++) { pAcadLine = new AcDbLine(); pAcadLine->setStartPoint(vertexArray[i]); pAcadLine->setEndPoint(vertexArray[i + 1]); pAcadLine->setNormal(normal()); if ((es = ent->intersectWith(pAcadLine, intType, projPlane, points)) != Acad::eOk) { delete pAcadLine; return es; } delete pAcadLine; }
371
// All the points that we selected in this process are on // the other curve; we are dealing with apparent // intersection. If the other curve is 3D or is not // on the same plane as poly, the points are not on // poly. // // In this case, we need to do some more work. Project the // points back onto the plane. They should lie on // the projected poly. Find points on real poly // corresponding to the projected points. // AcGePoint3d projPt, planePt; AcGePoint3dArray pts; AcGeLine3d line; AcGePlane polyPlane; AcDb::Planarity plnrty; getPlane(polyPlane,plnrty); for (i = 0; i < points.length(); i++) { // Define a line starting from the projPt and // along the normal. Intersect the polygon with // that line. Find all the points and pick the // one closest to the given point. // projPt = points[i].orthoProject(projPlane); line.set(projPt, projPlane.normal()); if ((es = intLine(this, line, pts)) != Acad::eOk) { return es; } planePt = projPt.project(polyPlane, projPlane.normal()); points[i] = pts[0]; double length = (planePt - pts[0]).length(); double length2; for (int j = 1; j < pts.length(); j++) { if ((length2 = (planePt - pts[j]).length()) < length) { points[i] = pts[j]; length = length2; } } } } return es; }
372
Chapter 13
Each custom entity is expected to be able to intersect with native entities. Native entities are the entities defined in AutoCAD, for example, AcDbLine, AcDbEllipse, and AcDbSpline. If the intersectWith() function of your custom entity is called with another entity that is not a native entity, you need to explode your custom entity (for example, by using the explode() function) to a set of recognizable native entities, then turn around and call intersectWith() on the entity that came in as an argument to your intersectWith() function. Because everyone is expected to be able to intersect with native entities, the entity in the argument would be able to intersect with your exploded version.
During this process, you need to be careful about how you call the
intersectWith() function of the argument and how you interpret the points
that are the results of intersection. For example, if the intersection type was
kExtendArg, you would want to change it to kExtendThis before calling intersectWith() on the argument. Similarly, if the intersection is an appar-
not necessarily on your entity. Youre supposed to return the intersection points on your entity; therefore, you need to project the points back onto the projection plane (where they will lie on your projected entity) and then project them back onto your entity before returning.
Exploding an Entity
You must override the explode() function of a custom entity for the AutoCAD commands BHATCH and EXPLODE to work. Your explode() function should break the entity down into less complex entities. If the resulting entities are not native entities, your function should return eExplodeAgain. This will cause BHATCH to recursively call the explode() function on the entities that you return, until they have been reduced to native entities. The native entities upon which BHATCH can operate directly are AcDb2dPolyline,
373
AcDb3dPolyline, AcDbPolyline, AcDbText, AcDbMText, AcDbShape, AcDbTrace, AcDbSolid, AcDbFace, AcDbViewport, AcDbFcf, AcDbDimension, AcDbRegion, AcDbBlockReference, and AcDbHatch.
col extension class allows you to copy color, layer, linetype, and linetype scale properties from one entity to another. It is recommended that AcDbMatchProperties be implemented as a protocol extension class for all custom objects derived from AcDbEntity, to provide full support for MATCHPROP. If the default protocol extension class is overridden with AcDbMatchProperties, it must include functions to copy the base class properties as well.
Using AcEdJig
The AcEdJig class is used to perform drag sequences, usually to acquire, create, edit, and add a new entity to the database. If you are deriving a new entity class, you will usually want to implement your own version of AcEdJig. This class enables the AutoCAD user to define certain aspects of an entity using a pointing device, and it gives the programmer access to the AutoCAD drag mechanism. (The class takes its name from jig, a device used to hold a machine part that is being bent or molded in place.) Each time the user moves the pointing device, your application acquires a geometric value and you need to provide graphical feedback for the pointing device event. This feedback consists of two elements:
I I
A cursor of the specified type Entity graphics, returned by your AcEdJig object
374
Chapter 13
AcEdJig is generally used on entities that do not reside in the database. It operates on a single entity. Do not use AcEdJig to operate on complex entities such as polylines.
which acquires a geometric value (an angle, a distance, or a point) AcEdJig::update(), which analyzes the geometric value and stores it or updates the entity AcEdJig::entity(), which returns a pointer to the entity to be regenerated
To use the AcEdJig class 1 Create an instance of your derived class of AcEdJig. 2 Establish your prompt text with the AcEdJig::setDispPrompt() function. 3 Call the AcEdJig::drag() function, which controls the drag loop and in turn calls the sampler(), update(), and entity() functions until the user ends the drag sequence. 4 Check within the sampler() function: If you are using a prompt with keywords, invoke the
AcEdJig::setKeywordList() function.
can typically be omitted.) If desired, place limitations on the drag sequence and the return value using the AcEdJig::setUserInputControls() function. 5 Check the return status from the AcEdJig::drag() function and commit the changes of the drag sequence. If the user canceled or aborted the process, perform the appropriate cleanup.
Using AcEdJig
375
Drag Loop
After you have set the display prompt for the drag sequence, you call the AcEdJig::drag() function, which performs the drag loop until the user presses ENTER or the space bar, or picks with the pointing device. The following list describes the sequence of the drag loop: 1 The drag loop receives an event. 2 It calls the AcEdJig::sampler() function. The sampler() function sets up the keyword list (if any) with a call to the AcEdJig::setKeywordList() function, a special cursor type (if desired) with a call to the AcEdJig::setSpecialCursorType() function, and any user input controls with a call to the AcEdJig::setUserInputControls() function. Next, it calls one of the acquireXXX() functions to obtain a geometric value (an angle, distance, or point). The function always returns immediately after polling the current pointing device position. 3 Your sampler() function should check to see if there is any change in the geometric value sampled. If there is no change, your sampler() function should return kNoChange and return to step 1. This will allow the image to complete its last update on screen. This is especially important for images containing curves. 4 Even if the geometric value sampled has changed, your sampler() function can return kNoChange (so that the image is not updated) and return to step 1. If the sampled value has changed and the image needs to be updated, proceed to step 5. 5 The dragger calls the AcEdJig::update() function, using the acquired geometric value to update the entity. 6 The dragger then calls the AcEdJig::entity() function, passing in a pointer to be set to the address of the entity to be regenerated. Next, the dragger calls the worldDraw() function on the entity to regenerate it. 7 Return to step 1 unless the current dragger event was generated by selecting with the pointing device, pressing CANCEL, or issuing a string termination character to end dragging.
376
Chapter 13
<myJig>::sampler()
TS=AcEdJig::acquireXXX()
Yes TS==kNoChange
No
Do you need to update the drag image? Yes return TS other than KNoChange
No
<myJig>::update()
<myJig>::entity()->worldDraw()
No
Yes
Using AcEdJig
377
Keyword List
If you have keywords that are meaningful in the drag sequence, use the following function to specify them:
void AcEdJig::setKeywordList(const char* kyWdList);
The keyword list is a single string in which each keyword is separated from the others by spaces. The required characters are capitalized, and the remainder of each keyword is lowercase. For example, Close Undo specifies two keywords. The DragStatus enum associates values with each keyword. The first keyword is kKW1, the second is kKW2, and so on. When you implement your AcEdJig class, you can use these return values in your implementations of the sampler(), update(), and entity() functions.
Display Prompt
The display prompt is the text shown on the command line during the drag sequence. Use the following function to set the display prompt:
void AcEdJig::setDispPrompt(const char* prompt);
Cursor Types
If you want to set a special cursor type, use the following function:
void AcEdJig::setSpecialCursorType(AcEdJig::CursorType);
378
Chapter 13
Cursor types
Cursor kCrosshair kRectCursor Description Crosshairs aligned with the user coordinate system (UCS) Rectangular window cursor aligned with the display coordinate system Same as kCrosshair, except also displays a rubber band from the base point OSNAP cursor; similar to kEntitySelect cursor, except its size is controlled by the system variable $APERTURE Crosshairs aligned with the display coordinate system No cursor graphics; only entity graphics are displayed Single entity pick box; the entity is not actually selected in this case. Entity selection is handled with acedSSGet() Rectangle aligned with the UCS (can be a parallelogram on the display) Same as kEntitySelect, except the pick box is suppressed in perspective view; used when a precise geometric point is needed along with the picked entity Default cursor; what the cursor looks like between commands Displays the arrow cursor used for dialog boxes in AutoCAD
kRubberBand
kTargetBox
kParallelogram
kEntitySelectNoPersp
kPkfirstOrGrips
kArrow
This step is optional. The acquirePoint() functions allow you to specify this alternate cursor. Setting the cursor type for the acquireDist() and acquireAngle() functions has no effect. The acquireXXX() functions will select a cursor for you if you dont explicitly specify one.
Using AcEdJig
379
The user input controls put limitations on the drag sequence or the type of acceptable return value (for example, by not allowing negative responses, by not allowing a zero response, or by restricting the input value to a 2D coordinate). They also specify how various user actions affect the drag sequence. For example, kAcceptMouseUpAsPoint specifies that releasing the mouse button indicates the input value. The user input controls can be one of the following values:
I I I I I I I I I I I
kGovernedByOrthoMode kNullResponseAccepted kDontEchoCancelForCtrlC kDontUpdateLastPoint kNoDwgLimitsChecking kNoZeroResponseAccepted kNoNegativeResponseAccepted kAccept3dCoordinates kAcceptMouseUpAsPoint kAnyBlankTerminatesInput kInitialBlankTerminatesInput
Once you have established the keyword list, cursor type, and user input controls, your sampler() function should call one of the following functions of AcEdJig to obtain an angle, a distance, or a point:
DragStatus AcEdJig::acquireAngle(double &ang); DragStatus AcEdJig::acquireAngle( double &ang, const AcGePoint3d &basePt); DragStatus AcEdJig::acquireDist(double &dist); DragStatus AcEdJig::acquireDist( double &dist, const AcGePoint3d &basePt); DragStatus AcEdJig::acquirePoint(AcGePoint3d &point); DragStatus AcEdJig::acquirePoint( AcGePoint3d &point, const AcGePoint3d &basePt);
After invoking the sampler() function, you can perform any further analysis on the obtained geometric value and drag status. You will also want to cache the return value in a static variable for access in your update() or entity() functions.
380
Chapter 13
The update() function is typically where you modify the entity, usually by applying a transformation to a source entity. The entity() function returns a pointer to the entity to be regenerated.
Sample Code
This example creates a class that enables the user to create an ellipse by picking its center point and then dragging to select the desired major axis and minor axis lengths. During the drag operations, the user will be able to see what the ellipse looks like at any time.
NOTE If the user tries to make the minor axis longer than the major axis, the
ellipse will end up as a circle because the radius ratio cannot be larger than 1.0.
class AsdkEllipseJig : public AcEdJig // This class allows the user to create an ellipse by // picking its center point and then dragging to select the // desired major axis and minor axis lengths. During the // drag operations, the user will be able to visually see // what the ellipse looks like at any time. // { public: AsdkEllipseJig(const AcGePoint3d&, const AcGeVector3d&); void doIt(); virtual DragStatus sampler(); virtual Adesk::Boolean update(); virtual AcDbEntity* entity() const; private: AcDbEllipse *mpEllipse; AcGePoint3d mCenterPt, mAxisPt; AcGeVector3d mMajorAxis, mNormal; double mRadiusRatio; int mPromptCounter; };
Using AcEdJig
381
// The following defines the constructor that accepts a point to be // used as the centerpoint of the ellipse and the current UCS normal // vector to be used as the normal for the ellipse. It also // initializes the radius ratio to a small value so that during // selection of the major axis, the ellipse will appear as a line. // The prompt counter is also initialized to 0. // AsdkEllipseJig::AsdkEllipseJig( const AcGePoint3d& pt, const AcGeVector3d& normal) : mCenterPt(pt), mNormal(normal), mRadiusRatio(0.00001), mPromptCounter(0) { } // This function creates an AcDbEllipse object and gets the // jig started acquiring the necessary info to properly fill // it in. // void AsdkEllipseJig::doIt() { mpEllipse = new AcDbEllipse; // Get the major axis vector from the user. // At this time, mPromptCounter == 0. // setDispPrompt("\nEllipse major axis: "); AcEdJig::DragStatus stat = drag(); // Get the ellipse's radius ratio. // mPromptCounter++; // now == 1 setDispPrompt("\nEllipse minor axis: "); stat = drag(); // Now add the ellipse to the database's current space. // append(); } // This function is called by the drag function to // acquire a sample input. // AcEdJig::DragStatus AsdkEllipseJig::sampler() { DragStatus stat; setUserInputControls((UserInputControls) (AcEdJig::kAccept3dCoordinates | AcEdJig::kNoNegativeResponseAccepted | AcEdJig::kNoZeroResponseAccepted));
382
Chapter 13
if (mPromptCounter == 0) { // Aquire the major axis endpoint. // // If the newly acquired point is the same as it was // in the last sample, then we return kNoChange so the // AsdkEllipseJig::update() function will not be called // and the last update call will be able to finish, thus // allowing the ellipse to fully elaborate. // static AcGePoint3d axisPointTemp; stat = acquirePoint(mAxisPt, mCenterPt); if (axisPointTemp != mAxisPt) axisPointTemp = mAxisPt; else if (stat == AcEdJig::kNormal) return AcEdJig::kNoChange; } else if (mPromptCounter == 1) { // Aquire the distance from ellipse center to minor // axis endpoint. This will be used to calculate the // radius ratio. // // If the newly acquired distance is the same as it was // in the last sample, then we return kNoChange so the // AsdkEllipseJig::update() function will not be called // and the last update call will be able to finish, thus // allowing the ellipse to fully elaborate. // static double radiusRatioTemp = -1; stat = acquireDist(mRadiusRatio, mCenterPt); if (radiusRatioTemp != mRadiusRatio) radiusRatioTemp = mRadiusRatio; else if (stat == AcEdJig::kNormal) return AcEdJig::kNoChange; } return stat; } // This function is called to update the entity based on the // input values. // Adesk::Boolean AsdkEllipseJig::update() { switch (mPromptCounter) { case 0: // At this time, mAxis contains the value of one // endpoint of the desired major axis. The // AcDbEllipse class stores the major axis as the // vector from the center point to where the axis // intersects the ellipse path (such as half of the true // major axis), so we already have what we need. // mMajorAxis = mAxisPt - mCenterPt; break;
Using AcEdJig
383
case 1: // Calculate the radius ratio. mRadiusRatio // currently contains the distance from the ellipse // center to the current pointer position. This is // half of the actual minor axis length. Since // AcDbEllipse stores the major axis vector as the // vector from the center point to the ellipse curve // (half the major axis), to get the radius ratio we // simply divide the value currently in mRadiusRatio // by the length of the stored major axis vector. // mRadiusRatio = mRadiusRatio / mMajorAxis.length(); break; } // Now update the ellipse with the latest setting. // mpEllipse->set(mCenterPt, mNormal, mMajorAxis, mRadiusRatio); return Adesk::kTrue; } // This function must be implemented to return a pointer to // the entity being manipulated by the jig. // AcDbEntity* AsdkEllipseJig::entity() const { return mpEllipse; } // This function uses the AcEdJig mechanism to create and // drag an ellipse entity. The creation criteria are // slightly different from the AutoCAD command. In this // case, the user selects an ellipse center point and // drags to visually select the major and minor axes // lengths. This sample is somewhat limited; if the // minor axis ends up longer than the major axis, then the // ellipse will just be round because the radius ratio // cannot be greater than 1.0. // void createEllipse() { // First, have the user select the ellipse center point. // We don't use the jig for this because there is // nothing to see yet. // AcGePoint3d tempPt; struct resbuf rbFrom, rbTo; acedGetPoint(NULL, "\nEllipse center point: ", asDblArray(tempPt));
384
Chapter 13
// The point we just got is in UCS coordinates, but // AcDbEllipse works in WCS, so convert the point. // rbFrom.restype = RTSHORT; rbFrom.resval.rint = 1; // from UCS rbTo.restype = RTSHORT; rbTo.resval.rint = 0; // to WCS acedTrans(asDblArray(tempPt), &rbFrom, &rbTo, Adesk::kFalse, asDblArray(tempPt)); // Now you need to get the current UCS z-Axis to be used // as the normal vector for the ellipse. // AcGeVector3d x = acdbHostApplicationServices()->workingDatabase()->ucsxdir(); AcGeVector3d y = acdbHostApplicationServices()->workingDatabase()->ucsydir(); AcGeVector3d normalVec = x.crossProduct(y); normalVec.normalize(); // Create an AsdkEllipseJig object passing in the // center point just selected by the user and the normal // vector just calculated. // AsdkEllipseJig *pJig = new AsdkEllipseJig(tempPt, normalVec); // Now start up the jig to interactively get the major // and minor axes lengths. // pJig->doIt(); // Now delete the jig object, since it is no longer needed. // delete pJig; } void initApp() { acedRegCmds->addCommand("ASDK_VISUAL_ELLIPSE", "ASDK_VELLIPSE", "VELLIPSE", ACRX_CMD_MODAL, createEllipse); } void unloadApp() { acedRegCmds->removeGroup("ASDK_VISUAL_ELLIPSE"); }
Using AcEdJig
385
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(); } return AcRx::kRetOK; }
386
Chapter 13
Part 4
Specialized Topics
Chapter 14 Chapter 15 Chapter 16 Chapter 17 Chapter 18 Chapter 19 Chapter 20 Chapter 21 Chapter 22 Chapter 23 Chapter 24
Proxy Objects 389 Notification 395 The Multiple Document Interface 415 Transaction Management 447 Deep Cloning 465 Protocol Extension 509 ObjectARX Global Utility Functions 519 Input Point Processing 555 Application Configuration 583 Object Enablers 589 DesignXML APIs 595
387
388
Proxy Objects
14
In this chapter
I Proxy Objects Defined I Proxy Object Life Cycle I User Encounters with Proxy
This section describes proxy objects and the conditions of their creation. It also discusses user encounters with proxies, displaying proxy entities, and editing proxy entities. The effect of unloading an application on custom objects and entities is discussed as well.
Objects
I Displaying Proxy Entities I Editing Proxy Entities I Unloading an Application
389
390
Chapter 14
Proxy Objects
Under most circumstances, the proxy wrapper is shed when the drawing database is written to a file. The same binary object that was read in is written out. If the save operation involves converting the file type between DWG and DXF (for which the conversion function of the parent application is not present), the proxy wrapper is saved with the custom binary data as well. When the parent application is not loaded before writing to a file, the data is handled as follows:
I
If the input and output files are the same file type (both DWG or DXF), no translation operation is necessary, and the same data that was read in is written out. The data stored in the proxy object is written to the output file. If the input and output files differ in file type (that is, DWG in and DXF out, or vice versa), the format cannot be translated because the translation function defined by the parent application is not present. The entire proxy object is therefore written to the output file. When the file is subsequently read by AutoCAD, the proxy will either convert to a custom object (in the presence of the parent application), or remain a proxy in memory (in the absence of the parent application).
391
Users can control the display of proxy objects with the PROXYSHOW system variable, which has the following options: Proxy Objects
Options None shown Graphical representation Bounding box Value 1 2 3
392
Chapter 14
Proxy Objects
Note that kNoOperation means none of the other options listed here. You can logically OR PROXY_FLAG options to permit a combination of editing operations. As proxy entities only encapsulate data below the AcDbEntity base class level, any changes made to color, layer, linetype, linetype scale, and visibility will be written out as part of the proxy entity data. Rigid body transformations (such as move, scale, and rotate) cannot be applied until the parent application is present. When a transformation is applied to a proxy, the transformation is made to the graphics metafile, and a copy of the transformation matrix is saved in a custom record in the proxy entitys extension dictionary. If multiple transformations are performed, the matrix is updated to reflect the cumulative transformation. When the custom entity is returned to memory with its parent application, AutoCAD calls the entitys transformBy() function, passes it the transformation matrix data, and removes the custom data storage record from the extension dictionary. In effect, the transformation is deferred until the parent application is present to apply the transformation to the custom entity.
393
Unloading an Application
When an application is unloaded and the appropriate cleanup operations have been performed, custom objects and entities are transformed into proxy objects. For this to occur, all custom classes must be removed from ObjectARX at unload time with the deleteAcRxClass() function. For a description of the requirements for making an application unloadable, see chapter 3, ObjectARX Application Basics.
394
Notification
15
In this chapter
I Notification Overview I Using Reactors I Notification Use Guidelines
This section describes how you can create reactors that respond to different event types and register the reactors with the appropriate objects to receive notification.
395
Notification Overview
When an event occurs in the system, certain objects, called notifiers, automatically relay the event to other objects. For example, when a user copies, erases, or modifies an object or when a user issues an UNDO or REDO command, a corresponding notification for each event is automatically triggered. The objects receiving the events are called reactors. A reactor must be explicitly added to a notifiers reactor list before it can receive events from the notifier. A given notifier can have a number of reactors in its reactor list. The reactors class definition includes various notification functions. When an event occurs, the notifier automatically invokes the corresponding notification function of each reactor in its reactor list. To use a reactor in an application 1 Derive a new reactor class and implement the notification functions for the events your reactor will respond to. 2 Instantiate the reactor. 3 Add the reactor to the reactor list of the notifier. When finished using the reactor 1 Remove the reactor from the reactor lists of all notifiers to which it has been added. 2 Delete the reactor (unless it is a database-resident object). Using reactors requires creating subclasses of reactor classes or of AcDbObject classes. This section assumes you are familiar with the material presented in chapter 11, Deriving a Custom ObjectARX Class, and chapter 12, Deriving from AcDbObject.
Reactor Classes
Reactor classes are derived from AcRxObject, not AcDbObject. Because these reactors are not database objects, ownership does not apply to them and they dont have object IDs. Different kinds of reactors receive different types of notification events. A database reactor (derived from AcDbDatabaseReactor) receives events related to the state of the databasefor example, when an object is appended to the database, modified in the database, or erased. The reactors notifier is the database, so it is added to the reactor list of the AcDbDatabase. An object reactor (derived from AcDbObjectReactor) responds to events at the object
396
Chapter 15
Notification
level, such as copying, erasing, or modifying an object. It can be added to the reactor list of any AcDbObject. An editor reactor (derived from AcEditorReactor) responds to AutoCAD --specific events such as loading and unloading a drawing, starting or ending a command, and other kinds of user interaction. The AcEditor object is the only notifier for an AcEditorReactor. The following is the class hierarchy for reactor classes:
AcApDocManagerReactor AcApLongTransactionReactor AcDbDatabaseReactor AcDbObjectReactor AcDbEntityReactor AcDbRasterImageDefFileAccessReactor AcDbRasterImageDefTransReactor AcEdInputContextReactor AcRxDLinkerReactor AcRxEventReactor AcEditorReactor AcTransactionReactor
Notification Overview
397
3 Add the object reactor to the database and give it an owner, preferably a container object, so that it is filed out correctly. 4 Add the object reactor to the notifiers reactor list using the addPersistentReactor() function. This function requires you to pass in the object ID of the object reactor you created in step 2. AutoCAD will delete the object reactor, because it is a database object.
NOTE When you copy an object, any persistent reactors attached to the
object are copied as well. Transient reactor attachments are not copied when an object is copied.
Using Reactors
To use a transient reactor, derive a new class from one of the following base classes: AcRxDLinkerReactor Monitors ObjectARX application loading and unloading. AcEditorReactor Monitors AutoCAD-specific events such as commands and AutoLISP evaluations. AcDbDatabaseReactor Monitors creation, modification, and erasure of database objects. AcTransactionReactor Monitors events related to the transaction manager start, abort, or end of a transaction. AcDbObjectReactor Monitors events pertaining to a specific database objectcreation, modification, erasure. AcDbEntityReactor Monitors an extra, entity-specific event, such as modified graphics.
398
Chapter 15
Notification
In most cases, only standard C++ techniques are needed for creating new transient reactor classes. The ObjectARX macros, which create a class descriptor object for the new reactor class, are usually not used to derive from these reactor classes. Each parent class contains a set of virtual notification functions that can be implemented by your new derived class. For example, the AcDbObjectReactor class contains the following notification functions that respond to object-related events:
I I I I I I I I I I I I
cancelled() copied() erased() goodbye() openedForModify() modified() subObjModified() modifyUndone() modifiedXData() unappended() reappended() objectClosed()
Each of these functions requires a pointer to the notifier of the event. The base class, AcDbObjectReactor, has NULL implementations for all of these functions. In your derived reactor class, implement the functions corresponding to the type of notifications you are interested in. Then instantiate the reactor and add it to any number of database objects using the AcDbObject::addReactor() function. To add or delete a transient reactor to a notifier object, the object can be open in any state (read, write, or notify). Adding or deleting a transient reactor is not monitored by the undo mechanism. (For persistent reactors, the notifier object must be opened for write, and adding or removing the reactors is monitored by the undo mechanism.) Because you created the transient reactor object, you are also responsible for deleting it. When an object is erased, for example, it calls the corresponding erased() notification function on each reactor in its list. If you have implemented an erased() function for your reactor, that function will be called by the database object, and you can then take whatever special action is appropriate for your application when an object is erased.
Using Reactors
399
that object. To monitor these events, use the equivalent notifications on the database, not just the object:
AcDbDatabaseReactor::objectErased() AcDbDatabaseReactor::objectUnappended() AcDbDatabaseReactor::objectReappended()
Custom Notifications
When modifications are committed on an object, the object is closed, which invokes the subClose() virtual function of AcDbObject. In the override of this function in your custom class, you can notify others that you are closing after modification. These notifications should be your custom notification in the form of custom functions on your class. Do not use the notifications provided on AcDbObjectReactor for this purpose.
400
Chapter 15
Notification
Using Reactors
401
// Called whenever an object is erased from the database. // void AsdkDbReactor::objectErased(const AcDbDatabase* db, const AcDbObject* pObj, Adesk::Boolean pErased) { if (pErased) { printDbEvent(pObj, "objectErased"); gEntAcc--; } else { printDbEvent(pObj, "object(Un)erased"); gEntAcc++; } acutPrintf(" Db==%lx\n", (long) db); acutPrintf("Entity Count = %d\n", gEntAcc); } // Prints the message passed in by pEvent; then // calls printObj() to print the information about // the object that triggered the notification. // void printDbEvent(const AcDbObject* pObj, const char* pEvent) { acutPrintf(" Event: AcDbDatabaseReactor::%s ", pEvent); printObj(pObj); } // Prints out the basic information about the object pointed // to by pObj. // void printObj(const AcDbObject* pObj) { if (pObj == NULL) { acutPrintf("(NULL)"); return; } AcDbHandle objHand; char handbuf[17]; // Get the handle as a string. // pObj->getAcDbHandle(objHand); objHand.getIntoAsciiBuffer(handbuf); acutPrintf( "\n (class==%s, handle==%s, id==%lx, db==%lx)", pObj->isA()->name(), handbuf, pObj->objectId().asOldId(), pObj->database()); }
402
Chapter 15
Notification
// Adds a reactor to the database to monitor changes. // This can be called multiple times without any ill // effect because subsequent calls will be ignored. // void watchDb() { if (gpDbr == NULL) { gpDbr = new AsdkDbReactor(); } acdbHostApplicationServices()->workingDatabase()->addReactor( gpDbr); acutPrintf( " Added Database Reactor to " "acdbHostApplicationServices()->workingDatabase().\n"); } // Removes the database reactor. // void clearReactors() { if (acdbHostApplicationServices()->workingDatabase() != NULL) { acdbHostApplicationServices()->workingDatabase( )->removeReactor(gpDbr); delete gpDbr; gpDbr = NULL; } } // ObjectARX entry point function // AcRx::AppRetCode acrxEntryPoint(AcRx::AppMsgCode msg, void* appId) { switch (msg) { case AcRx::kInitAppMsg: acrxDynamicLinker->unlockApplication(appId); acrxDynamicLinker->registerAppNotMDIAware(appId); acedRegCmds->addCommand("ASDK_NOTIFY_TEST", "ASDK_WATCH", "WATCH", ACRX_CMD_TRANSPARENT, watchDb); acedRegCmds->addCommand("ASDK_NOTIFY_TEST", "ASDK_CLEAR", "CLEAR", ACRX_CMD_TRANSPARENT, clearReactors); break; case AcRx::kUnloadAppMsg: clearReactors(); acedRegCmds->removeGroup("ASDK_NOTIFY_TEST"); break; } return AcRx::kRetOK; }
Using Reactors
403
This mechanism allows you to define dependencies within a database that are preserved when the database is saved and recreated whenever it is reinstantiated. Use the ObjectARX macros when you derive the new object reactor class so that a class descriptor object will be created for it. (If you dont use the ObjectARX macros, your class will inherit the class description of its parent when it is saved, and its identity will be lost when the file is read in.)
404
Chapter 15
Notification
// Is it a persistent reactor? // if (acdbIsPersistentReactor(pSomething)) { persObjId = acdbPersistentReactorObjectId( pSomething); acutPrintf("\n\nPersistent reactor found."); // Echo the keyname to the user. // char *keyname = NULL; getPersReactorKey(keyname, persObjId); if (keyname) { acutPrintf("\nThis is the reactor named %s", keyname); free (keyname); } // Open it up and see if its one of ours. If it is, // fire the custom notification. // if ((retStat = acdbOpenAcDbObject(pPersReacObj, persObjId, AcDb::kForNotify)) != Acad::eOk) { acutPrintf("\nFailure for" " openAcDbObject: retStat==%d\n", retStat); return; } AsdkPersReactor *pTmpPers; if ((pTmpPers = AsdkPersReactor::cast((AcRxObject*) pPersReacObj)) != NULL) { pTmpPers->custom(); } pPersReacObj->close(); } else { // Or is it transient? // pObjReactor = (AcDbObjectReactor *) (pReactors->at(i)); acutPrintf("\n\nTransient Reactor found"); // Report what kind we found. // if (pObjReactor->isKindOf( AsdkSimpleObjReactor::desc())) { acutPrintf(" of type" " AsdkSimpleObjReactor");
Using Reactors
405
} else if (pObjReactor->isKindOf( AcDbEntityReactor::desc())) { acutPrintf(" of type" " AcDbEntityReactor"); } else if (pObjReactor->isKindOf( AcDbObjectReactor::desc())) { acutPrintf(" of type" " AcDbObjectReactor"); } else { acutPrintf(" of unknown type."); } } } } else { acutPrintf("\nThis entity has no reactors.\n"); }
406
Chapter 15
Notification
// This function is called every time the line it's // "watching" is modified. When it's called, it opens the // other line of the pair and changes that line's length to // match the new length of the line that's just been // modified. // void AsdkObjectToNotify::modified(const AcDbObject* pObj) { AcDbLine *pLine = AcDbLine::cast(pObj); if (!pLine) { const char* cstr = pObj->isA()->name(); acutPrintf("This is a %s.\n", cstr); acutPrintf("I only work with lines. Sorry.\n"); return; } acutPrintf("\nReactor attached to %lx calling %lx.\n", pLine->objectId(), mId); // This open will fail during notification caused by a // reactor being added to the entity or when this // notification is in reaction to a change due to the // other line's reactor changing this line. This will // properly prevent an infinite recursive loop // between the two lines and their reactors. // AcDbLine *pLine2; if (acdbOpenObject((AcDbObject*&)pLine2, mId, AcDb::kForWrite) == Acad::eOk) { // Get length of line entity we're being notified // has just been modified. // AcGePoint3d p = pLine->startPoint(); AcGePoint3d q = pLine->endPoint(); AcGeVector3d v = q-p; double len = v.length(); // update other entity to match: // p = pLine2->startPoint(); q = pLine2->endPoint(); v = q-p; v = len * mFactor * v.normal(); pLine2->setEndPoint(p+v); pLine2->close(); } }
Using Reactors
407
// Files an object's information in. // Acad::ErrorStatus AsdkObjectToNotify::dwgInFields(AcDbDwgFiler* filer) { assertWriteEnabled(); AcDbObject::dwgInFields(filer); filer->readItem(&mFactor); filer->readItem((AcDbSoftPointerId*) &mId); return filer->filerStatus(); } // Files an object's information out. // Acad::ErrorStatus AsdkObjectToNotify::dwgOutFields(AcDbDwgFiler* filer) const { assertReadEnabled(); AcDbObject::dwgOutFields(filer); filer->writeItem(mFactor); filer->writeItem((AcDbSoftPointerId&)mId); return filer->filerStatus(); } // Files an object's information in from DXF and AutoLISP. // Acad::ErrorStatus AsdkObjectToNotify::dxfInFields(AcDbDxfFiler* filer) { assertWriteEnabled(); Acad::ErrorStatus es; if ((es = AcDbObject::dxfInFields(filer)) != Acad::eOk) { return es; } // Check if we're at the right subclass data marker. // if(!filer->atSubclassData("AsdkObjectToNotify")) { return Acad::eBadDxfSequence; } struct resbuf rbIn; while (es == Acad::eOk) { if ((es = filer->readItem(&rbIn)) == Acad::eOk) { if (rbIn.restype == AcDb::kDxfReal) { mFactor = rbIn.resval.rreal; } else if (rbIn.restype == AcDb::kDxfSoftPointerId) { // ObjectIds are filed in as ads_names. // acdbGetObjectId(mId, rbIn.resval.rlname);
408
Chapter 15
Notification
} else { // invalid group return(filer->pushBackItem()); } } } return filer->filerStatus(); } // Files an object's information out to DXF and AutoLISP. // Acad::ErrorStatus AsdkObjectToNotify::dxfOutFields(AcDbDxfFiler* filer) const { assertReadEnabled(); AcDbObject::dxfOutFields(filer); filer->writeItem(AcDb::kDxfSubclass, "AsdkObjectToNotify"); filer->writeItem(AcDb::kDxfReal, mFactor); filer->writeItem(AcDb::kDxfSoftPointerId, mId); return filer->filerStatus(); } // Creates two lines and two AsdkObjectToNotify objects and // ties them all together. // void assocLines() { AcDbDatabase *pDb = acdbHostApplicationServices()->workingDatabase(); AcDbObjectId aId, bId; AcDbLine *pLineA = new AcDbLine; pLineA->setDatabaseDefaults(pDb); pLineA->setStartPoint(AcGePoint3d(1, 1, 0)); pLineA->setEndPoint(AcGePoint3d(2, 1, 0)); addToModelSpace(aId, pLineA); acutPrintf( "Line A is %lx from 1,1 to 2,1.\n", pLineA->objectId()); AcDbLine *pLineB = new AcDbLine; pLineB->setDatabaseDefaults(pDb); pLineB->setStartPoint(AcGePoint3d(1, 2, 0)); pLineB->setEndPoint(AcGePoint3d(2, 2, 0)); addToModelSpace(bId, pLineB); acutPrintf("Line B is %lx from 1,2 to 2,2.\n", pLineB->objectId()); // Open the named object dictionary, and check if there is // an entry with the key "ASDK_DICT". If not, create a // dictionary and add it. // AcDbDictionary *pNamedObj; AcDbDictionary *pNameList; pDb->getNamedObjectsDictionary(pNamedObj, AcDb::kForWrite);
Using Reactors
409
if (pNamedObj->getAt("ASDK_DICT", (AcDbObject*&)pNameList, AcDb::kForWrite) == Acad::eKeyNotFound) { pNameList = new AcDbDictionary; AcDbObjectId DictId; pNamedObj->setAt("ASDK_DICT", pNameList, DictId); } pNamedObj->close(); // Create the AsdkObjectToNotify for line A. // AsdkObjectToNotify *pObj = new AsdkObjectToNotify(); pObj->eLinkage(bId); AcDbObjectId objId; if ((pNameList->getAt("object_to_notify_A", objId)) == Acad::eKeyNotFound) { pNameList->setAt("object_to_notify_A", pObj, objId); pObj->close(); } else { delete pObj; acutPrintf("object_to_notify_A already exists\n"); } // Set up persistent reactor link between line A // and AsdkObjectToNotify. // pLineA->addPersistentReactor(objId); pLineA->close(); // Create the AsdkObjectToNotify for line B. // pObj = new AsdkObjectToNotify(); pObj->eLinkage(aId); if ((pNameList->getAt("object_to_notify_B", objId)) == Acad::eKeyNotFound) { pNameList->setAt("object_to_notify_B", pObj, objId); pObj->close(); } else { delete pObj; acutPrintf("object_to_notify_B already exists\n"); } pNameList->close();
410
Chapter 15
Notification
// Set up persistent reactor link between line B // and AsdkObjectToNotify. // pLineB->addPersistentReactor(objId); pLineB->close(); } // Adds an entity to model space, but does not close // the entity. // void addToModelSpace(AcDbObjectId &objId, AcDbEntity* pEntity) { AcDbBlockTable *pBlockTable; AcDbBlockTableRecord *pSpaceRecord; acdbHostApplicationServices()->workingDatabase() ->getSymbolTable(pBlockTable, AcDb::kForRead); pBlockTable->getAt(ACDB_MODEL_SPACE, pSpaceRecord, AcDb::kForWrite); pBlockTable->close(); pSpaceRecord->appendAcDbEntity(objId, pEntity); pSpaceRecord->close(); return; } // This is the initialization function called from acrxEntryPoint() // during the kInitAppMsg case. This function is used to add // commands to the command stack. // void initApp() { acedRegCmds->addCommand("ASDK_ALINES", "ASDK_ALINES", "ALINES", ACRX_CMD_MODAL, assocLines); AsdkObjectToNotify::rxInit(); acrxBuildClassHierarchy(); } // This is the clean-up function called from acrxEntryPoint() during // the kUnloadAppMsg case. This function removes this application's // command set from the command stack. // void unloadApp() { acedRegCmds->removeGroup("ASDK_ALINES"); // Remove the AsdkObjectToNotify class from the ACRX // runtime class hierarchy. If this is done while the // database is still active, it should cause all objects // of class AsdkObjectToNotify to be turned into proxies. // deleteAcRxClass(AsdkObjectToNotify::desc()); }
Using Reactors
411
// ObjectARX entry point // 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(); } return AcRx::kRetOK; }
412
Chapter 15
Notification
The modified() notification of AcDbObjectReactor is an example of committime notification. Suppose an object is opened and a modification function is called on it. The modification function calls assertWriteEnabled() and all reactors receive the openedForModify() reaction. Subsequent modification functions on the object do not result in any further notification. When the object is finally closed, a modified() notification is sent. However, if the opener had chosen to call cancel() on the object instead of close(), a cancelled() notification would have been sent instead of the modified() notification. When you receive a deferred notification such as modified() at commit time, one of the arguments is a pointer to an object. At this time, the object is in a read-only state. You are not able to modify it until the end of the commit process. Attempting to modify an object before the commit process is finished causes AutoCAD to abort with the error message eWasNotOpenForWrite or eInProcessOfCommitting. You can use the following functions to check that the commit process is ended before you open the object for write:
AcDbObjectReactor::objectClosed(AcDbObjectId objId); AcTransactionReactor::transactionEnded( int numActiveAndSuccessful);
The objectClosed() notification is sent when the object is completely closed and the pointer is no longer valid. You can open the object again using the ID that is passed in the argument and operate on it. Be careful not to create infinite notification loops at this point. In the transactionEnded() notification, you can use the numActiveTransactions() function to query the transaction manager to see how many transactions are active. If there are no active transactions, the transaction has ended and all the objects in the transaction have been committed.
Using Reactors
413
Sometimes you may need to know when the outermost transaction is ending and the commit process is beginning. Use the following notification for this purpose:
AcTransactionReactor::endCalledOnOutermostTransaction()
When the outermost transaction ends, the commit process begins and close() is called on each object. You might receive objectClosed() notification as part of this close. However, its generally best not to act immediately. Instead, wait until the whole transaction is finished before you perform any operations on these objects.
Do not rely on the sequence of notification firing. You can count on commandWillStart() being fired before commandEnded(), and beginInsert() being fired before endInsert(). Relying on any other sequences might result in problems for your application if the sequence is changed when new notifications are introduced, or existing ones are rearranged.
Do not rely on the sequence of operations (function calls) between notifications. If you tie your application to this level of detail, your application may fail in future releases. Instead of relying on sequences, rely on notifications to indicate the state of the system. For example, when you receive erased(kTrue) notification on object A, it means that object A is erased. If you receive erased() notification on A followed by an erased() notification on B, it means only that both objects A and B are erased. The system will not guarantee that B will always be erased after A.
Do not use any user interaction functions in your notification callback function, such as acedCommand(), acedGetPoint(), acedGetKword(), or any other acedXXX() function.
Similar interpretations apply to notifications on database reactors, editor reactors, and transaction reactors.
414
Chapter 15
Notification
16
In this chapter
I MDI Overview I MDI Terminology I SDI System Variable I Levels of Compatibility I Interacting with Multiple
AutoCAD supports a multiple document interface (MDI) that allows you to have more than one drawing loaded at once in a single AutoCAD session. This section describes how to work with the MDI in your ObjectARX application.
Documents
I Document Event Notification I Application-Specific Document
Objects
I Nonreentrant Commands I Multi-Document Commands I Disabling Document Switching I Application Execution Context I Database Undo and Transaction
Management Facilities
I Document-Independent
Databases
I An MDI-Aware Example
Application
415
MDI Overview
AutoCAD supports a multiple document interface, and ObjectARX applications running within AutoCAD must operate properly in the MDI environment. Three principles must be observed for an ObjectARX application to provide MDI support:
I
An application must maintain document-specific state on the stack, in a database, or in a structure that can be indexed through the corresponding document pointer. All documents must be locked to be modified. Basic document locking is handled automatically for AutoCAD commands, ObjectARX commands, and AutoLISP functions. Modeless dialog and toolbar code, and any commands that need to work outside the active document must manually perform document locking. The application must maintain the relationships between documents and databases. The AutoCAD database library (AcDb) is unaware of documents and MDI, and should remain so.
Several architectural features of ObjectARX make supporting the MDI possible. These include separate execution contexts, data instances, document locking, and the document management classes. The following sections discuss these topics in more detail.
Data Instances
There is a separate instance of all data elements related to the database and current command processing state for each document. This includes the command processor, input processor, Visual LISP environment, databases, selection sets, and (most, but not all) system variables. The current command processing state is maintained in a heap. In addition to these built-in system elements, all ObjectARX applications must also maintain a documentspecific state either on the stack or in structures on the heap that correspond to each active document.
416
Chapter 16
Each document has its own current database, plus any number of xref databases and side databases. By default, a database is associated with one document, and it participates in the undo recording and playback for that document. However, databases may also be created independently of any document, in which case their undo state is either disabled or maintained by an applications custom undo facility.
Document Locking
All documents must be locked in order to be modified. Documents may also be locked to prevent code in other execution contexts from modifying them temporarily. Documents do not have to be locked to perform query (read) operations, and they are never prevented from performing query operations on other documents. Basic document locking is handled automatically for AutoCAD commands, ObjectARX commands, and AutoLISP functions. Modeless dialog and toolbar code, and any commands that need to work outside the active document must manually perform document locking. For more detailed information on document locking, see Explicit Document Locking on page 425.
AcApDocument
The AcApDocument object contains information such as the filename, MFC CDocument object, current database, and save format of the current drawing. Additionally, the AcApDocument object contains functions that query the status of document locking.
AcApDocManager
The AcApDocumentManager object contains all the document objects in an application (there is one document object for each drawing that is open and being edited). There is only one instance, which can be obtained using the macro acDocManager().
AcApDocumentIterator
The AcApDocumentIterator class provides the ability to iterate over the set of currently open AcApDocument objects.
MDI Overview
417
AcApDocManagerReactor
The AcApDocManagerReactor class provides a reactor that applications can use to track modifications in documents and switches between documents. For more information on the document management classes, see the ObjectARX Reference. An MDI-Aware Example Application on page 439 shows code using these classes.
MDI Terminology
The following section defines some commonly used terms to describe the multiple document interface. Active Document The document that has window focus, and receives the next user input event, unless the user is switching to (activating) another document. The active document is always the current document when processing user input, but programs can temporarily change the current document during some operations. The overall running program and associated objects that are common to all open documents, such as the MFC class CwinApp. There is one and only one application per invocation of an executable Windows program. Short for application execution context. See Execution Context, Application on page 420. Throughout this section, the term command refers to a variety of AutoCAD constructs. A command consists of a program sequence performed as a logical unit of work that can be requested by the user or one of the AutoCAD scripting engines. No matter what construct is used, a command can be undone independently of other actions performed during system operation. Specifically for the MDI API, a command is a sequence of code that begins by locking a document and ends by unlocking a document. In common cases, this locking and unlocking will be performed by ObjectARX but during other times the application must do the locking
Application
418
Chapter 16
and unlocking directly. All of the following AutoCAD constructs are commands:
I I
I I I
I I I
AutoCAD built-in commands. Built-in commands executed directly from the command processor, such as F2 for change screen. This includes function and control keys. AutoLISP function invocations, which can be defined either in AutoLISP or in an ObjectARX application using acedDefun(). External program commands defined in acad.pgp. AcEd-registered commands registered from AutoCAD. Actions taken from a modeless dialog window or some other external process, typically hosted by an ObjectARX application. A set of actions taken from an ActiveX application in an external process. Actions taken from VBA through the ActiveX interface. Right-click context menu invocations.
Command, Multi-Document
A set of commands that appears as one command to the user, during which the user can change the current document and a continued logical flow of user prompting is maintained. For example, if an active command is prompting for user input in the current document and the user switches the document, the application cancels the command in the old current document, and queues up a command to commence execution in the new current document. A command that cannot be executed in more than one document at a time. Nonreentrancy can be used for commands that should not be available for more than one document at a time, or when the demands of supporting multiple instantiation are too great to be worth the overhead. The standard input message polling mechanism in AutoCAD that facilitates combined keyboard and digitizer interaction. A separate command processor exists for each open document. The state of the
Command, Nonreentrant
Command Processor
MDI Terminology
419
command processor is maintained as an execution context. NOTE Commands that execute outside the context of a single document, such as modeless dialogs and toolbars posted by AutoCAD or ObjectARX applications, execute from within the application context. Current Document Programmatic requests can be made to cause a documents execution context to become active without the user actually perceiving the document as activated. This is a transient state only, used primarily by ActiveX and VBA applications. An AutoCAD drawing, specifically an instance of
AcDbDatabase. Although the database is part of a
Database
document, it is not synonymous with a document. Document A document consists of an MDI document window, an execution context, an associated editor state, and a single current database, plus any number of side databases that are opened in association with it. The current database is the one being displayed and edited via commands. The side databases are either used by xref or for general use. The document also includes system variables that are associated with a given drawing such as the current viewport variable. Documents are uniquely identified by their address, which is the AcApDocument* pointer. Synonymous with database. Usually synonymous with document, but sometimes includes its entire history since the document was opened, as well as the current state of the session. The command state that is active when new Windows messages are pending. It is independent from all
420
Chapter 16
document execution contexts. The following types of commands execute from this context:
I I I
External ActiveX Automation requests (such as Visual Basic) VBA Modeless dialog boxes
These types of commands typically work on the active document, although they are not bound to do so. The intent is to handle document locking and unlocking reasonably transparently for external ActiveX applications and VBA. However, ObjectARX applications posting modeless dialogs will be required to lock and unlock documents explicitly in order to interact with their databases. MDI-Aware ObjectARX applications (and ActiveX and COM applications, but not necessarily Visual LISP applications) that meet all criteria needed to be successfully executed in an MDI AutoCAD. These criteria are listed in the section MDI-Aware Level on page 423. ObjectARX applications can register themselves as MDI-Aware by calling
acrxDynamicLinker->registerAppMDIAware(pkt);
when receiving the kInitAppMsg within their acrxEntryPoint() function. Per-Application Per-Context A data structure that needs to exist only once per application. A data structure that needs to be instantiated and maintained for each execution context, including document execution contexts and the application execution context. The AutoCAD command processor is an example of a per-context instantiation. Any data structure, value, or other item that needs to be instantiated and maintained for each document. When the command processor in a given edit session has no active AutoCAD commands, ObjectARX commands, Visual LISP evaluations, ActiveX requests, AutoCAD menu macros, or VBA macros. At this point, the Command prompt is being displayed in the command window. Notice that modeless dialogs and
Per-Document Quiescent
MDI Terminology
421
toolbars can be posted, as they do not operate through the command processor. Session Undo Stack Synonymous with application. A repository of state recorded during an edit session, which is used to undo the edit session command by command, when requested. Databases are often associated with an undo stack, which is supplied by the host application. In the case of AutoCAD, databases are typically opened under only one document at a time, because undo stacks correspond to documents.
can only be set to values of 2 or 3 by AutoCAD, as applications are loaded and unloaded. Always check the current value of SDI before making changes. If the current value is 2 or 3, do not change it. Changing SDI to 1 from 0 will fail when AutoCAD has more than one document open.
All document lock checking is disabled when AutoCAD is running in any of the SDI modes.
422
Chapter 16
NOTE This compatibility mode and the SDI variable will be removed in the
next full release of AutoCAD.
Levels of Compatibility
Your ObjectARX application can have one of four levels of compatibility with MDI:
I I I I
SDI-Only is the minimum requirement, but MDI-Capable compatibility is recommended. The MDI supports an execution context per document and provides a facility for allowing a single execution context to be active when switching documents.
SDI-Only Level
This is the basic level of compatibility and is not sufficient for most robust applications. This level will not allow your application to run under MDI, but it should work without failing under SDI. To create an SDI-Only application 1 Register your application as SDI-Only by calling
AcRxDynamicLinker::registerAppNotMDIAware() in the kInitAppMsg handler of acrxEntryPoint().
2 Ensure that your application can take an AcRx::kUnloadAppMsg immediately following a return from processing the AcRx::kInitAppMsg. This will occur if your application doesnt register as being MDI-Aware, and AutoCAD already has multiple documents open.
MDI-Aware Level
This level of compatibility provides the minimal requirements for an ObjectARX application to function in an MDI environment without failing. An ObjectARX application must meet the requirements of this level of
Levels of Compatibility
423
compatibility before being able to legitimately set the SDI system variable to 0. The following list summarizes the minimum requirements.
I I I I
Applications cannot keep per-document data in global or static variables. If a command is executed from the application context, it must provide explicit document locking. AutoLISP-registered commands must be prepared to act within multiple AutoLISP states. Applications must register themselves as MDI-Aware during their AcRx::kInitAppMsg handler in acrxEntryPoint().
Per-Document Data
Applications cannot keep per-document data in global or static variables. This includes AcDbDatabase objects, AcDbObject objects, AcDbObjectId values, header variable values, document variable values, selection sets, and other document-specific information. Any occurrence of per-document data in global and static variables might be corrupted if your application is run in multiple concurrent edit sessions. To avoid corruption of data, you can encapsulate command behavior and data into classes. An instance of the class can be instantiated for each call to the command. As the command acquires document-specific data, it keeps its own per-instance copies of that data. Another solution is to encapsulate all of the global and static data into a structure or class. A copy of the data is instantiated for each document. A local pointer to the appropriate instance is set at each entry point in the application. The local pointer is then used to access the per-document data. Use the documentActivated() reactor to switch between instances of the encapsulated data. You can create the per-document data on an as-needed basis, or create it when the application is first loaded. If created on an as-needed basis, as the applications registered commands or reactors are called, the current document is determined and a query is made to get the documents data. If it is not found, it is created at that time. To create the per-document data when the application is first loaded, use an
AcApDocumentIterator in the AcRx::kInitAppMsg handler to get a list of all open documents. Then use AcApDocManagerReactor::documentCreated() to
know when to create additional per-document data for documents opened after the application is loaded.
424
Chapter 16
Whichever method is used to allocate the per-document data, the application must use the AcApDocManagerReactor::documentToBeDestroyed() reactor in order to know when to delete the data. Applications should also delete the remaining data during the AcRx::kUnloadAppMsg handler.
For read-only access to objects, locking is not necessary. For example, to open an AcDbObject for Acad::kForRead or to call acedGetVar(), locking not necessary.
Exclusive read
AcAp::kRead
ACRX_CMD_DOCREADLOCK ACRX_CMD_DOCEXCLUSIVELOCK
Using exclusive read mode prevents any other execution context from locking the document for write. This mode guarantees that the document will not be modified during the lock.
Levels of Compatibility
425
Exclusive write
AcAp::kXWrite
ACRX_CMD_DOCEXCLUSIVELOCK
AutoLISP Commands
AutoLISP commands must be aware that there is a separate AutoLISP stack for each document. This means that AutoLISP variables should be handled in the same way as other per-document global and static data. For more information, see Per-Document Data on page 424. The AcRx::kLoadDwgMsg message in acrxEntryPoint() is sent for each document open when an application is first loaded, and when any new documents are opened while the application is running. The messages are sent from the corresponding documents execution context.
Registering as MDI-Aware
Applications that have met all of these criteria must register themselves as MDI-Aware in their AcRx::kInitAppMsg handler in acrxEntryPoint(), by using the function acrxDynamicLinker->registerAppAsMDIAware(). Applications not registered as MDI-Aware cannot be loaded when more than one document is open. If such an application is loaded, additional documents cannot be opened.
MDI-Capable Level
Reaching this level of compliance involves making your code work as efficiently and naturally in MDI mode as it does (or did) in SDI mode.
I
Support document switching. Code sections comprising AcEd-registered commands that are invoked directly from those constructs will likely pause for user input, and are more likely susceptible to corruption when multiple sessions are being done. The most common case is to enter a command in one document,
426
Chapter 16
prompt for user input, then switch documents and enter the same command in a different document. As long as you are maintaining vital information on a per-open-document basis, your application should function properly. Otherwise, your code should disable document switching.
I
Maintain good performance. When it becomes time to look at performance, a lot of heap-resident pointer dereferencing to what were formerly static addresses can bog down program performance. In this case, the alternative would be to maintain a static memory buffer of elements of the current document, which would be scanned in from and written out to the documentspecific heap elements.
MDI-Enhanced Level
These additional steps will make your application integrate completely with the MDI.
I
I I I I
Consider migrating your AcEditorReactor::commandXxx() and AcEditorReactor::LispXxx() callbacks to work instead from AcDocMananagerReactor::documentLockModeWillChange() and AcDocMananagerReactor::documentLockModeChanged() notifications. Then they will account for application execution context operations that were previously difficult to detect by ObjectARX applications. Avoid using acedCommand(). Use AcApDocManager::setStringToExecute() instead, because it has a document parameter. Avoid using the kLoadDwg and kUnloadDwg cases, and use the documentToBeCreated() and documentToBeDestroyed() reactors instead. Support the document-independent database feature. For more information, see Document-Independent Databases on page 438. Support multi-document commands. For more information, see MultiDocument Commands on page 432. Support all events that occur within the application execution context. For more information, see Application Execution Context on page 435.
427
built-in AutoCAD command, an AcEd-registered command, or an AutoLISP function, subject to restrictions on use of acDocManager->curDocument().
NOTE The current document is not always the active document. This is the
case during transitional states, such as when the documentToBeActivated() reactor occurs. Do not attempt extensive processing during transitional states. Consider using mdiActiveDocument() if you are interested in the active document. From the current document, you can determine the current database, the relevant transaction manager, and your applications associated documentspecific state, and then do whatever needs to be done before returning. Once a command has stored the current document and associated information on its stack, it does not need to query the current document again until completion. Whenever a prompt for user input is made, the user can switch documents, but if that is done, the current command is suspended and its stack state is saved until the document is reactivated. If your application is operating from the application execution context, it must lock and unlock the current document to modify anything associated with it. It can do so by directly invoking the AcApDocManager::lockDocument() and unlockDocument() member function. If your application is operating from an ObjectARX or AutoLISP function, no locking should be necessary, as the system establishes the locks and removes them automatically around commands and AutoLISP expressions.
428
Chapter 16
To merely examine databases associated with other documents, you need not lock the document, although if the document is locked in AcAp::kXWrite mode by another execution context, you will be denied access to any of its elements. To modify databases associated with other documents, or to prevent other execution contexts from modifying them for a period of time, you must lock the document, specifying AcAp::kXWrite, AcAp::kWrite, or AcAp::kRead, depending on your intent. If the documents command processor is not quiescent, it is usually already locked, and if it mutually excludes your lock, you will be denied access.
Using any user interaction functions, such as the acedXXX() functions. Creating a database to be associated with a particular document. Obtaining or manipulating a selection set without requiring user interaction. Using functions described in aced.h.
When the active and current documents are different, all user input functions and members concerning the document, such as the graphics screen, will be disabled. This includes functions for both ObjectARX and ActiveX applications. Whenever you set the current document without also activating it, the setCurDocument() caller should restore the current document to be the same
429
as the active document when finished. However, if this is not done by the time that the next input event is processed, the current document will be switched back to the active document.
They need to manage per-document state. They need to be notified whenever a document or its database(s) are going to be modified, or are done being modified. They need to keep track of document switches, that is, which document is being made current or active.
The AcApDocManReactor makes callbacks when changes in document status take place, such as opening, closing, activation, deactivation, and changing the lock status of documents.
they will likely be reused as documents are terminated and created. As an alternative, you can implement handlers for when your acrxEntryPoint() function is invoked with AcRx::kLoadDwgMsg and AcRx::kUnloadDwgMsg messages, which are invoked with the document in question being current. Such application-specific data should contain any state that must be associated with each open document that must persist across commands. One implementation alternative would be to maintain an AcArray template of a class whose instances consist of an AcApDocument pointer and a pointer to, or instance of, your document-specific state, and whose == operator is
430
Chapter 16
overloaded to compare only the AcApDocument* member. Another approach would be to maintain a pair of arrays with corresponding elements, do a find on the document pointers, and fetch the corresponding element out of the other array.
Nonreentrant Commands
Nonreentrant commands are those commands that cannot be invoked in one document when already in progress in a different document. This should be regarded as a way to make your application MDI-Aware quickly, without having to isolate all of your command-specific state information.
The TABLET command is restricted because the user is defining how the pointing device is going to work, so that operation needs to be finished before anything else can be done. Because NEW and OPEN have to open a window, and then ask for a name (when FILEDIA=0), switching to another window at that moment could be error-prone.
Nonreentrant Commands
431
Multi-Document Commands
A multi-document command allows users to switch documents at selected user prompts, while the command remains in control. The ability to have a single command remain active across documents is very complex. At the point of execution when a user has picked another document to switch to, all documents are polling for input. They therefore have potentially intricate command processor states established, including possibly nested commands, AutoLISP expressions, scripts active, and all in arbitrary nesting order. One cant easily define, let alone graft, the pertinent elements of one state into another without major effort. Instead, the best strategy is for the application to retain control across user-specified document switches, even if the application has to be implemented as different commands in different windows. Then, all an application needs is a mechanism to chain separate commands running in different documents, and control which command to start when changing documents. To synchronize the actions of the multiple commands, implement a reactor that overrides the following AcApDocManager reactor functions:
virtual void documentActivated( AcApDocument* pActivatedDoc); virtual void documentToBeDeactivated( AcApDocument* pDeActivatedDoc);
The documentToBeActivated() reactor function can also be used, but it occurs before the document is activated. The document context has not been set in this case. These callbacks are invoked whenever the user clicks on a different document to activate it. The reactors should only be used on AcApDocManager when in an initial command just before prompting for user input, at a point when document switching is to be supported. The reactor should be removed when any or all such prompts are completed or canceled. From the callback, invoke:
virtual Acad::ErrorStatus sendStringToExecute( AcApDocument* pAcTargetDocument, const char * pszExecute, bool bActivate = true, bool bWrapUpInactiveDoc = false) = 0;
This function queues up a string to be interpreted the next time the specified document is activated. The string should typically be a command invocation
432
Chapter 16
(well call this the secondary command), but can also be an AutoLISP expression, a command fragment, or a menu token. The string limit is 296 bytes, so longer sequences should be implemented as a SCRIPT command running a temporary script, or as an AutoLISP expression to load and execute an AutoLISP program. The new document will be locked according to the new commands lock level as specified during its registration. If the input prompt in the initial command looks the same as the first prompt in the secondary command, the user need not be aware that two separate commands are taking place.
parameter to avoid errors or infinite loops. Also, to manage the flow of control across documents, this callback should maintain whatever transition state the application needs. For example, a nonreentrant variant could remember the original document, and a flag for each document to indicate whether it is already active, and therefore not have to invoke sendStringToExecute(). When a multi-document command completes, the controlling application should be sure the command left no pending command states in previous documents. The application can do this by sending an ESC or ENTER to the documents it has traversed through, by using the bWrapUpInactiveDoc parameter of sendStringToExecute(). If this is not done, documents may be left in a non-quiescent state. Coordination between the initial command and the secondary command (and possibly multiple invocations thereof) must be managed through static or heap-resident variables. Both the initial and secondary commands should be registered through
acedRegCmds() or acedDefun(). The initial command should be coded to
complete successfully on its own, in case the user decides to perform the entire command without switching documents. The second command need not be a full command, just a portion of a command that can be invoked to accumulate information (rooted in a static structure) from different open documents, and apply the results. The second command may also have to be coded such that it can be reentered in yet another document, if that is how the command is supposed to be structured. Remember that UNDO runs separately in each document when designing these constructs.
Multi-Document Commands
433
the user is really done, or RTCAN, which can be either a real cancel or a moved to another document signal. Set the local done flag, perform the action, then queue up ESC to every other active document so that the command is finished up in that document the next time the user goes to click into it.
In addition, AcApDocManager::disableDocumentActivation() and AcApDocManager::enableDocumentActivation() will disable and re-enable document activation. The prototypes for these methods are:
virtual Acad::ErrorStatus disableDocumentActivation() = 0; virtual Acad::ErrorStatus enableDocumentActivation() = 0;
434
Chapter 16
VBA-initiated ActiveX requests (implemented in ObjectARX). ActiveX requests made from external processes, including Visual Basic. Modeless dialog boxes posted by ObjectARX applications, or any DLL loaded by AutoCAD. All ObjectARX service calls made from the application context, including any ObjectARX-defined callouts that can be triggered from them, including custom object and entity virtual members, AcDb*, AcRx* reactor members, and AcEditorReactor members concerning database objects.
I I I
It is not part of the command processor state of any specific document. It can activate different documents without immediately suspending itself, although it must complete and return before the new active document can process its input. Document switching is disabled when prompting for user input, either via ActiveX or ObjectARX user input requests. AutoLISP is also disabled when prompting for user input in this context. In the cases of modeless dialogs and external process-generated ActiveX requests, the code must lock the documents, including the current document. The use of the IAcadDocument methods StartUndoMarker() and EndUndoMarker() will apply a kWriteLock to the document. The command facility may not be used from the application execution context, specifically the acedCommand() and acedCmd() functions.
435
The AcApDocManager::sendStringToExecute() and AcApDocManager::activateDocument() methods change the active document but do not suspend execution of the code running under the application context. They will suspend execution of code running in a document execution context. The AcApDocManager::sendStringToExecute() method will always queue the string when invoked from the application context, while invoking it from a document context will either queue the string if the activate parameter is kFalse, or immediately suspend the document context if the activate parameter is kTrue.
When the execution context your code is running under is not implicit in your code structure, you can make this query to find if it is the application execution context:
All ActiveX user input members may be used, but make sure that you are invoking them on the utility object associated with the active and current document. As noted above, document switching will be disabled when user input is obtained in this context. You can obtain the IAcadDocument* instance that corresponds to the current AcApDocument via the call:
All ObjectARX user input functions may be called, with the current active document being implicitly used. As noted above, document switching will be disabled when user input is obtained in this context. Application code executing from the application context can use the following member function to switch either the current and active document, together or individually, as desired.
virtual Acad::ErrorStatus setCurDocument( AcApDocument* pDoc, AcAp::DocLockMode = AcAp::kNone, bool activate = false) = 0;
I
By alternating between prompting for user input and changing or activating the current document, one can prompt for input from multiple documents from a single execution context and a single sequence of code. The drawback is that document switching by the user is disabled when
436
Chapter 16
prompting for input, so the code needs to know which document it wants to switch to. When the active and current documents differ, be aware that the ActiveX and ObjectARX user input functions will not operate properly. Use the curDocument() and mdiActiveDocument() functions to check the current and active documents. If the application leaves with the current document and active document different, the next input event will restore the current document back to the active document. When code executing from the application context is prompting for user input using the ActiveX user input functions, automatic interactive document switching is disabled, although current document switching can be performed.
I I
Undo and transaction management is performed on a per-document basis. In AutoCAD, it is controlled through (or in conjunction with) document locking. Whenever documents are locked for kWrite or kXWrite, a begin command undo bracket is written to the file, and then database and other modifications are performed. When the documents are unlocked from kWrite or kXWrite status, the corresponding end command undo bracket will be written. (Note that these can be nested.) By the time an application is finished operating on a document, it should have balanced its document lock and unlock requests. If the requests are not balanced, the undo file will work incorrectly, leaving actions out of sync from after the first write lock, and through the first lock balanced with an unlock. A subsequent undo request should put it back in sync. A parameter for establishing the command name is provided, which is displayed when an UNDO command is performed. The undo markers created are the same as for built-in AutoCAD and ObjectARX commands, and can therefore be managed via UNDO GROUP. Documents can have undo performed independently from each other. By default, when an instance of AcDbDatabase is created, its undo and transaction management is associated with the current document.
437
Note that there are two methods of AcEditorReactor that are used to hook up databases with a documents undo facilities and transaction manager: databaseConstructed() and databaseToBeDestroyed(). If you receive such notification, be aware that the association between the database and any documents is undefined at that time, so document locking may or may not be required in the databaseConstructed() callback. Of course, any action that undoes any modifications done at that time will also undo the creation of the database. The default AcDbDatabase constructors will query the AcDbHostApplicationServices object for an undo controller.
Document-Independent Databases
To participate in undo in AutoCAD, databases must be associated with a document, because each document has an independent undo stack. However, this feature is in direct conflict with the need to load databases whose contents are intended to be shared across document edit sessions. In other words, you must decide between the following two scenarios for your side databases:
I
Associate a database with a specific document, and dont allow edits to it from other edit sessions, and possibly load a DWG or DXF file into multiple databases for each edit session that needs it. Load a DWG or DXF file to share it across edit sessions, and have no automatic undo for it. Either dont support undo for them at all (it is fine if they are read-only, or only updated for actual saves, or are under revision control), or be very careful when using undo.
In ObjectARX, the former scenario is the default. Whenever a new instance of AcDbDatabase is instantiated, it is associated with the current document. This is one of the reasons an application needs to change the current document without activating the new document. The AcDbDatabase class provides the following function, which disables the database undo and disassociates the database from the document:
void disableUndoRecording( Adesk::Boolean disable);
438
Chapter 16
Any AcDb reliance on any document-specific system variables will assume the built-in defaults for document-independent databases. Also, there is no need to lock any documents to access document-independent databases.
439
class AsdkAppDocGlobals { public: AsdkAppDocGlobals(AcApDocument* pDoc); void setGlobals(AcApDocument* pDoc); void removeDocGlobals(AcApDocument *pDoc); void removeAllDocGlobals(AsdkPerDocData* pTarget); void unload(); long &entityCount(); void incrementEntityCount(); void decrementEntityCount(); AsdkDbReactor *dbReactor(); void setDbReactor(AsdkDbReactor *pDb); private: AsdkPerDocData *m_pHead; AsdkPerDocData *m_pData; AsdkDocReactor *m_pDocReactor; }; AsdkAppDocGlobals *gpAsdkAppDocGlobals; // Custom AcDbDatabaseReactor class for Database // event notification. // class AsdkDbReactor : public AcDbDatabaseReactor { public: virtual void objectAppended(const AcDbDatabase* dwg, const AcDbObject* dbObj); virtual void objectModified(const AcDbDatabase* dwg, const AcDbObject* dbObj); virtual void objectErased(const AcDbDatabase* dwg, const AcDbObject* dbObj, Adesk::Boolean pErased); }; // This is called whenever an object is added to the database. // void AsdkDbReactor::objectAppended(const AcDbDatabase* db, const AcDbObject* pObj) { printDbEvent(pObj, "objectAppended"); acutPrintf(" Db==%lx\n", (long) db); gpAsdkAppDocGlobals->incrementEntityCount(); acutPrintf("Entity Count = %d\n", gpAsdkAppDocGlobals->entityCount()); }
440
Chapter 16
// This is called whenever an object in the database is modified. // void AsdkDbReactor::objectModified(const AcDbDatabase* db, const AcDbObject* pObj) { printDbEvent(pObj, "objectModified"); acutPrintf(" Db==%lx\n", (long) db); } // This is called whenever an object is erased from the database. // void AsdkDbReactor::objectErased(const AcDbDatabase* db, const AcDbObject* pObj, Adesk::Boolean pErased) { if (pErased) { printDbEvent(pObj, "objectErased"); gpAsdkAppDocGlobals->decrementEntityCount(); else { printDbEvent(pObj, "object(Un)erased"); gpAsdkAppDocGlobals->incrementEntityCount(); } acutPrintf(" Db==%lx\n", (long) db); acutPrintf("Entity Count = %d\n", gpAsdkAppDocGlobals->entityCount()); }
// Prints the message passed in by pEvent; then // proceeds to call printObj() to print the information about // the object that triggered the notification. // void printDbEvent(const AcDbObject* pObj, const char* pEvent) { acutPrintf(" Event: AcDbDatabaseReactor::%s ", pEvent); printObj(pObj); } // Prints out the basic information about the object pointed // to by pObj. // void printObj(const AcDbObject* pObj) { if (pObj == NULL) { acutPrintf("(NULL)"); return; }
441
AcDbHandle objHand; char handbuf[17]; // Get the handle as a string. // pObj->getAcDbHandle(objHand); objHand.getIntoAsciiBuffer(handbuf); acutPrintf( "\n (class==%s, handle==%s, id==%lx, db==%lx)", pObj->isA()->name(), handbuf, pObj->objectId().asOldId(), pObj->database()); } // Document swapping functions // void AsdkDocReactor::documentToBeActivated(AcApDocument *pDoc) { gpAsdkAppDocGlobals->setGlobals(pDoc); } void AsdkDocReactor::documentCreated(AcApDocument *pDoc) { gpAsdkAppDocGlobals->setGlobals(pDoc); } void AsdkDocReactor::documentToBeDestroyed(AcApDocument *pDoc) { gpAsdkAppDocGlobals->removeDocGlobals(pDoc); } AsdkPerDocData::AsdkPerDocData(AcApDocument *pDoc) { m_pDoc = pDoc; m_pNext = NULL; m_EntAcc = 0; m_pDbr = NULL; } AsdkAppDocGlobals::AsdkAppDocGlobals(AcApDocument *pDoc) { m_pData = m_pHead = NULL; m_pDocReactor = new AsdkDocReactor(); acDocManager->addReactor(m_pDocReactor); }
442
Chapter 16
// Iterate through the list until the documentss global data is // found. If it is not found, create a new set of document globals. // void AsdkAppDocGlobals::setGlobals(AcApDocument *pDoc) { AsdkPerDocData *p_data = m_pHead, *prev_data = m_pHead; while (p_data != NULL) { if (p_data->m_pDoc == pDoc) { m_pData = p_data; break; } prev_data = p_data; p_data = p_data->m_pNext; } if (p_data == NULL) { if (m_pHead == NULL) m_pHead = m_pData = new AsdkPerDocData(pDoc); else prev_data->m_pNext = m_pData = new AsdkPerDocData(pDoc); } } // Delete the globals associated with pDoc. // void AsdkAppDocGlobals::removeDocGlobals(AcApDocument *pDoc) { AsdkPerDocData *p_data = m_pHead, *prev_data = m_pHead; while (p_data != NULL) { if (p_data->m_pDoc == pDoc) { if (p_data == m_pHead) m_pHead = p_data->m_pNext; else prev_data->m_pNext = p_data->m_pNext; if (m_pData == p_data) m_pData = m_pHead; delete p_data; break; } prev_data = p_data; p_data = p_data->m_pNext; } }
443
// Delete all the doc globals in the list (recursively). // void AsdkAppDocGlobals::removeAllDocGlobals(AsdkPerDocData *p_target) { if (p_target == NULL) return; if (p_target->m_pNext != NULL) removeAllDocGlobals(p_target->m_pNext); if (p_target->m_pDbr != NULL) { acdbHostApplicationServices()->workingDatabase( )->removeReactor(p_target->m_pDbr); delete p_target->m_pDbr; p_target->m_pDbr = NULL; } delete p_target; } // Application was unloaded - delete everything associated with this // document. // void AsdkAppDocGlobals::unload() { acDocManager->removeReactor(m_pDocReactor); delete m_pDocReactor; removeAllDocGlobals(m_pHead); m_pHead = m_pData = NULL; } long & AsdkAppDocGlobals::entityCount() { return m_pData->m_EntAcc; } void AsdkAppDocGlobals::incrementEntityCount() { m_pData->m_EntAcc++; } void AsdkAppDocGlobals::decrementEntityCount() { m_pData->m_EntAcc--; } AsdkDbReactor * AsdkAppDocGlobals::dbReactor() { return m_pData->m_pDbr; }
444
Chapter 16
void AsdkAppDocGlobals::setDbReactor(AsdkDbReactor *pDb) { m_pData->m_pDbr = pDb; } // Adds a reactor to the database to monitor changes. // This can be called multiple times without any ill // effects because subsequent calls will be ignored. // void watchDb() { AsdkDbReactor *pDbr; if (gpAsdkAppDocGlobals->dbReactor() == NULL) { pDbr = new AsdkDbReactor(); gpAsdkAppDocGlobals->setDbReactor(pDbr); acdbHostApplicationServices()->workingDatabase( )->addReactor(pDbr); acutPrintf( " Added Database Reactor to " "acdbHostApplicationServices()->workingDatabase().\n"); } } // Removes the database reactor. // void clearReactors() { AsdkDbReactor *pDbr; if ((pDbr = gpAsdkAppDocGlobals->dbReactor()) != NULL) { acdbHostApplicationServices()->workingDatabase( )->removeReactor(pDbr); delete pDbr; gpAsdkAppDocGlobals->setDbReactor(NULL); } }
445
// ObjectARX entry point function // AcRx::AppRetCode acrxEntryPoint(AcRx::AppMsgCode msg, void* appId) { switch (msg) { case AcRx::kInitAppMsg: acrxUnlockApplication(appId); acrxRegisterAppMDIAware(appId); gpAsdkAppDocGlobals = new AsdkAppDocGlobals(curDoc()); gpAsdkAppDocGlobals->setGlobals(curDoc()); acedRegCmds->addCommand("ASDK_NOTIFY_TEST", "ASDK_WATCH", "WATCH", ACRX_CMD_TRANSPARENT, watchDb); acedRegCmds->addCommand("ASDK_NOTIFY_TEST", "ASDK_CLEAR", "CLEAR", ACRX_CMD_TRANSPARENT, clearReactors); break; case AcRx::kUnloadAppMsg: if (gpAsdkAppDocGlobals != NULL) { gpAsdkAppDocGlobals->unload(); delete gpAsdkAppDocGlobals; gpAsdkAppDocGlobals = NULL; } acedRegCmds->removeGroup("ASDK_NOTIFY_TEST"); break; } return AcRx::kRetOK; }
446
Chapter 16
Transaction Management
17
In this chapter
I Overview of Transaction
This section describes the transaction model, which can be used to operate on AcDb objects. In this model, multiple operations on multiple objects are grouped together into one atomic operation called a transaction. Transactions can be nested and can be ended or aborted at the discretion of the client. This model can be used in conjunction with the regular per-object open and close mechanism described in chapter 5, Database Objects.
Management
I Transaction Manager I Nesting Transactions I Transaction Boundaries I Obtaining Pointers to Objects
in a Transaction
I Newly Created Objects and
Transactions
I Commit-Time Guidelines I Undo and Transactions I Mixing the Transaction Model
Generation
I Transaction Reactors I Example of Nested
Transactions
447
448
Chapter 17
Transaction Management
Transaction Manager
The transaction manager is a global manager object, similar to the editor, that is in charge of maintaining transactions. It is an instance of AcTransactionManager and is maintained in the system registry. You can obtain it from the system registry using the macro actrTransactionManager, which expands to
#define actrTransactionManager \ AcTransactionManager::cast( acrxSysRegistry()->at(AC_TRANSACTION_MANAGER_OBJ))
The transaction manager should be used to start, end, or abort transactions. It can also provide information such as the number of active transactions at any moment (see the following section, Nesting Transactions) and a list of all the objects whose pointers have been obtained in all the transactions. The transaction manager maintains a list of reactors to notify clients of events such as the start, end, or abort of a transaction. In addition to these managerial capabilities, the transaction manager can also be used to obtain pointers from object IDs. When this is done, the object is associated with the top (most recent) transaction. The transaction manager can also be used to queue up all the objects in all the transactions for graphics update and to flush the queue. The transaction manager object is created and managed by the system. You should not delete it.
Nesting Transactions
Transactions can be nestedthat is, you can start one transaction inside another and end or abort the recent transaction. The transaction manager maintains transactions in a stack, with the most recent transaction at the top of the stack. When you start a new transaction using AcTransactionManager::startTransaction(), the new transaction is added to the top of the stack and a pointer to it is returned (an instance of AcTransaction). When someone calls AcTransactionManager::endTransaction() or AcTransactionManager::abortTransaction(), the transaction at the top of the stack is ended or aborted. When object pointers are obtained from object IDs, they are always associated with the most recent transaction. You can obtain the recent transaction using AcTransactionManager::topTransaction(), then use
Transaction Manager
449
AcTransaction::getObject() or AcTransactionManager::getObject() to
obtain a pointer to an object. The transaction manager automatically associates the object pointers obtained with the recent transaction. You can use AcTransaction::getObject() only with the most recent transaction. When nested transactions are started, the object pointers obtained in the outer containing transactions are also available for operation in the innermost transaction. If the recent transaction is aborted, all the operations done on all the objects (associated with either this transaction or the containing ones) since the beginning of the recent transaction are canceled and the objects are rolled back to the state at the beginning of the recent transaction. The object pointers obtained in the recent transaction cease to be valid once its aborted. If the innermost transaction is ended successfully by calling
AcTransactionManager::endTransaction(), the objects whose pointers
were obtained in this transaction become associated with the containing transaction and are available for operation. This process is continued until the outermost (first) transaction is ended, at which time modifications on all the objects are committed. If the outermost transaction is aborted, all the operations on all the objects are canceled and nothing is committed.
Transaction Boundaries
Because you, not the system, are in charge of starting, ending, or aborting transactions, its important to be aware of transaction boundaries. A transaction boundary is the time between the start and end or abort of a transaction. Its recommended that you confine your boundary to the smallest possible scope. For example, if you start a transaction in a function, try to end or abort the transaction before you return from that function, because you may not have knowledge of the transaction outside of the function. You need not follow this rule if you maintain some kind of a global manager for your transaction activities, but you still are responsible for aborting or ending all the transactions you start. Multiple applications can use transaction management for their work, and operations on objects are committed at the end of the outermost transaction. Therefore, an AutoCADcommand boundary is as far as you can stretch the boundary of your transactions. When a command ends, there should not be any active transactions. If there are any active transactions (the transaction stack is not empty) when a command ends, AutoCAD will abort. As an exception, transactions can still be active when an acedCommand() or a transparent command ends, but they should all be resolved when a main command ends and AutoCAD returns to the Command prompt.
450
Chapter 17
Transaction Management
Its generally a good idea to start a transaction when one of your functions is invoked as part of a command registered by you and end it when you return from that function. You can generalize it to all the commands in AutoCAD using the AcEditorReactor::commandWillStart() and AcEditorReactor::commandEnded() notifications, but there are certain commands that should not be transacted. The following commands should not be transacted:
I I I I I I I I I I I I I I
ARX DXFIN INSERT NEW OPEN PURGE QUIT RECOVER REDO SAVE SCRIPT U UNDO XREF
451
obtained the pointer using acdbOpenObject() or the object was newly created. For more information on when you can call close() on an object pointer, see the following sections, Newly Created Objects and Transactions and Mixing the Transaction Model with the Open and Close Mechanism.
Commit-Time Guidelines
When the outermost transaction ends, the transaction manager fires an endCalledOnOutermostTransaction() notification (see Transaction Reactors on page 454) and begins the commit process, in which modifications on all the objects associated with the transaction are committed to the database. Each object is committed individually, one after another, until all of them are committed. During this operation, do not modify any of the objects involved in the commit process and do not start any new transactions. If you do so, AutoCAD will abort with the error message eInProcessOfCommitting. You can modify individual objects after each has been committed, but it is recommended that you cache the IDs of the objects you want to modify and wait until you receive the transactionEnded() notification signaling the end of all the transactions, then do the modifications.
452
Chapter 17
Transaction Management
Mixing the Transaction Model with the Open and Close Mechanism
The transaction model coexists with the regular open and close mechanism described in chapter 5, Database Objects. However, if you are using the transaction model, it is recommended that you do not mix it with the open and close mechanism. For example, if you obtained a pointer to an object using AcTransaction::getObject(), you should not call close() on the object pointer, which could cause unexpected results and may crash AutoCAD. However, you are free to open and close a particular object even if transactions are active. You can also instantiate new objects, add them to the database, and close them while transactions are active. The primary purpose of having the mixed model is to allow simultaneous execution of multiple applications where some use transaction management and others do not, but all of them are operating on the same objects.
453
transaction when all the modifications to all the entities are drawn. Use AcTransactionManager::enableGraphicsFlush() to enable or disable the drawing of entities. When a command ends, you relinquish control of graphics generation and it is automatically enabled.
Transaction Reactors
The transaction manager has a list of reactors through which it notifies clients of events relevant to the transaction model. Currently, there are four events that send notification:
virtual void transactionStarted( int& numTransactions); virtual void transactionEnded( int& numTransactions); virtual void transactionAborted( int& numTransactions); virtual void endCalledOnOutermostTransaction( int& numTransactions);
The first three notifications are fired when any transaction, including nested ones, is started, ended, or aborted. You can use these notifications in conjunction with AcTransactionManager::numActiveTransactions() to determine the transaction that is relevant to the notification. For example, if a call to AcTransactionManager::numActiveTransactions() returns zero in your override of AcTransactionReactor::transactionEnded() or AcTransactionReactor::transactionAborted(), you know the outermost transaction is ending or aborting. The endCalledOnOutermostTransaction() notification signals the beginning of the commit process of all the modifications done in all the transactions. You can use this callback to do any necessary cleanup work before commit begins. The argument in all the notifications represents the number of transactions that are active plus the ones that have completed successfully. It doesnt include the transactions that were started and aborted.
454
Chapter 17
Transaction Management
Select the polygon and obtain a pointer to it. Open it for read. Create an extruded solid using the polygon. Create a cylinder in the middle of the extended polygon.
3 Start Transaction 2: Subtract the cylinder from the extrusion (creates a hole in the middle of the solid). 4 Start Transaction 3:
I I
Slice the shape in half along the X/Z plane and move it along the X axis so you can view the two pieces. Abort the transaction? Answer yes.
5 Start Transaction 3 (again): Slice the shape in half along the Y/Z plane and move it along Y. 6 End Transaction 3. 7 End Transaction 2.
NOTE If you abort at this point, Transactions 2 and 3 are both canceled. If
you abort a containing transaction, all the nested transactions are aborted, even if they were successfully ended. 8 End Transaction 1.
455
456
Chapter 17
Transaction Management
if (pPoly == NULL) { acutPrintf("\nNot a polygon. Try again"); continue; } break; case RTNONE: case RTCAN: actrTransactionManager->abortTransaction(); return; default: continue; } break; } // Now that a poly is created, convert it to a region // and extrude it. // acutPrintf("\nWe will be extruding the poly."); AcGePoint2d c2d = pPoly->center(); ads_point pt; pt[0] = c2d[0]; pt[1] = c2d[1]; pt[2] = pPoly->elevation(); acdbEcs2Ucs(pt,pt,asDblArray(pPoly->normal()),Adesk::kFalse); double height; if (acedGetDist(pt, "\nEnter Extrusion height: ", &height) != RTNORM) { actrTransactionManager->abortTransaction(); return; } if ((es = extrudePoly(pPoly, height,savedExtrusionId)) != Acad::eOk) { actrTransactionManager->abortTransaction(); return; } // Create a cylinder at the center of the polygon of // the same height as the extruded poly. // double radius = (pPoly->startPoint() - pPoly->center()).length() * 0.5; pSolid = new AcDb3dSolid; assert(pSolid != NULL); pSolid->createFrustum(height, radius, radius, radius); AcGeMatrix3d mat(AcGeMatrix3d::translation( pPoly->elevation()*pPoly->normal())* AcGeMatrix3d::planeToWorld(pPoly->normal())); pSolid->transformBy(mat);
457
// Move it up again by half the height along // the normal. // AcGeVector3d x(1, 0, 0), y(0, 1, 0), z(0, 0, 1); AcGePoint3d moveBy(pPoly->normal()[0] * height * 0.5, pPoly->normal()[1] * height * 0.5, pPoly->normal()[2] * height * 0.5); mat.setCoordSystem(moveBy, x, y, z); pSolid->transformBy(mat); addToDb(pSolid, savedCylinderId); actrTransactionManager ->addNewlyCreatedDBRObject(pSolid); pSolid->draw(); acutPrintf("\nCreated a cylinder at the center of" " the poly."); // Start another transaction. Ask the user to select // the extruded solid followed by selecting the // cylinder. Make a hole in the extruded solid by // subtracting the cylinder from it. // pTrans = actrTransactionManager->startTransaction(); assert(pTrans != NULL); acutPrintf("\n\n###### Started transaction two." " ######\n"); AcDb3dSolid *pExtrusion, *pCylinder; if ((es = getASolid("\nSelect the extrusion: ", pTrans, AcDb::kForWrite, savedExtrusionId, pExtrusion)) != Acad::eOk) { actrTransactionManager->abortTransaction(); actrTransactionManager->abortTransaction(); return; } assert(pExtrusion != NULL); if ((es = getASolid("\nSelect the cylinder: ", pTrans, AcDb::kForWrite, savedCylinderId, pCylinder)) != Acad::eOk) { actrTransactionManager->abortTransaction(); actrTransactionManager->abortTransaction(); return; } assert(pCylinder != NULL); pExtrusion->booleanOper(AcDb::kBoolSubtract, pCylinder); pExtrusion->draw(); acutPrintf("\nSubtracted the cylinder from the" " extrusion.");
458
Chapter 17
Transaction Management
// At this point, the cylinder is a NULL solid. Erase it. // assert(pCylinder->isNull()); pCylinder->erase(); // Start another transaction and slice the resulting // solid into two halves. // pTrans = actrTransactionManager->startTransaction(); assert(pTrans != NULL); acutPrintf("\n\n##### Started transaction three." " ######\n"); AcGeVector3d vec, normal; AcGePoint3d sp,center; pPoly->getStartPoint(sp); pPoly->getCenter(center); vec = sp - center; normal = pPoly->normal().crossProduct(vec); normal.normalize(); AcGePlane sectionPlane(center, normal); AcDb3dSolid *pOtherHalf = NULL; pExtrusion->getSlice(sectionPlane, Adesk::kTrue, pOtherHalf); assert(pOtherHalf != NULL); // Move the other half three times the vector length // along the vector. // moveBy.set(vec[0] * 3.0, vec[1] * 3.0, vec[2] * 3.0); mat.setCoordSystem(moveBy, x, y, z); pOtherHalf->transformBy(mat); AcDbObjectId otherHalfId; addToDb(pOtherHalf, otherHalfId); actrTransactionManager ->addNewlyCreatedDBRObject(pOtherHalf); pOtherHalf->draw(); pExtrusion->draw(); acutPrintf("\nSliced the resulting solid into half" " and moved one piece.");
459
// Now abort transaction three, to return to the hole in // the extrusion. // Adesk::Boolean yes = Adesk::kTrue; if (getYOrN("\nLet's abort transaction three, yes?" " [Y] : ", Adesk::kTrue, yes,interrupted) == Acad::eOk && yes == Adesk::kTrue) { acutPrintf("\n\n$$$$$$ Aborting transaction" " three. $$$$$$\n"); actrTransactionManager->abortTransaction(); acutPrintf("\nBack to the un-sliced solid."); pExtrusion->draw(); char option[256]; acedGetKword("\nHit any key to continue.", option); } else { acutPrintf("\n\n>>>>>> Ending transaction three." " <<<<<<\n"); actrTransactionManager->endTransaction(); } // Start another transaction (three again). This time, // slice the solid along a plane that is perpendicular // to the plane we used last time: that is the slice // we really wanted. // pTrans = actrTransactionManager->startTransaction(); assert(pTrans != NULL); acutPrintf("\n\n##### Started transaction three." " ######\n"); moveBy.set(normal[0] * 3.0, normal[1] * 3.0, normal[2] * 3.0); normal = vec; normal.normalize(); sectionPlane.set(center, normal); pOtherHalf = NULL; pExtrusion->getSlice(sectionPlane, Adesk::kTrue, pOtherHalf); assert(pOtherHalf != NULL); mat.setCoordSystem(moveBy, x, y, z); pOtherHalf->transformBy(mat); addToDb(pOtherHalf, otherHalfId); actrTransactionManager ->addNewlyCreatedDBRObject(pOtherHalf); pOtherHalf->draw(); pExtrusion->draw(); acutPrintf("\nSliced the resulting solid into half" " along a plane"); acutPrintf("\nperpendicular to the old one and moved" " one piece.");
460
Chapter 17
Transaction Management
// Now, give the user the option to end all the transactions. // yes = Adesk::kFalse; if (getYOrN("\nAbort transaction three? <No> : ", Adesk::kFalse, yes,interrupted) == Acad::eOk && yes == Adesk::kTrue) { acutPrintf("\n\n$$$$$$ Aborting transaction" " three. $$$$$$\n"); actrTransactionManager->abortTransaction(); acutPrintf("\nBack to the un-sliced solid."); } else { acutPrintf("\n\n>>>>>> Ending transaction three." " <<<<<<\n"); actrTransactionManager->endTransaction(); } yes = Adesk::kFalse; if (getYOrN("\nAbort transaction two? <No> : ", Adesk::kFalse, yes,interrupted) == Acad::eOk && yes == Adesk::kTrue) { acutPrintf("\n\n$$$$$$ Aborting transaction two." " $$$$$$\n"); actrTransactionManager->abortTransaction(); acutPrintf("\nBack to separate extrusion and" " cylinder."); } else { acutPrintf("\n\n>>>>>> Ending transaction two." " <<<<<<\n"); actrTransactionManager->endTransaction(); } yes = Adesk::kFalse; if (getYOrN("\nAbort transaction one? <No> : ", Adesk::kFalse, yes,interrupted) == Acad::eOk && yes == Adesk::kTrue) { acutPrintf("\n\n$$$$$$ Aborting transaction one." " $$$$$$\n"); actrTransactionManager->abortTransaction(); acutPrintf("\nBack to just the Poly."); } else { actrTransactionManager->endTransaction(); acutPrintf("\n\n>>>>>> Ending transaction one." " <<<<<<\n"); } }
461
static Acad::ErrorStatus createAndPostPoly() { int nSides = 0; while (nSides < 3) { acedInitGet(INP_NNEG, ""); switch (acedGetInt("\nEnter number of sides: ", &nSides)) { case RTNORM: if (nSides < 3) acutPrintf("\nNeed at least 3 sides."); break; default: return Acad::eInvalidInput; } } ads_point center, startPt, normal; if (acedGetPoint(NULL, "\nLocate center of polygon: ", center) != RTNORM) { return Acad::eInvalidInput; } startPt[0] = center[0]; startPt[1] = center[1]; startPt[2] = center[2]; while (asPnt3d(startPt) == asPnt3d(center)) { switch (acedGetPoint(center, "\nLocate start point of polygon: ", startPt)) { case RTNORM: if (asPnt3d(center) == asPnt3d(startPt)) acutPrintf("\nPick a point different" " from the center."); break; default: return Acad::eInvalidInput; } } // Set the normal to the plane of the polygon to be // the same as the Z direction of the current UCS, // (0, 0, 1) since we also got the center and // start point in the current UCS. (acedGetPoint() // returns in the current UCS.) normal[X] = 0.0; normal[Y] = 0.0; normal[Z] = 1.0; acdbUcs2Wcs(normal, normal, Adesk::kTrue); acdbUcs2Ecs(center, center, normal, Adesk::kFalse); acdbUcs2Ecs(startPt, startPt, normal, Adesk::kFalse); double elev = center[2];
462
Chapter 17
Transaction Management
AcGePoint2d cen = asPnt2d(center), start = asPnt2d(startPt); AcGeVector3d norm = asVec3d(normal); AsdkPoly *pPoly = new AsdkPoly; if (pPoly==NULL) return Acad::eOutOfMemory; Acad::ErrorStatus es; if ((es=pPoly->set(cen, start, nSides, norm, "transactPoly",elev))!=Acad::eOk) return es; pPoly->setDatabaseDefaults( acdbHostApplicationServices()->workingDatabase()); postToDb(pPoly); return Acad::eOk; } // Extrudes the poly to a given height. // static Acad::ErrorStatus extrudePoly(AsdkPoly* pPoly, double height, AcDbObjectId& savedExtrusionId) { Acad::ErrorStatus es = Acad::eOk; // Explode to a set of lines. // AcDbVoidPtrArray lines; pPoly->explode(lines); // Create a region from the set of lines. // AcDbVoidPtrArray regions; AcDbRegion::createFromCurves(lines, regions); assert(regions.length() == 1); AcDbRegion *pRegion = AcDbRegion::cast((AcRxObject*)regions[0]); assert(pRegion != NULL); // Extrude the region to create a solid. // AcDb3dSolid *pSolid = new AcDb3dSolid; assert(pSolid != NULL); pSolid->extrude(pRegion, height, 0.0); for (int i = 0; i < lines.length(); i++) { delete (AcRxObject*)lines[i]; } for (i = 0; i < regions.length(); i++) { delete (AcRxObject*)regions[i]; }
463
// Now we have a solid. Add it to database, then // associate the solid with a transaction. After // this, transaction management is in charge of // maintaining it. // pSolid->setPropertiesFrom(pPoly); addToDb(pSolid, savedExtrusionId); actrTransactionManager ->addNewlyCreatedDBRObject(pSolid); pSolid->draw(); return Acad::eOk; } static Acad::ErrorStatus getASolid(char* prompt, AcTransaction* pTransaction, AcDb::OpenMode mode, AcDbObjectId checkWithThisId, AcDb3dSolid*& pSolid) { AcDbObject *pObj = NULL; AcDbObjectId objId; ads_name ename; ads_point pickpt; for (;;) { switch (acedEntSel(prompt, ename, pickpt)) { case RTNORM: AOK(acdbGetObjectId(objId, ename)); if (objId != checkWithThisId) { acutPrintf("\n Select the proper" " solid."); continue; } AOK(pTransaction->getObject(pObj, objId, mode)); assert(pObj != NULL); pSolid = AcDb3dSolid::cast(pObj); if (pSolid == NULL) { acutPrintf("\nNot a solid. Try again"); AOK(pObj->close()); continue; } break; case RTNONE: case RTCAN: return Acad::eInvalidInput; default: continue; } break; } return Acad::eOk; }
464
Chapter 17
Transaction Management
Deep Cloning
18
In this chapter
I Deep Clone Basics I Implementing deepClone() for
This section describes the deep clone functions, which copy an object or any objects owned by the copied object. It covers both basic use of the
AcDbDatabase::deepCloneObjects()
Custom Classes
function, as well as
the more advanced topic of overriding the deepClone() and wblockClone() functions of the AcDbObject class. Editor reactor notification functions related to the deep clone, wblock clone, and insert operations are also discussed.
465
destination database). When using AcDbDatabase::insert(), only insert to destination databases that have already been built. You can obtain a fully built (and possibly fully populated) destination database by using the current drawing to build a new database with a constructor parameter of Adesk::kTrue or by creating an empty new database using a constructor parameter of Adesk::kFalse and then calling AcDbDatabase::readDwgFile() on it to fill it in. In general, to use AcDbDatabase::deepCloneObjects(), AcDbDatabase::wblock(), or AcDbDatabase::insert() functions in your code, you do not need to be aware of how the object ID map is filled in or exactly what happens during each stage of deep cloning. If you are creating a new class and you want to override the AcDbObject::deepClone() or AcDbObject::wblockClone() functions, youll need to be familiar with the details of those functions, which are described in Implementing deepClone() for Custom Classes on page 474. The AcDbObject::deepClone() and AcDbObject::wblockClone() functions should not be called directly on a custom object in application code. They are only called as part of a chain from a higher-level cloning operation.
466
Chapter 18
Deep Cloning
467
wblock clone follows hard owner and pointer connections, as shown in the following figure:
Hard Owner
Hard Pointer
wblock clone
Soft Owner
Soft Pointer
deep clone
468
Chapter 18
Deep Cloning
469
// Check to be sure this has the same owner as the first // object. // AcDbObject *pObj; acdbOpenObject(pObj, objId, AcDb::kForRead); if (pObj->ownerId() == ownerId) objList.append(objId); else if (i == 0) { ownerId = pObj->ownerId(); objList.append(objId); } pObj->close(); } acedSSFree(sset); // Step 3: Get the object ID of the desired owner for // the cloned objects. We'll use model space for // this example. // AcDbBlockTable *pBlockTable; acdbHostApplicationServices()->workingDatabase() ->getSymbolTable(pBlockTable, AcDb::kForRead); AcDbObjectId modelSpaceId; pBlockTable->getAt(ACDB_MODEL_SPACE, modelSpaceId); pBlockTable->close(); // Step 4: Create a new ID map. // AcDbIdMapping idMap; // Step 5: Call deepCloneObjects(). // acdbHostApplicationServices()->workingDatabase() ->deepCloneObjects(objList, modelSpaceId, idMap); // Now we can go through the ID map and do whatever we'd // like to the original and/or clone objects. // // For this example, we'll print out the object IDs of // the new objects resulting from the cloning process. // AcDbIdMappingIter iter(idMap); for (iter.start(); !iter.done(); iter.next()) { AcDbIdPair idPair; iter.getMap(idPair); if (!idPair.isCloned()) continue; acutPrintf("\nObjectId is: %Ld", idPair.value().asOldId()); } }
470
Chapter 18
Deep Cloning
The deepCloneObjects() function is then called twice, using the same ID map. It is necessary to do all the cloning using a single ID map for the reference translation to be done properly. On the first call, the deferXlation parameter is set to kTrue. On the second (the last) call to deepCloneObjects(), deferXlation defaults to kFalse:
acdbHostApplicationServices()->workingDatabase()-> deepCloneObjects(mslist, modelSpaceId, idMap, Adesk::kTrue); acdbHostApplicationServices()->workingDatabase()-> deepCloneObjects(pslist, paperSpaceId, idMap);
At this point, cloning concludes and all the references are translated. The following code deep clones objects belonging to different owners:
void cloneDiffOwnerObjects() { // Step 1: Obtain the set of objects to be cloned. // For the two owners we'll use model space and // paper space, so we must perform two acedSSGet() calls. // calls. // acutPrintf("\nSelect entities to be cloned to" " Model Space"); ads_name ssetMS; acedSSGet(NULL, NULL, NULL, NULL, ssetMS); long lengthMS; acedSSLength(ssetMS, &lengthMS); acutPrintf("\nSelect entities to be cloned to" " Paper Space"); ads_name ssetPS; if (acedSSGet(NULL, NULL, NULL, NULL, ssetPS) != RTNORM && lengthMS == 0) { acutPrintf("\nNothing selected"); return; }
471
long lengthPS; acedSSLength(ssetPS, &lengthPS); // // // // Step 2: Add obtained object IDs to the lists of objects to be cloned: one list for objects to be owned by model space and one for those to be owned by paper space.
AcDbObjectId ownerId = AcDbObjectId::kNull; // For model space // AcDbObjectIdArray objListMS; for (int i = 0; i < lengthMS; i++) { ads_name ent; acedSSName(ssetMS, i, ent); AcDbObjectId objId; acdbGetObjectId(objId, ent); // Check to be sure this has the same owner as the first // object. // AcDbObject *pObj; acdbOpenObject(pObj, objId, AcDb::kForRead); if (pObj->ownerId() == ownerId) objListMS.append(objId); else if (i == 0) { ownerId = pObj->ownerId(); objListMS.append(objId); } pObj->close(); } acedSSFree(ssetMS); // For paper space // ownerId = AcDbObjectId::kNull; AcDbObjectIdArray objListPS; for (i = 0; i < lengthPS; i++) { ads_name ent; acedSSName(ssetPS, i, ent); AcDbObjectId objId; acdbGetObjectId(objId, ent); // Check to be sure this has the same owner as the first // object. // AcDbObject *pObj; acdbOpenObject(pObj, objId, AcDb::kForRead);
472
Chapter 18
Deep Cloning
if (pObj->ownerId() == ownerId) objListPS.append(objId); else if (i == 0) { ownerId = pObj->ownerId(); objListPS.append(objId); } pObj->close(); } acedSSFree(ssetPS); // Step 3: Get the object ID of the desired owners for // the cloned objects. We're using model space and // paper space for this example. // AcDbBlockTable *pBlockTable; acdbHostApplicationServices()->workingDatabase() ->getSymbolTable(pBlockTable, AcDb::kForRead); AcDbObjectId modelSpaceId, paperSpaceId; pBlockTable->getAt(ACDB_MODEL_SPACE, modelSpaceId); pBlockTable->getAt(ACDB_PAPER_SPACE, paperSpaceId); pBlockTable->close(); // Step 4: Create a new ID map. // AcDbIdMapping idMap; // Step 5: Call deepCloneObjects(). // acdbHostApplicationServices()->workingDatabase() ->deepCloneObjects(objListMS, modelSpaceId, idMap, Adesk::kTrue); acdbHostApplicationServices()->workingDatabase() ->deepCloneObjects(objListPS, paperSpaceId, idMap); // Now we can go through the ID map and do whatever we'd // like to the original and/or clone objects. // // For this example we'll print out the object IDs of // the new objects resulting from the cloning process. // AcDbIdMappingIter iter(idMap); for (iter.start(); !iter.done(); iter.next()) { AcDbIdPair idPair; iter.getMap(idPair); if (!idPair.isCloned()) continue; acutPrintf("\nObjectId is: %Ld", idPair.value().asOldId()); } }
473
BLOCK INSERT
474
Chapter 18
Deep Cloning
WBLOCK
Uses wblockClone(). This function follows hard ownership and hard pointer connections only. All other copy commands that use deepClone() follow both hard and soft ownership connections from the primary object. Uses wblockClone() to bring the referenced entities into your current drawing. When you explode an object into its parts, no cloning is performed. When you explode a block reference, AutoCAD deletes the block reference entity and copies the individual entities into the drawing. This version of EXPLODE uses deepClone().
Cloning Phase
During the cloning phase, when you call deepClone() on an object, AutoCAD checks to see if the cloned object (the primary object) owns any other objects. If it does, it calls deepClone() on those objects as well. This process continues until all owned objects have been cloned. Both hard and soft ownership connections are followed. When you call wblockClone() on an object, AutoCAD follows hard owner and hard pointer connections and calls wblockClone() on those objects as well.
Translation Phase
For both the deep clone and wblock clone functions, objects that are referenced by the primary object are also translated. After the objects are copied, AutoCAD translates the references as described in the following three cases.
I
Case 1: If the referenced object has been copied, the old reference is translated to refer to the copied object. In this case, it does not matter if the copied object is in the same database as the source objects or in a new database. Case 2: This case assumes that the source object and the copied object reside in the same database. If the referenced object has not been copied, the reference is left in place. Case 3: This case assumes that the source object and the copied object are in different databases. If the referenced object has not been copied, the reference to it is set to NULL (because it isnt in the destination database).
475
EntityA2
EntityA2
EntityB2
476
Chapter 18
Deep Cloning
EntityA2
477
cloned objects
478
Chapter 18
Deep Cloning
ID map. It does not need to add a new ID to the destination named object dictionary because this task was performed automatically. The following example shows how you might write an AcEditorReactor::beginDeepCloneXlation() function for a user-defined dictionary of objects that is placed in the named object dictionary. The example refers only to the kDcWblock and kDcInsert contexts.
// This example demonstrates a way to handle objects in // the named object dictionary for WBLOCK and INSERT. // Our object is an AcDbDictionary, which is called // "AsdkDictionary" in the named objects dictionary, // containing our custom objects. // const char *kpDictionary = "AsdkDictionary"; // AsdkNODEdReactor is derived from AcEditorReactor. // void AsdkNODEdReactor::beginDeepCloneXlation( AcDbIdMapping& idMap, Acad::ErrorStatus* pRetStat) { Acad::ErrorStatus es; AcDbObjectId dictId; if ( idMap.deepCloneContext() != AcDb::kDcWblock && idMap.deepCloneContext() != AcDb::kDcInsert) return; // Get the "from" and "to" databases. // AcDbDatabase *pFrom, *pTo; idMap.origDb(pFrom); idMap.destDb(pTo); // See if the "from" database has our dictionary, and // open it. If it doesn't have one, we are done. // AcDbDictionary *pSrcNamedObjDict; pFrom->getNamedObjectsDictionary(pSrcNamedObjDict, AcDb::kForRead); es = pSrcNamedObjDict->getAt(kpDictionary, dictId); pSrcNamedObjDict->close(); if (es == Acad::eKeyNotFound) return; AcDbDictionary *pSrcDict; acdbOpenObject(pSrcDict, dictId, AcDb::kForRead); AcDbObject *pClone;
479
switch (idMap.deepCloneContext()) { case AcDb::kDcWblock: // WBLOCK clones all or part of a drawing into a // newly created drawing. This means that the // named object dictionary is always cloned, and // its AcDbObjectIds are in flux. Therefore, you // cannot use getAt() or setAt() on the dictionary // in the new database. This is because the // cloned dictionary references all refer to the // original objects. During deep clone translation, // all cloned entries will be translated to the // new objects, and entries not cloned will be // "removed" by getting "translated" to NULL. // // The cloning of entries in our own dictionary are // not handled here. If all are to be cloned, then // call setTreatElementsAsHard(Adesk::kTrue) on the // dictionary. Otherwise, only those entries that // are referred to by hard references in other // wblocked objects will have been cloned via // those references. // In this example, we will always write out all of // the records. Since TreatElementsAsHard is not // currently persistent, we reset it here each time. // pSrcDict->upgradeOpen(); pSrcDict->setTreatElementsAsHard(Adesk::kTrue); pClone = NULL; pSrcDict->wblockClone(pTo, pClone, idMap, Adesk::kFalse); if (pClone != NULL) pClone->close(); break; case AcDb::kDcInsert: // In INSERT, an entire drawing is cloned, and // "merged" into a pre-existing drawing. This // means that the destination drawing may already // have our dictionary, in which case we have to // merge our entries into the destination // dictionary. First we must find out if // the destination named objects dictionary contains // our dictionary. // AcDbDictionary *pDestNamedDict; pTo->getNamedObjectsDictionary(pDestNamedDict, AcDb::kForWrite); // // // // es Since INSERT does not clone the destination named object dictionary, we can use getAt() on it. = pDestNamedDict->getAt(kpDictionary, dictId);
480
Chapter 18
Deep Cloning
// // // // // // // if
If our dictionary does not yet exist in the named object dictionary, which is not itself cloned, we have to both clone and add our dictionary to it. Since dictionary entries are ownership references, all of our entries will also be cloned at this point, so we are done. (es == Acad::eKeyNotFound) { pClone = NULL; pSrcDict->deepClone(pDestNamedDict, pClone, idMap); // // // // if Unless we have overridden the deepClone() of our dictionary, we should expect it to always be cloned here. (pClone == NULL) { *pRetStat = Acad::eNullObjectId; break;
} pDestNamedDict->setAt(kpDictionary, pClone, dictId); pDestNamedDict->close(); pClone->close(); break; } pDestNamedDict->close(); // Our dictionary already exists in the destination // database, so now we must "merge" the entries // into it. Since we have not cloned our // destination dictionary, its object IDs are not in // flux, and we can use getAt() and setAt() on it. // AcDbDictionary *pDestDict; acdbOpenObject(pDestDict, dictId, AcDb::kForWrite); AcDbObject *pObj, *pObjClone; AcDbDictionaryIterator* pIter; pIter = pSrcDict->newIterator(); for (; !pIter->done(); pIter->next()) { const char *pName = pIter->name(); pIter->getObject(pObj, AcDb::kForRead); // If the dictionary contains any references // and/or other objects have references to it, // you must either use deepClone() or put the // ID pairs into the ID map here, so that they // will be in the map for translation. // pObjClone = NULL; pObj->deepClone(pDestDict, pObjClone, idMap);
481
// // // // // // // // if
INSERT usually uses a method of cloning called CheapClone, where it "moves" objects into the destination database instead of actually cloning them. When this happens, pObj and pObjClone are pointers to the same object. We only want to close pObj here if it really is a different object. (pObj != pObjClone) pObj->close();
if (pObjClone == NULL) continue; // // // // // // // // if If the name already exists in our destination dictionary, it must be changed to something unique. In this example, the name is changed to an anonymous entry. The setAt() method will automatically append a unique identifier to each name beginning with "*", for example: "*S04". ( pDestDict->getAt(pName, dictId) == Acad::eKeyNotFound) pDestDict->setAt(pName, pObjClone, dictId); pDestDict->setAt("*S", pObjClone, dictId); pObjClone->close(); } delete pIter; pDestDict->close(); break; default: break; } pSrcDict->close(); }
else
Cloning (you can override this stage) Translation (you will not need to reimplement this stage; it can be controlled by what is put into the ID map)
During the cloning stage in this example, information about the old object is copied to the new object using a specific type of filer to write out the object
482
Chapter 18
Deep Cloning
and read it back. The filer keeps track of objects owned by the primary object so that they can be copied as well. To complete the cloning stage 1 Create a new object of the same type as the old one. 2 Append the new object to its owner.
I I
If the object is an entity, its owner is a block table record and you can use the appendAcDbEntity() function. If the object is an AcDbObject, its owner is an AcDbDictionary and you can use the setAt() function to add it to the dictionary. If this is not a primary object, you would normally add it to the database using addAcDbObject() and then identify its owner using setOwnerId(). To establish ownership, the owner must file out the ID of the owned object using the appropriate ownership type.
3 Call dwgOut() on the original object, using a deep clone filer (AcDbDeepCloneFiler) to write out the object. (Or, if you are overriding the wblockClone() function, use an AcDbWblockCloneFiler.) 4 Rewind the filer and then call dwgIn() on the new object. 5 Call setObjectIdsInFlux() on each new object before you add its value to the object ID map. This important step is used to indicate that the newly created object is part of a deep clone operation and its object ID is subject to change as part of the translation stage. This flag is automatically turned off when translation is complete. 6 Add the new information to the idMap. The idMap contains AcDbIdPairs, which are pairs of old (original) and new (cloned) object IDs. The constructor for the ID pair sets the original object ID and the isPrimary flag. At this point, you set the object ID for the cloned object, set the isCloned flag to TRUE, and add (assign) it to the idMap. 7 Clone the owned objects. (This step is recursive.)
I I I
Ask the filer if there are any more owned objects. (For wblock clone, ask if there are any more hard objects.) To clone a subobject, obtain its ID and open the object for read. Call deepClone() on the object. (Note that isPrimary is set to FALSE, because this is an owned object.) The deepClone() function clones the object and sets its owner. It also adds a record to the ID map. Close the subobject if it was created at this time.
483
484
Chapter 18
Deep Cloning
// Some form of this code is only necessary if // anyone has set up an ownership for the object // other than with an AcDbBlockTableRecord. // pOwner->database()->addAcDbObject(pClone); pClone->setOwnerId(pOwner->objectId()); } // Step 3: Now contents are copied to the clone. // using an AcDbDeepCloneFiler. This filer keeps // AcDbHardOwnershipIds and AcDbSoftOwnershipIds // the object and its derived classes. This list // to know what additional, "owned" objects need // below. // AcDbDeepCloneFiler filer; dwgOut(&filer); This is done a list of all contained in is then used to be cloned
// Step 4: Rewind the filer and read the data into the clone. // filer.seek(0L, AcDb::kSeekFromStart); pClone->dwgIn(&filer); // Step 5: This must be called for all newly created objects // in deepClone(). It is turned off by endDeepClone() // after it has translated the references to their // new values. // pClone->setAcDbObjectIdsInFlux(); // Step 6: Add the new information to the ID map. // the ID pair started above. // idPair.setValue(pClonedObject->objectId()); idPair.setIsCloned(Adesk::kTrue); idMap.assign(idPair); We can use
// Step 7: Using the filer list created above, find and clone // any owned objects. // AcDbObjectId id; while (filer.getNextOwnedObject(id)) { AcDbObject *pSubObject; AcDbObject *pClonedSubObject; // Some object's references may be set to NULL, // so don't try to clone them. // if (id == NULL) continue;
485
// Open the object and clone it. Note that "isPrimary" is // set to kFalse here because the object is being cloned, // not as part of the primary set, but because it is owned // by something in the primary set. // acdbOpenAcDbObject(pSubObject, id, AcDb::kForRead); pClonedSubObject = NULL; pSubObject->deepClone(pClonedObject, pClonedSubObject, idMap, Adesk::kFalse); // // // // // // // // if If this is a kDcInsert context, the objects may be "cheap" cloned. In this case, they are "moved" instead of cloned. The result is that pSubObject and pClonedSubObject will point to the same object. Therefore, we only want to close pSubObject if it really is a different object than its clone. (pSubObject != pClonedSubObject) pSubObject->close(); The pSubObject may either already have been cloned, or for some reason has chosen not to be cloned. In that case, the returned pointer will be NULL. Otherwise, since we have no immediate use for it now, we can close the clone. (pClonedSubObject != NULL) pClonedSubObject->close();
// // // // // // if }
486
Chapter 18
Deep Cloning
to check the owner of the subobject. At this point, youll do one of two things:
I I
If you are the owner of the object, set the owner of the subobject to be the clone of yourself. If you are not the owner of the object, pass in the clones database as the pOwner parameter in the wBlockClone() function call. At this time, the object is appended to the new database, receives an object ID, and is put into the ID map. The ID map entry for this object will specify FALSE for the isOwnerTranslated field.
If pOwner is set to the database, wblockClone() must set the owner of the cloned object to the same owner as that of the original object. Then, when the references are translated by AutoCAD, it will update the owner reference to the cloned object in the new database. It is important to ensure that all owning objects are cloned. AutoCAD always clones the symbol tables, named object dictionary, model space, and paper space (for clone contexts other than AcDb::kDcXrefBind) during wblock clone. Applications with owning objects are responsible for ensuring that these objects are cloned if necessary. If an owning object is not cloned and not found in the ID map, wblock clone aborts with AcDb::eOwnerNotSet. You must pass in the database as the owner of an object when you are copying an entity that references a symbol table record. For example, suppose you are calling wblockClone() on a sphere object. A block table record is the hard owner of this sphere object. The sphere object contains a hard reference to the layer table. First, at the beginDeepClone() phase, the new database is created and set up with the default elements. The following figure shows the model space block table record and the layer table, because theyre relevant to this topic. The cloning that occurs at this stage always happens during a wblock operation.
487
At the beginWblock() stage, the selection set is cloned, as shown in the following figure. In this example, the sphere is cloned.
ModelSpaceBTR1
LayerTable1
ModelSpaceBTR2
LayerTable2
cloned objects
ModelSpaceBTR2
Sphere2
LayerTable2
488
Chapter 18
Deep Cloning
ModelSpaceBTR2
Sphere2
LayerTable2
Next, pointers need to be translated to refer to the cloned objects, as shown in the following figure. The beginDeepCloneXlation() notification indicates the beginning of this stage.
beginDeepCloneXlation() ModelSpaceBTR1 Sphere1 Layer1 LayerTable1
ModelSpaceBTR2
Sphere2
LayerTable2
The ID map for the previous figure at the time of beginDeepCloneXlation() is as follows: ID map of the previous figure
KEY BTR1 SPH1 VALUE BTR2 SPH2 isCloned TRUE TRUE isPrimary FALSE TRUE isOwnerXlated TRUE TRUE
489
* The layer tables owner is the database itself, so this entry is meaningless. ** During translation, this setting indicates that the layer will have its owner translated from LayerTable1 to LayerTable2. The wblock clone process is used for xref bind as well as wblock. The needs of both are very similar, but there are a few differences that require special attention when overriding the wblockClone(). Wblock clones all selected entities. However, xref bind never clones entities that are in paper space. This leaves two things to consider when creating objects or entities, and using AcDbHardPointerIds. First, at the beginning of any AcDbEntitys wblockClone(), check to see if the cloning context is AcDb::kDcXrefBind and, if so, whether the entity is being cloned in paper space. If it is, then no cloning should be done and wblockClone() should return Acad::eOk. If your custom class has any AcDbHardPointerIds that can point to entities (such as we do with AcDbGroup), then the entities might be in paper space and will therefore not get cloned. In that event, the AcDbHardPointerIds will be set to NULL. Wblock does not follow hard pointer references across databases. However, xref bind does this all the time. For example, an entity in an xref drawing can be on a VISRETAIN layer in the host drawing. So, if you implement your wblockClone() with a loop to check for subobjects, and the subobjects database is not the same as that of the object being cloned, you must skip the subobject if the cloning context is not AcDb::kDcXrefBind. For example:
if(pSubObject->database() != database() && idMap.deepCloneContext() != AcDb::kDcXrefBind) { pSubObject->close(); continue; }
490
Chapter 18
Deep Cloning
The following code shows overriding wblockClone()to implement it for a custom entity (AsdkPoly). This function is invoked with the code shown in Editor Reactor Notification Functions on page 502.
Acad::ErrorStatus AsdkPoly::wblockClone(AcRxObject* pOwner, AcDbObject*& pClonedObject, AcDbIdMapping& idMap, Adesk::Boolean isPrimary) const { // You should always pass back pClonedObject == NULL // if, for any reason, you do not actually clone it // during this call. The caller should pass it in // as NULL, but to be safe, it is set here as well. // pClonedObject = NULL; // If this is a fast wblock operation, no cloning // should take place, so we simply call the base class's // wblockClone() and return whatever it returns. // // For fast wblock, the source and destination databases // are the same, so we can use that as the test to see // if a fast wblock is in progress. // AcDbDatabase *pDest, *pOrig; idMap.destDb(pDest); idMap.origDb(pOrig); if (pDest == pOrig) return AcDbCurve::wblockClone(pOwner, pClonedObject, idMap, isPrimary); // If this is an xref bind operation and this AsdkPoly // entity is in paper space, then we don't want to // clone because xref bind doesn't support cloning // entities in paper space. Simply return Acad::eOk. // static AcDbObjectId pspace = AcDbObjectId::kNull; if (pspace == AcDbObjectId::kNull) { AcDbBlockTable *pTable; database()->getSymbolTable(pTable, AcDb::kForRead); pTable->getAt(ACDB_PAPER_SPACE, pspace); pTable->close(); } if ( idMap.deepCloneContext() == AcDb::kDcXrefBind && ownerId() == pspace) return Acad::eOk;
491
// If this object is in the idMap and is already // cloned, then return. // bool isPrim = false; if (isPrimary) isPrim = true; AcDbIdPair idPair(objectId(), (AcDbObjectId)NULL, false, isPrim); if (idMap.compute(idPair) && (idPair.value() != NULL)) return Acad::eOk; // The owner object can be either an AcDbObject or an // AcDbDatabase. AcDbDatabase is used if the caller is // not the owner of the object being cloned (because it // is being cloned as part of an AcDbHardPointerId // reference). In this case, the correct ownership // will be set during reference translation. If // the owner is an AcDbDatabase, then pOwn will be left // NULL here, and is used as a "flag" later. // AcDbObject *pOwn = AcDbObject::cast(pOwner); AcDbDatabase *pDb = AcDbDatabase::cast(pOwner); if (pDb == NULL) pDb = pOwn->database(); // Step 1: Create the clone. // AsdkPoly *pClone = (AsdkPoly*)isA()->create(); if (pClone != NULL) pClonedObject = pClone; // Set the return value. else return Acad::eOutOfMemory; // // // // // // // // // // // // // // // // // // // // Step 2: If the owner is an AcDbBlockTableRecord, go ahead and append the clone. If not, but we know who the owner is, set the clone's ownerId to it. Otherwise, we set the clone's ownerId to our own ownerId (in other words, the original ownerId). This ID will then be used later, in reference translation, as a key to finding who the new owner should be. This means that the original owner must also be cloned at some point during the wblock operation. EndDeepClone's reference translation aborts if the owner is not found in the ID map. The most common situation where this happens is AcDbEntity references to symbol table records, such as the layer an entity is on. This is when you will have to pass in the destination database as the owner of the layer table record. Since all symbol tables are always cloned in wblock, you do not need to make sure that symbol table record owners are cloned.
492
Chapter 18
Deep Cloning
// However, if the owner is one of your own classes, // then it is up to you to make sure that it gets // cloned. This is probably easiest to do at the end // of this function. Otherwise you may have problems // with recursion when the owner, in turn, attempts // to clone this object as one of its subobjects. // AcDbBlockTableRecord *pBTR = NULL; if (pOwn != NULL) pBTR = AcDbBlockTableRecord::cast(pOwn); if (pBTR != NULL) { pBTR->appendAcDbEntity(pClone); } else { pDb->addAcDbObject(pClonedObject); pClone->setOwnerId( (pOwn != NULL) ? pOwn->objectId() : ownerId()); } // Step 3: The AcDbWblockCloneFiler makes a list of // AcDbHardOwnershipIds and AcDbHardPointerIds. These // are the references that must be cloned during a // wblock operation. // AcDbWblockCloneFiler filer; dwgOut(&filer); // Step 4: Rewind the filer and read the data into the clone. // filer.seek(0L, AcDb::kSeekFromStart); pClone->dwgIn(&filer); // Step 5: // This must be called for all newly created objects // in wblockClone. It is turned off by endDeepClone() // after it has translated the references to their // new values. // pClone->setAcDbObjectIdsInFlux(); // Step 6: Add the new information to the ID map. We can use // the ID pair started above. We must also let the // idMap entry know whether the clone's owner is // correct, or needs to be translated later. // idPair.setIsOwnerXlated((Adesk::Boolean)(pOwn != NULL)); idPair.setValue(pClonedObject->objectId()); idPair.setIsCloned(Adesk::kTrue); idMap.assign(idPair);
493
// Step 7: Using the filer list created above, find and clone // any hard references. // AcDbObjectId id; while (filer.getNextHardObject(id)) { AcDbObject *pSubObject; AcDbObject *pClonedSubObject; // Some object references may be set to NULL, // so don't try to clone them. // if (id == NULL) continue; // If the referenced object is from a different // database, such as an xref, do not clone it. // acdbOpenAcDbObject(pSubObject, id, AcDb::kForRead); if (pSubObject->database() != database()) { pSubObject->close(); continue; } // To find out if this is an AcDbHardPointerId // versus an AcDbHardOwnershipId, check if we are the // owner of the pSubObject. If we are not, // then we cannot pass our clone in as the owner // for the pSubObject's clone. In that case, we // pass in our clone's database (the destination // database). // // Note that "isPrimary" is set to kFalse here // because the object is being cloned, not as part // of the primary set, but because it is owned by // something in the primary set. // pClonedSubObject = NULL; if (pSubObject->ownerId() == objectId()) { pSubObject->wblockClone(pClone, pClonedSubObject, idMap, Adesk::kFalse); } else { pSubObject->wblockClone( pClone->database(), pClonedSubObject, idMap, Adesk::kFalse); } pSubObject->close();
494
Chapter 18
Deep Cloning
// // // // // // if
The pSubObject may either already have been cloned, or for some reason has chosen not to be cloned. In that case, the returned pointer will be NULL. Otherwise, since there is no immediate use for it now, the clone can be closed. (pClonedSubObject != NULL) pClonedSubObject->close();
495
To find the destination model space, you must look it up in the ID map:
void AsdkWblockReactor::otherWblock( AcDbDatabase* pDestDb, AcDbIdMapping& idMap, AcDbDatabase* pSrcDb) { // To find the destination model space, you must look // it up in the ID map: AcDbBlockTable *pSrcBlockTable; pSrcDb->getSymbolTable(pSrcBlockTable, AcDb::kForRead); AcDbObjectId srcModelSpaceId; pSrcBlockTable->getAt(ACDB_MODEL_SPACE, srcModelSpaceId); pSrcBlockTable->close(); AcDbIdPair idPair; idPair.setKey(srcModelSpaceId); idMap.compute(idPair); AcDbBlockTableRecord *pDestBTR; acdbOpenAcDbObject((AcDbObject*&)pDestBTR, idPair.value(), AcDb::kForRead, Adesk::kTrue); }
in order to do the append properly. During cloning, an entity may be appended to an AcDbBlockTableRecord only if AcDbBlockTableRecord::isObjectIdsInFlux() returns Adesk::kFalse. This indicates that the AcDbBlockTableRecord itself is not currently being cloned. The one exception to this rule occurs when the cloned AcDbBlockTableRecord is empty. Because an empty AcDbBlockTableRecord contains no untranslated AcDbObjectIds, the append will work properly. This situation arises during some forms of wblock(), and is described in more detail shortly. If deep cloning is being called on individual entities, then their clones must be appended to the destination AcDbBlockTableRecord. However, when the AcDbBlockTableRecord itself is being deep cloned, then all its entities are cloned with it, and a call to AcDbBlockTableRecord::appendAcDbEntity() will not only be unnecessary, but would corrupt the cloned AcDbBlockTableRecord. Default implementations of deepClone() and wblockClone() know when to call AcDbBlockTableRecord::appendAcDbEntity() by checking the isPrimary value. When an entity is being deep cloned by itself, isPrimary is
496
Chapter 18
Deep Cloning
true, and append is called. If the entity is being cloned as the result of a deep cloning of an AcDbBlockTableRecord, then isPrimary is false, and append is not called. Normally, applications do not need to be concerned with this detail and can rely on the default implementation of deepClone() and wblockClone() to handle entities. However, situations can arise when applications may want to add entities during cloning, or use hard references to entities. The hard referenced entity will have an isPrimary value of Adesk::kFalse and will not call append, even when it may need to do so. This situation is covered in the next section. The following examples and rules illustrate important aspects of cloning.
deepClone()
The AcDbHardPointerId reference problem mentioned above will not occur in this case because deepClone() does not follow AcDbHardPointerId references for cloning. An application may create problems during deepClone() if it attempts to add new entities while the AcDbObjectIds are still in flux. Therefore, never attempt to call AcDbBlockTableRecord::appendAcDbEntity() on any cloned, user-defined AcDbBlockTableRecords until after the AcEditorReactor::endDeepClone() notification has been given. In contrast, you may safely append to the model space and paper space AcDbBlockTableRecords, because these are never cloned in deepClone(). Never try to add vertices to cloned AcDb2dPolylines, AcDb3dPolylines, AcDbPolyFaceMeshes, or AcDbPolygonMeshes, attributes to cloned AcDbBlockReferences, or entries to cloned dictionaries, until after the AcEditorReactor::endDeepClone() notification. If you must create the entities during the cloning, then you will need to keep them in memory, along with their future owners ID, until after the AcEditorReactor::endDeepClone() notification. They can be safely appended once the deep clone is completed.
497
wblockClone()
There are three versions of AcDbDatabase::wblock(): 1 WBLOCK*
Acad::ErrorStatus AcDbDatabase::wblock( AcDbDatabase*& pOutputDatabase)
One of the main internal differences between these three versions of wblock is their treatment of the model space and paper space AcDbBlockTableRecords. Because the entire database is being cloned in version one, all the entities in model space and paper space are cloned along with their containing paper and model space AcDbBlockTableRecords. However, in versions two and three, the intent is to clone only a selected set of entities. Although the model space and paper space AcDbBlockTableRecords are processed, these use a shallow clone, which does not in turn clone all the entities contained in model space and paper space. Even though the model space and paper space blocks have been cloned in versions two and three, they are empty. Therefore, it is not only acceptable to call AcDbBlockTableRecord::AppendAcDbEntity() to place the cloned entities into them, it is necessary to do so. (This is the exception to using AcDbBlocKTableRecord::AppendAcDbEntity() on AcDbBlockTableRecords, whose IDs are in flux). Also, in both versions two and three, the entities will have isPrimary set to Adesk::kTrue when they get their wblockClone() call. This is because the internal code individually clones the entities of the selection set, or the entities of the selected AcDbBlockTableRecord. It does not clone the AcDbBlockTableRecord itself. (Entities in nested blocks, however, will still have isPrimary set to Adesk::kFalse). This behavior is useful, as will be seen in the next section, in Case 1. It saves applications from having to know what type of WBLOCK operation is occurring.
498
Chapter 18
Deep Cloning
Here are some more rules to keep in mind: 1 Never use AcDbBlocKTableRecord::AppendAcDbEntity() during WBLOCK*. If you must create new entities, you must keep them in memory, along with their future owners ID, and then append them after AcEdItorReactor::endDeepClone(). This also applies to appending objects to AcDbDictionaries, polylines, polyfacemeshes, polygonmeshes, and block references. 2 In the other two forms of WBLOCK, only use
AcDbBlocKTableRecord::ApPendAcDbEntity() when appending to model
space or paper space. But with that exception, all the other restrictions mentioned for WBLOCK* still apply.
499
correct. Once youve cloned the referenced entity, when you call your own
wblockClone(), it will see that the referenced entity is already cloned and
will not attempt to clone it using the default settings. The following sample demonstrates this. The data member, mRefEnt, is the reference AcDbHardPointerId.
Acad::ErrorStatus AsdkEntity::wblockClone(AcRxObject* pOwner, AcDbObject*& pClone, AcDbIdMapping& idMap, Adesk::Boolean isPrimary) const { // If isPrimary is kTrue, then override the default cloning // within our own cloning, which would set it to kFalse, // by cloning our referenced entity first. // if (isPrimary) { Acad::ErrorStatus es; AcDbEntity* pEnt; es = acdbOpenAcDbEntity(pEnt, mRefEnt, AcDb::kForRead); if (es != Acad::eOk) return es;
500
Chapter 18
Deep Cloning
// Use the same owner, and pass in the same isPrimary // value. // AcDbObject* pSubClone = NULL; es = pEnt->wblockClone(pOwner, pSubClone, idMap, kTrue); if (pSubClone != NULL) pSubClone->close(); pEnt->close(); if (es != Acad::eOk) return es; } // Now we can clone ourselves by calling our parent's method. // return AcDbEntity::wblockClone(pOwner, pClone, idMap, isPrimary); }
AcDbBlockTableRecord::appendAcDbEntity() in the custom classs override of wblockClone(), during callbacks, or in any other place. I If it is a WBLOCK of a user-defined block, it may depend on where the ref-
erenced entity currently exists. First, remember that the selected block is getting exploded into model space of the destination drawing. You may want to define this behavior in some other way, but a couple scenarios may be as follows: 1) always clone the referenced entities into model space as well. In this case, you would always set isPrimary to Adesk::kTrue, or, 2) check the current location of the referenced entity. If it is in model space or paper space, clone it to the corresponding space and set isPrimary to Adesk::kTrue. If it is in the selected block, also clone it to model space. If it is in some other user-defined block, then call
501
wblockClone() on that block record. Just be sure that you do not try to
clone the selected block. In this case, the block table record will take care of cloning your referenced entity. If it is a WBLOCK of a selection set, only reset isPrimary to Adesk::kTrue if the referenced entity is going into model space or paper space. If it is in a user-defined block, call wblockClone() on that AcDbBlockTableRecord, instead of on your referenced entity.
Finally, it should be noted that setting up a hard reference to an AcDbEntity is not currently supported by the AcDbProxyObject system, even if you use an AcDbHardPointerId for the reference. AcDbProxyObject uses the default wblockClone() implementation, and thus will not do the append of any referenced entities during either form of WBLOCK. If a WBLOCK happens when your entities are proxies, the references will get cloned, but without the append they will be ownerless and are not persistent. The result is that when the wblocked drawing gets loaded, your reference ID will be NULL, and the referenced entity will be missing. You must code your custom object to handle this situation gracefully.
Insert
The insert operation is a special case of deep cloning. In the case of an insert, the objects are not copied into the destination database; instead, they are moved into the new database. When this occurs, the source database is no longer valid, because it has been cannibalized when its objects were moved into the new database. If you override the deepClone() function, your objects will simply be cloned when an insert operation is called for. If you use the default form of deepClone(), cheap cloning is performed internally. When an object is copied in this way, the ID map still contains two object IDs for each cloned object (the source ID and the destination ID), but these IDs point temporarily to the same object. When the insert operation finishes, the source database is deleted.
502
Chapter 18
Deep Cloning
The beginDeepClone() function is called after the AcDbIdMapping instance is created and before any objects are cloned. The ID map will be empty, but it can be queried for destDb() and deepCloneContext() at this time. The beginDeepCloneXlation() function is called after all of the objects in the primary selection set have been cloned and before the references are translated. This is the first time it is possible to see the entire set of what was cloned in the ID map. It is also the time to clone any additional objects and add them to the ID map. Remember that any objects cloned have their object IDs in flux at this point. The abortDeepClone() function is called at any time between beginDeepClone() and endDeepClone(). The endDeepClone() function is called at the end of the cloning and translation process. The object IDs are no longer in flux. However, this call does not mean that the entities are in their final state for whatever command is being executed. Often the cloned entities are transformed or other operations are performed following the cloning process. There are additional callback functions that can be used to access the entities later, including commandEnded(). In addition to the previous four functions, the following notification functions are provided in the wblock clone operation:
I I I I
These calls come in the following order with the deep clone functions: 1 beginDeepClone() This call is sent as soon as the destination AcDbDatabase instance has been created, but it is in a raw state and is not ready for appending. 2 beginWblock() The new database now has its basic elements, such as a handle table, a class ID map, and model space and paper space block table records. It is still empty. The cloning has not begun, but the new database is now ready for appending. 3 otherWblock() and beginDeepCloneXlation() These two calls are made back-to-back and can be used for the same purpose. The primary set of objects has been cloned, but the reference translation has not started yet. 4 endDeepClone() The translation process has now completed, but the entities are not yet in their final state.
503
5 endWblock() The entities have now been transformed, and the model space and paper space origins have been set. The new database is complete but has not yet been saved. There are three types of AcEditorReactor::beginWblock(). They are listed here along with their corresponding AcDbDatabase functions: 1 WBLOCK*
void AcEditorReactor::beginWblock( AcDbDatabase* pTo, AcDbDatabase* pFrom) Acad::ErrorStatus AcDbDatabase::wblock(AcDbDatabase*& pOutputDatabase)
All three versions clone both the model space and paper space AcDbBlockTableRecord before calling beginWblock(). However, for the entities within these block table records, the order of notification will appear to come differently in the first type and last two types. In version one, entities in model space that are being cloned will receive the call for wblockClone() before the AcEditorReactor::beginWblock(). In versions two and three, entities in the AcDbBlockTableRecord or the selection set will get their wblockClone() call after the AcEditorReactor::beginWblock() notification call.
504
Chapter 18
Deep Cloning
Objects that have been cloned during a partial XBIND are automatically redirected just after endDeepClone() notification. This means that their AcDbObjectIds in the externally referenced database are forwarded to the AcDbObjectIds of the clone objects in the host drawing, and the objects in the externally referenced database are deleted. Objects that reference the forwarded AcDbObjectIds end up referencing the clones in the host drawing. If you need to disable this automatic redirection for your objects, then remove the idPair() from the idMap, for your cloned objects, during endDeepClone() notification. The following function calls occur during an INSERT or INSERT* command:
I I I I
These calls come in the following order with the deep clone functions: 1 beginInsert() and beginDeepClone() These calls come back-to-back and can be used for the same purpose. 2 otherInsert() and beginDeepCloneXlation() These calls also come backto-back and can be used for the same purpose. 3 endDeepClone() The cloning and translation processes are completed. The entities are cloned but have not been appended to a block, so they are not graphical. You cannot use the entities in a selection set yet. 4 endInsert() The entities have now been transformed and have been appended to a block. If this is an INSERT*, they are now in model space and have their graphics. They can be used in selection sets. However, if this is an INSERT, they have only been appended to a block table record; that record has not yet been added to the block table. In this case, you must wait until commandEnded() notification to use these entities in a selection set. The sample code in this section uses the beginDeepCloneXlation() notification function. This sample illustrates how you could write a reactor to add behavior to the WBLOCK command to tell it to include all text styles in the new drawing, instead of only the text styles that are referenced by the entities. It thus shows how to use wblock with nonentities.
505
text in which the deep clone function was called. The contexts are the following: kDcCopy Copying within a database; uses COPY, ARRAY, MIRROR (if you are not deleting the original), LEADER acquisition, or copy of an INSERT
EXPLODE of a block reference BLOCK creation XREF Bind and XBIND XREF Attach, DXFIN, and IGESIN (only symbol table
The AcEditorReactor::abortDeepClone() function is called when a call to AcDbDatabase::abortDeepClone() is made. The following code uses a transient editor reactor derived from
AcEditorReactor and overrides the beginDeepCloneXlation() function for
the reactor.
// // // // // // // Since AcDbDatabase::wblock() only supports AcDbEntities in its array of IDs, this code demonstrates how to add additional objects during beginDeepCloneXlation(). If it is a WBLOCK command, it asks the user if all text styles should be wblocked. Otherwise, only those text styles referenced by entities being wblocked will be included (wblock's default behavior).
506
Chapter 18
Deep Cloning
void AsdkEdReactor::beginDeepCloneXlation(AcDbIdMapping& idMap, Acad::ErrorStatus* es) { if (idMap.deepCloneContext() == AcDb::kDcWblock && getYorN("Wblock all Text Styles")) { AcDbDatabase *pOrigDb, *pDestDb; if (idMap.origDb(pOrigDb) != Acad::eOk) return; *es = idMap.destDb(pDestDb); if (*es != Acad::eOk) return; AcDbTextStyleTable *pTsTable; *es = pOrigDb->getSymbolTable(pTsTable, AcDb::kForRead); if (*es != Acad::eOk) return; AcDbTextStyleTableIterator *pTsIter; *es = pTsTable->newIterator(pTsIter); if (*es != Acad::eOk) { pTsTable->close(); return; } AcDbTextStyleTableRecord *pTsRecord; AcDbObject *pClonedObj; for (; !pTsIter->done(); pTsIter->step()) { *es = pTsIter->getRecord(pTsRecord, AcDb::kForRead); if (*es != Acad::eOk) { delete pTsIter; pTsTable->close(); return; } // It is not necessary to check for already cloned // records. If the text style is already // cloned, wblockClone() will return Acad::eOk // and pCloneObj will be NULL. // pClonedObj = NULL; *es = pTsRecord->wblockClone(pDestDb, pClonedObj, idMap, Adesk::kFalse); if (*es != Acad::eOk) { pTsRecord->close(); delete pTsIter; pTsTable->close(); return; }
507
*es = pTsRecord->close(); if (*es != Acad::eOk) { delete pTsIter; pTsTable->close(); return; } if (pClonedObj != NULL) { *es = pClonedObj->close(); if (*es != Acad::eOk) { delete pTsIter; pTsTable->close(); return; } } } delete pTsIter; *es = pTsTable->close(); } }
508
Chapter 18
Deep Cloning
Protocol Extension
19
In this chapter
I Protocol Extension Defined I Implementing Protocol
All C++ class definitions are fixed at compile time. Under normal circumstances, if you write a file translator or an editing command that operates on a number of existing AutoCAD classes, you have to redefine all the existing classes to include the new translator or editing functions. And you would have to recompile your library as well as all the applications that use it. By using the ObjectARX protocol extension mechanism described in this section, you can add functionality to existing ObjectARX classes at runtime, without any modification of existing classes and recompilation.
Extension
I Protocol Extension for the
MATCH Command
I Protocol Extension Example
509
510
Chapter 19
Protocol Extension
AcRxObject
EntTemperature
DefaultTemperature
RegionTemperature
CircleTemperature
The first step in using protocol extension is to declare and define each of the protocol extension classes. The base class, AsdkEntTemperature, is an abstract base class that is defined using the ACRX_NO_CONS_DEFINE_MEMBERS() macro. This class will eventually be registered as part of the ObjectARX class hierarchy. The child classes are defined using standard C++ syntax for deriving new classes. These classes should not be registered in the ObjectARX class hierarchy, so you dont need to use the ObjectARX macros for them. For each class, you implement the functions that constitute the protocol extension. In this example, each class has only one function, reflectedEnergy(), which calculates a temperature for the entity.
These function calls are required for any new ObjectARX class, as described in chapter 11, Deriving a Custom ObjectARX Class.
511
2 Create an object of each protocol extension class and add the objects to the appropriate AcRxClass descriptor objects using the addX() function as shown in the following example:
pDefaultTemp = new AsdkDefaultTemperature(); pRegionTemp = new AsdkRegionTemperature(); pCircleTemp = new AsdkCircleTemperature(); // Add the protocol extension objects to the appropriate // AcRxClass objects. // AcDbEntity::desc()->addX(AsdkEntTemperature::desc(), ppDefaultTemp); AcDbRegion::desc()->addX(AsdkEntTemperature::desc(), ppRegionTemp); AcDbCircle::desc()->addX(AsdkEntTemperature::desc(), ppCircleTemp);
At runtime, ObjectARX constructs a class descriptor object structure that includes the basic ObjectARX class hierarchy as well as the protocol extension objects associated with the ObjectARX class descriptor objects. The following figure shows the class descriptor object structure for the classes that relate to the AsdkEntTemperature example in this section:
EntTemperature::desc() DefaultTemperature
AcRxClass "AcDbEntity"
512
Chapter 19
Protocol Extension
You can use the ACRX_X_CALL macro to simplify this code as follows:
double eTemp = ACRX_X_CALL(pEnt, AsdkEntTemperature)->reflectedEnergy(pEnt);
513
a protocol extension base class that provides entity support for the
MATCHPROP command. (See the AutoCAD Command Reference for information about MATCHPROP.) The default protocol extension class for AcDbEntity
allows copying of color, layer, linetype, and linetype scale properties from one entity to another. It is, nevertheless, preferable to implement AcDbMatchProperties as a protocol extension class for all custom objects derived from AcDbEntity, to provide full support for MATCHPROP. If the default protocol extension class is overridden with AcDbMatchProperties, it must include functions to copy the base class properties as well.
Declaration and definition of four protocol extension classes: AsdkEntTemperature, AsdkDefaultTemperature, AsdkRegionTemperature, and AsdkCircleTemperature. The implementation of the energy() function for the ENERGY command, which allows the user to select an entity and then calculates a temperature for that entity. The ObjectARX module interface functions: initApp(), unloadApp(), and acrxEntryPoint().
// This is the AsdkEntTemperature protocol extension abstract base // class. Notice that this is the lowest level that uses // the ACRX macros. // class AsdkEntTemperature : public AcRxObject { public: ACRX_DECLARE_MEMBERS(AsdkEntTemperature); virtual double reflectedEnergy(AcDbEntity*) const = 0; };
514
Chapter 19
Protocol Extension
ACRX_NO_CONS_DEFINE_MEMBERS(AsdkEntTemperature, AcRxObject); // This is the default implementation to be attached to AcDbEntity // as a catch-all. This guarantees that this protocol extension will // be found for any entity, so the search up the AcRxClass tree will // not fail and abort AutoCAD. // class AsdkDefaultTemperature : public AsdkEntTemperature { public: virtual double reflectedEnergy(AcDbEntity* pEnt) const; }; double AsdkDefaultTemperature::reflectedEnergy( AcDbEntity* pEnt) const { acutPrintf( "\nThis entity has no area, and no reflection.\n"); return -1.0; } // AsdkEntTemperature implementation for Regions // class AsdkRegionTemperature : public AsdkEntTemperature { public: virtual double reflectedEnergy(AcDbEntity* pEnt) const; }; double AsdkRegionTemperature::reflectedEnergy( AcDbEntity* pEnt) const { AcDbRegion *pRegion = AcDbRegion::cast(pEnt); if (pRegion == NULL) acutPrintf("\nThe impossible has happened!"); // Compute the reflected energy as the region area multiplied // by a dummy constant. // double retVal; if (pRegion->getArea(retVal) != Acad::eOk) return -1.0; return retVal * 42.0; } // AsdkEntTemperature implementation for circles // class AsdkCircleTemperature : public AsdkEntTemperature { public: virtual double reflectedEnergy(AcDbEntity* pEnt) const; };
515
double AsdkCircleTemperature::reflectedEnergy( AcDbEntity* pEnt) const { AcDbCircle *pCircle = AcDbCircle::cast(pEnt); // Compute the reflected energy in a manner distinctly // different than for AcDbRegion. // return pCircle->radius() * 6.21 * 42.0; } // This function has the user select an entity and then // calls the reflectedEnergy() function in the protocol // extension class attached to that entity's class. // void energy() { AcDbEntity *pEnt; AcDbObjectId pEntId; ads_name en; ads_point pt; if (acedEntSel("\nSelect an Entity: ", en, pt) != RTNORM) { acutPrintf("Nothing Selected\n"); return; } acdbGetObjectId(pEntId, en); acdbOpenObject(pEnt, pEntId, AcDb::kForRead); // call the protocol extension class's method // double eTemp = ACRX_X_CALL(pEnt, AsdkEntTemperature)->reflectedEnergy(pEnt); acutPrintf("\nEnergy == %f\n", eTemp); pEnt->close(); } // Pointers for protocol extension objects. These pointers // are global so that they can be accessed during // initialization and cleanup. // AsdkDefaultTemperature *pDefaultTemp; AsdkRegionTemperature *pRegionTemp; AsdkCircleTemperature *pCircleTemp;
516
Chapter 19
Protocol Extension
// Initialization function called from acrxEntryPoint() during // kInitAppMsg case. This function is used to add commands // to the command stack and to add protocol extension // objects to classes. // void initApp() { acrxRegisterService("AsdkTemperature"); AsdkEntTemperature::rxInit(); acrxBuildClassHierarchy(); pDefaultTemp = new AsdkDefaultTemperature(); pRegionTemp = new AsdkRegionTemperature(); pCircleTemp = new AsdkCircleTemperature(); // Add the protocol extension objects to the appropriate // AcRxClass objects. // AcDbEntity::desc()->addX(AsdkEntTemperature::desc(), pDefaultTemp); AcDbRegion::desc()->addX(AsdkEntTemperature::desc(), pRegionTemp); AcDbCircle::desc()->addX(AsdkEntTemperature::desc(), pCircleTemp); acedRegCmds->addCommand("ASDK_TEMPERATURE_APP", "ASDK_ENERGY", "ENERGY", ACRX_CMD_TRANSPARENT, energy); } void unloadApp() { delete acrxServiceDictionary->remove("AsdkTemperature"); acedRegCmds->removeGroup("ASDK_TEMPERATURE_APP"); // Remove protocol extension objects from the AcRxClass // object tree. This must be done before removing the // AsdkEntTemperature class from the ACRX runtime class // hierarchy, so the AsdkEntTemperature::desc() // still exists. // AcDbEntity::desc()->delX(AsdkEntTemperature::desc()); delete pDefaultTemp; AcDbRegion::desc()->delX(AsdkEntTemperature::desc()); delete pRegionTemp; AcDbCircle::desc()->delX(AsdkEntTemperature::desc()); delete pCircleTemp; // Remove the AsdkEntTemperature class from the ARX // runtime class hierarchy. // deleteAcRxClass(AsdkEntTemperature::desc()); }
517
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(); } return AcRx::kRetOK; }
518
Chapter 19
Protocol Extension
20
In this chapter
I Common Characteristics of
This section discusses some general characteristics of the ObjectARX global utility functions. For more information on specific functions, see the ObjectARX Reference.
Defined in ObjectARX
I Lists and Other Dynamically
Allocated Data
I Extended Data Exclusive Data
Types
I Text String Globalization Issues
519
NOTE The functions described in this section were known as the ADS
functions in previous releases of AutoCAD .
520
Chapter 20
variable-length argument list and in addition takes arguments to specify the type of the values being passed; acedCmd() requires a similar set of values but is passed as a linked list. Therefore, acedCommand() and acedCmd() arguments do not correspond exactly to the AutoLISP command function. Finally, the AutoLISP entget function has an optional argument for retrieving extended data. In ObjectARX, the acdbEntGet() function does not have a corresponding argument. Instead, there is an additional function, acdbEntGetX(), provided specifically for retrieving extended data.
Memory Considerations
The memory requirements of an ObjectARX application are different from those of AutoLISP. On the one hand, the data structures employed by C++ programs tend to be more compact than AutoLISP lists. On the other hand, there is a rather large, fixed overhead for running ObjectARX applications. Part of this consists of code that must be present in the applications themselves; the larger part is the ObjectARX library. Memory Management Some ObjectARX global functions allocate memory automatically. In most cases, the application must explicitly release this memory as if the application itself had allocated it. AutoLISP has automatic garbage collection, but ObjectARX does not.
WARNING! Failure to do this slows down the system and can cause AutoCAD
to terminate.
NOTE Do not confuse a library functions result arguments and values with its
return value. The function returns an integer status code. It places its results in arguments passed (by reference) back to the function that calls it.
521
Consider the following prototyped declarations for a few typical ObjectARX functions:
int acdbEntNext(ads_name ent, ads_name result); int acedOsnap(ads_point pt, char *mode, ads_point result); int acedGetInt(char *prompt, int *result);
An application could call these functions with the following C++ statements:
stat = acdbEntNext(ent, entres); stat = acedOsnap(pt, mode, ptres); stat = acedGetInt(prompt, &intres);
After each function is called, the value of the stat variable indicates either success (stat == RTNORM) or failure (stat == RTERROR or another error code, such as RTCAN for cancel). The last argument in each list is the result argument, which must be passed by reference. If successful, acdbEntNext() returns an entity name in its entres argument, acedOsnap() returns a point in ptres, and acedGetInt() returns an integer result in intres. (The types ads_name and ads_point are array types, which is why the entres and ptres arguments dont explicitly appear as pointers.)
External Functions
Once an ObjectARX application has defined its external functions (with calls to acedDefun()), the functions can be called by the AutoLISP user and by AutoLISP programs and functions as if they were built-in or user-defined AutoLISP functions. An external function can be passed AutoLISP values and variables, and can return a value to the AutoLISP expression that calls it. Some restrictions apply and are described in this section.
522
Chapter 20
The following call to acedDefun() specifies that AutoLISP will recognize an external function called doit in AutoLISP, and that when AutoLISP invokes doit, it passes the function code zero (0) to the ObjectARX application:
acedDefun("doit", 0);
The string that specifies the name of the new external function can be any valid AutoLISP symbol name. AutoLISP converts it to all uppercase and saves it as a symbol of the type Exsubr. External functions are defined separately for each open document in the MDI. The function gets defined when the document becomes active. For more information, see chapter 16, The Multiple Document Interface.
In this case, DOIT can now be invoked from the AutoCAD Command prompt without enclosing its name in parentheses. Functions defined as AutoCAD commands can still be called from AutoLISP expressions, provided that the C: prefix is included as a part of their names. For example, given the previous acedDefun() call, the AutoCAD user could also invoke the DOIT command as a function with arguments: Command: (c:doit x y)
causes an error.
523
NOTE The function handler must verify the number and type of arguments
passed to it, because there is no way to tell AutoLISP what the requirements are. Function handlers that expect arguments can be written so that they prompt the user for values if acedGetArgs() returns a NULL argument list. This technique is often applied to external functions defined as AutoCAD commands. A group of ObjectARX functions known as value-return functions (such as
acedRetInt(), acedRetReal(), and acedRetPoint()) enable an external
function to return a value to the AutoLISP expression that invoked it. Arguments that are passed between external functions and AutoLISP must evaluate to one of the following types: integer, real (floating-point), string, point (represented in AutoLISP as a list of two or three real values), an entity name, a selection set name, the AutoLISP symbols t and nil, or a list that contains the previous elements. AutoLISP symbols other than t and nil are not passed to or from external functions, but an ObjectARX application can retrieve and set the value of AutoLISP symbols by calling acedGetSym() and acedPutSym(). If, for example, an external function in an ObjectARX application is called with a string, an integer, and a real argument, the AutoLISP version of such a function can be represented as follows:
(doitagain pstr iarg rarg)
524
Chapter 20
Assuming that the function has been defined with acedDefun(), an AutoCAD user can invoke it with the following expression: Command: (doitagain Starting width is 3 7.12) This call supplies values for the functions string, integer, and real number arguments, which the doitagain() function handler retrieves by a call to acedGetArgs(). For an example of retrieving arguments in this way, see the first example in Lists and Other Dynamically Allocated Data on page 544.
Error Handling
The AutoCAD environment is complex and interactive, so ObjectARX applications must be robust. ObjectARX provides several error-handling facilities. The result codes returned during handshaking with AutoLISP indicate error conditions, as do the result codes library functions returned to the application. Functions that prompt for input from the AutoCAD user employ the built-in input-checking capabilities of AutoCAD. In addition, three functions let an application notify users of an error: acdbFail(), acedAlert(), and acrxAbort(). The acdbFail() function simply displays an error message (passed as a single string) at the AutoCAD Command prompt. This function can be called to identify recoverable errors such as incorrect argument values passed by the user. The statement in the following example calls acdbFail() from a program named test.arx:
acdbFail("invalid osnap point\n");
The acdbFail() function displays the following: Application test.arx ERROR: invalid osnap point You can also warn the user about error conditions by displaying an alert box. To display an alert box, call acedAlert(). Alert boxes are a more emphatic way of warning the user, because the user has to choose OK before continuing. For fatal errors, acrxAbort() should be called. This function prompts the user to save work in progress before exiting. The standard C++ exit() function should not be called. To obtain detailed information about the failure of an ObjectARX function, inspect the AutoCAD system variable ERRNO. When certain ObjectARX function calls (or AutoLISP function calls) cause an error, ERRNO is set to a value that the application can retrieve by a call to acedGetVar(). ObjectARX
525
defines symbolic names for the error codes in the header file ol_errno.h, which can be included by ObjectARX applications that examine ERRNO. These codes are shown in the ObjectARX Reference.
526
Chapter 20
if (result != NULL) { acutPrintf("\nSuccess: factorial of %d is %d\n", x, result->resval.rint); acutRelRb(result); } else acutPrintf("Test failed\n"); }
If a function is meant to be called with acedInvoke(), the application that defines it should register the function by calling acedRegFunc(). (In some cases the acedRegFunc() call is required, as described later in this section.) When acedRegFunc() is called to register the function, ObjectARX calls the function directly, without going through the applications dispatch loop. To define the function, call acedRegFunc(). An external function handler registered by acedRegFunc() must have no arguments and must return an integer (which is one of the application result codeseither RSRSLT or RSERR). The following excerpt shows how the funcload() function in fact.cpp can be modified to register its functions as well as define them:
typedef int (*ADSFUNC) (void); // First, define the structure of the table: a string // giving the AutoCAD name of the function, and a pointer to // a function returning type int. struct func_entry { char *func_name; ADSFUNC func; }; // Declare the functions that handle the calls. int fact (void); // Remove the arguments int squareroot (void); // Here we define the array of function names and handlers. // static struct func_entry func_table[] = { {"fact", fact}, {"sqr", squareroot}, }; ... static int funcload() { int i;
527
for (i = 0; i < ELEMENTS(func_table); i++) { if (!acedDefun(func_table[i].func_name, i)) return RTERROR; if (!acedRegFunc(func_table[i].func, i)) return RTERROR; } return RTNORM; }
As the code sample shows, the first argument to acedRegFunc() is the function pointer (named after the function handler defined in the source code), and not the external function name defined by acedDefun() and called by AutoLISP or acedInvoke(). Both acedDefun() and acedRegFunc() pass the same integer function code i. If a registered function is to retrieve arguments, it must do so by making its own call to acedGetArgs(). The acedGetArgs() call is moved to be within the function fact(). The result-buffer pointer rb is made a variable rather than an argument. (This doesnt match the call to fact() in the dofun() function. If all external functions are registered, as this example assumes, the dofun() function can be deleted completely; see the note that follows this example.) The new code is shown in boldface type:
static int fact() { int x; struct resbuf *rb; rb = acedGetArgs(); if (rb == NULL) return RTERROR; if (rb->restype == RTSHORT) { x = rb->resval.rint; // Save in local variable. } else { acdbFail("Argument should be an integer."); return RTERROR; } if (x < 0) { // Check the argument range. acdbFail("Argument should be positive."); return RTERROR; } else if (x > 170) { // Avoid floating-point overflow. acdbFail("Argument should be 170 or less."); return RTERROR; } acedRetReal(rfact(x)); // Call the function itself, and // return the value to AutoLISP. return RTNORM; }
528
Chapter 20
function. If you design an application that requires more than a single ObjectARX code file, this technique is preferable, because it places the burden of handling function calls on the ObjectARX library rather than on the acrxEntryPoint() function. If a function call starts a calling sequence that causes a function in the same application to be called with acedInvoke(), the latter function must be registered by acedRegFunc(). If the called function isnt registered, acedInvoke() reports an error. The following figure illustrates this situation:
application A A_tan() A_pi()
application C
invokes B_sin() invokes C_cos() B_sin() invokes A_pi() C_cos() invokes A_pi()
where application A defines A_tan() and A_pi(), application B defines B_sin(), and application C defines C_cos(). The A_pi() function must be registered by acedRegFunc(). To prevent acedInvoke() from reporting registration errors, register any external function that is meant to be called with acedInvoke(). The acedRegFunc() function can be called also to unregister an external function. The same application must either register or unregister the function; ObjectARX prohibits an application from directly managing another application.
529
When your program is finished with myapp, it can unload it by calling acedArxUnload():
acedArxUnload("myapp");
530
Chapter 20
The function acedArxLoaded() can be used to obtain the names of all currently loaded applications, as in the following code:
struct resbuf *rb1, *rb2; for (rb2 = rb1 = acedArxLoaded(); rb2 != NULL; rb2 = rb2->rbnext) { if (rb2->restype == RTSTR) acutPrintf("%s\n", rb2->resval.rstring); } acutRelRb(rb1);
You can call the functions acedArxLoaded() and acedArxUnload() in conjunction with each other. The following example unloads all applications except the current one:
struct resbuf *rb1, *rb2; for (rb2 = rb1 = acedArxLoaded(); rb2 != NULL; rb2 = rb2->rbnext) { if (strcmp(ads_appname, rb2->resval.rstring) != 0) acedArxUnload(rb2->resval.rstring); } acutRelRb(rb1);
531
Real Numbers
Real values in AutoCAD are always double-precision floating-point values. ObjectARX preserves this standard by defining the special type ads_real, as follows:
typedef double ads_real;
Points
AutoCAD points are defined as the following array type:
typedef ads_real ads_point[3];
A point always includes three values. If the point is two-dimensional, the third element of the array can be ignored; it is safest to initialize it to 0. ObjectARX defines the following point values:
#define X 0 #define Y 1 #define Z 2
Unlike simple data types (or point lists in AutoLISP), a point cannot be assigned with a single statement. To assign a pointer, you must copy the individual elements of the array, as shown in the following example:
newpt[X] = oldpt[X]; newpt[Y] = oldpt[Y]; newpt[Z] = oldpt[Z];
You can also copy a point value with the ads_point_set() macro. The result is the second argument to the macro. The following sample code sets the point to equal to the point from:
ads_point to, from; from[X] = from[Y] = 5.0; from[Z] = 0.0; ads_point_set(from, to);
Because of the argument-passing conventions of the C language, points are passed by reference without the address (indirection) operator &. (C always
532
Chapter 20
passes array arguments by reference, with a pointer to the first element of the array.) The acedOsnap() library function takes a point as an argument, and returns a point as a result. It is declared as follows:
int acedOsnap(pt, mode, result) ads_point pt; char *mode; ads_point result;
The acedOsnap() function behaves like the AutoLISP osnap function. It takes a point (pt) and some object snap modes (specified in the string mode), and returns the nearest point (in result). The int value that acedOsnap() returns is a status code that indicates success (RTNORM) or failure. The following code fragment calls acedOsnap():
int findendpoint(ads_point oldpt, ads_point newpt) { ads_point ptres; int foundpt; foundpt = acedOsnap(oldpt, "end", ptres); if (foundpt == RTNORM) { ads_point_set(ptres, newpt); } return foundpt; }
Because points are arrays, oldpt and ptres are automatically passed to acedOsnap() by reference (that is, as pointers to the first element of each array) rather than by value. The acedOsnap() function returns its result (as opposed to its status) by setting the value of the newpt argument. ObjectARX defines a pointer to a point when a pointer is needed instead of an array type.
typedef ads_real *ads_pointp;
Transformation Matrices
The functions acedDragGen(), acedGrVecs(), acedNEntSelP(), and acedXformSS() multiply the input vectors by the transformation matrix defined as a 4x4 array of real values.
typedef ads_real ads_matrix[4][4];
The first three columns of the matrix specify scaling and rotation. The fourth column of the matrix is a translation vector. ObjectARX defines the symbol T to represent the coordinate of this vector, as follows:
#define T 3
533
M00 M01 M02 M03 M10 M11 M12 M13 M20 M21 M22 M23 0.0 0.0 0.0 1.0
The functions that pass arguments of the ads_matrix type treat a point as a column vector of dimension 4. The point is expressed in homogeneous coordinates, where the fourth element of the point vector is a scale factor that is normally set to 1.0. The final row of the matrix has the nominal value of [0,0,0,1]; it is ignored by the functions that pass ads_matrix arguments. In this case, the following matrix multiplication results from the application of a transformation to a point:
X' Y' Z' 1.0 M00 M01 M02 M03 X Y Z 1.0
M10 M11 M12 M13 M20 M21 M22 M23 0.0 0.0 0.0 1.0
534
Chapter 20
X' = M00X + M01Y + M02Z + M03(1.0) Y' = M10X + M11Y + M12Z + M13(1.0) Z' = M20X + M21Y + M22Z + M23(1.0)
As these equations show, the scale factor and the last row of the matrix have no effect and are ignored. This is known as an affine transformation.
NOTE To transform a vector rather than a point, do not add in the translation
vector M3 M13 M23 (from the fourth column of the transformation matrix). The following function implements the previous equations to transform a single point:
void xformpt(xform, pt, newpt) ads_matrix xform; ads_point pt, newpt; { int i, j; newpt[X] = newpt[Y] = newpt[Z] = 0.0; for (i=X; i<=Z; i++) { for (j=X; j<=Z; j++) newpt[i] += xform[i][j] * pt[j]; // Add the translation vector. newpt[i] += xform[i][T]; } }
The following figure summarizes some basic geometrical transformations. (The values in an ads_matrix are actually ads_real, but they are shown here as integers for readability and to conform to mathematical convention.)
1000 0100 0010 0001 1 0 0 TX 0 1 0 TY 0 0 1 TZ 0001
translation
SX 0 0 0 0
scaling
0 0 SZ 0
0 0 0 1
cos sin 0 0
-sin cos 0 0
0 0 1 0
0 0 0 1
SY 0 0
535
The acedXformSS() functionunlike the acedDragGen(), acedGrVecs(), or acedNEntSelP() functionsrequires the matrix to do uniform scaling. That is, in the transformation matrix that you pass to acedXformSS(), the elements in the scaling vector SX SY SZ must all be equal; in matrix notation, M00 = M11 = M22. Three-dimensional rotation is a slightly different case, as shown in the following figure:
cos sin 0 0 -sin cos 0 0 0 0 1 0 0 0 0 1 1 0 0 0 0 cos sin 0 0 -sin cos 0 0 0 0 1 cos 0 -sin 0 0 1 0 0 sin 0 cos 0 0 0 0 1
For uniform rotations, the 3x3 submatrix delimited by [0,0] and [2,2] is orthonormal. That is, each row is a unit vector and is perpendicular to the other rows; the scalar (dot) product of two rows is zero. The columns are also unit vectors that are perpendicular to each other. The product of an orthonormal matrix and its transpose equals the identity matrix. Two complementary rotations have no net effect. Complex transformations can be accomplished by combining (or composing) nonidentity values in a single matrix.
As with ads_point variables, ads_name variables are always passed by reference but must be assigned element by element. You can also copy an entity or selection set name by calling the
ads_name_set() macro. As with ads_point_set() and ObjectARX functions,
536
Chapter 20
The following sample code sets the name newname to equal oldname.
ads_name oldname, newname; if (acdbEntNext(NULL, oldname) == RTNORM) ads_name_set(oldname, newname);
To assign a null value to a name, call the ads_name_clear() macro, and test for a null entity or selection set name with the macro ads_name_nil(). The following sample code clears the oldname set in a previous example:
ads_name_clear(oldname);
ObjectARX creates the following data type for situations that require a name to be a pointer rather than an array:
typedef long *ads_namep;
Useful Values
ObjectARX defines the following preprocessor directives:
#define TRUE 1 #define FALSE 0 #define EOS'\0' // String termination character
The PAUSE symbol, a string that contains a single backslash, is defined for the acedCommand() and acedCmd() functions, as follows:
#define PAUSE "\\" // Pause in command argument list
NOTE The ObjectARX library doesnt define the values GOOD and BAD, which
appear as return values in the code samples throughout this guide (especially in error-handling code). You can define them if you want, or substitute a convention that you prefer.
537
Result-Buffer Lists
Result buffers can be combined in linked lists, described later in detail, and are therefore suitable for handling objects whose lengths can vary and objects that can contain a mixture of data types. Many ObjectARX functions return or accept either single result buffers (such as acedSetVar()) or resultbuffer lists (such as acdbEntGet() and acdbTblSearch()).
struct resbuf
The following result-buffer structure, resbuf, is defined in conjunction with a union, ads_u_val, that accommodates the various AutoCAD and ObjectARX data types, as follows:
union ads_u_val { ads_real rreal; ads_real rpoint[3]; short rint; // Must be declared short, not int. char *rstring; long rlname[2]; long rlong; struct ads_binary rbinary; }; struct resbuf { struct resbuf *rbnext; // Linked list pointer short restype; union ads_u_val resval; };
NOTE The long integer field resval.rlong is like the binary data field
resval.rbinary; both hold extended entity data.
538
Chapter 20
539
A unique handle that is always enabled and that persists for the lifetime of the drawing An optional xdata list An optional persistent reactor set An optional ownership pointer to an extension dictionary, which owns other database objects placed in it by the application
Database objects are objects without layer, linetype, color, or any other geometric or graphical properties, and entities are derived from objects and have geometric and graphical properties. Because DXF codes are always less than 2,000 and the result type codes are always greater, an application can easily determine when a result-buffer list contains result values (as returned by acedGetArgs(), for example) or contains entity definition data (as returned by acdbEntGet() and other entity functions). The following figure shows the result-buffer format of a circle retrieved by
acdbEntGet():
540
Chapter 20
The following sample code fragment shows a function, dxftype(), that is passed a DXF group code and the associated entity, and returns the corresponding type code. The type code indicates what data type can represent the data: RTREAL indicates a double-precision floating-point value, RT3DPOINT indicates an ads_point, and so on. The kind of entity (for example, a normal entity such as a circle, a block definition, or a table entry such as a viewport) is indicated by the type definitions that accompany this function:
#define #define #define #define #define #define #define #define #define // // // // // // // ET_NORM 1 ET_TBL 2 ET_VPORT ET_LTYPE ET_LAYER ET_STYLE ET_VIEW ET_UCS ET_BLOCK // Normal entity // Table 3 // Table numbers 4 5 6 7 8 9
Get basic C-language type from AutoCAD DXF group code (RTREAL, RTANG are doubles, RTPOINT double[2], RT3DPOINT double[3], RTENAME long[2]). The etype argument is one of the ET_ definitions. Returns RTNONE if grpcode isn't one of the known group codes. Also, sets "inxdata" argument to TRUE if DXF group is in XDATA.
short dxftype(short grpcode, short etype, int *inxdata) { short rbtype = RTNONE; *inxdata = FALSE; if (grpcode >= 1000) { // Extended data (XDATA) groups *inxdata = TRUE; if (grpcode == 1071) rbtype = RTLONG; // Special XDATA case else grpcode %= 1000; // All other XDATA groups match. } // regular DXF code ranges if (grpcode <= 49) { if (grpcode >= 20) // 20 to 49 rbtype = RTREAL; else if (grpcode >= 10) { // 10 to 19 if (etype == ET_VIEW) // Special table cases rbtype = RTPOINT; else if (etype == ET_VPORT && grpcode <= 15) rbtype = RTPOINT; else // Normal point rbtype = RT3DPOINT; // 10: start point, 11: endpoint }
541
else if (grpcode >= 0) // 0 to 9 rbtype = RTSTR; // Group 1004 in XDATA is binary else if (grpcode >= -2) // -1 = start of normal entity -2 = sequence end, etc. rbtype = RTENAME; else if (grpcode == -3) rbtype = RTSHORT; // Extended data (XDATA) sentinel } else { if (grpcode <= 59) // 50 to 59 rbtype = RTANG; // double else if (grpcode <= 79) // 60 to 79 rbtype = RTSHORT; else if (grpcode < 210) ; else if (grpcode <= 239) // 210 to 239 rbtype = RT3DPOINT; else if (grpcode == 999) // Comment rbtype = RTSTR; } return rbtype; }
An application obtains a result-buffer list (called rb), representing an entry in the viewport symbol table, and the following C statement calls dxftype():
ctype = dxftype(rb->restype, ET_VPORT, &inxdata);
Suppose rb->restype equals 10. Then dxftype() returns RTPOINT, indicating that the entity is a two-dimensional point whose coordinates (of the type ads_real) are in rb->resval.rpoint[X] and rb->resval.rpoint[Y].
542
Chapter 20
The meanings of these codes, summarized in the table, are as follows: RTNORM RTERROR The library function succeeded. The library function did not succeed; it encountered a recoverable error.
The RTERROR condition is exclusive of the following special cases: RTCAN The AutoCAD user entered ESC to cancel the request. This code is returned by the user-input (acedGetxxx) functions and by the following functions: acedCommand, acedCmd, acedEntSel, acedNEntSelP, acedNEntSel, and acedSSGet. AutoCAD rejected the operation as invalid. The operation request may be incorrectly formed, such as an invalid acdbEntMod() call, or it simply may not be valid for the current drawing. The link with AutoLISP failed. This is a fatal error that probably means AutoLISP is no longer running correctly. If it detects this error, the application should quit. (Not all applications check for this code, because the conditions that can lead to it are likely to hang AutoCAD, anyway.) The AutoCAD user entered a keyword or arbitrary input instead of another value (such as a point). The userinput acedGetxxx() functions, as well as acedEntSel, acedEntSelP, acedNEntSel, and acedDragGen, return this result code.
RTREJ
RTFAIL
RTKWORD
NOTE Not all ObjectARX global functions return these status codes; some
return values directly. Also, the user-input (acedGetxxx, acedEntSel, acedEntSelP, acedNEntSel, and acedDragGen) functions can return the RTNONE result type code, and acedDragGen() indicates arbitrary input by returning RTSTR instead of RTKWORD.
543
The following code segment shows how to implement a function with such a calling sequence. The sample function checks that the argument list is correct and saves the values locally before operating on them (operations are not
544
Chapter 20
shown). The example assumes that a previous call to acedDefun() has assigned the external subroutine a function code of 0, and that all functions defined by this application take at least one argument:
// Execute a defined function. int dofun() { struct resbuf *rb; char str[64]; int ival, val; ads_real rval; ads_point pt; // Get the function code. if ((val = acedGetFuncode()) == RTERROR) return BAD; // Indicate failure. // Get the arguments passed in with the function. if ((rb = acedGetArgs()) == NULL) return BAD; switch (val) { // Which function is called? case 0: // (doit) if (rb->restype != RTSTR) { acutPrintf("\nDOIT called with %d type.", rb->restype); acutPrintf("\nExpected a string."); return BAD; } // Save the value in local string. strcpy(str, rb->resval.rstring); // Advance to the next result buffer. rb = rb->rbnext; if (rb == NULL) { acutPrintf("\nDOIT: Insufficient number of arguments."); return BAD; } if (rb->restype != RTSHORT) { acutPrintf("\nDOIT called with %d type.", rb->restype); acutPrintf("\nExpected a short integer."); return BAD; } // Save the value in local variable. ival = rb->resval.rint;
545
// Advance to the last argument. rb = rb->rbnext; if (rb == NULL) { acutPrintf("\nDOIT: Insufficient number of arguments."); return BAD; } if (rb->restype != RTREAL) { acutPrintf("\nDOIT called with %d type.", rb->restype); acutPrintf("\nExpected a real."); return BAD; } // Save the value in local variable. rval = rb->resval.rreal; // Check that it was the last argument. if (rb->rbnext != NULL) { acutPrintf("\nDOIT: Too many arguments."); return BAD; } // Operate on the three arguments. . . . return GOOD; // Indicate success break; case 1: // Execute other functions. . . . } }
546
Chapter 20
The acutRelRb() function releases the entire list that follows the specified result buffer, including the specified (head) buffer itself and any string values that the buffers in the list point to. To release a string without removing the buffer itself, or to release a string belonging to a static result buffer, the application must call the standard C library function free().
WARNING! Do not write data to a dynamic location that hasnt been allocated with direct calls to malloc() or with the ObjectARX library (including acutNewRb()). This can corrupt data in memory. Conversely, calling free() or acutRelRb() to release data that was allocated staticallyin a static or automatic variable declarationalso can corrupt memory. Inserting a statically allocated variable, such as a string, into a result-buffer list causes your program to fail when you release the list with acutRelRb().
Sample calls to acutRelRb() appear in several of the code examples in the following sections.
If the new result buffer is to contain a string, the application must explicitly allocate memory to contain the string:
struct resbuf *head; if ((head=acutNewRb(RTSTR)) == NULL) { acdbFail("Unable to allocate buffer\n"); return BAD; }
547
if ((head->resval.rstring = malloc(14)) == NULL) { acdbFail("Unable to allocate string\n"); return BAD; } strcpy(head->resval.rstring, "Hello, there.");
Memory allocated for strings that are linked to a dynamic list is released when the list is released, so the following call releases all memory allocated in the previous example:
acutRelRb(head);
To release the string without releasing the buffer, call free() and set the string pointer to NULL as shown in the following example:
free(head->resval.rstring); head->resval.rstring = NULL;
Setting resval.rstring to NULL prevents a subsequent call to acutRelRb() from trying to release the string a second time. If the elements of a list are known beforehand, a quicker way to construct it is to call acutBuildList(), which takes a variable number of argument pairs (with exceptions such as RTLB, RTLE, -3, and others) and returns a pointer to a list of result buffers that contains the specified types and values, linked together in the order in which they were passed to acutBuildList(). This function allocates memory as required and initializes all values. The last argument to acutBuildList() must be a single argument whose value is either zero or RTNONE. The following sample code fragment constructs a list that consists of three result buffers. These contain a real value, a string, and a point, in that order:
struct resbuf *result; ads_point pt1 = {1.0, 2.0, 5.1}; result = acutBuildList( RTREAL, 3.5, RTSTR, "Hello, there.", RT3DPOINT, pt1, 0 );
If it cannot construct the list, acutBuildList() returns NULL; otherwise, it allocates space to contain the list. This list must be released by a subsequent call to acutRelRb():
if (result != NULL) acutRelRb(result);
548
Chapter 20
AutoLISP Lists
The acutBuildList() function is called in conjunction with acedRetList(), which returns a list structure to AutoLISP. The following sample code fragment passes a list of four points:
struct resbuf *res_list; ads_point ptarray[4]; // Initialize the point values here. . . . res_list = acutBuildList( RT3DPOINT, ptarray[0], RT3DPOINT, ptarray[1], RT3DPOINT, ptarray[2], RT3DPOINT, ptarray[3], 0); if (res_list == NULL) { acdbFail("Couldn't create list\n"); return BAD; } acedRetList(res_list); acutRelRb(res_list);
Dotted pairs and nested lists can be returned to AutoLISP by calling acutBuildList() to build a list created with the special list-construction type codes. These codes are needed only for complex lists. For ordinary (that is, one-dimensional) lists, acedRetList() can be passed a simple list of result buffers, as shown in the previous example.
549
NOTE This is a change from earlier versions. Applications that receive a dotted
pair from AutoLISP no longer have to modify the format of the dotted pair before returning it with acedRetList(). (The earlier order, with RTDOTE at the end, is still supported.)
WARNING! The acutBuildList() function does not check for a wellformed AutoLISP list. For example, if the RTLB and RTLE codes are not balanced, this error is not detected. If the list is not well formed, AutoLISP can fail. Omitting the RTLE code is guaranteed to be a fatal error. The following sample code fragment constructs a nested list to return to AutoLISP:
res_list = acutBuildList( RTLB, // Begin sublist. RTSHORT, 1, RTSHORT, 2, RTSHORT, 3, RTLE, // End sublist. RTSHORT, 4, RTSHORT, 5, 0); if (res_list == NULL) { acdbFail("Couldn't create list\n"); return BAD; } acedRetList(res_list); acutRelRb(res_list);
The list that this example returns to AutoLISP has the following form: ((1 2 3) 4 5) The following code fragment constructs a dotted pair to return to AutoLISP:
res_list = acutBuildList( RTLB, // Begin dotted pair. RTSTR, "Sample", RTDOTE, RTSTR, "Strings", RTLE, // End dotted pair. 0);
550
Chapter 20
The list that this example returns to AutoLISP has the following form: ((Sample . Strings))
NOTE In AutoLISP, dotted pairs associate DXF group codes and values. In an
ObjectARX application this is unnecessary, because a single result buffer contains both the group code (in its restype field) and the value (in its resval field). While ObjectARX provides the list-construction type codes as a convenience, most ObjectARX applications do not require them.
NOTE Entity definitions begin with a zero (0) group that describes the entity
type. Because lists passed to acutBuildList() are terminated with 0 (or RTNONE), this creates a conflict. The special result type code RTDXF0 resolves the conflict. Construct the zero group in DXF lists passed to acutBuildList() with RTDXF0. If you attempt to substitute a literal zero for RTDXF0, acutBuildList() truncates the list. The following sample code fragment creates a DXF list that describes a circle and then passes the new entity to acdbEntMake(). The circle is centered at (4,4), has a radius of 1, and is colored red:
struct resbuf *newent; ads_point center = {4.0, 4.0, 0.0}; newent = acutBuildList( RTDXF0, "CIRCLE", 62, 1, // 1 == red 10, center, 40, 1.0, // Radius 0 ); if (acdbEntMake(newent) != RTNORM) { acdbFail("Error making circle entity\n"); return BAD; }
551
The value of the clen field must be in the range of 0 to 127. If an application requires more than 127 bytes of binary data, it must organize the data into multiple chunks. With Release 13, the DXF representation of a symbol table can include extended entity data. Xdata is returned as a handle.
552
Chapter 20
These functions count out-of-code-page characters differently. The acdbXdSize() and acdbXdRoom() functions now recognize \U+XXXX as 1 byte, but other ObjectARX functions recognize \U+XXXX as 7 bytes. The Asian version of AutoCAD recognizes \M+XXXX as 2 bytes.
NOTE ObjectARX applications that make explicit assumptions about the limit
of the string length of symbol table names and TEXT entities are affected by outof-code-page characters.
553
554
21
In this chapter
I Custom Object Snap Modes I Input Point Management
ObjectARX allows applications to customize input point processing. The application can associate new object snap points and AutoSnapalignment lines with custom and existing entities, and can monitor the input point process and modify the input points.
555
Create and register a custom object snap mode. Create protocol extension classes to perform the input point processing. Create a custom glyph.
Typically, custom object snap modes are registered when the application is first loaded and are removed when the application is unloaded, although they can be registered and removed at any time.
556
Chapter 21
Keyword The custom object snap keyword that the user types in to activate the object snap. Both local and global keywords must be specified.
Protocol extension class A pointer to the class that performs the per-entity processing of the object snap mode.
Glyph The custom glyph for the object snap mode. ToolTip string The default ToolTip string for the custom object snap mode.
For more information on setting these attributes, see the ObjectARX Reference. A custom object snap mode is defined by registering an instance of the
AcDbCustomOsnapMode class with the custom object snap manager, described
in the previous section. When a custom object snap mode is used, AutoCAD takes the class object returned by AcDbCustomOsnapMode::entityOsnapClass(), looks up the corresponding protocol extension object for the picked entity, and invokes AcDbCustomOsnapInfo::getOsnapInfo() to obtain the points or lines associated with that entity and object snap mode. If the final candidate point is associated with that object snap mode, AutoCAD displays the glyph object from the instance returned by AcDbCustomOsnapMode::glyph() and the ToolTip string returned by AcDbCustomOsnapMode::tooltipString().
557
To create protocol extension classes for custom object snap modes 1 Define an abstract base protocol extension class derived from AcDbCustomOsnapInfo. For example, if your custom class is called AcmeSocketInfo, define it as follows:
class AcmeSocketInfo : public AcDbCustomOsnapInfo{ public: ACRX_DECLARE_MEMBERS(AcDbSocketInfo); virtual Acad::ErrorStatus getOsnapInfo( AcDbEntity* pickedObject, int gsSelectionMark, const AcGePoint3d& pickPoint, const AcGePoint3d& lastPoint, const AcGeMatrix3d& viewXform, AcArray<AcGePoint3d>& snapPoints, AcArray<int>& geomIdsForPts, AcArray<AcGeLine3d>& snapLines, AcArray<int>& geomIdsForLines); }; ACRX_NO_CONS_DEFINE_MEMBERS(AcmeSocketInfo, AcDbCustomOsnapInfo);
2 Initialize the base protocol extension class and add it to the runtime class hierarchy. For example, add the following lines to your acrxEntryPoint() function:
AcmeSocketInfo::rxInit(); acrxBuildClassHierarchy();
558
Chapter 21
3 For every relevant entity class, derive a protocol extension class from the base class. For example, you might derive a class called AcmeSocketForLines that implements getOsnapInfo() to handle the input point processing for lines.
NOTE If you return a NULL pointer instead of a custom glyph, AutoCAD will
not draw any glyph for the object snap mode.
559
// Socket Osnap mode protocol extension class. // class AcmeSocketInfo : public AcDbCustomOsnapInfo { public: ACRX_DECLARE_MEMBERS(AcmeSocketInfo); virtual Acad::ErrorStatus getOsnapInfo( AcDbEntity* int const AcGePoint3d& const AcGePoint3d& const AcGeMatrix3d& AcArray<AcGePoint3d>& AcArray<int>& AcArray<AcGeCurve3d>& AcArray<int>& };
// This class is registered with AcRx, to be used by the host // application to look up entity class-specific implementations. // ACRX_NO_CONS_DEFINE_MEMBERS(AcmeSocketInfo,AcDbCustomOsnapInfo); Acad::ErrorStatus AcmeSocketInfo::getOsnapInfo( AcDbEntity*, int, const AcGePoint3d&, const AcGePoint3d&, const AcGePoint3d&, AcArray<AcGePoint3d>& snapPoints, AcArray<int>& geomIdsForPts, AcArray<AcGeCurve3d>& snapCurves, AcArray<int>& geomIdsForLines) {
560
Chapter 21
// Associate with AcDbEntity to define default behavior. // snapPoints.setLogicalLength(0); geomIdsForPts.setLogicalLength(0); snapLiness.setLogicalLength(0); geomIdsForLines.setLogicalLength(0); } // Socket Osnap mode protocol extension object for AcDbLine. // class AcmeSocketForLine : public AcmeSocketInfo { public: virtual Acad::ErrorStatus getOsnapInfo( AcDbEntity* int const AcGePoint3d& const AcGePoint3d& const AcGeMatrix3d& AcArray<AcGePoint3d>& AcArray<int>& AcArray<AcGeCurve3d>& AcArray<int>& };
Acad::ErrorStatus AcmeSocketForLine::getOsnapInfo( AcDbEntity* pickedObject, int, const AcGePoint3d& pickPoint, const AcGePoint3d&, const AcGeMatrix3d& viewXform, AcArray<AcGePoint3d>& snapPoints, AcArray<int>& geomIdsForPts, AcArray<AcGeCurve3d>& snapCurves, AcArray<int>& geomIdsForLines) { // Protocol extension ensures that the following assertion // is always true, but check in non-production versions // just to be safe. // ASSERT(pickedObject->isKindOf(AcDbLine::desc())); // In production, a hard cast is fastest. AcDbLine* lineEnt = (AcDbLine*)pickedObject; // // // // Do computation using AcDbLine protocol, pickPoint, and viewXform. For example, if you want to find the closest socket to the pick point and return just that, set snapPoints and geomIdsForPts accordingly.
561
// But this isn't an AutoSnap mode... // snapLiness.setLogicalLength(0); geomIdsForLines.setLogicalLength(0); } // Actual protocol extension objects // static AcmeSocketInfo* pDefaultSocketInfo = NULL; static AcmeSocketForLine* pSocketForLine = NULL; // "SOCket" Osnap mode glyph object // class AcmeSocketGlyph : public AcGiGlyph { public: virtual Acad::ErrorStatus setLocation(const AcGePoint3d& dcsPoint); virtual void viewportDraw(AcGiViewportDraw* vportDrawContext); private: AcGePoint3d mCurDcsLoc; }; Acad::ErrorStatus AcmeSocketGlyph::setLocation(const AcGePoint3d& dcsPoint) { mCurDCSLoc = dcsPoint; } // These variables are extremely transient, and are // made static to save constructor/destructor cost. static AcGePoint2d& sPixelArea; AcArray<AcGePoint3d> sSegmentPoints[2]; void AcmeSocketGlyph::viewportDraw(AcGiViewportDraw* vportDrawContext) { // Taking mCurDCSLoc, the pixel size, and the AutoSnap // marker size into account, plus anything else, such as socket // orientation, draw the glyph. // If this ASSERT fails, then the pixel size is really position// dependent. // ASSERT(!vportDrawContext->viewport()->isPerspective()); vportDrawContext->viewport()-> getNumPixelsInUnitSquare( AcGePoint3d::kOrigin, pixelArea);
562
Chapter 21
double halfGlyphSizeInDCS = acdbCustomOsnapManager->osnapGlyphSize() * pixelArea.x / 2.0; // Draw an asterisk with 4 segments. // sSegmentPoints[0].set( mCurDCSLoc.x-halfGlyphSizeInDCS, mCurDCSLoc.y-halfGlyphSizeInDCS, 0.0); sSegmentPoints[1].set( mCurDCSLoc.x+halfGlyphSizeInDCS, mCurDCSLoc.y+halfGlyphSizeInDCS, 0.0); vportDrawContext->geometry().polylineDc( 2, &(sSegmentPoints[0])); sSegmentPoints[0].set( mCurDCSLoc.x-halfGlyphSizeInDCS, mCurDCSLoc.y+halfGlyphSizeInDCS, 0.0); sSegmentPoints[1].set( mCurDCSLoc.x+halfGlyphSizeInDCS, mCurDCSLoc.y-halfGlyphSizeInDCS, 0.0); vportDrawContext->geometry().polylineDc( 2, &(sSegmentPoints[0])); sSegmentPoints[0].set( mCurDCSLoc.x-halfGlyphSizeInDCS, mCurDCSLoc.y, 0.0); sSegmentPoints[1].set( mCurDCSLoc.x+halfGlyphSizeInDCS, mCurDCSLoc.y, 0.0); vportDrawContext->geometry().polylineDc( 2, &(sSegmentPoints[0])); sSegmentPoints[0].set( mCurDCSLoc.x, mCurDCSLoc.y-halfGlyphSizeInDCS, 0.0); sSegmentPoints[1].set( mCurDCSLoc.x, mCurDCSLoc.y+halfGlyphSizeInDCS, 0.0); vportDrawContext->geometry().polylineDc( 2, &(sSegmentPoints[0])); }; AcmeSocketGlyph* pSocketGlyph = NULL; // Master object for the socket custom Osnap mode. // class AcmeSocketMode : public AcDbCustomOsnapMode { public: virtual const char* localModeString() const
{return "SOCket"};
virtual const char* globalModeString() const {return "SOCket"}; virtual const AcRxClass* entityOsnapClass() const {return AcmeSocketInfo::desc());
563
virtual AcGiGlyph* glyph() const {return pSocketGlyph;); virtual const char* tooltipString() const };
/* ================ ObjectARX application interface ============ */ AcRx::AppRetCode acrxEntryPoint(AcRx::AppMsgCode msg, void*) { switch(msg) { case AcRx::kInitAppMsg: // Register the class. // AcmeSocketInfo::rxInit(); acrxBuildClassHierarchy(); pDefaultSocketInfo = new AcmeSocketInfo; AcDbEntity::desc()->addX(AcmeSocketInfo::desc(), pDefaultSocketInfo); pSocketForLine = new AcmeSocketForLine; AcDbLine::desc()->addX(AcmeSocketInfo::desc(), pSocketForLine); }; // Create the glyph object to be returned by the socket // mode object. // pSocketGlyph = new AcmeSocketGlyph; // Create and register the custom Osnap Mode pSocketMode = new AcmeSocketMode; acdbCustomOsnapManager->addCustomOsnapMode(pSocketMode); // The SOCket Osnap mode is now plugged in and ready to // use. // break; case AcRx::kUnloadAppMsg: // Clean up. acdbCustomOsnapManager->removeCustomOsnapMode(pSocketMode); delete pSocketMode; // Unregister, then delete the protocol extension object. // AcDbEntity::desc()->delX(AcmeSocketInfo::desc()); delete pDefaultSocketInfo;
564
Chapter 21
AcDbLine::desc()->delX(AcmeSocketInfo::desc()); delete pSocketForLine; // Remove the protocol extension class definition. // acrxClassDictionary->remove("AcmeSocketInfo"); break; default: // Between the initialization and termination of the // application, all registered objects will be directly // invoked as needed. No commands or AutoLISP // expressions are necessary. // break; } return AcRx::kRetOK; }
The input point manager registers and deregisters input point filters, input point monitors, and input context reactors. The input point manager also enables and disables system-generated cursor graphics, so that custom cursor graphics can be drawn.
AcEdInputPointManager provides a function, disableSystemCursorGraphics(), that disables the system cursor.
ObjectARX maintains a count of the calls to disable the system cursor for each document, so if your application invokes disableSystemCursorGraphics() multiple times, it should invoke
565
system cursor.
during input point acquisition without an active object snap mode during single point entity picking during command quiescence
Finally, the input point manager contains a function, mouseHasMoved(), that input point filters and monitors can call to determine whether there is another digitizer event pending. If there is a digitizer event pending, the filter or monitor should return from its callback as soon as possible, without doing any further calculations, to avoid cursor lags.
566
Chapter 21
The following table describes the transitions between the different input states that can be detected using input context events: Input context events
Type Quiescent Description Entered when the beginQuiescentState() callback is made, and exited when the endQuiescentState() callback is made as a result of an AutoCAD command, AutoLISP function, or ActiveX input function being initiated. This should be the only state in the stack for a document when it is entered; it cannot be stacked on top of another state. The CMDACT system variable is zero when in this state. Entered if the beginGetPoint() callback is made without already being in a Geometric, Nonpoint Transient, Selecting Transient, or Drag Sequence state. Whenever this state is entered, the returned point is the ultimate goal, not an intermediate value. Input point filters and monitors are called for all events in this state. Entered from Geometric, Nonpoint Transient when the beginGetPoint() callback is made. From this state, another call to point input means stacking a new state. Any point is an intermediate value and can be overridden by a directly typed value. This state is exited when any of the endGetAngle(), endGetDistance(), endGetOrientation(), endGetCorner(), or endGetScaleFactor() callbacks are made. Input point filters and monitors are called for all events in this state. Entered from Selecting, Transient when the beginGetPoint() callback is made. Input point filters and monitors are called for all events in this state. Entered when any of the beginGetString(), beginGetKeyword(), beginGetInteger(), beginGetColor(), or beginGetReal() callbacks are made. These contexts directly poll for input and do not perform object snap, AutoSnap, or input point filtering, even though the cursor is active when interactive digitizer tracking is performed. Forced entity picking must be enabled for input point monitors to get callbacks from this state. Input point filters are not called from this state. Entered when the beginDragSequence() callback is made, and exited when the endDragsequence() callback is made. Nested calls to beginGetPoint(), beginGetAngle(), and beginGetDistance() are made in intermediate mode, so no state transition is made. Input point filters and monitors are called for all events in this state.
Geometric, Point
Geometric, Nonpoint
Selecting
Nongeometric, Nonselecting
Drag Sequence
567
Selecting Transient
Drag Sequence, Nested Entered from Drag Sequence when the Action, Transient AcEditorReactor::commandWillBegin() or AcEditorReactor::LispWillStart() callbacks are made. These suspend the Drag Sequence and stack a new input state on top of it. From this state, it is possible to transition to any other input state. This stacked state will end when the balancing AcEditorReactor callback is made, and the state under the top state is Drag Sequence.
When your application is loaded, you can query the value of the system variable CMDACT to find out which commands are active. Input context events can be used to note transitions between input states.
568
Chapter 21
// The input context reactor class. // class MyContextReactor : public AcEdInputContextReactor { public: void beginGetPoint( const AcGePoint3d* pointIn, const char* promptString, int initGetFlags, const char* pKeywords); void endGetPoint( Acad::PromptStatus returnStatus, const AcGePoint3d& pointOut, const char*& pKeyword); void beginGetOrientation( const AcGePoint3d* pointIn, const char* promptString, int initGetFlags, const char* pKeywords); void endGetOrientation( Acad::PromptStatus returnStatus, double& angle, const char*& pKeyword); void beginGetCorner( const AcGePoint3d* firstCorner, const char* promptString, int initGetFlags, const char* pKeywords); void endGetCorner( Acad::PromptStatus returnStatus, AcGePoint3d& secondCorner, const char*& pKeyword); void beginSSGet( const char* pPrompt, int initGetFlags, const char* pKeywords, const char* pSSControls, const AcArray<AcGePoint3d>& points, const resbuf* entMask);
569
void MyContextReactor::beginGetPoint( const AcGePoint3d* pointIn, const char* promptString, int initGetFlags, const char* pKeywords) { acutPrintf("beginGetPoint: pointIn = %.2f,%.2f,%.2f\n", (*pointIn)[0], (*pointIn)[1], (*pointIn)[2]); if (NULL != promptString) acutPrintf("%s", promptString); acutPrintf("initGetFlags: %d\n", initGetFlags); if (NULL != pKeywords) acutPrintf("Keywords: %s\n", pKeywords); } void MyContextReactor::endGetPoint( Acad::PromptStatus returnStatus, const AcGePoint3d& pointOut, const char*& pKeyword) { acutPrintf("endGetPoint: %d\n", returnStatus); acutPrintf("%.2f,%.2f,%.2f\n", pointOut[0], pointOut[1], pointOut[2]); if (NULL != pKeyword) acutPrintf("Keyword: %s\n", pKeyword); } void MyContextReactor::beginGetOrientation( const AcGePoint3d* pointIn, const char* promptString, int initGetFlags, const char* pKeywords) { acutPrintf("beginGetOrientation: %.2f, %.2f, %.2f\n", (*pointIn)[0], (*pointIn)[1], (*pointIn)[2]); }
570
Chapter 21
void MyContextReactor::endGetOrientation( Acad::PromptStatus returnStatus, double& angle, const char*& pKeyword) { acutPrintf("endGetOrientation: %.2f\n", angle); } void MyContextReactor::beginGetCorner( const AcGePoint3d* firstCorner, const char* promptString, int initGetFlags, const char* pKeywords) { if (NULL != firstCorner) { acutPrintf( "beginGetCorner: %.2f, %.2f, %.2f\n", (*firstCorner)[0], (*firstCorner)[1], (*firstCorner)[2]); } } void MyContextReactor::endGetCorner( Acad::PromptStatus returnStatus, AcGePoint3d& secondCorner, const char*& pKeyword) { acutPrintf("endGetCorner\n"); } void MyContextReactor::beginSSGet( const char* pPrompt, int initGetFlags, const char* pKeywords, const char* pSSControls, const AcArray<AcGePoint3d>& points, const resbuf* entMask) { acutPrintf("beginSSGet:%s\n", NULL != pPrompt ? pPrompt : ""); for (int i = 0; i < points.length(); i++) acutPrintf("%d: %.2f, %.2f, %.2f\n", i, points[i][X], points[i][Y], points[i][Z]); }
571
void MyContextReactor::endSSGet( Acad::PromptStatus returnStatus, const AcArray<AcDbObjectId>& ss) { acutPrintf("endSSGet\n"); for (int i = 0; i < ss.length(); i++) acutPrintf("Entity %d: <%x>\n", i, ss[i].asOldId()); } // My context reactor object MyContextReactor my_icr; extern "C" __declspec(dllexport) AcRx::AppRetCode acrxEntryPoint( AcRx::AppMsgCode msg, void *p) { switch (msg) { case AcRx::kInitAppMsg: acrxUnlockApplication(p); acrxRegisterAppMDIAware(p); break; case AcRx::kLoadDwgMsg: // Attach a context reactor to the current document. // curDoc()->inputPointManager()-> addInputContextReactor(&my_icr); break; case AcRx::kUnloadAppMsg: // Warning! This sample attaches a context reactor, // but it never detaches it. A real-life application // will need to monitor to which document it attached // the reactor, and will need to detach it. // break; } return AcRx::kRetOK; }
Posting modal dialogs ads_grread() calls from ObjectARX and Visual LISP applications
572
Chapter 21
I DTEXT I
I SKETCH
When modal dialog boxes are posted, all digitizer tracking is completely bypassed.
OSNAP
AutoSnap
CAcGetUserInput:: GetPoint
573
The ToolTip string can be set by using the appendToTooltipStr and additionalTooltipString parameters when creating the input point monitor. Input point monitors are processed after all other input point computations are performed by AutoCAD, including custom input point filtering if a filter exists. The output points are still subject to AutoCAD XYZ point filtering. There can be any number of input point monitors active at a given time, since they only allow viewing of the input points.
Be careful about performing substantial computation from within input point filters and monitors. Minimize the number of callouts made from filters and monitors. Take advantage of available work that has already been done by AutoCAD, such as the list of all entities under the object snap cursor and all of the interim point computations. Be sure to free the memory for strings you pass into the additionalTooltipString parameter when creating an input point filter or monitor.
NOTE It is recommended that you disable your point filter in all entity
selection contexts. Object snap mode and AutoSnaps are always disabled in this context.
Filter Chaining
The AcEdInputPointFilter class provides a member function to support chaining input point filters. Since only one filter can be active at a time, this function allows you to query the current filter, embed that filter in your custom filter, and then revoke the current filter and register your own. The following function provides the chaining functionality:
AcEdInputFilter * AcEdInputPointFilter::revokeFilter(AcEdInputPointFilter *);
574
Chapter 21
Retrying
Input point filters and monitors can be invoked multiple times for a single input point under the following conditions:
I
When the TAB key is pressed after a digitizer motion event, the object snap candidate for the cursor position is cycled. As soon as the cursor motion is detected, the object snap candidate cycling index is reset. When a registered input point filter returns a Boolean indicating to retry for a point, the system prompts for a new event, not returning the point from the event that will be retried.
class IPM : public AcEdInputPointMonitor { public: virtual Acad::ErrorStatus monitorInputPoint( // Output. If changedTooltipStr is kTrue, newTooltipString // has the new ToolTip string in it. // bool& appendToTooltipStr, char*& additionalTooltipString, // Input/Output // AcGiViewportDraw* // Input parameters: // AcApDocument* bool int const AcGePoint3d& const AcGePoint3d& const AcGePoint3d& const AcGePoint3d&
drawContext,
575
const AcGePoint3d& osnappedPoint, AcDb::OsnapMask osnapMasks, // const AcArray<AcDbCustomOsnapMode>& customOsnapModes const AcArray<AcDbObjectId>& apertureEntities, const AcArray< AcDbObjectIdArray, AcArrayObjectCopyReallocator< AcDbObjectIdArray > >& nestedPickedEntities, int gsSelectionMark, const AcArray<AcDbObjectId>& keyPointEntities, const AcArray<AcGeCurve3d*>& alignmentPaths, const AcGePoint3d& computedPoint, const char* tooltipString); }; Acad::ErrorStatus IPM::monitorInputPoint( // Output. If changedTooltipStr is kTrue, then newTooltipString // has the new ToolTip string in it. // bool& appendToTooltipStr, char*& additionalTooltipString, // Input/Output // AcGiViewportDraw*
pDrawContext,
// Input parameters: // AcApDocument* document, bool pointComputed, int history, const AcGePoint3d& lastPoint, const AcGePoint3d& rawPoint, const AcGePoint3d& grippedPoint, const AcGePoint3d& cartesianSnappedPoint, const AcGePoint3d& osnappedPoint, AcDb::OsnapMask osnapMasks, // const AcArray<AcDbCustomOsnapMode>& customOsnapModes const AcArray<AcDbObjectId>& apertureEntities, const AcArray< AcDbObjectIdArray, AcArrayObjectCopyReallocator< AcDbObjectIdArray > >& nestedPickedEntities, int gsSelectionMark, const AcArray<AcDbObjectId>& keyPointEntities, const AcArray<AcGeCurve3d*>& alignmentPaths, const AcGePoint3d& computedPoint, const char* tooltipString) { acutPrintf("\nhistory: %d\n", history);
576
Chapter 21
if (pointComputed) { acutPrintf( "rawPoint: rawPoint[0], rawPoint[1], rawPoint[2]); if (history & Acad::eGripped) acutPrintf( "grippedPoint: grippedPoint[0], grippedPoint[1], grippedPoint[2]);
if (history & Acad::eCartSnapped) acutPrintf( "cartesianSnappedPoint: %.2f, %.2f, %.2f\n", cartesianSnappedPoint[0], cartesianSnappedPoint[1], cartesianSnappedPoint[2]); if (history & Acad::eOsnapped) { acutPrintf( "osnappedPoint: osnappedPoint[0], osnappedPoint[1], osnappedPoint[2]);
#define OSMASK_CHECK(x) if (osnapMasks & AcDb:: ## x) acutPrintf("%s ", #x) OSMASK_CHECK(kOsMaskEnd); OSMASK_CHECK(kOsMaskMid); OSMASK_CHECK(kOsMaskCen); OSMASK_CHECK(kOsMaskNode); OSMASK_CHECK(kOsMaskQuad); OSMASK_CHECK(kOsMaskInt); OSMASK_CHECK(kOsMaskIns); OSMASK_CHECK(kOsMaskPerp); OSMASK_CHECK(kOsMaskTan); OSMASK_CHECK(kOsMaskNear); OSMASK_CHECK(kOsMaskQuick); OSMASK_CHECK(kOsMaskApint); OSMASK_CHECK(kOsMaskImmediate); OSMASK_CHECK(kOsMaskAllowTan); OSMASK_CHECK(kOsMaskDisablePerp); OSMASK_CHECK(kOsMaskRelCartesian); OSMASK_CHECK(kOsMaskRelPolar); #undef OSMASK_CHECK acutPrintf("\n"); }
577
acutPrintf("%d apertureEntities: ", apertureEntities.length()); for (int i = 0; i < apertureEntities.length(); i++) acutPrintf("<%x> ", apertureEntities[i].asOldId()); acutPrintf("\n"); } else { acutPrintf("No point computed"); if (history & Acad::eCyclingPt) acutPrintf(", but new cycling osnap: %.2f, %.2f, %.2f\n", osnappedPoint[0], osnappedPoint[1], osnappedPoint[2]); else acutPrintf(".\n"); } if (NULL != pDrawContext) { pDrawContext->subEntityTraits().setColor(2); pDrawContext->geometry().circle(rawPoint, 1.0, AcGeVector3d::kZAxis); } else acutPrintf("ViewportDraw is NULL!\n"); if (history & Acad::eNotDigitizer) acutPrintf("PICK!\n"); if (NULL != tooltipString) { acutPrintf("TooltipString: %s\n", tooltipString); additionalTooltipString = ", anotherString!"; appendToTooltipStr = true; } if (history & Acad::eOrtho) { acutPrintf("Ortho found at %.2f, %.2f, %.2f\n", computedPoint[0], computedPoint[1], computedPoint[2]); } return Acad::eOk; } class IPF : public AcEdInputPointFilter { public: Acad::ErrorStatus processInputPoint( bool& AcGePoint3d& bool& char*& bool& AcGiViewportDraw*
578
Chapter 21
AcApDocument* document, bool pointComputed, int history, const AcGePoint3d& lastPoint, const AcGePoint3d& rawPoint, const AcGePoint3d& grippedPoint, const AcGePoint3d& cartesianSnappedPoint, const AcGePoint3d& osnappedPoint, AcDb::OsnapMask osnapMasks, // const AcArray<AcDbCustomOsnapMode>& customOsnapModes const AcArray<AcDbObjectId>& pickedEntities, const AcArray< AcDbObjectIdArray, AcArrayObjectCopyReallocator< AcDbObjectIdArray > >& nestedPickedEntities, // Of 0th element in pickedEntities. int gsSelectionMark, // AutoSnap Info: const AcArray<AcDbObjectId>& const AcArray<AcGeCurve3d*>& const AcGePoint3d& const char* }; Acad::ErrorStatus IPF::processInputPoint( bool& changedPoint, AcGePoint3d& newPoint, bool& changedTooltipStr, char*& newTooltipString, bool& retry, AcGiViewportDraw* pDrawContext, AcApDocument* document, bool pointComputed, int history, const AcGePoint3d& lastPoint, const AcGePoint3d& rawPoint, const AcGePoint3d& grippedPoint, const AcGePoint3d& cartesianSnappedPoint, const AcGePoint3d& osnappedPoint, // const AcArray<AcDbCustomOsnapMode>& customOsnapModes const AcArray<AcDbObjectId>& pickedEntities, const AcArray< AcDbObjectIdArray, AcArrayObjectCopyReallocator< AcDbObjectIdArray > >& nestedPickedEntities, // Of 0th element in pickedEntities. int gsSelectionMark, // AutoSnap Info: const AcArray<AcDbObjectId>& keyPointEntities, const AcArray<AcGeCurve3d*>& alignmentPaths, const AcGePoint3d& computedPoint, const char* tooltipString)
579
{ // Change the computed point to an offset of (0.2, 0.2, 0.2) // if the current computed point is an object snap point. // if (pointComputed && history & Acad::eOsnapped) { changedPoint = true; newPoint = osnappedPoint + AcGeVector3d(0.2,0.2,0.0); pDrawContext->geometry().circle(newPoint, 0.1, AcGeVector3d::kZAxis); } return Acad::eOk; } // Input point monitor IPM my_ipm; // Input point filter IPF my_ipf; // Installs an input point monitor. // void testipm() { curDoc()->inputPointManager()->addPointMonitor(&my_ipm); } // Installs an input point filter. // void testipf() { curDoc()->inputPointManager()->registerPointFilter(&my_ipf); } // Turns on forced entity picking. // void testfp() { curDoc()->inputPointManager()->turnOnForcedPick(); } // Disables the system cursor graphics. // void testcursor() { curDoc()->inputPointManager()->disableSystemCursorGraphics(); }
580
Chapter 21
extern "C" __declspec(dllexport) AcRx::AppRetCode acrxEntryPoint(AcRx::AppMsgCode msg, void *p) { switch (msg) { case AcRx::kInitAppMsg: acrxRegisterAppMDIAware(p); acrxUnlockApplication(p); acedRegCmds->addCommand("mkr", "testipm", "ipm", ACRX_CMD_TRANSPARENT, testipm); acedRegCmds->addCommand("mkr", "testipf", "ipf", ACRX_CMD_TRANSPARENT, testipf); acedRegCmds->addCommand("mkr", "testfp", "fp", ACRX_CMD_TRANSPARENT, testfp); acedRegCmds->addCommand("mkr", "testcursor", "cursor", ACRX_CMD_TRANSPARENT, testcursor); break; case AcRx::kUnloadAppMsg: acedRegCmds->removeGroup("mkr"); break; } return AcRx::kRetOK; }
581
582
Application Configuration
22
In this chapter
I Profile Manager
This section discusses configuring your application for the end user.
583
Profile Manager
The Profile Manager allows you to perform all the operations provided in the Profiles tab of the Options Dialog. This API consists of two classes and several methods used to manipulate user profiles easily.
AcApProfileManager Class
There is only one Profile Manager object per AutoCADsession. ObjectARX provides a global function to get access to this Profile Manager object, called acProfileManagerPtr(). This function returns a pointer to an AcApProfileManager object, upon which the methods can then be called. The AcApProfileManager class provides a container for the operations provided by the Profiles tab. There is no method provided to obtain the current profile name, since this is stored in the system variable called CPROFILE and can be obtained using a call to the acedGetVar(). AcApProfileManager class provides the following capabilities
Capabilities Retrieve the registry path for a specified profile Retrieve the profile names that currently exist for a users configuration Associated Method AcApProfileManager::ProfileRegistryKey
AcApProfileManager::ProfileListNames
Export a given profile to an AcApProfileManager::ProfileExport AutoCAD registry profile file (.arg) in REGEDIT4 format Import an AutoCAD registry profile AcApProfileManager::ProfileImport file (.arg) into a given profile Delete a specified profile from the registry AcApProfileManager::ProfileDelete
Reset a specified profile to AutoCAD AcApProfileManager::ProfileReset default settings Make a specified profile active or current for the AutoCAD session Copy an existing profile to a new profile AcApProfileManager::ProfileSetCurrent
AcApProfileManager::ProfileCopy
584
Chapter 22
Application Configuration
AcApProfileManagerReactor Class
The AcApProfileManagerReactor class provides a container for different event notifications based on user profile changes. AcApProfileManagerReactor class notifications
Notification Associated Method
The current profile is about to be AcApProfileManagerReactor::currentProfileWillChange changed The current profile has been changed AcApProfileManagerReactor::currentProfileChanged
The current profile is about to be AcApProfileManagerReactor::currentProfileWillBeReset reset The current profile has been reset AcApProfileManagerReactor::currentProfileReset A non-current profile is about to AcApProfileManagerReactor::profileWillReset be reset A non-current profile has been reset AcApProfileManagerReactor::profileReset
Profile Manager
585
586
Chapter 22
Application Configuration
void AsdkProfileManagerReactor:: profileReset(const char *profName) { acutPrintf("\nNon-current profile has been reset:%s", profName); } void aFunction() { acutPrintf("This is AsdkProfileSample Test Application...\n"); // Attach the reactor for the duration of this command. Normally // this would be added upon application initialization. // AsdkProfileManagerReactor *pProfileRector = new AsdkProfileManagerReactor(); acProfileManagerPtr()->addReactor(pProfileRector); // Obtain the path for the registry keys and print it out. // char *pstrKey; acProfileManagerPtr()->ProfileRegistryKey(pstrKey, NULL); if (pstrKey != NULL) { acutPrintf("\nThe profiles registry key is: %s", pstrKey); acutDelString(pstrKey); } // Get the list of all profiles in the users configuration // and print them out. // AcApProfileNameArray arrNameList; int nProfiles = acProfileManagerPtr()->ProfileListNames(arrNameList); acutPrintf("\nNumber of profiles currently " "in the user profile list is: %d", nProfiles); for (int i = 0; i < nProfiles; i++) acutPrintf("\nProfile name is: %s", arrNameList[i]); // Copy the unnamed profile to the AsdkTestProfile. // acProfileManagerPtr()->ProfileCopy( "AsdkTestProfile", "<<Unnamed Profile>>", "This is a test"); // Reset the newly copied profile to AutoCAD defaults. // acProfileManagerPtr()->ProfileReset("AsdkTestProfile");
Profile Manager
587
// Make this new profile current. // acProfileManagerPtr()->ProfileSetCurrent("AsdkTestProfile"); // Change a value in the profile. For this example, make the // cursor big. // struct resbuf rbCursorSize; rbCursorSize.restype = RTSHORT; rbCursorSize.resval.rint = 100; acedSetVar("CURSORSIZE", &rbCursorSize); // Rename the profile to a new name. // acProfileManagerPtr()->ProfileRename( "AsdkTestProfile2", "AsdkTestProfile", "This is another test"); // Export the profile. // acProfileManagerPtr()->ProfileExport( "AsdkTestProfile2", "./AsdkTestProfile2.arg"); // Import the profile. // acProfileManagerPtr()->ProfileImport( "AsdkTestProfile3", "./AsdkTestProfile2.arg", "This is a copy of AsdkTestProfile2" "by Exporting/Importing", Adesk::kTrue); // Remove the reactor. // acProfileManagerPtr()->removeReactor(pProfileRector); }
588
Chapter 22
Application Configuration
Object Enablers
23
In this chapter
I Developing an Object Enabler I Using Live Enabler Technology
Object enablers allow custom objects in a drawing to behave with more intelligence than proxy graphics, even when the originating application is not present. By providing object enablers for custom objects, you ensure that your customers can work collaboratively on drawings without worrying about whether others will be able to manipulate the custom objects in those drawings. Your objects will even behave intelligently in
, other ObjectDBX host applications like AutoCAD LT
VoloView, and 3ds max . The Live Enabler technology introduced in AutoCAD 2000i automatically downloads and installs the appropriate object enabler when a user opens a DWG file that requires it. Autodesk Developers Network members can include their object enablers in this search by packaging and posting them on Autodesk PointA the Autodesk , design portal.
589
Name your object enabler <logicalAppName>OBJ.dbx. Name your cabinet file <logicalAppName>OBJ.cab. Name your self-extracting archive <logicalAppName>OBJ.exe.
If your files are not named properly, AutoCAD may not be able to detect, download, decompress, and install your object enablers.
590
Chapter 23
Object Enablers
After you have built your object enablers, you can post them through Autodesk Developer Network to take advantage of the Live Enabler technology. This procedure involves the following general steps 1 Building register.dbx to Populate the Registry 2 Creating a Self-Extracting Package 3 Testing Your Object Enabler 4 Uploading to the ADN Website
591
Before you begin, remove any previous versions of the object enablers from your system. You can do so by deleting the ObjectDBX files from the Program Files\Common Files\Autodesk Shared directory and deleting the appropriate registry keys from the ObjectDBX section of the registry.
I I
Perform all tests on all supported operating systems. Test your object enabler with a variety of ObjectDBX host applications such as AutoCAD, AutoCAD LT, Volo View or Volo View Express, Architectural Desktop, and Mechanical Desktop. ObjectDBX developers can also use the sample ObjectDBX host application ViewAcDb.
To install your object enabler 1 Copy the self-extracting object enabler file to the Program Files\Command Files\Autodesk Shared directory; then double-click it. The object enabler(s) and register.dbx should be extracted in the Autodesk Shared folder. 2 In AutoCAD, load register.dbx. 3 After loading register.dbx, unload it. 4 Delete register.dbx to ensure other object enablers can load in the future (optional).
592
Chapter 23
Object Enablers
To test the functionality of your object enabler 1 Make sure that the object enabler functions without any AutoCAD dependencies (such as the AutoCAD editor, editor reactors, and so on). 2 Confirm that AutoCAD and other ObjectDBX host applications (Volo View Express, for example) can open and close properly after the object enabler is loaded. 3 Open a test drawing that contains all the custom objects supported by the object enabler, with the PROXYNOTICE system variable set to 1. Make sure that the Proxy dialog box does not display for the enabled custom objects on the first or subsequent times you open the file. 4 With the object enabler loaded, INSERT the test drawing, XREF Attach it, XREF Reload it, and access it in AutoCAD DesignCenter A proxy . warning should not display. 5 Remove the registry key, the ObjectDBX file, and both the registry key and the ObjectDBX file in turn, and confirm that the Proxy dialog box displays and that AutoCAD and other host applications work correctly in each case. 6 Use the LIST command on each enabled custom object to ensure that all the appropriate information about the object is listed. No proxy information should be present in this listing. 7 Test the following AutoCAD editing commands with the enabled custom objects: MOVE, COPY, ARRAY, ROTATE, SCALE, MIRROR, CHANGE, CHPROP. To verify that your object enabler does not corrupt drawing files 1 Open a drawing containing your custom objects on a computer without the object enablers installed; make sure that it opens successfully. 2 Run the AUDIT command; confirm that it does not find any errors. 3 Install your object enablers; make sure the drawing containing your custom objects opens successfully. 4 Run the AUDIT command; confirm that it does not find any errors. 5 Save the drawing on a computer that has the object enablers installed. 6 Open the drawing with and without the object enablers installed. Make sure that the drawing opens successfully in both cases and that the AUDIT command does not find any errors.
593
If your products object enablers are in multiple ObjectDBX files, drawings containing custom objects should trigger the download of all relevant object enablers. Drawings containing all your custom objects should trigger the download of all your object enablers. Although you cant test the automatic download until your files have been posted, you should test the various combinations of manually loaded ObjectDBX files using the following procedure. To test multiple ObjectDBX files before uploading 1 Open a drawing that requires multiple ObjectDBX files, only a subset of which are installed. 2 Confirm that the Proxy dialog box is displayed for custom objects that do not have object enablers. 3 Test the functionality of the custom objects that do have object enablers. (See To test the functionality of your object enabler on page 593.) 4 Confirm that the combination of enabled custom objects and proxies does not corrupt drawing files. (See To verify that your object enabler does not corrupt drawing files on page 593.) 5 Repeat using all combinations of installed and uninstalled ObjectDBX files.
594
Chapter 23
Object Enablers
DesignXML APIs
24
In this chapter
I Overview of DesignXML I Providing XML Support for
Extensible Markup Language (XML) is a World Wide Web Consortium (W3C) standard for structuring data in tag-delimited text files. Autodesk publishes an XML schema called DesignXML, which describes geometric data. Within DesignXML, six possible channels represent the data with varying degrees of detail. The AcDbXML model channel corresponds to an AutoCAD DWG file. With the ObjectARX XML APIs, you can determine how your custom object data will be written to and read from an AcDbXML file. This section assumes you are familiar with XML, the Document Object Model (DOM), and XML schemas; and with ObjectARX demand loading, object enablers, and the protocol extension mechanism.
Custom Objects
I Reading and Writing
595
Overview of DesignXML
XML is a structured text format used for passing data and documents over the Web. The standards documents that define XML and XML schema are produced by the W3C and are available at the W3C website, www.w3.org/XML. DesignXML is an XML data format that represents geometry-oriented models as a set of data channels defined in XML schemas. Each channel represents a redundant view of the same model, with a different level of complexity and detail. The DesignXML channels are as follows:
I I I I I I
For more information about the DesignXML schemas, see the DesignXML and AcDbXML portions of the ObjectARX help file. The schemas and the style sheets that document them are also available at the following locations: http://www.DesignXML.org/schema/DesignXML_V_100.xdr http://www.DesignXML.org/schema/AcDbXML_V_100.xdr http://www.DesignXML.org/schema/DesignXML_V_100.xsl http://www.DesignXML.org/schema/AcDbXML_V_100.xsl The DesignXML schemas are currently written in XML-Data Reduced (XDR) format, which is an early version of XML schema representation. The schemas will be converted to the W3C XML schema language definition standard (XSD) in the future.
Prerequisite Knowledge
In addition to the W3C site, the following resources might be helpful starting points if you are unfamiliar with XML. msdn.microsoft.com/xml The Microsoft XML Developer Center; includes links to a detailed XML tutorial, the XML DOM Reference, and an XML Schema Developers Guide (for XDR).
596
Chapter 24
DesignXML APIs
www.xml.org A community registry for XML schemas and related information; hosted by the Organization for the Advancement of Structured Information Standards (OASIS). www.xml-zone.com The Development Exchange (DevX) site; features articles, discussions, and links to numerous XMLrelated sites. For information about demand loading an ObjectARX application, see Demand Loading on page 43. The ObjectARX protocol extension mechanism is covered in chapter 19, Protocol Extension. Refer to chapter 23, Object Enablers or the Autodesk Developer Network (ADN) website for information about object enablers.
597
If you do not provide a schema and an XML object enabler for your custom objects, they will be treated as proxies for XML input and output.
Use your registered developer prefix as the XML namespace. Post the schema at any stable, publicly available URL. You might want to publish your schema on a schema library site such as www.biztalk.org or www.oasis-open.org. Use initial capital letters for element names. Use initial lowercase letters for attribute names. Use a single root element. Include the parent data for a class as the first element within the class element. Data for all parent classes in an objects hierarchy should be represented in nested fashion up to the first native ObjectARX object or entity level. Use attributes for single data values so they can be easily accessed with AcDbXML filer functions (optional). Name elements using the class name without the prefix (optional).
Sample Schema The ObjectARX XML sample application implements an XML object enabler for AcDbPoly, a custom object from the ObjectARX SDK samples directory. Here is the schema for AcDbPoly:
<?xml version="1.0" encoding="UTF-8"?> <!-- Schema for the AutoCAD custom object, AcDbPoly, based on the AcDbXml and DesignXML schema. --> <Schema name="AcDbPoly_V_beta1.xdr" xmlns="urn:schemas-microsoft-com:xml-data" xmlns:dt="urn:schemas-microsoft-com:datatypes" xmlns:dxml="x-schema:http://betaprograms.autodesk.com/schema/xml/ DesignXml_V_beta1.xdr" xmlns:acdb="x-schema:http://betaprograms.autodesk.com/schema/xml/ AcDbXML_V_beta1.xdr">
598
Chapter 24
DesignXML APIs
<!-- AcDbPoly --> <ElementType name="Poly" content="eltOnly" model="closed"> <AttributeType name="numSides" dt:type="i2" required="yes"/> <AttributeType name="name" dt:type="string" required="yes"/> <attribute type="numSides"/> <attribute type="name"/> <element type="acdb:Entity"/> <element type="dxml:CenterPoint"/> <element type="dxml:StartPoint"/> <element type="acdb:NormalVector"/> <element type="acdb:TextStyleTableRecordId"/> </ElementType> </Schema>
Declaring Protocol Extension Classes Derived from AcDbXmlObjectProtocol Defining an ObjectDBX Application for Your XML Object Enabler Implementing xmlOut() Implementing xmlIn()
599
To define an ObjectDBX application interface for an XML object enabler 1 Create an ObjectDBX application with an exported acrxEntryPoint() function. 2 Call addX() to add the protocol extension to the AcRxClass object for your class:
gpPolyXmlExtension = new AcDbXmlPoly(AsdkPoly::desc()); AsdkPoly::desc()->addX(AcDbXmlObjectProtocol::desc(), gpPolyXmlExtension);
3 Update the registry for demand loading using <logicalAppName>XML. For example, the polysamp application has the logical application name AsdkPolyOBJ, so the polyxml sample registers an XML object enabler with the logical application name AsdkPolyOBJXML. See Demand Loading on Detection of Custom Objects on page 49 for registry key details. 4 Name your application <appName>XML.dbx (optional). Sample Application Interface for XML Object Enabler The following polyxml sample code shows the necessary updateRegistry() and acrxEntryPoint() functions. This sample uses the following macros: MY_APPLOC MY_APPNAME MY_MODULE_NAME File name of the XML object enabler
#define MY_APPLOC "Software\\Autodesk\\ObjectARX\\3.0\\SampleApps\\PolySamp" #define MY_APPNAME "AsdkPolyOBJXML" #define MY_MODULE_NAME "asdkPolyXMLObj.dbx" void updateRegistry() { // Represents your company's Registry location const char pAppInfoLoc[] = MY_APPLOC; // The final argument of Adesk::kTrue means that this app will // be // registered under the Autodesk/ObjectDBX area of the registry. // All ObjectDBX apps can search for it there, in addition to // AutoCAD
Location in the registry for the demand-loading registrations, qualified with the application name Logical appname plus the "XML" suffix
600
Chapter 24
DesignXML APIs
acrxRegisterApp((AcadApp::LoadReasons)(AcadApp::kOnProxyDetection) , MY_APPNAME, pAppInfoLoc , 2, Adesk::kTrue); // Now to write out the application specific information. // We will install our "application specific" data in: // Software\Autodesk\ObjectARX\3.0\SampleApps\Polysamp HKEY rkey; DWORD result; LONG status = RegCreateKeyEx(HKEY_LOCAL_MACHINE, pAppInfoLoc, 0, "", REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &rkey, &result); if (status != ERROR_SUCCESS) return; HKEY prodKey; status = RegCreateKeyEx(rkey, MY_APPNAME, 0, "", REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &prodKey, &result); RegCloseKey(rkey); if (status != ERROR_SUCCESS) return; HKEY loaderKey; status = RegCreateKeyEx(prodKey, "Loader", 0, "", REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &loaderKey, &result); if (status == ERROR_SUCCESS) { HMODULE modHandle = GetModuleHandle(MY_MODULE_NAME); if (modHandle) { // Long filenames have a max length of 255. Total // directory path length is also 255 or less, so a // buffer of 512 bytes should be fine. // char modulePath[512]; DWORD pathLength = GetModuleFileName(modHandle, modulePath, 512); if (pathLength) RegSetValueEx(loaderKey, "MODULE", 0, REG_SZ, (const unsigned char*)modulePath, pathLength + 1); } RegCloseKey(loaderKey); }
601
HKEY nameKey; status = RegCreateKeyEx(prodKey, "Name", 0, "", REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &nameKey, &result); RegCloseKey(prodKey); if (status != ERROR_SUCCESS) return; RegSetValueEx(nameKey, MY_APPNAME, 0, REG_SZ, (const unsigned char*)MY_APPNAME, 12); RegCloseKey(nameKey); } extern "C" AcRx::AppRetCode acrxEntryPoint(AcRx::AppMsgCode msg, void* pkt) { switch(msg) { case AcRx::kInitAppMsg: acrxUnlockApplication(pkt); // Try to allow unloading acrxRegisterAppMDIAware(pkt); if (acrxServiceIsRegistered("AsdkPoly") && AsdkPoly::desc() != NULL && AcDbXmlObjectProtocol::desc() != NULL) { // add XML I/O protocol extension to AsdkPoly // gpPolyXmlExtension = new AcDbXmlPoly(AsdkPoly::desc()); AsdkPoly::desc()->addX(AcDbXmlObjectProtocol::desc(), gpPolyXmlExtension); acrxBuildClassHierarchy(); updateRegistry(); } break; case AcRx::kUnloadAppMsg: if (acrxServiceIsRegistered("AsdkPoly") && AsdkPoly::desc() != NULL && AcDbXmlObjectProtocol::desc() != NULL) { AsdkPoly::desc()->delX(AcDbXmlObjectProtocol::desc()); } if (gpPolyXmlExtension) delete gpPolyXmlExtension; break; } return AcRx::kRetOK; }
602
Chapter 24
DesignXML APIs
Implementing xmlOut()
Implement xmlOut() for all classes in your hierarchy. The minimum implementation is to call superXMLOut(). To implement xmlOut() 1 Establish a new child node using the AcDbXmlOutFiler::pushNode() function and pass it the namespace-qualified root element name from your schema and the URI for your schema. 2 Get the newly created node as an element. 3 Call superXmlOut(). This function calls the xmlOut() function of the protocol extension class for the parent object, which in turn calls superXmlOut(). 4 Append desired attributes using the appropriate version of AcDbXmlOutFiler::appendAttribute(). 5 Create subelements (child nodes) and attributes according to the schema.
AcDbXmlOutFiler provides functions for known types, or you may use the DOM framework with pushNode() and popNode() or appendNode().
603
pElement = new AcUtXMLDOMElement(pNode); if (pElement == NULL) throw eOutOfMemory; es = superXmlOut(pObj, pFiler); if (es != eOk) throw es; es = pFiler->appendAttribute(pPoly->numSides(), L"numSides", pElement); if (es != eOk) throw es; AcUtString name=pPoly->name(); es = pFiler->appendAttribute(name, L"name", pElement); if (es != eOk) throw es; AcGePoint2d pt2d = pPoly->center(); AcGePoint3d centerPt(pt2d.x, pt2d.y, pPoly->elevation()); // Convert the point from ECS to WCS acdbEcs2Wcs(asDblArray(centerPt), asDblArray(centerPt), asDblArray(pPoly->normal()), true); es = pFiler->writePoint(L"dxml:CenterPoint", centerPt); if (es != eOk) throw es; pt2d = pPoly->startPoint(); AcGePoint3d startPt(pt2d.x, pt2d.y, pPoly->elevation()); // Convert the point from ECS to WCS acdbEcs2Wcs(asDblArray(startPt), asDblArray(startPt), asDblArray(pPoly->normal()), true); es = pFiler->writePoint(L"dxml:StartPoint", startPt); if (es != eOk) throw es; es = pFiler->writeNormalVector(pPoly->normal()); if (es != eOk) throw es; es = pFiler->writeHardPointerId (L"acdb:TextStyleTableRecordId", pPoly->styleId()); if (es != eOk) throw es; es = pFiler->popNode(); if (es != eOk) throw es; }
604
Chapter 24
DesignXML APIs
catch (Acad::ErrorStatus thrownEs) { es = thrownEs; } if (pAttribute) delete pAttribute; if (pElement) delete pElement; if (pNode && pNode != pFiler->getCurrentNode()) delete pNode; return es; }
Implementing xmlIn()
Implement xmlIn() to read the portions of the DesignXML file written by your xmlOut() function according to your schema. To implement xmlIn() 1 Call AcDbXmlInFiler::getCurrentNode() and save the pointer it returns. This is a pointer to the acdb:Object node that is the parent of your root node. It is used later to restore this node as the current node. 2 Get the first child of the current node using AcUtXMLDOMNode::get_firstChild() and make it the current node using
AcDbXmlInFiler::setCurrentNode().
3 Process attributes by getting the AcUtXMLDOMElement representation and using the appropriate AcDbXmlInFiler::readAttribute() function or by parsing the information. 4 Get the first child of the current node and make it the current node. 5 Call superXmlIn() to process the current node. 6 Get the next sibling of the current node using AcUtXMLDOMNode::get_nextSibling() and make that the current node. 7 Using AcUtXMLDOMNode and AcDbXmlInFiler functions, process the remaining siblings to restore data to your object. 8 When you have finished filing in data, restore the acdb:Object node you were originally given as the current node. 9 Delete all other returned objects from memory.
605
Sample Implementation of xmlIn() The following code shows the polyxml implementation of xmlIn().
Acad::ErrorStatus AcDbXmlPoly::xmlIn( AcDbObject* pObj, AcDbXmlInFiler* pFiler) { if (pObj == NULL || pFiler == NULL) return eInvalidInput; AsdkPoly* pPoly = AsdkPoly::cast(pObj); if (pPoly == NULL) return eInvalidInput; AcUtXMLDOMNode* pFirstNode = pFiler->getCurrentNode(); // don't delete if (pFirstNode == NULL) return eInvalidInput; AcUtXMLDOMNode* pCurrent = NULL; AcUtXMLDOMNode* pChild = NULL; AcUtXMLDOMElement* pElement = NULL; AcUtXMLDOMResult hr; Acad::ErrorStatus es = eOk; AcGePoint2d centerPt; AcGePoint3d centerPt3d; AcGePoint2d startPt; AcGePoint3d startPt3d; AcGeVector3d normal; AcDbObjectId textStyleId; AcUtString name; int numSides; double elevation = 0; AcUtString nodeName = pFiler->getCurrentNodeName(); try { // first child should be asdk:Poly hr = pFirstNode->get_firstChild(pCurrent); if (hr != S_OK) throw eInvalidInput; // make it current pFiler->setCurrentNode(pCurrent); // verify node name is Poly nodeName = pFiler->getCurrentNodeName(); if (nodeName.compare(L"asdk:Poly") != 0) throw eInvalidInput;
606
Chapter 24
DesignXML APIs
// Get the numSides and name atributes pElement = new AcUtXMLDOMElement(pCurrent); if (pElement == NULL) throw eOutOfMemory; es = pFiler->readAttribute(pElement, L"numSides", numSides); if (es != eOk) throw es; es = pFiler->readAttribute(pElement, L"name", name); if (es != eOk) throw es; // Get the first child element of the asdk:Poly hr = pCurrent->get_firstChild(pChild); if (hr != S_OK) throw eInvalidInput; // Make it current pFiler->setCurrentNode(pChild); delete pCurrent; pCurrent = pChild; pChild = NULL; nodeName = pFiler->getCurrentNodeName(); // This first child node might be an optional acdb:Entity if (nodeName.compare(AcUtString(L"acdb:Entity")) == 0) { // Read the acdb:Entity node es = superXmlIn(pObj, pFiler); if (es != eOk) throw es; hr = pCurrent->get_nextSibling(pChild); if (hr != S_OK) throw eInvalidInput; pFiler->setCurrentNode(pChild); delete pCurrent; pCurrent = pChild; pChild = NULL; } // Expect the current node to be a dxml:CenterPoint. es = pFiler->readPoint(L"dxml:CenterPoint", centerPt3d); if (es != eOk) throw es; // Get the next sibiling to the circle - expect StartPoint. hr = pCurrent->get_nextSibling(pChild); if (hr != S_OK) throw eInvalidInput;
607
pFiler->setCurrentNode(pChild); delete pCurrent; pCurrent = pChild; pChild = NULL; es = pFiler->readPoint(L"dxml:StartPoint", startPt3d); if (es != eOk) throw es; // Get the next sibiling to the circle - expect NormalVector. hr = pCurrent->get_nextSibling(pChild); if (hr != S_OK) throw eInvalidInput; pFiler->setCurrentNode(pChild); delete pCurrent; pCurrent = pChild; pChild = NULL; es = pFiler->readNormalVector(normal); if (es != eOk) throw es; // // hr if Get the next sibiling to the circle - expect TextStyleTableRecordId. = pCurrent->get_nextSibling(pChild); (hr != S_OK) throw eInvalidInput;
pFiler->setCurrentNode(pChild); delete pCurrent; pCurrent = pChild; pChild = NULL; es = pFiler->readObjectId(L"acdb:TextStyleTableRecordId", textStyleId); if (es != eOk) throw es; es = pPoly->setTextStyle(textStyleId); if (es != eOk) throw es; // Convert the center point from WCS to ECS acdbWcs2Ecs(asDblArray(centerPt3d), asDblArray(centerPt3d), asDblArray(normal), true); centerPt.x = centerPt3d.x; centerPt.y = centerPt3d.y; elevation = centerPt3d.z;
608
Chapter 24
DesignXML APIs
// Convert the start point from WCS to ECS acdbWcs2Ecs(asDblArray(startPt3d), asDblArray(startPt3d), asDblArray(normal), true); startPt.x = startPt3d.x; startPt.y = startPt3d.y; es = pPoly->set(centerPt, startPt, numSides, normal, name, elevation); if (es != eOk) throw es; } catch (Acad::ErrorStatus thrownEs) { es = thrownEs; } // Restore first node pFiler->setCurrentNode(pFirstNode); if (pElement) delete pElement; if (pCurrent && pCurrent != pFiler->getCurrentNode()) delete pCurrent; if (pChild) delete pChild; return es; }
609
610
Chapter 24
DesignXML APIs
Part 5
Interacting with Other Environments
COM, ActiveX Automation, and the Object Property Manager 613 AutoCAD DesignCenter COM API 663 eTransmit COM APIs 683
611
612
25
In this chapter
I COM Overview I Using AutoCAD COM from
Supported Environments
I AutoCAD ActiveX Automation
Implementation
I Interacting with AutoCAD I Document Locking I Creating a Registry File I Exposing Automation
application running on Windows. Additionally, by using COM with ObjectARX, you can augment AutoCADs ActiveX Automation model. The elements, objects, or entities you expose will be available to other programming environments such as Visual Basic for Applications (VBA), and to AutoCAD such as the Properties window.
features
Functionality
I Object Property Manager API I Static OPM COM Interfaces I Implementing Static OPM
Interfaces
I Dynamic Properties and OPM
613
COM Overview
Microsofts Component Object Model (COM) was originally designed to support Object Linking and Embedding (OLE); it also became the basis of ActiveX Automation. As the emergent standard for Windows component development, COM has relevance beyond OLE and ActiveX. Component architecture separates interface from implementation, allowing applications to consist of dynamically linked components rather than a single binary executable. Developers can write programs that take advantage of existing COM components, or they can use COM to create their own components. ObjectARX applications can be designed as COM clients. For instance, an ObjectARX application that needs to communicate with another program could implement COM access to that program. Depending on the COM interfaces that the other application provides, the ObjectARX application could then exchange information with that application or even drive it. An ObjectARX application can also act as an Automation server. You can write COM wrappers to expose additional elements or custom ObjectARX objects. New APIs, templates, classes, and support for the Microsoft Active Template Library (ATL) make it easier than ever to add to the AutoCAD ActiveX Automation model.
614
Chapter 25
NOTE Most of the project frameworks described in this chapter are automatically provided if you use the ObjectARX Wizard. To install the Wizard, see the objectarx\utils\objarxwiz directory in the ObjectARX SDK.
To set up a new MFC project file To add ObjectARX compatibility To exclude debug MFC code from the debug build of your project To import AutoCAD ActiveX interfaces To implement AutoCAD ActiveX Automation calls
To set up a new MFC project file 1 Start Visual C++ and create a new MFC AppWizard(dll) project called AsdkComMfcDocSamp. 2 Choose MFC Extension DLL (using shared MFC DLL).
NOTE You can choose any of the options, but Extension DLL is the recommended choice. See MFC Topics for more information on options to choose for different tasks. 3 Select Finish and then OK to create the project.
615
To begin adding specific ObjectARX functionality, you must adjust the project settings and add some boilerplate code. To add ObjectARX compatibility 1 Add the appropriate values to the project settings to make the project build as an ObjectARX program. Be sure that the ObjectARX \inc and \lib directories are in the projects search path. This program needs to link with the following libraries:
acad.lib rxapi.lib acrx15.lib acedapi.lib
2 Choose OK to dismiss the Project Settings dialog box. 3 In your projects definitions (DEF) file, add the following lines to the EXPORTS section:
acrxEntryPoint acrxGetApiVersion PRIVATE PRIVATE
4 Open the AsdkComMfcDocSamp.cpp source file and add the following code to make the program ObjectARX compatible:
#include <rxregsvc.h> #include <aced.h> #include <rxmfcapi.h> static void initApp() { acedRegCmds->addCommand( "ASDK_MFC_COM", "AsdkMfcComCircle", "MfcComCircle", ACRX_CMD_MODAL, addCircleThroughMfcCom); } static void unloadApp() { acedRegCmds->removeGroup("ASDK_MFC_COM"); }
616
Chapter 25
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; default: break; } return AcRx::kRetOK; }
5 Stub in your new AutoCAD command handler function by adding the following code to the AsdkComMfcDocSamp.cpp source file:
void addCircleThroughMfcCom() { }
In order to build your project without linker warnings, you must make MFC aware that your debug project links to libraries that are built with the release MFC libraries. This is accomplished by suspending the definition of the debug symbol only while the compiler processes the MFC header files. To exclude debug MFC code from the debug build of your project 1 In the generated stdafx.h file, immediately before the first MFC include statement (usually #include <afxwin.h>), add the following code:
ifdef _DEBUG define MYARXDEBUG undef _DEBUG endif
2 In the stdafx.h file, add the following code after the last MFC include statement, just before the AFX_INSERT_LOCATION marker:
ifdef MYARXDEBUG define _DEBUG undef MYARXDEBUG endif
3 From the Build menu, select Build AsdkComMfcDocSamp to verify that your project setup is correct.
617
Your project should build successfully, but at this point it doesnt do anything. The next step is to implement your command handler with ActiveX Automation calls. First you must decide which interfaces are necessary to add a new circle to model space. In this case, the IAcadApplication, IAcadDocument, and IAcadModelSpace interfaces are required. You use the AutoCAD type library (acad.tlb) to get the definitions of these interfaces, as shown in the next procedure. To import AutoCAD ActiveX interfaces 1 Choose ClassWizard from the Microsoft Visual C++ View menu. 2 Choose Add Class. 3 Choose From a Type Library. 4 In the Import from Type Library dialog box, choose the acad.tlb file from your root AutoCAD directory. 5 Choose Open. 6 From the Confirm Classes dialog box, multiselect the IAcadApplication, IAcadDocument, and IAcadModelSpace interface classes. 7 Choose OK. The ClassWizard imports these interface classes from the type library. ClassWizard creates wrapper files and adds them to your project. The new header and implementation file names default to acad.h and acad.cpp, respectively. 8 Open the acad.cpp and acad.h files to explore the classes and methods that were imported.
2 In the empty addCircleThroughMfcCom() function, add declarations for the three interface classes:
618
Chapter 25
3 Use the acedGetAcadWinApp() function to obtain the CWinApp MFC object for AutoCAD and call the GetIDispatch method:
IDispatch *pDisp = acedGetAcadWinApp()-> GetIDispatch(TRUE);
4 Once you have the IDispatch object, attach it to the locally defined IAcadApplication object and make sure that AutoCAD is visible:
IApp.AttachDispatch(pDisp); IApp.SetVisible(true);
5 Obtain the active document dispatch and attach it to the locally defined IAcadDocument object:
pDisp = IApp.GetActiveDocument(); IDoc.AttachDispatch(pDisp);
7 A circle requires a center point and radius. To make this efficient and transparent to different programming languages, the COM interface uses the VARIANT type. A point is stored in a VARIANT as a SAFEARRAY. Add the following code to set up a SAFEARRAY and store it in a VARIANT:
SAFEARRAYBOUND rgsaBound; rgsaBound.lLbound = 0L; rgsaBound.cElements = 3; SAFEARRAY* pStartPoint = NULL; pStartPoint = SafeArrayCreate(VT_R8, 1, &rgsaBound); // X value. // long i = 0; double value = 4.0; SafeArrayPutElement(pStartPoint, &i, &value); // Y value. // i++; value = 2.0; SafeArrayPutElement(pStartPoint, &i, &value); // Z value. // i++; value = 0.0; SafeArrayPutElement(pStartPoint, &i, &value);
619
Here is the finished function, with clean-up code and exception handling added:
void addCircleThroughMfcCom() { TRY { IAcadApplication IApp; IAcadDocument IDoc; IAcadModelSpace IMSpace; IDispatch *pDisp = acedGetAcadWinApp()-> GetIDispatch(TRUE); //AddRef is called on the pointer IApp.AttachDispatch(pDisp); // does not call AddRef() IApp.SetVisible(true); pDisp = IApp.GetActiveDocument(); //AddRef is called IDoc.AttachDispatch(pDisp); pDisp = IDoc.GetModelSpace(); //AddRef is called IMSpace.AttachDispatch(pDisp); SAFEARRAYBOUND rgsaBound; rgsaBound.lLbound = 0L; rgsaBound.cElements = 3; SAFEARRAY* pStartPoint = NULL; pStartPoint = SafeArrayCreate(VT_R8, // X value long i = 0; double value = 4.0; SafeArrayPutElement(pStartPoint, &i, // Y value i++; value = 2.0; SafeArrayPutElement(pStartPoint, &i, // Z value i++; value = 0.0; SafeArrayPutElement(pStartPoint, &i, VARIANT pt1; VariantInit(&pt1); V_VT(&pt1) = VT_ARRAY | VT_R8; V_ARRAY(&pt1) = pStartPoint; IMSpace.AddCircle(pt1, 2.0); VariantClear(&pt1); // Release() is called implicitly on the local objects // the local objects }
1, &rgsaBound);
&value);
&value);
&value);
620
Chapter 25
9 Rebuild your project. 10 Load the ARX file in AutoCAD, and enter MFCCOMCIRCLE at the command line to test your code. COM programmers are normally required to call Release() on any interface pointer for which AddRef() previously has been called. In the example above, MFC relieves you of this responsibility by internally calling Release() on its local interface wrapper objects. However, you should always be mindful of the necessity of releasing COM interface pointers when you are finished using them. The technique of explicitly releasing interface pointers is demonstrated in the following procedure.
To set up a new ObjectARX project To import AutoCAD ActiveX interfaces To implement AutoCAD ActiveX Automation calls
To set up a new ObjectARX project 1 Start Visual C++ and create a new Win32 Dynamic-Link Library project called AsdkComDocSamp. 2 Add the appropriate values to the project settings to make the project build as an ObjectARX program. This program needs to link with the following libraries:
acad.lib rxapi.lib acrx15.lib acutil15.lib acedapi.lib
621
4 Open the new DEF file, and add the following lines to the EXPORTS section:
acrxEntryPoint acrxGetApiVersion PRIVATE PRIVATE
5 Add a new source (CPP) file named AsdkComDocSamp.cpp to the project. 6 Open the new CPP file, and add the following code to make the program ObjectARX compatible:
#include <rxregsvc.h> #include <aced.h> // Used to add/remove the menu with the same command. // static bool bIsMenuLoaded = false; static void initApp() { acedRegCmds->addCommand( "ASDK_PLAIN_COM", "AsdkComMenu", "ComMenu", ACRX_CMD_MODAL, addMenuThroughCom); } static void unloadApp() { acedRegCmds->removeGroup("ASDK_PLAIN_COM"); } 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; default: break; } return AcRx::kRetOK; }
7 Stub in your new AutoCAD command handler function by adding the following code to the AsdkComDocSamp.cpp source file:
void addMenuThroughCom() { }
622
Chapter 25
The next step is to decide which interfaces are necessary to access the AutoCAD menu bar. In this case, IAcadApplication, IAcadMenuBar, IAcadMenuGroups, IAcadMenuGroup, IAcadPopupMenus, IAcadPopupMenu, and IAcadPopupMenuItem are required. To get the definitions of these interfaces, use the AutoCAD type library (acad.tlb) as shown in the next procedure. To import AutoCAD ActiveX interfaces 1 Add the following line to the top of the AsdkComDocSamp.cpp file, making sure to use the path where AutoCAD is installed on your system:
import "c:\\acad\\acad.tlb" no_implementation \ raw_interfaces_only named_guids
Now that your application imports the proper interfaces, you can use them to implement AutoCAD functionality. The more direct COM approach to Automation uses QueryInterface to access the IUnknown of AutoCAD and its Automation components. The next procedure shows how to accomplish this. All code examples in this procedure should be added to your addMenuThroughCOM() function in the sequence presented. To implement AutoCAD ActiveX Automation calls 1 In the AsdkComDocSamp.cpp file, add the following code to the empty addMenuThroughCOM() function to get AutoCADs IUnknown:
HRESULT hr = NOERROR; CLSID clsid; LPUNKNOWN pUnk = NULL; LPDISPATCH pAcadDisp = NULL; hr = ::CLSIDFromProgID(L"AutoCAD.Application", &clsid); if (FAILED(hr)) return; if(::GetActiveObject(clsid, NULL, &pUnk) != S_OK) return; hr = pUnk->QueryInterface(IID_IDispatch, (LPVOID*) &pAcadDisp); pUnk->Release(); if (FAILED(hr)) return;
623
2 Use IUnknown to get the AutoCAD application object. Also, make sure AutoCAD is visible. This is shown in the following code:
hr = pAcadDisp->QueryInterface(AutoCAD::IID_IAcadApplication, (void**)&pAcad); pAcadDisp->Release(); if (FAILED(hr)) return; pAcad->put_Visible(true);
3 From the AutoCAD application interface, get the menu bar and menu groups collections:
pAcad->get_MenuBar(&pMenuBar); pAcad->get_MenuGroups(&pMenuGroups); pAcad->Release();
5 Get the first menu from the menu groups collection. This will normally be ACAD, but could be something else:
VARIANT index; VariantInit(&index); V_VT(&index) = VT_I4; V_I4(&index) = 0; pMenuGroups->Item(index, &pMenuGroup); pMenuGroups->Release();
6 Get the shortcut menus collection from the first menu group:
pMenuGroup->get_Menus(&pPopUpMenus); pMenuGroup->Release();
7 Depending on whether the menu is already created, either construct a new shortcut menu or remove the previously created one. The following code completes the example:
624
Chapter 25
WCHAR wstrMenuName[256]; MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, "AsdkComAccess", -1, wstrMenuName, 256); if (!bIsMenuLoaded) { pPopUpMenus->Add(wstrMenuName, &pPopUpMenu); if (pPopUpMenu != NULL) { pPopUpMenu->put_Name(wstrMenuName); WCHAR wstrMenuItemName[256]; MultiByteToWideChar(CP_ACP, 0,"&Add A ComCircle", -1, wstrMenuItemName, 256); WCHAR wstrMenuItemMacro[256]; MultiByteToWideChar(CP_ACP, 0, "AsdkComCircle ", -1, wstrMenuItemMacro, 256); VariantInit(&index); V_VT(&index) = VT_I4; V_I4(&index) = 0; pPopUpMenu->AddMenuItem(index, wstrMenuItemName, wstrMenuItemMacro, &pPopUpMenuItem); VariantInit(&index); V_VT(&index) = VT_I4; V_I4(&index) = 1; pPopUpMenu->AddSeparator(index, &pPopUpMenuItem); MultiByteToWideChar(CP_ACP, 0, "Auto&LISP Example", -1, wstrMenuItemName, 256); MultiByteToWideChar(CP_ACP, 0, "(prin1 \"Hello\") ", -1, wstrMenuItemMacro, 256); VariantInit(&index); V_VT(&index) = VT_I4; V_I4(&index) = 2; pPopUpMenu->AddMenuItem(index, wstrMenuItemName, wstrMenuItemMacro, &pPopUpMenuItem); VariantInit(&index); V_VT(&index) = VT_I4; V_I4(&index) = numberOfMenus - 2; pPopUpMenu->InsertInMenuBar(index); pPopUpMenu->Release(); pPopUpMenuItem->Release(); bIsMenuLoaded = true; }else { acutPrintf("\nMenu not created."); } }else { VariantInit(&index); V_VT(&index) = VT_BSTR; V_BSTR(&index) = wstrMenuName; pPopUpMenus->RemoveMenuFromMenuBar(index); VariantClear(&index); bIsMenuLoaded = false; } pPopUpMenus->Release();
625
626
Chapter 25
if (!bIsMenuLoaded) { pPopUpMenus->Add(wstrMenuName, &pPopUpMenu); if (pPopUpMenu != NULL) { pPopUpMenu->put_Name(wstrMenuName); WCHAR wstrMenuItemName[256]; MultiByteToWideChar(CP_ACP, 0,"&Add A ComCircle", -1, wstrMenuItemName, 256); WCHAR wstrMenuItemMacro[256]; MultiByteToWideChar(CP_ACP, 0, "AsdkComCircle ", -1, wstrMenuItemMacro, 256); VariantInit(&index); V_VT(&index) = VT_I4; V_I4(&index) = 0; pPopUpMenu->AddMenuItem(index, wstrMenuItemName, wstrMenuItemMacro, &pPopUpMenuItem); VariantInit(&index); V_VT(&index) = VT_I4; V_I4(&index) = 1; pPopUpMenu->AddSeparator(index, &pPopUpMenuItem); MultiByteToWideChar(CP_ACP, 0, "Auto&LISP Example", -1, wstrMenuItemName, 256); MultiByteToWideChar(CP_ACP, 0, "(prin1 \"Hello\") ", -1, wstrMenuItemMacro, 256); VariantInit(&index); V_VT(&index) = VT_I4; V_I4(&index) = 2; pPopUpMenu->AddMenuItem(index, wstrMenuItemName, wstrMenuItemMacro, &pPopUpMenuItem); VariantInit(&index); V_VT(&index) = VT_I4; V_I4(&index) = numberOfMenus - 2;; pPopUpMenu->InsertInMenuBar(index); pPopUpMenu->Release(); pPopUpMenuItem->Release(); bIsMenuLoaded = true; } else { acutPrintf("\nMenu not created."); } } else { VariantInit(&index); V_VT(&index) = VT_BSTR; V_BSTR(&index) = wstrMenuName; pPopUpMenus->RemoveMenuFromMenuBar(index); VariantClear(&index); bIsMenuLoaded = false; } pPopUpMenus->Release(); }
627
Both of these examples can be found in the ObjectARX SDK. They are located in the docsamps\comsamps directory. Each sample contains code for adding a circle and a menu using either Win32 API or MFC programming techniques. Because these methods access AutoCAD through COM interfaces, these programming techniques can be used from other C++ contexts--not just from ObjectARX. Similar techniques can also be used in other languages, such as Visual Basic.
IAcadBaseObject
AcAxOleLinkManager
628
Chapter 25
The IUnknown link allows you to retrieve the existing IUnknown pointer of the COM object from an AcDbObject pointer, as shown in the following code:
AcAxOleLinkManager* pOleLinkManager = AcAxGetOleLinkManager(); // pObject is an AcDbObject* // IUnknown* pUnk = pOleLinkManager->GetIUnknown(pObject); // NOTE: AcAxOleLinkManager::GetIUnknown() does not AddRef() // the IUnknown pointer.
Conversely, you can retrieve the AcDbObjectId of the database-resident object being represented by a COM object from an interface pointer, as shown in the following code:
IAcadBaseObject* pAcadBaseObject = NULL; // pUnk is the IUnknown* of a COM object representing // some object in the database // HRESULT hr = pUnk->QueryInterface(IID_IAcadBaseObject, (LPVOID*) &pAcadBaseObject); AcDbObjectId objId; if(SUCCEEDED(hr)) { pAcadBaseObject->GetObjectId(&objId); }
IAcadBaseObject
IAcadBaseObject is the interface used to manage the link from a COM object
to a database-resident object. It is the COM objects responsibility to reset the link from the AcDbObject to the COM object when the COM object is being destroyed. This is usually done in the destructor of the COM class, using the AcAxOleLinkManager class discussed in the following section. Here is a description of the IAcadBaseObject interface:
interface DECLSPEC_UUID("5F3C54C0-49E1-11cf-93D5-0800099EB3B7") IAcadBaseObject : public IUnknown { // IUnknown methods // STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj) PURE; STDMETHOD_(ULONG, AddRef)(THIS) PURE; STDMETHOD_(ULONG, Release)(THIS) PURE;
629
// IAcadBaseObject methods // STDMETHOD(SetObjectId)(THIS_ AcDbObjectId& objId, AcDbObjectId ownerId = AcDbObjectId::kNull, TCHAR* keyName = NULL) PURE; STDMETHOD(GetObjectId)(THIS_ AcDbObjectId* objId) PURE; STDMETHOD(Clone)(THIS_ AcDbObjectId ownerId, LPUNKNOWN* pUnkClone) PURE; STDMETHOD(GetClassID)(THIS_ CLSID& clsid) PURE; STDMETHOD(NullObjectId)(THIS) PURE; STDMETHOD(OnModified)(THIS) PURE; };
SetObjectId() This method is used to identify which database-resident object the COM object represents. If the objId argument is equal to AcDbObjectId::kNull, the COM object is being instructed to create a new AcDbObject-derived object and append it to the database. The ownerId and keyName arguments are specified only in this situation. GetObjectId() This method is used to retrieve the AcDbObjectId of the database-resident object being represented. Clone() This method is reserved for future use. GetClassID() This method returns the CLSID of the COM object. NullObjectId() This method is used to tell the COM object that it is no longer representing a database-resident object. OnModified() This method tells the COM object that the AcDbObject it represents has been modified. This notification is explained in the section AcAxOleLinkManager on page 630. The COM object is then responsible for firing notification to all its clients through established connection points.
AcAxOleLinkManager
AcAxOleLinkManager is used to manage the link from the database-resident
object to its COM object. This is done by attaching a transient reactor to the
AcDbObject. The transient reactor has one variable containing a pointer to the IUnknown of the COM object. This transient reactor is also used to call IAcadBaseObject::OnModified() when the AcDbObject is modified.
630
Chapter 25
AutoCAD maintains a single instance of AcAxOleLinkManager in each AutoCAD session. To get a pointer to the OLE link manager, use the AcAxGetOleLinkManager() function. The AcAxOleLinkManager class, which is declared in the oleaprot.h file, is described below:
// AcAxOleLinkManager is used to maintain the link between ARX // objects and their respective COM wrapper. // class AcAxOleLinkManager { public: // Given a pointer to a database-resident object, return // the IUnknown of the COM wrapper. NULL is returned if // no wrapper is found. // virtual IUnknown* GetIUnknown(AcDbObject* pObject) = 0; // Set the link between a database-resident object and a // COM wrapper. If the IUnknown is NULL, then the link // is removed. // virtual Adesk::Boolean SetIUnknown(AcDbObject* pObject, IUnknown* pUnknown) = 0; // Given a pointer to a database object, return // the IUnknown of the COM wrapper. NULL is returned if // no wrapper is found. // virtual IUnknown* GetIUnknown(AcDbDatabase* pDatabase) = 0; // Set the link between a database object and a COM wrapper. // If the IUnknown is NULL, then the link is removed. // virtual Adesk::Boolean SetIUnknown(AcDbDatabase* pDatabase, IUnknown* pUnknown) = 0; // Given a pointer to a database object, return the // IDispatch of the document object. NULL is returned if // the database does not belong to a particular document. // virtual IDispatch* GetDocIDispatch(AcDbDatabase* pDatabase)= 0; // Set the link between a database object and the IDispatch // of the document it belongs to. If the IDispatch is NULL, then // the link is removed. // virtual Adesk::Boolean SetDocIDispatch(AcDbDatabase* pDatabase, IDispatch* pDispatch) = 0; };
631
This function is defined at the AcDbObject level. It is overridden for every other class that traces its derivation to AcDbObject and is represented by a different COM object type. Thus, if you create a custom entity derived from AcDbEntity, and do not override getClassID(), this function returns the CLSID for AcadEntity. This means your custom entities will have at least baselevel functionality, even if you do not provide COM support for your entity. To obtain the CLSID for a given AcDbObject-derived class name, AutoCAD searches the registry for an entry that correlates the class name to its CLSID value. The registry layout looks like this:
HKEY_LOCAL_MACHINE\SOFTWARE\Autodesk\ ObjectDBX\ ActiveXCLSID\ AcRxClassName\CLSID:REG_SZ: {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
In the example above, AcRxClassName represents the name of your AcDbObject-derived class (for instance, AcDbMyObject).
Using CoCreateInstance
At times you may need to create a COM object for a given AcDbObjectId or AcDbObject pointer. This can be done by using CoCreateInstance to create the object, and then calling AcAxOleLinkManager and IAcadBaseObject
632
Chapter 25
functions to set up the appropriate links. As an alternative, you can use the following exported global functions to do this work for you:
// Get the IUnknown of the existing COM object (or newly created COM // object if one does not exist) that represents the AcDbObject // passed in. // HRESULT AcAxGetIUnknownOfObject(LPUNKNOWN* ppUnk, AcDbObjectId& objId, LPDISPATCH pApp); HRESULT AcAxGetIUnknownOfObject(LPUNKNOWN* ppUnk, AcDbObject* pObj, LPDISPATCH pApp);
In addition to creating a new COM object, these functions establish two-way linking between the COM object and its AutoCAD counterpart.
Using only the AcDbObject-derived class name (for example, AcDbMyObject), these APIs create the COM object and then call its IAcadBaseObject::SetObjectId() implementation. SetObjectId() in turn calls your COM wrappers IAcadBaseObject::CreateNewObject() function. Your implementation of CreateNewObject() is expected to create an instance of the AcDbObject-derived class and add it to the database. See Adding a Custom Object or Entity to an Object Model on page 644 for an example of how to write CreateNewObject().
633
IAcadObjectEvents Source interface that notifies COM clients when the AcDbObject has been modified. IRetrieveApplication Used to tell the COM object what to return for the Application property. IAcadObject Exposes all common properties and methods that apply to every object in the database. IAcadEntity Exposes all common properties and methods that apply to every entity in the database. (Applicable only for AcDbEntity-derived classes.) The following interfaces are not AutoCAD specific, but are required for proper behavior: IDispatch Allows late binding. Browsers such as the Properties window require this interface. IConnectionPointContainer Contains a list of connection points. IConnectionPoint Allows COM clients to ask for notification. ISupportErrorInfo Informs COM clients that the object supports error info. If you are creating a COM class to represent an AcDbEntity- derived class, you will need to implement all of these interfaces. For an AcDbObject-derived class, you will need to implement all except IAcadEntity.
ATL Templates
If you use ATL along with ATL-based templates from AutoCAD to create your Automation objects, all the interfaces listed above will be implemented automatically. This frees you to concentrate on the specific properties and methods for your AcDbObject-derived class; everything else is implemented by either Autodesk or Microsoft.
634
Chapter 25
Autodesk provides the following ATL-based templates: ATL-based templates (declared in axtempl.h)
Template CProxy_AcadObjectEvents IAcadBaseObjectImpl IRetrieveApplicationImpl IAcadObjectDispatchImpl IAcadEntityDispatchImpl Implements IAcadObjectEvents, IConnectionPoint IAcadBaseObject, IConnectionPointContainer IRetrieveApplication IAcadObject, IDispatch IAcadEntity
By changing the derivation from the ATL IDispatchImpl template to IAcadEntityDispatchImpl or IAcadObjectDispatchImpl, you will have automatic implementation for all the required interfaces. The steps required to implement automation are covered in detail in Exposing Automation Functionality on page 639.
635
For example:
// Get a point in AutoCAD, even though the point is not used. // STDMETHODIMP CMyApp::GetPoint() { // Establishing a lock informs AutoCAD to reject any other // out-of-process Automation requests. If this call is // made from an unknown context (e.g., not a normal AutoCAD // registered command or lisp), then it also saves the // current AutoCAD state. // if (acedSetOLELock(5) != Adesk::kTrue) // arbitrary handle value { return E_FAIL; } // Do the input acquisition (interaction). // ads_point result; if(acedGetPoint(NULL, "Pick a point: ", result) != RTNORM) { acedClearOLELock(5); return E_FAIL; } // Clear the lock to allow out-of-process Automation // requests to be accepted again. If the AutoCAD state was saved // during the call to acedSetOLELock(), then the saved state is // restored. // acedClearOLELock(5); // use same handle used in acedSetOLELock() // Forces AutoCAD to redisplay the command prompt. // acedPostCommandPrompt(); return S_OK; }
Document Locking
Automation requests can occur in any AutoCAD context, coming from any number of client applications. To prevent conflicts with other requests, you are responsible for locking a document before modifying it. Failure to lock the document in certain contexts will cause a lock violation during modification of the database. At times you also will want to make a document current temporarily. For example, when adding an entity to *MODELSPACE or *PAPERSPACE, you need to both lock the document and make it current. Failure to make the
636
Chapter 25
document current will cause your entity to be invisible in the graphics display, even after a display regeneration. In the ObjectARX API, the document manager class provides locking and context-switching functionality. Because these are common tasks, the Automation API encapsulates this behavior in an exported class named AcAxDocLock. Instantiating an AcAxDocLock object, with appropriate arguments, both locks the document and makes it current. For example:
STDMETHODIMP CMyEntity::Modify() { AcAxDocLock docLock(m_objId, AcAxDocLock::kNormal); if(docLock.lockStatus() != Acad::eOk) { return E_FAIL; } // It is now safe to modify the database // return S_OK; }
the COM objects AcRxClass name (so that GetIUnknownOfObject can locate the class) the COM objects type library (so that when ATL calls LoadRegTypeLib, it will succeed) the application name as the server for the COM object with that particular CLSID (so that CoCreateInstance() will work correctly)
Below you will find information to help create a registry file (.reg), which identifies the minimum amount of information required for your COM server. REG files are highly useful for creating registry entries from install scripts.
637
You repeat the coclass and interface sections for every coclass and interface in your type library. The IDL file used to build the type library will contain all the uuids you need to fill in the blanks above. Following are commented excerpts from compoly.idl that identify each uuid.
[ // uuid of type lib. // uuid(45C7F028-CD9A-11D1-A2BD-080009DC639A), version(1.0), helpstring("compoly 1.0 Type Library") ] library COMPOLYLib { // ... Code cut out for brevity. // IComPolygon interface [ object, // uuid of interface // uuid(45C7F035-CD9A-11D1-A2BD-080009DC639A), dual, helpstring("IComPolygon Interface"), pointer_default(unique)
638
Chapter 25
] interface IComPolygon : IAcadEntity { // ... Code cut out for brevity. }; // ... Code cut out for brevity. // ComPolygon coclass [ // uuid of coclass // uuid(45C7F036-CD9A-11D1-A2BD-080009DC639A), helpstring("ComPolygon Class"), noncreatable ] coclass ComPolygon { [default] interface IComPolygon; [source] interface IAcadObjectEvents; }; };
For a project-specific example of a REG file, see Building and Registering a COM DLL on page 649.
639
To set up a project for a COM wrapper To add a COM object and interface to your project To configure your ATL project for use with AutoCAD Automation interfaces
The final procedure applies only if you wish to combine your ObjectARX application with your COM wrapper:
I
To set up a project for a COM wrapper 1 Make sure axauto15.dll, which should be in the same directory as acad.exe, is in your search path. 2 From the Microsoft Visual C++ File menu, choose New. 3 Select ATL COM AppWizard on the Projects tab and enter a project name. 4 Choose the DLL server type. Additional project settings are optional. 5 Choose Finish and OK. This first procedure sets up the basic COM framework for your wrapper application, including an IDL file and a DLL housing for the object. In the next procedure, you add a new interface. Microsoft Visual C++ creates a skeletal interface definition and its corresponding COM class. Later, you can add methods and properties to this interface. To add a COM object and interface to your project 1 From the Microsoft Visual C++ Insert menu or the ClassView context menu, choose New ATL Object. 2 Select a Simple Object in the Objects category and choose Next.
640
Chapter 25
3 Enter a C++ Short Name on the Names tab. The Wizard will supply defaults for the remaining names. You can modify the suggested names if you wish. 4 On the Attributes tab, select Support IErrorInfo. 5 Choose OK. The following procedure configures your project to work with AutoCAD Automation. To configure your ATL project for use with AutoCAD Automation interfaces 1 From the Project menu, choose Settings. 2 On the C/C++ tab, choose C++ Language from the Category drop-down list and select Enable exception handling. 3 On the Link tab, add axauto15.lib, oleaprot.lib, and any other referenced ObjectARX libraries. 4 Choose OK. Follow the next procedure only if you are combining your ObjectARX code with your COM wrapper. To incorporate an existing ObjectARX application into your ATL project 1 Add the CPP and H files from your ObjectARX application to your project. 2 Add project settings, as well as include and library paths, as appropriate for an ObjectARX application. 3 In your projects definitions (DEF) file, add the following lines to the EXPORTS section:
acrxEntryPoint acrxGetApiVersion PRIVATE PRIVATE
4 In your acrxEntryPoint function, add a call to DllRegisterServer in the kInitAppMsg case block, as shown below. This step is not necessary if you are certain your server is registered.
case AcRx::kInitAppMsg: //unlock the application acrxDynamicLinker->unlockApplication(pkt); acrxRegisterAppMDIAware(pkt); //register ourselves DllRegisterServer(); break;
641
5 If other initialization or cleanup occurs in your acrxEntryPoint() function, move this initialization to your DllMain.
To create an Automation wrapper project for an ObjectARX application To expose your functionality through ActiveX
To create an Automation wrapper project for an ObjectARX application 1 Set up your project according to the steps in Setting Up an ATL Project File on page 640. 2 In the generated COM class header file, add the following code:
#include "dbmain.h" #include "acad15.h" #include "axtempl.h" // main ActiveX Automation template // header file
3 Change the derivation of your COM class by adding the following declaration to the end of its derivation list:
public IRetrieveApplicationImpl
642
Chapter 25
5 In the IDL file, add the following code after importlib(stdole32.tlb) and importlib(stdole2.tlb):
importlib("c:\ACAD\acad.tlb"); // revise the path to match your // own AutoCAD installation
Be careful to substitute the correct path that matches your AutoCAD installation. Once the wrapper is set up, you need to provide interfaces to expose your functionality through ActiveX. To expose your functionality through ActiveX 1 Add the desired ActiveX methods and properties to your wrapper class by choosing Add Method or Add Property from the ClassView Interface shortcut menu. In your COM class CPP file, ATL generates stubs similar to the following:
STDMETHODIMP CAsdkSquareWrapper::get_Number(short *pVal) { } STDMETHODIMP CAsdkSquareWrapper::put_Number(short newVal) { }
Typically, these members will be used to wrap calls to your ObjectARX application. 2 Add implementation code to your new COM class methods and properties. The following example shows a sample property implementation:
STDMETHODIMP CAsdkSquareWrapper::get_Number(short *pVal) { // TODO: Add your implementation code here AcDbObjectPointer<AsdkSquare> pSq(m_objId, AcDb::kForRead); if (pSq.openStatus() != Acad::eOk) return E_ACCESSDENIED; int id; pSq->squareId(id); *pVal = id; return S_OK; }
643
STDMETHODIMP CAsdkSquareWrapper::put_Number(short newVal) { AcAxDocLock docLock(m_objId, AcAxDocLock::kNormal); if(docLock.lockStatus() != Acad::eOk) return E_ACCESSDENIED; AcDbObjectPointer<AsdkSquare> pSq(m_objId, AcDb::kForWrite); if (pSq.openStatus() != Acad::eOk) return E_ACCESSDENIED; pSq->setSquareId(newVal); return S_OK; }
Build and register the application according to the steps in Building and Registering a COM DLL on page 649.
To create an Automation wrapper project for a custom object or entity To adapt the ATL-generated code for ObjectARX compatibility To add ObjectARX custom object and custom entity support To adapt the IDL file for ObjectARX usage To prepare your ObjectARX project for ActiveX compatibility
To create an Automation wrapper project for a custom object or entity 1 Set up your project according to the steps in Setting Up an ATL Project File on page 640. 2 In stdafx.h, immediately after #include <atlcom.h>, include acad15.h, dbmain.h, and any other necessary ObjectARX header files. 3 In stdafx.cpp, add the following code at the end of the file:
#include acad15_i.c
The project you have just created contains a considerable amount of generated code. Some of this code must be extended for ObjectARX usage, as demonstrated in the next procedure.
644
Chapter 25
To adapt the ATL-generated code for ObjectARX compatibility 1 In the COM object header file, include axtempl.h (the main ActiveX Automation template header file) and the header file(s) for your custom objects or entities. 2 Change the derivation of the COM object or entity by removing the IDispatchImpl part of the derivation and replacing it with one of the following declarations:
// For a custom object. // public IAcadObjectDispatchImpl<CWrapperClass, &CLSID_WrapperClass,IWrapperClass, &IID_IWrapperClass,&LIBID_LIBRARYLib> // For a custom entity. // public IAcadEntityDispatchImpl<CWrapperClass, &CLSID_WrapperClass,IWrapperClass, &IID_IWrapperClass,&LIBID_LIBRARYLib>
You must implement a special function in your COM code to expose your custom objects and entities. The following procedure demonstrates how to provide this support. To add ObjectARX custom object and custom entity support 1 Add the following required override to the public declarations of your COM class:
// IAcadBaseObjectImpl // virtual HRESULT CreateNewObject( AcDbObjectId& objId, AcDbObjectID& ownerId, TCHAR* keyName);
This abstract function is a public member of the IAcadBaseObjectImpl class. Your COM wrapper class inherits IAcadBaseObjectImpl through either IAcadObjectDispatchImpl or IAcadEntityDispatchImpl. CreateNewObject must be overridden to allow you to add default objects to the database.
645
2 Implement the CreateNewObject() function and any other object- or entity-specific functions. The following example shows the implementation of CreateNewObject() from AsdkSquareWrapper:
HRESULT CAsdkSquareWrapper::CreateNewObject( AcDbObjectId& objId, AcDbObjectId& ownerId, TCHAR* keyName) { try { AcAxDocLock docLock(ownerId, AcAxDocLock::kNormal); Acad::ErrorStatus es; AcDbObjectPointer<AsdkSquare> pSq; if((es = pSq.create()) != Acad::eOk) throw es; AcDbDatabase* pDb = ownerId.database(); pSq->setDatabaseDefaults(pDb); AcDbBlockTableRecordPointer pBlockTableRecord(ownerId, AcDb::kForWrite); if((es = pBlockTableRecord.openStatus()) != Acad::eOk) throw es; if((es = pBlockTableRecord-> appendAcDbEntity(objId, pSq.object())) != Acad::eOk) throw es; } catch(const Acad::ErrorStatus) { //To become more sophisticated // return Error(L"Failed to create square", IID_IAsdkSquareWrapper, E_FAIL); } return S_OK; }
3 Add the desired ActiveX methods and properties to your wrapper class as described in To expose your functionality through ActiveX on page 643. In order for your objects automation interfaces to be defined correctly, you must revise the generated Interface Definition Language (IDL) file. To adapt the IDL file for ObjectARX usage 1 In the IDL file, change the interface derivation for your COM object from IDispatch to IAcadObject for a custom object, or IAcadEntity for a custom entity. Here is an example from the AsdkSquareLib.idl sample file:
interface IAsdkSquareWrapper : IAcadEntity
646
Chapter 25
2 Add the following code after importlib stdole32.tlb and importlib stdole2.tlb:
importlib("c:\ACAD\acad.tlb"); // revise the path to match your // own AutoCAD installation
Be careful to substitue the correct path that matches your AutoCAD installation. 3 In the section of the IDL file that corresponds to your wrapper coclass, add [source] interface IAcadObjectEvents; after the [default] line in order to support events. 4 Move the acad.tlb section to the top of the IDL file, and move your custom object code so that it is within that section. The IDL file will now appear similar to the following code:
import "oaidl.idl"; import "ocidl.idl"; [ uuid(800F70A1-6DE9-11D2-A7A6-0060B0872457), version(1.0), helpstring("AsdkSquareLib 1.0 Type Library") ] library ASDKSQUARELIBLib { importlib("stdole32.tlb"); importlib("stdole2.tlb"); importlib("v:\acad\acad2000\acad.tlb"); [ object, uuid(800F70AD-6DE9-11D2-A7A6-0060B0872457), dual, helpstring("IAsdkSquareWrapper Interface"), pointer_default(unique) ] interface IAsdkSquareWrapper : IAcadEntity { [propget, id(1), helpstring("property Number")] HRESULT Number([out, retval] short *pVal); [propput, id(1), helpstring("property Number")] HRESULT Number([in] short newVal); }; [ uuid(800F70AE-6DE9-11D2-A7A6-0060B0872457), helpstring("AsdkSquareWrapper Class") ] coclass AsdkSquareWrapper { [default] interface IAsdkSquareWrapper; [source] interface IAcadObjectEvents; }; };
647
NOTE The IDL file modifications will cause the compiler to issue a warning
stating that the interface does not conform. You can ignore this message. Next, make sure that your ObjectARX component implements the necessary overrides to support ActiveX Automation. If you are building separate COM and ObjectARX DLLs, switch to your ObjectARX project to perform the next procedure. The AutoCAD Properties window uses the getClassID() function to retrieve your custom entitys type information. See Object Property Manager API on page 651 for information on implementing Properties window functionality. If you do not override getClassID() as described below, the Properties window displays only base class information for your custom entity. However, if you override getClassID() but do not implement OPM interfaces for your custom entity, the Properties window cannot display either your own class information or the base class data. See Obtaining a CLSID for a Custom Class on page 632 for more information on getClassID(). To prepare your ObjectARX project for ActiveX compatibility 1 In every ObjectARX-based class being wrapped, override the getClassId() function:
Acad::ErrorStatus AcDbMyClass::getClassID(CLSID* pClsid) const { *pClsid = CLSID_WrapperClass; // replace CLSID_WrapperClass with // your COM class CLSID return Acad::eOk; }
2 In each file that contains an override for getClassId(), include the necessary COM header files, as well as the compiler-generated ID file:
#include <objbase.h> #include <initguid.h> #include "library_i.c" // // // // //
File containing definitions of the IIDs and CLSIDs for the COM project. library is replaced by your COM project name. This file resides in your COM project directory.
648
Chapter 25
3 Build and register the COM application according to the steps in Building and Registering a COM DLL on page 649. If you are building separate COM and ObjectARX or ObjectDBX DLLs, build the ObjectARX or ObjectDBX module before building the COM application.
To prepare a COM DLL that is separate from the ObjectARX application To build and register your component server
To prepare a COM DLL that is separate from the ObjectARX application 1 Build the COM DLL. 2 If any decorated names representing your ObjectARX classes appear in linker errors as unresolved external symbols, add these names to the Exports section of the ObjectARX DEF file. 3 If you changed your ObjectARX DEF file in step 2, rebuild the ObjectARX application. 4 In the COM DLL, choose Settings from the Project menu. 5 On the Link tab, choose Input. 6 Add the library for your ObjectARX application to the Object/library modules list, after rxapi.lib. The following example is from the AsdkSquareWrapper sample project:
..\..\..\..\lib\rxapi.lib ..\square\ReleaseEnt\square.lib etc.
7 Choose OK. 8 Build and register your COM DLL as described in the next procedure.
649
To build and register your component server 1 Build the COM application. A message may appear indicating that REGSRVR32 failed to load AutoCAD. To eliminate this message in the future, remove all custom build steps for registering a COM server from your COM project settings. 2 Create a REG file for your application with GUIDs for the object and the type library that match those in the IDL file. The following example is the REG file for AsdkSquareWrapper:
REGEDIT ; This .REG file may be used by your SETUP program. ; If a SETUP program is not available, the entries below will be ; registered in your InitInstance automatically with a call to ; CWinApp::RegisterShellFileTypes and ; COleObjectFactory::UpdateRegistryAll. HKEY_CLASSES_ROOT\TypeLib\{800F70A1-6DE9-11D2-A7A6-0060B0872457} HKEY_CLASSES_ROOT\TypeLib\{800F70A1-6DE9-11D2-A7A6-0060B0872457} \1.1 = AsdkSquareLib 1.0 Type Library HKEY_CLASSES_ROOT\TypeLib\{800F70A1-6DE9-11D2-A7A6-0060B0872457} \1.1\0\win32= E:\TEMP\square\AsdkSquareLib\Debug\AsdkSquareLib.dll HKEY_CLASSES_ROOT\TypeLib\{800F70A1-6DE9-11D2-A7A6-0060B0872457} \1.1\9\win32 = E:\TEMP\square\AsdkSquareLib\Debug\AsdkSquareLib.dll HKEY_CLASSES_ROOT\CLSID\{800F70AE-6DE9-11D2-A7A6-0060B0872457} = AsdkSquareWrapper Class HKEY_CLASSES_ROOT\CLSID\{800F70AE-6DE9-11D2-A7A6-0060B0872457} \InProcServer32 = E:\TEMP\square\AsdkSquareLib\Debug\AsdkSquareLib.dll HKEY_CLASSES_ROOT\Interface\{800F70AD-6DE9-11D2-A7A6-0060B0872457} = IAsdkSquareWrapper Interface HKEY_CLASSES_ROOT\Interface\{800F70AD-6DE9-11D2-A7A6-0060B0872457} \TypeLib = {E3D2C633-69C9-11D2-A7A2-0060B0872457} HKEY_CLASSES_ROOT\Interface\{800F70AD-6DE9-11D2-A7A6-0060B0872457} \ProxyStubClsid32 = {00020424-0000-0000-C000-000000000046} HKEY_LOCAL_MACHINE\SOFTWARE\Autodesk\ObjectDBX\ActiveXCLSID \AsdkPoly\CLSID = {800F70AE-6DE9-11D2-A7A6-0060B0872457}
See Creating a Registry File on page 637 for more information on REG files. 3 Run the REG file from Explorer.
650
Chapter 25
651
These COM object wrappers implement IDispatch as well as other interfaces. IDispatch is the COM interface the Properties window uses to get and set property data. It is also the native object representation in VB and VBA. To determine which properties are available for an object, the Properties window calls IDispatch::GetTypeInfo(), which all AutoCAD COM wrappers implement. This function returns the type information for the object (an object that implements ITypeInfo). ITypeInfo is a standard Microsoft interface that wraps a data structure describing the methods and properties available on that object. Collections of type information used by VB and VBA to define the ActiveX object model are called type libraries. The Properties window takes property information and, based on the type of property as defined in the IDL, constructs a property editor window appropriate for that type of property. For example, if the property type is numeric or textual, it constructs an edit box. If it is an enum, it creates a combo box with the enumerated value list. If it is a stock property such as color, layer, linetype, lineweight, or other built-in property, it constructs the standard drop-downs for those that are the same as for the Object Property toolbar (OPT). The static type information for each COM object is not the only source of property information for the Properties window. The Properties window also queries the object for a few other interfaces to control things such as property categorization, property value names for drop-down lists, and instancing dialog boxes for per-property editing (such as the ellipsis button dialog boxes). These will be described in detail later in this section, but will be referred to collectively as flavoring interfaces.
652
ICategorizeProperties Interface
This interface is used by the Properties window to categorize the properties shown in the control. It is optional, but is strongly recommended. If the object does not implement this interface, all properties are categorized under General. The Properties window does not support nesting of categories. The Properties window uses QueryInterface to access ICategorizeProperties when it is collecting property information. Typically this occurs when the user selects objects, causing the pickfirst set to change. If the QueryInterface succeeds, it calls MapPropertyToCategory for each property defined by the type information for the object. If the category (PROPCAT) returned is not one of the predefined values, it calls GetCategoryName to determine the propertys category. If you are interested in categorizing only by using the predefined values, you can return E_NOTIMPL from GetCategoryName. This requires that you know the DISPID for each of your properties. The Active Template Library (ATL) automatically assigns DISPID values to properties in the IDL files that define your interface. These are the numbers next to the id keyword in the property attribute list.
IPerPropertyBrowsing Interface
IPerPropertyBrowsing is a standard Microsoft interface. See the Microsoft
documentation for a detailed explanation. It is typically used by property inspectors such as the Properties window to display property pages for objects that provide them. IPerPropertyBrowsing serves two basic functions. The first is to associate a property page or an other dialog box with a particular property via an ellipsis button on the Properties window dialog box. The second is to support custom property drop-down lists in the Properties window control.
IOPMPropertyExtension Interface
IOPMPropertyExtension is a collection of additional flavoring functionality. GetDisplayName is used to replace the type information name of a property with a more readable alternative. Editable is used to make properties that
653
IOPMPropertyExpander Interface
The main purpose of this class is to allow one property to be broken out into several properties in the Properties window. For example, Automation has a property called StartPoint for AcadLine. This property gets or sets an array of doubles representing the start point of the line. This is somewhat cleaner and more efficient from an API point of view than having Automation properties called StartX, StartY, StartZ on AcadLine. However, the Properties window needs to display the properties in this expanded fashion. In addition to splitting one property into an array of properties, you can also group the elements in that array. For example, polyline vertices have one Automation property, Coordinates, which returns an array of doubles, with each successive pair representing the X,Y vertices of the 2D polyline. If you specify a grouping, the Properties window automatically creates a spinner control for the property, allowing the user to enumerate and change the values of the vertices. These methods are optional, because in most cases you can create separate properties in the IDL.
654
Chapter 25
4 Choose OK. 5 In the get_* function stub that the Wizard created for you, add the appropriate code to return the property value. Here is the get_SquareSize function from the AsdkSquareWrapper sample:
STDMETHODIMP CAsdkSquareWrapper::get_SquareSize(double *pVal) AcDbObjectPointer<AsdkSquare> pSq(m_objId, AcDb::kForRead); if (pSq.openStatus() != Acad::eOk) return E_ACCESSDENIED; double size; pSq->squareSideLength(size); *pVal = size; return S_OK; }
6 In the put_* stub that the Wizard created, add the appropriate code to set the property value. Here is the put_SquareSize() function from the AsdkSquareWrapper sample:
STDMETHODIMP CAsdkSquareWrapper::put_SquareSize(double newVal) { AcDbObjectPointer<AsdkSquare> pSq(m_objId, AcDb::kForWrite); if (pSq.openStatus() != Acad::eOk) return E_ACCESSDENIED; pSq->setSquareSideLength(newVal); return S_OK; }
7 Compile and build your application. To test your custom objects properties 1 In AutoCAD, load your application and execute the command to create your custom entity. 2 Make sure the Properties window is loaded by entering properties at the command line. 3 Select your custom object. You should see and be able to change the entity-common properties and the side length in the Properties window interface. If you are using the AsdkSquareWrapper sample, notice that SquareSize property displays under the General category.
655
To categorize properties You may not want all your properties to show up under the General category, so this next section demonstrates how to use the built-in categories. 1 Add properties for the square center and ID number by following the To add properties procedure. 2 In the COM class CPP file, change the derivation of the COM class to include IOPMPropertyExtensionImpl and IOPMPropertyExpander:
public IOPMPropertyExtensionImpl<CAsdkSquareWrapper>, public IOPMPropertyExpander
3 Add the following interfaces to the end of the COM interface map:
COM_INTERFACE_ENTRY(IOPMPropertyExtension) COM_INTERFACE_ENTRY(ICategorizeProperties) COM_INTERFACE_ENTRY(IPerPropertyBrowsing) COM_INTERFACE_ENTRY(IOPMPropertyExpander)
4 Add the declaration for the IOPMPropertyExtension interface to the public declarations of the COM class:
// IOPMPropertyExtension // BEGIN_OPMPROP_MAP() OPMPROP_ENTRY(0, 0x00000001, PROPCAT_Data, \ 0, 0, 0, "", 0, 1, IID_NULL, IID_NULL, "") OPMPROP_ENTRY(0, 0x00000003, PROPCAT_Geometry, \ 0, 0, 0, "", 0, 1, IID_NULL, IID_NULL, "") END_OPMPROP_MAP()
656
Chapter 25
STDMETHOD(SetElementValue)( /* [in] */ DISPID dispID, /* [in] */ DWORD dwCookie, /* [in] */ VARIANT VarIn) ; // Used for property expansion (currently variant types) // STDMETHOD(GetElementStrings)( /* [in] */ DISPID dispID, /* [out] */ OPMLPOLESTR __RPC_FAR *pCaStringsOut, /* [out] */ OPMDWORD __RPC_FAR *pCaCookiesOut) ; //Used for property expansion (currently variant types) // STDMETHOD(GetElementGrouping)( /* [in] */ DISPID dispID, /* [out] */ short *groupingNumber) ; // Used for property expansion (currently variant types) // STDMETHOD(GetGroupCount)( /* [in] */ DISPID dispID, /* [out] */ long *nGroupCnt) ; STDMETHOD(GetPredefinedStrings)( /* [in] */ DISPID dispID, /* [out] */ CALPOLESTR *pCaStringsOut, /* [out] */ CADWORD *pCaCookiesOut); STDMETHOD(GetPredefinedValue)( /* [in] */ DISPID dispID, /* [out] */ DWORD dwCookie, /* [out] */ VARIANT *pVarOut);
7 Add the implementation for the function in the CPP source file. These examples are for the AsdkSquare object:
STDMETHODIMP CAsdkSquareWrapper::GetElementValue( /* [in] */ DISPID dispID, /* [in] */ DWORD dwCookie, /* [out] */ VARIANT * pVarOut) { if (pVarOut == NULL) return E_POINTER; AcDbObjectPointer<AsdkSquare> pSq(m_objId, AcDb::kForRead); if (pSq.openStatus() != Acad::eOk) return E_ACCESSDENIED; if (dispID == 0x03) { AcGePoint3d acgePt; pSq->squareCenter(acgePt); AcAxPoint3d acaxPt(acgePt); ::VariantCopy(pVarOut,&CComVariant(acaxPt[dwCookie])); } return S_OK; }
657
STDMETHODIMP CAsdkSquareWrapper::SetElementValue( /* [in] */ DISPID dispID, /* [in] */ DWORD dwCookie, /* [in] */ VARIANT VarIn) { AcDbObjectPointer<AsdkSquare> pSq(m_objId, AcDb::kForRead); if (pSq.openStatus() != Acad::eOk) return E_ACCESSDENIED; if (dispID == 0x03) { AcGePoint3d acgePt; pSq->squareCenter(acgePt); AcAxPoint3d acaxPt(acgePt); acaxPt[dwCookie] = V_R8(&VarIn); pSq->upgradeOpen(); pSq->setSquareCenter(acaxPt); } return S_OK; } STDMETHODIMP CAsdkSquareWrapper::GetElementStrings( /* [in] */ DISPID dispID, /* [out] */ OPMLPOLESTR __RPC_FAR *pCaStringsOut, /* [out] */ OPMDWORD __RPC_FAR *pCaCookiesOut) { if (dispID == 0x03) { long size; size = 3; pCaStringsOut->pElems = (LPOLESTR *)::CoTaskMemAlloc(sizeof(LPOLESTR) * size); pCaCookiesOut->pElems = (DWORD *)::CoTaskMemAlloc(sizeof(DWORD) * size); for (long i=0;i<size;i++) pCaCookiesOut->pElems[i] = i; pCaStringsOut->cElems = size; pCaCookiesOut->cElems = size; pCaStringsOut->pElems[0] = ::SysAllocString(L"Center X"); pCaStringsOut->pElems[1] = ::SysAllocString(L"Center Y"); pCaStringsOut->pElems[2] = ::SysAllocString(L"Center Z"); } return S_OK; } STDMETHODIMP CAsdkSquareWrapper::GetElementGrouping( /* [in] */ DISPID dispID, /* [out] */ short *groupingNumber) { return E_NOTIMPL; }
658
Chapter 25
STDMETHODIMP CAsdkSquareWrapper::GetGroupCount( /* [in] */ DISPID dispID, /* [out] */ long *nGroupCnt) { return E_NOTIMPL; } STDMETHODIMP CAsdkSquareWrapper::GetPredefinedStrings( DISPID dispID, CALPOLESTR *pCaStringsOut, CADWORD *pCaCookiesOut) { return E_NOTIMPL; } STDMETHODIMP CAsdkSquareWrapper::GetPredefinedValue( DISPID dispID, DWORD dwCookie, VARIANT *pVarOut) { return E_NOTIMPL; }
This is handled internally via protocol extensions. Once you have the property manager for the AcRxClass you are interested in, you can add your property classes to it via IPropertyManager::AddProperty(). When the user picks an object of that class, the Properties window will get the property manager for that class, enumerate all the property classes, and interrogate those classes for their property information, which it will then display along with that objects static properties. Note that the IDynamicProperty class makes no assumptions about where the property data is stored. It simply requires the IDynamicProperty implementer to provide it when
659
GetCurrentValueData() is called. Similarly, when the user changes a dynamic property, the Properties window will call SetCurrentValueData()
with the new value, requiring the implementer to decide how to set that value. This leaves it up to you to decide how to make dynamic property data persistent. The Properties window uses IPropertyManager and IDynamicProperty not only for properties of objects, but also for displaying properties of the current space when no object is selected. For example, when no object is selected in the drawing, the Properties window needs to display properties relating to the UCS. Also, certain commands require the Properties window to display property information (such as the orbit commands). These situations require defining special property managers for these specific modes. Getting the property managers for the modes requires a slightly different mechanism than the procedure for getting the property managers for selectable objects. As mentioned earlier for properties of objects, there is a protocol extension for each class of object. This protocol extension object can be used by the developer to get the property manager and add its property classes. For modal situations, the developer can use a set of predefined protocol extensions on the database to retrieve the property manager for that modal situation.
IDynamicProperty
As mentioned earlier, you should implement an instance of this class for each property that you wish to add to entities of a particular class.
660
Chapter 25
661
662
Chapter 25
26
In this chapter
I AutoCAD DesignCenter API I Registry Requirements for an
AutoCADhas features that use the COM mechanism to query and modify objects. The AutoCAD DesignCenter(ADC) uses the COM mechanism to provide easily accessible drawing content. This section describes the COM interfaces that must be implemented by your application in order for it to participate and extend the AutoCAD DesignCenter.
AutoCAD DesignCenter
I Customizing AutoCAD
DesignCenter
663
IAcDcContentBrowser Interface
This interface is implemented in the AutoCAD DesignCenter framework and is used by the components to communicate get and set information. A pointer to this interface will be given to the components when their initialization method is called and the components are expected to cache this pointer to talk back to the framework. This interface is similar to the IShellBrowser interface of the Windows namespace extension.
IAcDcContentView Interface
This interface is implemented by the components and is used by the AutoCAD DesignCenter framework to obtain content information from the component. A component that has registered itself as a content provider to AutoCAD DesignCenter would be queried for this interface at the appropriate time and will be asked to initialize itself. Once initialized, functions in this interface will be called at various times to get or set information in AutoCAD DesignCenter. This interface is similar to the IShellView interface of the Windows namespace extension.
IAcDcContentFinderSite Interface
This interface is implemented in the AutoCAD DesignCenter framework and is used by the components to provide search results of a content type.
664
Chapter 26
IAcDcContentFinder Interface
This interface is implemented by the components and is used by the AutoCAD DesignCenter framework to obtain search information from the components. A component that has registered itself as a content provider to AutoCAD DesignCenter would be queried for this interface at the appropriate time and will be asked to initialize itself. Once initialized, functions in this interface will be called at various times to get information appropriate for Finder dialog in the AutoCAD DesignCenter.
IAcPostDrop Interface
This interface is implemented by components and is used at the time of a right-click drag and drop of content entities.
Applications Key
This key is for content providers who want to register themselves and participate in AutoCAD DesignCenter custom mode.
Application Name
All information AutoCAD DesignCenter needs from a content provider participating in its custom mode is stored under this key. The name of the key
665
would be the name of the application, as it appears in the custom mode. Under the Application Name key can be the following subkeys:
I
Extensions This contains a list of extensions that a content provider supports (such as .dwg). Each key under extensions represents one extension. The name of the key is the name of the extension.
Finder This key is optional. If an application wants to participate in the find functionality, then it is required to populate this key.
Extensions Key
This key is for content providers who want to register themselves and participate in AutoCAD DesignCenter desktop mode. These content providers handle only particular types of extensions, and they are not interested in participating in the custom mode setting of AutoCAD DesignCenter.
666
Chapter 26
Extension Name
All information AutoCAD DesignCenter needs from a content provider participating in its desktop mode is stored under this key. An extension key could have any number of subkeys. The name of the subkey represents the name of the file extensions that are shown in the AutoCAD DesignCenter. There are two types of extensions stored in the registry, content type and container type. Under the Extension Name key can be the following subkeys:
I
Content type If an extension key does not have any subkeys, it is considered to be a content type extension unless a content provider specifically sets the Container key to a value of one.
Container type If an extension key does have subkeys, it is considered to be a container type extension. Values stored at this level of the key are ignored. A container key could have any number of subkeys. Each subkey represents a type of content that the container can handle.
CLASSID Registration
Minimum registration required by the component under HKEY_CLASSES_ROOT is as follows:
[HKEY_CLASSES_ROOT\CLSID\{<App CLSID xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx>}\InProcServer32] @="<full path to application>\mycomponent.arx"
667
During the installation, it adds the appropriate entries to the registry. Call functions in IAcDcContentBrowser interface that are implemented by the AutoCAD DesignCenter framework. These are described in the following table:
AddNavigatorNode
GetDCFrameWindow
SetItemDescription
SetPaletteImageList
SetNavigatorImageList
SetPaletteMultiSelect
InsertPaletteColumn
668
Chapter 26
GetSelectedNavNodeText GetCurrentViewMode
SetPaletteSubItem
SortPaletteItems
Implement functions in IAcDcContentView interface in the component. These are described in the following table:
NavigatorNodeClick
NavigatorMouseUp
PaletteMouseUp
PaletteItemClick
669
PaletteItemDblClick
PaletteColumnClick
PaletteBeginDrag
ReleaseBrowser GetLargeImage
QueryContextMenu
InvokeCommand
670
Chapter 26
3 You will also need to add the appropriate settings to the project and export the following symbols in the definition file (.def):
acrxEntryPoint _SetacrxPtp acrxGetApiVersion
671
if(SUCCEEDED(hRes)) { // Get the AutoCAD Product key from the // registry into a CString. // CString csProdKey = acrxProductKey(); // Use CStrings to obtain the authorization // stamp from the registry. // CString csPath = "SOFTWARE\\Autodesk\\AutoCAD\\R15.0\\"; CString csStamp = csProdKey.Right(csProdKey.GetLength() - csPath.GetLength()); _TCHAR szRegKey[_MAX_PATH]; _tcscpy(szRegKey, csStamp); LPOLESTR pszId = T2OLE("AUTH"); // do a runtime swap of the registry key value. // p->AddReplacement(pszId, T2OLE(szRegKey)); _TCHAR szModule[_MAX_PATH]; GetModuleFileName(hInstance, szModule, _MAX_PATH); LPCOLESTR szType = OLESTR("REGISTRY"); LPOLESTR pszModule = T2OLE(szModule); // Pull the registry entries from the resource ID. // hRes = p->ResourceRegister(pszModule, IDR_REGISTRY1, szType); if(FAILED(hRes)) AfxMessageBox("Error registering the app info."); } }
2 Add a new ATL Object that will support the IAcDcContentView interface. In Visual C++, choose Insert, New ATL Object. In the dialog, pick Objects and choose Simple Object. Click Next and enter the name for the ATL Object. For this example, call it AsdkDcContent. Now choose the Attributes tab and click support ISupportErrorInfo. Click OK to create the object. 3 Add registry information to the resource section of the project. First create a new file called AsdkDesignCenterSamp.rgs. The following listing should be changed for your specific project, where the class ID (CLSID) should be copied from your IDL file. Use the CLSID that corresponds to the IAsdkDcContent interface. Since these are GUID values, they are different for each new project. Also for other projects, you will need to change the extensions sections and also add the name of your specific class. Again, this example uses AsdkDcContent.
672
Chapter 26
HKLM { NoRemove 'SOFTWARE' { NoRemove 'Autodesk' { NoRemove 'AutoCAD' { NoRemove 'R15.0' { NoRemove '%AUTH%' { NoRemove 'AutodeskApps' { NoRemove 'AcadDC' { NoRemove 'Extensions' { ForceRemove '.txt' { val CLSID = s '{<Your CLSID>}' IconIndex = d '0' } } NoRemove 'Applications' { ForceRemove 'AsdkDcContent' { 'Extensions' { .txt { val CLSID = s '{<Your CLSID>}' val IconIndex = d '0' } } CustomView = s 'Yes' } } } } } } } } } }
Save this file and in Visual C++ bring the ResourceView forward. Open the resources listing and expand the REGISTRY node. Right click and import the registry file. It should give the resource an ID of IDR_REGISTRY1, but if it does not, rename it so that it matches the call in registerAppInfo.
673
2 Open the AsdkDcContent.h header file and change the derivation for the new class to include CWindowImplBase and IAcDcContentView as follows:
class ATL_NO_VTABLE CAsdkDcContent : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CAsdkDcContent, &CLSID_AsdkDcContent>, public ISupportErrorInfo, public IDispatchImpl<IAsdkDcContent, &IID_IAsdkDcContent, &LIBID_ASDKDESIGNCENTERSAMPLib>, public CWindowImplBase, public IAcDcContentView {
3 Enter the objects interfaces into the COM map using the COM_INTERFACE_ENTRY macro:
BEGIN_COM_MAP(CAsdkDcContent) COM_INTERFACE_ENTRY(IAsdkDcContent) COM_INTERFACE_ENTRY(IDispatch) COM_INTERFACE_ENTRY(ISupportErrorInfo) COM_INTERFACE_ENTRY(IAcDcContentView) END_COM_MAP()
5 Add the following declarations for the IAcDcContentView interface and some utility methods:
public: void OpenAndDisplayTextFile(); void OpenAndInsertTextFile(); CString OpenAndReadTextFile(DWORD& length); STDMETHOD(Initialize)(/*[in]*/ VARIANT varBrowser); STDMETHOD(SetImageLists)(); STDMETHOD(NavigatorNodeExpanding)( /*[in]*/ VARIANT varhNode , /*[in]*/ BSTR bstrFullPath); STDMETHOD(NavigatorNodeCollapsing)( /*[in]*/ VARIANT varhNode , /*[in]*/ BSTR bstrFullPath);
674
Chapter 26
STDMETHOD(NavigatorNodeClick)( /*[in]*/ VARIANT varhNode , /*[in, string]*/ BSTR bstrFullPath); STDMETHOD(NavigatorMouseUp)( /*[in]*/ VARIANT varhNode , /*[in, string]*/ BSTR bstrFullPath , /*[in]*/ VARIANT varX , /*[in]*/ VARIANT varY); STDMETHOD(PaletteItemClick)(/*[in]*/ BSTR bstrItemText); STDMETHOD(PaletteItemDblClick)(/*[in]*/ BSTR bstrItemText); STDMETHOD(PaletteColumnClick)(/*[in]*/ VARIANT varIndex); STDMETHOD(PaletteMouseUp)( /*[in]*/ VARIANT varButton , /*[in]*/ VARIANT varItemTexts , /*[in]*/ VARIANT varX , /*[in]*/ VARIANT varY); STDMETHOD(PaletteMouseDown)( /*[in]*/ VARIANT varButton , /*[in]*/ BSTR bstrFullText , /*[in]*/ VARIANT varX , /*[in]*/ VARIANT varY); STDMETHOD(RenderPreviewWindow)( /*[in]*/ BSTR bstrFullText , /*[in]*/ VARIANT varhPreviewWindow); STDMETHOD(PreviewMouseUp)( /*[in]*/ VARIANT varButton , /*[in]*/ VARIANT varX , /*[in]*/ VARIANT varY); STDMETHOD(Refresh)(); STDMETHOD(PaletteBeginDrag)( /*[in]*/ VARIANT varItemTexts , /*[in]*/ VARIANT varX , /*[in]*/VARIANT varY); STDMETHOD(ReleaseBrowser)(); STDMETHOD(QueryContextMenu)( /*[in]*/ VARIANT varhMenu , /*[in]*/ VARIANT varIndex , /*[in]*/ VARIANT varCmdFirst , /*[in]*/ VARIANT varCmdLast , /*[in]*/ VARIANT varItemTexts); STDMETHOD(InvokeCommand)(/*[in]*/ VARIANT varMenuID); STDMETHOD(IsExpandable)( /* [string][in] */ BSTR bstrItemText , /* [retval][out] */ VARIANT __RPC_FAR *pvarIsExpandable); STDMETHOD(GetLargeImage)( /* [in] */ BSTR bstrFileName , /* [out][in] */ VARIANT __RPC_FAR *pvarhLargeImage); STDMETHOD(GetSmallImageListForContent)( /*[in]*/ BSTR bstrFileName , /*[out]*/ VARIANT *pvarhImageList);
675
STDMETHOD(GetLargeImageListForContent)( /*[in]*/ BSTR bstrFileName , /*[out]*/ VARIANT *pvarhImageList); private: char * m_strSelectedItemText; IAcDcContentBrowser* m_pBrowser;
6 Add the code to implement the methods just added. Note that many of these methods do nothing but are necessary to complete the interface. The example makes use of the single-click (PaletteItemClick) and double-click (PaletteItemDblClick) events.
Adesk::Boolean append(AcDbEntity* pEntity) { AcDbBlockTable *pBlockTable; AcApDocument* pDoc = acDocManager->curDocument(); Acad::ErrorStatus es = acDocManager->lockDocument(pDoc); if (es != Acad::eOk) { acedAlert("Failed to lock the document!"); return Adesk::kFalse; } AcDbDatabase* pDb = pDoc->database(); es = pDb->getBlockTable(pBlockTable, AcDb::kForRead); if (es != Acad::eOk) { acedAlert("Failed to get block table!"); return Adesk::kFalse; } AcDbBlockTableRecord *pBlockRec; es = pBlockTable->getAt(ACDB_MODEL_SPACE, pBlockRec, AcDb::kForWrite); if (es != Acad::eOk) { acedAlert("Failed to get block table record!"); pBlockTable->close(); return Adesk::kFalse; } es = pBlockRec->appendAcDbEntity(pEntity); if (es != Acad::eOk) { acedAlert("Failed to append entity!"); pBlockTable->close(); pBlockRec->close(); delete pEntity; return Adesk::kFalse; } pBlockRec->close(); pBlockTable->close(); acDocManager->unlockDocument(pDoc); return Adesk::kTrue; }
676
Chapter 26
STDMETHODIMP CAsdkDcContent::Initialize(VARIANT varBrowser) { m_pBrowser = (IAcDcContentBrowser*)varBrowser.punkVal; m_pBrowser->AddRef(); return S_OK; } STDMETHODIMP CAsdkDcContent::SetImageLists() { return S_OK; } STDMETHODIMP CAsdkDcContent::NavigatorNodeExpanding( VARIANT varhNode, BSTR bstrFullPath) { return S_OK; } STDMETHODIMP CAsdkDcContent::NavigatorNodeCollapsing( VARIANT varhNode, BSTR bstrFullPath) { return S_OK; } STDMETHODIMP CAsdkDcContent::NavigatorNodeClick( VARIANT varhNode, BSTR bstrFullPath) { return S_OK; } STDMETHODIMP CAsdkDcContent::NavigatorMouseUp( VARIANT varhNode, BSTR bstrFullPath, VARIANT varX, VARIANT varY) { return S_OK; } CString CAsdkDcContent::OpenAndReadTextFile(DWORD& length) { CFile fileText; fileText.Open(m_strSelectedItemText, CFile::modeRead); length = fileText.GetLength(); char *strBuff = new char[length]; fileText.Read(strBuff, length); fileText.Close(); CString cstrBuff(strBuff); delete strBuff; cstrBuff.SetAt(length, '\0'); cstrBuff.FreeExtra(); return cstrBuff; }
677
void CAsdkDcContent::OpenAndDisplayTextFile() { DWORD length; CString cstrBuff = OpenAndReadTextFile(length); BSTR bstrBuf = cstrBuff.AllocSysString(); m_pBrowser->SetItemDescription(bstrBuf); ::SysFreeString(bstrBuf); } STDMETHODIMP CAsdkDcContent::PaletteItemClick(BSTR bstrItemText) { USES_CONVERSION; m_strSelectedItemText = OLE2T(bstrItemText); OpenAndDisplayTextFile(); return S_OK; } void CAsdkDcContent::OpenAndInsertTextFile() { DWORD length; CString cstrBuff = OpenAndReadTextFile(length); cstrBuff.Replace("\015\012", "\\P"); struct resbuf resbufViewCtr; resbufViewCtr.restype = RT3DPOINT; acedGetVar("VIEWCTR", &resbufViewCtr); AcGePoint3d pt(resbufViewCtr.resval.rpoint[X], resbufViewCtr.resval.rpoint[Y], resbufViewCtr.resval.rpoint[Z]); AcDbMText *pMText = new AcDbMText(); pMText->setLocation(pt); pMText->setContents(cstrBuff); append(pMText); pMText->downgradeOpen(); pMText->draw(); pMText->close(); } STDMETHODIMP CAsdkDcContent::PaletteItemDblClick( BSTR bstrItemText) { USES_CONVERSION; m_strSelectedItemText = OLE2T(bstrItemText); OpenAndInsertTextFile(); return S_OK; } STDMETHODIMP CAsdkDcContent::PaletteColumnClick( VARIANT varIndex) { return S_OK; }
678
Chapter 26
STDMETHODIMP CAsdkDcContent::PaletteMouseUp( VARIANT varButton, VARIANT varItemTexts, VARIANT varX, VARIANT varY) { return S_OK; } STDMETHODIMP CAsdkDcContent::PaletteMouseDown( VARIANT varButton, BSTR bstrFullText, VARIANT varX, VARIANT varY) { return S_OK; } STDMETHODIMP CAsdkDcContent::RenderPreviewWindow( BSTR bstrFullText, VARIANT varhPreviewWindow) { return S_OK; } STDMETHODIMP CAsdkDcContent::PreviewMouseUp( VARIANT varButton, VARIANT varX, VARIANT varY) { return S_OK; } STDMETHODIMP CAsdkDcContent::Refresh() { return S_OK; } STDMETHODIMP CAsdkDcContent::PaletteBeginDrag( VARIANT varItemTexts, VARIANT varX, VARIANT varY) { return S_OK; } STDMETHODIMP CAsdkDcContent::ReleaseBrowser() { return S_OK; }
679
STDMETHODIMP CAsdkDcContent::QueryContextMenu( VARIANT varhMenu, VARIANT varIndex, VARIANT varCmdFirst, VARIANT varCmdLast, VARIANT varItemTexts) { return S_OK; } STDMETHODIMP CAsdkDcContent::InvokeCommand(VARIANT varMenuID) { return S_OK; } STDMETHODIMP CAsdkDcContent::IsExpandable( /* [string][in] */ BSTR bstrItemText, /* [retval][out] */ VARIANT __RPC_FAR *pvarIsExpandable) { pvarIsExpandable->iVal = TRUE; return S_OK; } STDMETHODIMP CAsdkDcContent::GetLargeImage( /* [in] */ BSTR bstrFileName, /* [out][in] */ VARIANT __RPC_FAR *pvarhLargeImage) { return E_NOTIMPL; } STDMETHODIMP CAsdkDcContent::GetSmallImageListForContent( BSTR bstrFileName, VARIANT *pvarhImageList) { return E_NOTIMPL; } STDMETHODIMP CAsdkDcContent::GetLargeImageListForContent( BSTR bstrFileName, VARIANT *pvarhImageList) { return E_NOTIMPL; }
680
Chapter 26
7 Include the appropriate header files in the sdtafx.h file. You will also need to add a definition to undefine _DEBUG, since the AutoCAD libraries are non-debug. Here is what the file should look like:
#if defined(_DEBUG) && !defined(ARX_DEBUG) #undef _DEBUG #define ARX_DEBUG #endif #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #define STRICT #ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0400 #endif #define _ATL_APARTMENT_THREADED #include <afxwin.h> #include <afxdisp.h> #include <atlbase.h> // You may derive a class from CComModule and use // it if you want to override something, but do not // change the name of _Module. // extern CComModule _Module; #include <atlcom.h> #include #include #include #include #include #include #include #include <atlwin.h> <adslib.h> <dbmain.h> <dbsymtb.h> <dbmtext.h> <acdocman.h> <aced.h> <rxregsvc.h>
681
682
27
In this chapter
I Overview of the eTransmit
The AutoCAD eTransmit feature packages together an open drawing and all its required files. The file collection (the transmittal set) is copied into a folder or an archive file along with a report (the report text) that explains how the transmittal set was created and how to install the files. Users can then post the transmittal set to the Web or send it as an email attachment. For more information about packaging a drawing file set for Internet transmission, see the AutoCAD Users Guide. The eTransmit APIs allow developers to hook into transmittal set operations and add their own files to the transmittal set.
Interfaces
I Extracting the eTransmit IDL
683
684
Chapter 27
methods. 2 Subscribe to Add File notifications by using the subscribeToAddFileNotification() method on the ITransmittalOperation interface. This should be done at the time your application is loaded so that it will be ready for any transmissions.
eTransmit Example
The following sample code shows an implementation of the
ITransmittalAddFileNotificationHandler class. // TransmittalAddFileNotificationHandler.h : Declaration of the // CTransmittalAddFileNotificationHandler class. #ifndef __TRANSMITTALADDFILENOTIFICATIONHANDLER_H_ #define __TRANSMITTALADDFILENOTIFICATIONHANDLER_H_ ///////////////////////////////////////////////////////////////// // CTransmittalAddFileNotificationHandler class ATL_NO_VTABLE CTransmittalAddFileNotificationHandler : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CTransmittalAddFileNotificationHandler, &CLSID_TransmittalAddFileNotificationHandler>, public IDispatchImpl<ITransmittalAddFileNotificationHandler, &IID_ITransmittalAddFileNotificationHandler, &LIBID_TRANSMITTALLib> { public: CTransmittalAddFileNotificationHandler() { } DECLARE_REGISTRY_RESOURCEID\ (IDR_TRANSMITTALADDFILENOTIFICATIONHANDLER) DECLARE_PROTECT_FINAL_CONSTRUCT() BEGIN_COM_MAP(CTransmittalAddFileNotificationHandler) COM_INTERFACE_ENTRY(ITransmittalAddFileNotificationHandler) COM_INTERFACE_ENTRY(IDispatch) END_COM_MAP()
685
// ITransmittalAddFileNotificationHandler public: STDMETHOD(addFileNotificationHandler) (/*[in]*/ITransmittalFile* pFile, /*[in]*/ITransmittalOperation* pTransmit); STDMETHOD(beginFilesGraphCreation) (/*[in]*/ITransmittalOperation* pTransmit); STDMETHOD(endFilesGraphCreation) (/*[in]*/ITransmittalOperation* pTransmit); private: int m_nReportStorageIndex; }; #endif //__TRANSMITTALADDFILENOTIFICATIONHANDLER_H_
//=============================================================== // TransmittalAddFileNotificationHandler.cpp : Implementation of // CTransmittalAddFileNotificationHandler class. #define _ATL_APARTMENT_THREADED #include <atlbase.h> // You may derive a class from CComModule and use it if you want // to override something, but do not change the name of _Module. extern CComModule _Module; #include <atlcom.h> ///////////////////////////////////////////////////////////////// // CTransmittalAddFileNotificationHandler
STDMETHODIMP CTransmittalAddFileNotificationHandler::addFileNotificationHandle( const ITransmittalFile *pFile, ITransmittalOperation *pTransmit) { // Use ITransmittalFile interface methods on the "pFile" passed in // to determine if you need to add files. If you don't need to // add anything, just return. if (I_DO_NOT_NEED_TO_ADD_ANYTHING() == true) return S_OK; else // Else if you need to add a file, use the addFile() method on the // ITransmittalOperation interface pointer pTransmit, which was // passed in. You can also use the addToReport() method to add // information or special instructions to the report file for the // transmittal. { CComBSTR bstrFullPath = FULL_PATH_TO_FILE_TO_ADD; AddFileReturnVal returnVal;
686
Chapter 27
// // // //
Note: By passing "pFile" as the third parameter, the file added will be a child of it. If you want to add a file at the top level of the files graph (that is, no parent file) pass NULL as the third parameter. pTransmit->addFile(bstrFullPath, NULL, pFile, /*BOOL bAddedBy3rdParty*/TRUE, &pIReturnFile, &returnVal);
// You can now check "returnVal" to see if your file was // added (that is, if it wasn't already in the graph) and // investigate its properties if you want. // If you want to add report text corresponding to the addition // of this file, you can do so now. if (returnVal==eFileAdded || returnVal==eRelationshipAdded) { // Note: If you want to add report text, your implementation // needs to keep track of the report index associated with // your app for this particular transmittal. (See "addToReport" // method notes in the ITransmittalOperation interface description // for more info.) You implement GetReportIndex() to retrieve // info you've stored about your index for this transmittal. long reportStorageIndex = GetReportIndex(pTransmit); if (m_nReportStorageIndex == -1) // You haven't added any text yet { CComBSTR bstrHeaderText = HEADER_TEXT_FOR_YOUR_APP; pTransmit->addToReport(bstrHeaderText, -1, &m_nReportStorageIndex); } if (returnVal == eFileAdded) { CComBSTR bstrReportText = WHATEVER_TEXT_YOU_WANT_TO_ADD_FOR_THIS_FILE; pTransmit->addToReport(bstrReportText, m_nReportStorageIndex, NULL); } } } return S_OK; } STDMETHODIMP CTransmittalAddFileNotificationHandler::beginFilesGraphCreation( ITransmittalOperation* pTransmit) { // Don't need to use the "pTransmit" input parameter for this // implementation.
687
// Reset m_nReportStorageIndex as this call means we have a new // transmittal beginning, and we need to get a new index. m_nReportStorageIndex = -1; return S_OK; } STDMETHODIMP CTransmittalAddFileNotificationHandler::endFilesGraphCreation( ITransmittalOperation* pTransmit) { // We don't need any special implementation for this // example. return S_OK; }
The following code shows subscribing to Add File notifications. This should be done at the time your application is loaded so that it will be ready for any transmissions. At application load time:
#include "Transmittal.h" // Create an instance of your // ITransmittalAddFileNotificationHandler implementation // and store the notification handler interface pointer somewhere // persistent, like in a class member. m_pIAddFileNotificationHandler = NULL; HRESULT hr = ::CoCreateInstance( CLSID_TransmittalAddFileNotificationHandler, NULL, CLSCTX_ALL, IID_ITransmittalAddFileNotificationHandler, (void**)&m_pIAddFileNotificationHandler); // Get an instance of the ITransmittalOperation interface so you // can use its methods. Again, store the notification handler // interface pointer somewhere persistent, like in a class member. m_pITransmitOperation = NULL; hr = ::CoCreateInstance(CLSID_TransmittalOperation, NULL, CLSCTX_ALL, IID_ITransmittalOperation, (void**)&m_pITransmitOperation); m_pITransmitOperation->subscribeToAddFileNotification( m_pIAddFileNotificationHandler);
688
Chapter 27
Part 6
ObjectARX Libraries
The ObjectDBX Libraries 691 The Graphics Interface Library 719 Using the Geometry Library 761 Using the Boundary Representation Library 787
689
690
28
In this chapter
I Overview of ObjectDBX I Using ObjectDBX I Differences between
ObjectDBX is the successor to DWG Unplugged, and this chapter describes the changes and enhancements that the ObjectDBX SDK provides, along with a description of how to implement applications using ObjectDBX.
Libraries
I Tips and Techniques I Known Limitations
691
Overview of ObjectDBX
ObjectDBX comprises a set of DLLs that can be used to implement custom objects contained in an AutoCAD drawing file and to implement applications that manipulate DWG files without the presence of AutoCAD. Part of this capability was formerly presented in the DWG Unplugged product, but the ObjectDBX SDK replaces and goes beyond the DWG Unplugged technology by providing the support necessary for intelligent object systems. The ObjectDBX SDK allows you to create non-AutoCAD host applications that can read and write DWG files.
Host Applications
A host application is one that contains a main(), WinMain(), or dllMain() function in its code, and provides the host services that an ObjectDBX or ObjectARX program needs. There are two types of host applications that can take advantage of the interface ObjectDBX provides. One type of host application is AutoCAD, with or without associated ObjectARX applications. The second type is a nonAutoCAD host application. A non-AutoCAD host application cannot load an ObjectARX application, and can only take advantage of the specific interfaces provided by the DLLs contained in ObjectDBX.
ObjectDBX Libraries
ObjectDBX libraries contain intelligence that enables custom objects (geometry, components, non-graphic objects, and so on) to operate as extensions, or custom objects, inside AutoCAD. The files that implement these objects are given the extension .dbx, which stands for DataBase eXtension. A DBX file is basically an ObjectARX application that has been written to work with the ObjectDBX libraries instead of with the ObjectARX (AutoCAD) libraries.
692
Chapter 28
handled properly without the ObjectARX application that provided the user interface being present. For example, suppose you implement a custom object called Sink, and that the code to display and modify Sink is in sink.dbx, while the code to prompt the user for Sink creation values is in sink.arx. Your user can load sink.arx from AutoCAD and use it (with sink.dbx, which will be loaded automatically) to create a custom sink in a drawing. Later, that drawing can be loaded by any other host application (including AutoCAD), and if the user has a copy of the sink.dbx file available, the Sink objects will display properly, instead of as proxies.
Using ObjectDBX
Developing applications with ObjectDBX is very similar to developing applications with ObjectARX. The C++ API found in the class hierarchy of ObjectARX is largely the same in ObjectDBX. Your principal task as a developer is to understand exactly what subset of ObjectARX is at your disposal.
C Runtime Libraries
The release DLLs for ObjectDBX are linked to the release versions of the Microsoft C Runtime library, MSVCRT.dll. Your application should also link with the release version and not the debug version. Mixing release and debug or static and DLL combinations of the C Runtime libraries may cause memory allocation or deallocation errors.
Multithreading
While the ObjectDBX DLLs support multiple processes on WindowsNT and Windows 95, they have not been made thread safe.
AcDbDatabase
Always instantiate an AcDbDatabase before using any AcDb functions. Refer to Always Instantiate an AcDbDatabase on page 711.
Using ObjectDBX
693
When linking host applications, be sure to link acdb15.lib first, rxapi.lib second, and any other libraries afterwards.
694
Chapter 28
I I
Those you must override, because no default implementation is provided as the method is assumed to be very application-specific. These are declared to be pure virtual. Those you may override, but that have a default implementation that will minimally satisfy the database code. These are declared virtual. Those you may not override, as they are expected to operate identically in all host applications. These are generally not declared virtual.
It is required that any ObjectDBX host application must provide a class derived from AcDbHostApplicationServices. This is different from the way DWG Unplugged worked, where a default service was provided. A detailed description of the class exists in the ObjectARX Reference, where each method is described with its default implementation (if it has one), what you must do to override the method successfully, and how to call the method. When your application is initializing, it should create an instance of your class derived from AcDbHostApplicationServices. Configure it as necessary and make the object available to the application by calling the global function acdbSetHostApplicationServices().
695
AcEditorReactor Class
The following AcEditorReactor notifications are valid in ObjectDBX:
I I I I I I I I I I I I I I I I I
dwgFileOpened databaseToBeDestroyed saveComplete beginInsert otherInsert abortInsert endInsert wblockNotice beginWblock otherWblock endWblock beginDeepClone beginDeepCloneXlation abortDeepClone endDeepClone sysVarChanged sysVarWillChange
AcGi API
The intended use of the AcGi layer is described in AcGi on page 699. The AutoCAD-specific implementation of its AcGi layer is not part of ObjectDBX. Instead, ObjectDBX provides its own graphics interface for displaying AutoCAD entities.
696
Chapter 28
The acdb.xmx file is now named acdbLLL.xmx, where LLL is the three-letter language-localization abbreviation, which can be derived from the LCID. Autodesk supports, and will eventually ship or otherwise provide, acdbLLL.xmx files in the following languages. XMX file types
Language English (USA) Chinese (Taiwan) Chinese (Simplified) Czech French (Default) German (Default) Greek Hungarian Italian Japanese Korean Polish Portuguese (Brazilian) Portuguese (Default) Russian (Default) Spanish (Default) Language Abbreviation ENU CHT CHS CSY FRA DEU ELL HUN ITA JPN KOR PLK PTB PTG RUS ESP Language ID from LCID 0409 0404 0804 0405 040c 0407 0408 040e 0410 0411 0412 0415 0416 0816 0419 040a
697
You must ship the appropriate acdbLLL.xmx files along with your product. You must inform ObjectDBX which acdbLLL.xmx file to load, by passing the appropriate LCID to acdbValidateSetup().
If the LCID does not correspond to one of the three-letter abbreviations above, or if the appropriate XMX file was not shipped, your ObjectDBX application will fail to load properly. If it is unable to find the desired acdb.xmx file, acdbValidateSetup() will attempt to load English as a default. Again, it will first use findFile(), and next assume the same path as AcDb15.dll. If it finds English, but English was not the requested language, Acad::eFileNotFound is returned. If the function is unable to find any acdb.xmx file, it will halt with fatalError(), and your application will not load.
Transaction Management
Transaction handling is now part of ObjectDBX instead of AutoCAD, and the corresponding library is acdb.dll instead of the AutoCAD executable. There is one new class, AcDbTransactionManager, as part of this change.
698
Chapter 28
Creating a Viewer
ObjectDBX includes components that can be used to develop a viewer for geometric models stored in an AutoCAD database. These components work together to form a complete viewing library but may be used or replaced independently by developers. The components interact with AcDb models through the AcGi API, which is the same interface that the AutoCAD graphics system uses to interact with AcDb.
Viewer Components
ObjectDBX provides three distinct tools that work together to implement a viewer:
I I I
The AcGix elaboration library The SimpleView sample vector taker using HDC The WhipView display list vector taker
The sample ObjectDBX application ViewAcDb demonstrates the use of these components. It is supplied in binary form and in full source form with a Microsoft Visual C++ project file. Although it is not mandatory that an application make use of any of these components, it is assumed that most applications will want to use the AcGix library and that a substantial number of them will want to use or adapt the SimpleView code. The WhipView library uses Autodesks proprietary display list technology to provide a level of regen-free panning and zooming.
AcGi
The AcGi API is the interface between AcDb and rendering systems used to display AcDb models. This interface is used by the AcDbEntity member functions worldDraw(), viewportDraw(), and saveAs(), which are part of the standard entity protocol. One method of producing an application capable of basic viewing is to implement fully the AcGi API. Derive your own implementation classes from the AcGi classes, such as AcGiWorldDraw and AcGiWorldGeometry. To draw a given entity, call its worldDraw() function, then pass in a pointer to an instance of your AcGiWorldDraw-derived class. You will then receive callbacks into the various members of your class. The member functions are graphics primitives such as the circle() and polyline() functions. They will be passed all the necessary parameters needed to draw them. AcGi must be implemented by the host application wishing to use specific graphic
Creating a Viewer
699
rendering logic defined by entities. The advantage of using AcGi is that the host application need not know anything about how an entity is intended to be rendered beyond a fixed set of geometric primitives and graphical traits, such as color, linetype, and text font. AutoCAD has its own internal implementation of AcGi, while the AcGix library supplied with ObjectDBX breaks down much of the complex rendering logic specified by AcGi into a relatively simple set of graphics primitives. Some methods of AcGiWorldDraw are for query purposes (deviation() and numberOfIsolines()) and may be used by an entity to determine the extent to which various entities will be tessellated; in other words, how dense the lines making up a sphere (for example) would be. The AcGiWorldDraw::regenType() method can be used to tell AcGi whether the regen request is for wireframes or faces with normals. For example, this is from the acgi.h file:
// These are the current kinds of viewport regeneration modes. // This mode cannot be set by the user, but it can be queried // in case you need to take different actions for different // regeneration modes. // typedef enum { eAcGiRegenTypeInvalid = 0, kAcGiStandardDisplay = 2, kAcGiHideOrShadeCommand, kAcGiRenderCommand, kAcGiSaveWorldDrawForR12 } AcGiRegenType;
NOTE For examples of using the AcGi interface, see the sample module in
samples/common/myacgi.*.
AcGix
This library is an engine that breaks up AcGi-defined geometry and traits into a small, simple set of graphics primitives defined by the protocol of the class AcGixVectorTaker. It operates on a registered set of viewports for which the application must provide implementations of AcGixVectorTaker and AcGiViewport. AcGix queries the supplied AcGiViewport for regeneration parameters and translates the AcGi primitives it receives from entities into calls to the supplied vectortaker. AcGix does not make any interpretation of how the application-supplied viewports are actually displayed. This is up to the implementor of AcGixVectorTaker.
700
Chapter 28
To use AcGix with a custom graphics system rather than using SimpleView, you must supply your own implementations of the AcGixVectorTaker and AcGiViewport classes. The actual instances of viewport and vectortaker can be shared between multiple viewports if this makes sense for your application. It is assumed that the vectortaker implementation will perform the requisite clipping of primitives against the viewport extents. The AcGix library is supplied in binary form with a set of API header files. The following header files contain the source definition of the AcGix API:
I I I I I I I
AcGix clients link with release/AcGix.lib, which binds the application to release/AcGix.dll.
Explicit draw order Perspective views Viewport front/back clipping Lineweight Plot styles
Creating a Viewer
701
The coordinates supplied to textMsg() are in current model coordinates. These can be converted to the World Coordinate System (WCS) using the supplied transform. The task of representing TrueType fonts in 3D space is complex, and this exercise is recommended only for those looking to achieve complete AutoCAD display compatibility. One approach may be only to process this message if the text can be represented in plan view for your viewport, and to otherwise use the default processing. The textMsg() method is defined in AcGixVectorTaker.h:
virtual Adesk::Boolean textMsg( Adesk::Int16 nViewportId, const TextPacket * pPacket) = 0;
The TextPacket structure contains information about the text plus the transformation matrix used to convert from the current model to WCS.
struct TextPacket { TextPacket( const TextInfo* pInfo, int nColor, const AcGeMatrix3d& xModel); int m_nColor; const TextInfo* m_pInfo; const AcGeMatrix3d& m_xCurrentModelToWorld; };
The TextInfo structure contains all the information about the text:
struct TextInfo { AcGePoint3d m_Position; AcGeVector3d m_Normal; AcGeVector3d m_Direction; double m_Height; double m_Width; double m_Oblique; const char* m_pMsg; Adesk::Int32 m_Length; Adesk::Boolean m_Raw; double m_Thickness; const AcGiTextStyle * m_pTextStyle; };
SimpleView
SimpleView is a sample vectortaker. It implements a simple viewport manager and supplies AcGixSimpleView. AcGixSimpleView aggregates implementations of AcGiViewport and AcGixVectorTaker into a single object. This
702
Chapter 28
implementation uses the WindowsGDI to display the results of a regen on the screen. The full implementation of SimpleView is supplied in source form. It can be modified by developers who wish to define a view management system that fits their applications needs. SimpleView is intended to demonstrate what is required to manage a viewport layout and to work with AcGix to form a complete viewer tool and to serve as a starting point for such an implementation. SimpleView clients directly link with the library release/AcGixSimpleView.lib. The source and the corresponding Microsoft Visual C++ project file are supplied in the directory samples/AcGixSimpleView. AcGixBlockView provides a base class for different types of views to be managed by the SimpleView manager. This allows both SimpleView and WhipView to be controlled polymorphically by the same manager.
Creating a Viewer
703
WhipView
The WhipView library implements AcGixView and AcGixVectorTaker on top of the WHIP!graphics accelerator. WHIP! is a graphics accelerator with a 2D image cache built on top of Autodesks HEIDItechnology. It exports a single API function, acgixAllocateWhipView(), which creates and returns an instance of AcGixBlockView. The returned instance can be used in the same way as any other AcGixBlockView. The SimpleView library demonstrates the creation of drawing views using WhipView. Because of the display list, WhipView is able to service some actions like pan and zoom without the need for a regen. This gives better performance than SimpleViews direct GDI implementation. WhipView is supplied in binary form only, and consists of several DLLs and support files representing the WhipView library, the WHIP! component, HEIDI, and HDI device drivers. WhipView can be used independent of SimpleView, except for the requisite elements of AcGixBlockView. Direct use of the WHIP!, HEIDI, and HDI drivers by ObjectDBX developers is not supported. They are supplied in binary form only, with no associated headers. The API of the WhipView module consists of a single entry point that has the following signature:
AcGixBlockView* acgixAllocateWhipView();
This function is explicitly declared external and used in the SimpleView source module AcGixSimpleViewManager.cpp. There is no exported header file that declares acgixAllocateWhipView(). To reuse this element, you need to take AcGixBlockView as defined and as much else of the SimpleView complex that is needed. Be warned, however, that AcGixBlockView is rather complicated and uses much of the rest of SimpleView. The implementation is most easily done if you leave SimpleView in an essentially unaltered state. WhipView clients link directly with AcGixWhipView.lib. WhipView requires AcDb.dll, heidi3.dll, dllong3.dll, and the HDI files supplied in the release directory.
ViewAcDb
ViewAcDb is a Multiple Document Interface (MDI) drawing file viewer that uses SimpleView and WhipView for displaying views of DWG files. ViewAcDb is essentially a test harness for the AcDb.dll, AcGix.dll, and the view implementations.
704
Chapter 28
Configuration Suggestions
An ObjectDBX client application can choose one of the following methods to display the contents of a DWG file:
I
Adapt the supplied SimpleView library source for use by the host applications, and use all three components. Study and adapt any code from the ViewAcDb sample application as needed. This is probably the best way to get familiar with the usage of AcGix and of the SimpleView AcGix
Creating a Viewer
705
platform implementation. However, you are free to modify the SimpleView source as desired. Build a module to drive a desired graphics system through the AcGix library. This allows much of the existing elaboration logic to be reused and still offers considerable flexibility in actual graphics presentation and performance. This would involve using the AcGix module but writing the implementations of AcGiViewport and AcGixVectorTaker from scratch, in effect replacing the SimpleView and/or WhipView libraries entirely. Use the WhipView subsystem to retain elements of SimpleView needed to support the definition of AcGixBlockView. This includes the supplied combination of WHIP! and HEIDI DLLs. Direct use of the WHIP! and HEIDI components is not supported in this release. Write an entire custom implementation of the AcGi interface and do not use any of the supplied AcGix, SimpleView, or WhipView components. You can attain maximum performance this way with a maximum amount of development work.
Demand Loading
The demand loading mechanism is essentially the same for an ObjectDBX application as it is for an ObjectARX application. The only difference is in where the information is found in the registry. For demand loading ObjectARX applications, AutoCAD looks in the system registry under the following:
HKEY_LOCAL_MACHINE Software Autodesk AutoCAD R15.0 ACAD-xxxxxxx-xxxxxxxx Applications xxxxxxx-xxxxxxxx is a number unique to each installation.
For demand loading DBX applications, ObjectDBX will look in the system registry under the following:
HKEY_LOCAL_MACHINE Software Autodesk ObjectDBX R15.0 Applications
These are hardcoded keys and are the same for any application on any machine. Your application information goes under the Applications entry.
706
Chapter 28
Use COMMONFILES
Do not assume drive letters and paths when determining where to install the Autodesk Shared files. The InstallShield system constant COMMONFILES will return a path name that points to something like c:\Program Files\Common Files. You must then append the Autodesk Shared name. This can be done in a .rul script with the following line:
szSharedPath = COMMONFILES ^ "Autodesk Shared";
All files that you are redistributing from the ObjectDBX\release directory of your SDK installation should be treated as Autodesk Shared files for installation purposes.
707
The following InstallShield script is an example that installs the acge15.dll using the version and shared file mechanism.
TARGETDIR = COMMONFILES ^ "Autodesk Shared"; nReturn = XCopyFile ( "acge15.dll", "acge15.dll", COMP_UPDATE_SAME | COMP_UPDATE_VERSION | SHAREDFILE); if (nReturn < 0 ) then // Report failure endif;
Windows NT
You should also update the System Path in the registry. This can be found under HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment. The Path value stored there should be modified to include your path. Note that this string is a REGDB_STRING_EXPAND value type.
708
Chapter 28
The path is already there. You are affecting other path settings in the batch file.
When updating the PATH value, no matter which operating system you are dealing with, your installer should prompt the user to reboot so that the path change is properly recorded after installation is complete. Autodesk provides the InstallShield script below as an incomplete example of smart path updating:
function AdUpdateAUTOEXEC (szSharedPath) STRING szRootPath, szBatchName, szBatchFile, szBackupName,szTestLine,szCheckForPathLine; NUMBER nReturn, nvHandle; STRING szOutput; begin szOutput = "SET PATH=%PATH%;" + szSharedPath; // Obtain the filename of the system batch file. BatchGetFileName (szBatchFile); ParsePath(szRootPath, szBatchFile, PATH); // Make sure we're pointing at the root of the system VarSave(SRCTARGETDIR); TARGETDIR = szRootPath; SRCDIR = szRootPath; // See if we have an AUTOEXEC. if (Is(FILE_EXISTS,szBatchFile) = FALSE) then // If we don't, just write 'ours' out and no more OpenFileMode (FILE_MODE_NORMAL); ParsePath(szBatchFile,szBatchFile,FILENAME); CreateFile (nvHandle,SRCDIR,szBatchFile); WriteLine (nvHandle, szOutput); CloseFile (nvHandle); bNeedReboot = TRUE; else ParsePath(szBatchName, szBatchFile, FILENAME_ONLY); szBackupName = szBatchName + ".ADK"; ParsePath(szBatchFile, szBatchFile, FILENAME); OpenFileMode(FILE_MODE_NORMAL); nReturn = OpenFile (nvHandle, SRCDIR, szBatchFile); if (nReturn = 0 ) then
709
// Spin down to find the non-blank last line in // the file while (nReturn = 0 ) nReturn = GetLine(nvHandle, szTestLine); if (StrLength(szTestLine) > 0) then szCheckForPathLine = szTestLine; endif; endwhile; CloseFile(nvHandle); // We now have the last text entry in the batch // file. if (StrFind(szCheckForPathLine,szSharedPath) < 0) then Disable(LOGGING); // Backup up the original nReturn = CopyFile (szBatchFile, szBackupName); Enable(LOGGING); if (nReturn = 0 ) then OpenFileMode (FILE_MODE_APPEND); OpenFile(nvHandle,SRCDIR,szBatchFile); WriteLine(nvHandle,""); WriteLine(nvHandle,szOutput); CloseFile(nvHandle); bNeedReboot = TRUE; endif; endif; endif; endif; VarRestore(SRCTARGETDIR); end;
ACAD_OBJID_INLINE_INTERNAL
The header files acdb.h and dbidar.h contain an #ifdef section that selects a header to #include based on the ACAD_OBJID_INLINE_INTERNAL macro. Applications should never #define this value. This #define is intended for Autodesk internal use only. Applications that include a #define ACAD_OBJID_INLINE_INTERNAL macro will not compile successfully.
710
Chapter 28
AcDbDatabase Notes
ObjectDBX allows you to have several instances of the AcDbDatabase class, though you must be sure to delete them all before your application exits. It is also important that you always have one instance of AcDbDatabase that is the current database. These requirements are described in the following sections.
If you intend to read a drawing file into the database, use the AcDbDatabase constructor with the Adesk::kFalse argument, immediately followed by a call to the readDwgFile() function. This version of the constructor creates a completely empty database that relies on the subsequent call to readDwgFile() to fill out its internal data structures. Using these in combination improves efficiency when reading a DWG file over using the other form of the constructor because the tables and globals need only be initialized once by readDwgFile(). Here is an example of reading a drawing into a previously instantiated database:
AcDbDatabase *pDb = new AcDbDatabase(Adesk::kFalse); pDb->readDwgFile(filename);
711
In both ObjectARX and ObjectDBX, calling readDwgFile() after using the Adesk::kTrue form of the constructor is certain to cause failure if the version of the DWG file you are reading is Release 12 or earlier. This is due to an inconsistency in the way drawings are loaded prior to AutoCAD Release 13. Because you cannot predict which drawings your end-users will open, do not code the following:
// Do not do this. AcDbDatabase *pDb = new AcDbDatabase(Adesk::kTrue); pDb->readDwgFile(filename);
pointer to be set to database first. The code then reads in another database pointed to by pDbSecond (call this database second), which now causes the internal current database pointer to be set to database second. The code next inserts database second into database first and deletes database second. When database second is deleted, the internal current database pointer is pointing to it, so the internal pointer is set to NULL. This means that after the database deletion, AcDb has a NULL current drawing pointer. This leads to fatal errors if any code that references the internal current database pointer is accessed. To prevent this, after the deletion of database second in your applications code, the code needs to call the host services setWorkingDatabase() method, passing in a pointer to database first to reestablish database first as the current database for ObjectDBX, as follows:
// Make "first" the current database. AcDbDatabase *pDbFirst = new AcDbDatabase(Adesk::kFalse); pDbFirst->readDwgFile("first.dwg");
712
Chapter 28
// Now make "second" be the current database. AcDbDatabase *pDbSecond = new AcDbDatabase(Adesk::kFalse); pDbSecond->readDwgFile("second.dwg"); // Insert "second" into "first" as ABLOCK. Acad::ErrorStatus es; AcDbObjectId blockId; es = pDbFirst->insert(blockId, "ABLOCK", pDbSecond); // Deleting "second" makes the current database NULL. delete pDbSecond; // Make the current database "first" again. myHostServices->setWorkingDatabase(pDbFirst);
AcDbDatabase::insert()
When inserting one database into another, the order of destruction is critical. When using this function, the database executing the insert is the To database, and the database used as an argument is the From database. Always destroy the From database first, and the To database last; otherwise you will cause a fatal error in the ObjectDBX DLL.
713
your application and geometry. To set the model space viewport, insert the following:
// Set some viewport information. AcDbViewportTable* pViewportTable; if (db.getViewportTable(pViewportTable, AcDb::kForRead) == Acad::eOk) { // Find the first viewport and open it for write. AcDbViewportTableRecord *pRecord; if (pViewportTable->getAt( "*ACTIVE", pRecord, AcDb::kForWrite) == Acad::eOk) { pRecord->setCenterPoint(AcGePoint2d(0.5, 0.5)); pRecord->setHeight(1.0); pRecord->setWidth(1.0); pRecord->close(); } pViewportTable->close(); }
714
Chapter 28
perfectly valid to add entities to paper space without ever creating a paper space viewport. This is because AutoCAD can successfully open a drawing of this nature, and will automatically create the main paper space viewport the first time the TILEMODE 0 command is used. AutoCAD should correctly display the entities in the paper space block table record within the main paper space viewport at that time. In ObjectDBX, the AcDbViewport::number() function will always return 1. In ObjectARX, it reports the viewport number of the current viewport in the AutoCAD editor. Because AutoCAD is not present in ObjectDBX, this value has no meaning. ObjectDBX does provide the acdbGetCurVportId() function, which returns the current object ID of the viewport when the drawing was saved. It is highly recommended that you review the ObjectARX SDK documentation regarding viewports of all types.
715
EED is added to an AcDbEntity as a resbuf chain. When using resbuf types that require pointers (like resval.rstring), be sure to allocate the pointer with the acdbAlloc() function, and delete it with the acdbFree() function (declared in the dbmain.h file).
716
Chapter 28
Raster Images
If you wish to write an ObjectDBX application that manipulates raster entities, you must first link to the Imaging Support Module (ISM) DBX, then have your application explicitly load that DBX file before attempting to call any of the raster APIs. For example, if you were using the AutoCAD LT level of ACIS support, include this call:
AcRxDynamicLinker->loadModule( "acIsmobj.dbx" );
If you read a DWG file that contains raster entities, ObjectDBX will attempt to load acIsmObj.dbx upon encountering an AcDbRasterImage entity in the drawing file. ObjectDBX will only search the saved path for the image file. This differs from AutoCAD, which in addition searches the AutoCAD search path. Remember that the Image Engine readers (ie*rd.dll, in this case) must be in the same directory as the ism*.dbx files. It is not sufficient for these files to be on the search path; instead they should be in the same location as the ism*.dbx. This behavior matches that of AutoCAD.
Known Limitations
Please review the Tips section for the AcDbDimension class in the ObjectARX Reference, which documents the behavior of AcDbDimension entities in an external database. For the purposes of modifying or creating new AcDbDimension entities by the API, every ObjectDBX database behaves as an external database. Thus, a newly created or modified AcDbDimension object will have its dimBlockId set to NULL. Calling the acdbMakeDatabaseCurrent() function is not sufficient to change the behavior documented in the ObjectARX Readme. This does not prevent the creation of a valid drawing, as AutoCAD is capable of generating the correct dimBlockID for an AcDbDimension at regen time.
717
718
29
In this chapter
I AcGi Overview I Setting Entity Traits I Primitives I Using Drawables in your Object I Tessellation I Isolines I Transformations I Using Clip Boundaries in AcGi
AutoCAD uses the graphics interface library (AcGi) to display built-in and custom entities. This section discusses setting entity traits and using primitives to create custom graphical entities. For a complete description of all AcGi classes and their member functions, see the ObjectARX Reference.
719
AcGi Overview
The AcGi library defines a set of interfaces with which objects can render themselves to an underlying graphics system. This section discusses how AcGi works in the AutoCAD environment. However, it works in a similar way for other systems that implement the AcGi interfaces. The AcGi library enables entities to query for information about the regeneration process, and to detail a set of primitives using the geometry classes. Access to AcGi occurs within the following three member functions of the AcGiDrawable base class:
Adesk::Boolean worldDraw( AcGiWorldDraw*); void viewportDraw( AcGiViewportDraw*); Adesk::UInt32 setAttributes( AcGiDrawableTraits*); AcDbEntity inherits these functions from AcGiDrawable. Typically, when
implementing a custom entity, you will override these functions and provide your own implementation. When AutoCAD needs to regenerate the graphics to display an entity, it calls these functions in the following manner:
AcGiDrawable *pDrawable; pDrawable->setAttributes(pDt); if (!pDrawable->worldDraw(pWd)) { for each viewport pDrawable->viewportDraw(pVd); }
For custom entities, AutoCAD calls your setAttributes(), worldDraw(), and viewportDraw() functions if you have overridden them. AutoCAD passes in the appropriate AcGi objects to these functions. This enables AutoCAD to display your custom entity just as if it were a built-in entity. The setAttributes() function initializes attributes for the entity, such as color, layer, and linetype. The worldDraw() function builds the portion of the entitys graphical representation that can be specified independent of any particular model-space view or paper-space viewport contexts. The viewportDraw() function then builds the view-dependent portion of the entitys graphics. If any of the entitys graphics are view-dependent,
720
Chapter 29
worldDraw() must return kFalse and viewportDraw() must be implemented. Conversely, if the entity has no view-dependent graphics, then worldDraw() must return kTrue and the custom entity does not implement viewportDraw().
The following illustration shows the sequence in which an AutoCAD drawing with two viewports gets regenerated. In this example the drawing contains two blocks, Block 1 and Block 2. Block 1 is broken down into its component parts, a line and a circle. Block 2 consists of a custom entity. The custom entity is broken down to show the order in which functions are called as the drawing is generated:
Regen Time
Model Space
arc
line
Block 1
Block 2
line
circle
Custom Entity
Line
AcGiContext
The AcGiContext object provides a common context that can be accessed during all parts of the regeneration process. It provides information about the current state of the regen. For example, you can get the current database from the AcGiContext object at any time during the regen process.
AcGi Overview
721
AcGiCommonDraw AcGiWorldDraw AcGiWorldDraw AcGiContext AcGiEdgeData AcGiFaceData AcGiGeometry AcGiViewportGeometry AcGiWorldGeometry AcGiLinetypeEngine AcGiSubEntityTraits AcGiDrawableTraits AcGiTextStyle AcGiVertexData AcGiViewport AcGiDrawable AcGiGlyph
The AcGiCommonDraw base class encapsulates the common functionality of AcGiViewportDraw and AcGiWorldDraw. The AcGiGeometry base class encapsulates the common functionality of AcGiViewportGeometry and AcGiWorldGeometry. These base classes allow you to write more general code that can handle both cases, if desired.
722
Chapter 29
The AcGiWorldGeometry object can be accessed from within worldDraw() by using the AcGiWorldDraw::geometry() function, and the AcGiSubEntityTraits object can be accessed by using the AcGiWorldDraw::subEntityTraits() function. The AcGiWorldGeometry object writes vectors to AutoCADs display using its set of drawing primitives. A primitive is the lowest-level instruction used to draw graphical entities. The world geometry object has the following functions for drawing primitives in world coordinates, which are inherited from AcGiGeometry:
I I I I I I I I I I
Circle Circular arc Polyline Polygon Mesh Shell Text Xline Ray Draw
The draw method allows you to specify another drawable to be used as a part of your geometry. This might be another entity or an in-memory drawable. AcGi uses the same setAttributes(), worldDraw(), and viewportDraw() logic on this object as it uses on your object.
AcGi Overview
723
The AcGiSubEntityTraits object sets graphical attribute values using its set of traits functions:
I I I I I I I I
Color Layer Linetype Polygon fill type Selection marker Line weight Thickness Plot style name (should not be modified during worldDraw() or viewportDraw())
The viewport geometry object provides the same list of primitives as the world geometry object and adds to it the following primitives, which use eyeand display-space coordinates to draw polylines and polygons:
I I I I
The viewport subentity traits object is the same as that used by the world draw object (AcGiSubEntityTraits). The viewport object provides functions for querying the viewports transformation matrices and viewing parameters.
724
Chapter 29
I I
is the typical drawing mode and is used when the user issues a REGEN command or edits an entry. Entities should be rendered in wireframe in this mode. kAcGiHideOrShadeCommand performs hidden line removal and indicates that the HIDE or SHADE command is in effect. Entities should be rendered using faces in this mode. kAcGiRenderCommand uses materials and lighting models to create a realistically shaded image of a 3D model and is used when the user issues a RENDER command. Entities should be rendered using faces in this mode. kAcGiSaveWorldDrawForR12 is the type used for an explode operation. kAcGiSaveWorldDrawForProxy is the type used for the generation of proxy graphics. In this case all of your rendering should be done in worldDraw() since viewportDraw() is not supported for proxy graphics.
AcGi Overview
725
Drawable Level The implementation of setAttributes() specifies the default traits for the primitives used to display the drawable. For most entities, the entire object is rendered using the entitys current properties: linetype, color, layer, and so on. Subentity Level You can specify specific traits to be used for specific parts of the drawable during the worldDraw() or viewportDraw() implementation. You can use the AcGiSubEntityTraits interface to override traits that were specified in the setAttributes() call. Once a value for a trait is set it is used for all subsequent primitives until the end of the method or until a new value is specified.
NOTE In this section, the term subentity is used differently than in chapter
6, Entities, where the term refers to specific geometric pieces of an entity. In this section, subentity is not a piece of an entity; it is just a level at which trait values can be set and changed.
I
Subprimitive Level The mesh and shell primitive functions have optional parameters that let you specify a rich set of traits on a per-edge and perface basis. (See the code samples in Primitives.) For any trait, this mechanism requires that you set values for all of the edges or faces, or for none of them. You set only the traits you want. For example, you can set the colors of the edges of a shell or mesh without having to set layers or linetypes, but you must specify a color for every edge. In addition to mesh and shell subprimitive traits, there is a version of the text primitive function that has a text style parameter. Text style can be set only at the subprimitive (per-text primitive) level. Subprimitive trait values supersede values of the corresponding traits set at the subentity and drawable levels.
Subentity Traits
The following traits (properties) can be assigned at the subentity level by calling member functions of the AcGiSubEntityTraits object:
I I I
726
Chapter 29
I I I I I
Color, layer, and linetype are AutoCAD entity properties, so they can also be set at the drawable level as described in the previous section. Fill type and GS marker are not AutoCAD entity properties. Before each call to worldDraw() and viewportDraw(), AutoCAD calls setAttributes() to allow the drawable to initialize the color, layer, linetype, line weight, thickness, and line type scale subentity traits. It initializes fill type to correspond to the regen type, and it initializes the GS marker to zero (a zero marker signifies no marker).
Fill Type
The fill type enumerated value, AcGiFillType, can have one of two values:
I kAcGiFillAlways I kAcGiFillNever
Primitives that can be filled are circles, polygons, shells, meshes, text, arc sectors, and arc chords. Polylines and simple arcs cannot be filled. Before AutoCAD calls worldDraw(), it sets the fill type depending on the regen type. If the regen type is kAcGiStandardDisplay, AutoCAD sets the fill type to kAcGiFillNever. Otherwise, AutoCAD sets the fill type to kAcGiFillAlways. This value is reinitialized according to the regen type before viewportDraw() is called. If the user issues a FILL command specifying to turn Fill mode off, no objects are filled regardless of the regen type. Similarly, if the user explicitly turns Fill mode on, objects will be filled. If the user does not issue a FILL command, and AcGiSubEntityTraits::setFillType() has been set, that Fill mode is used regardless of the regen type.
GS Markers
GS markers are mainly useful in the object snap implementation of an entity. When the entity is selected for object snap, the GS marker for the selected portion of the entity is passed back to indicate which points should be returned. GS markers are also used in conjunction with the functions acedSSGet() and acedSSNameX() to permit your application to edit or operate on arbitrary sections of your custom entity objects. For a detailed description of how to
727
use GS markers (not how to set them), including the use of the acedSSGet(), acedSSNameX(), and AcDbEntity::getSubentPathsAtGsMarker() functions, see GS Markers and Subentities. The examples in chapter 6, Entities, set a GS marker for every edge of the entity. Your custom entity can use markers to identify a set of arbitrary sections of the entitythat is, any sequentially executed group of primitives can be identified by a single marker. The section of the entity generated by the group of primitive function calls is identified by preceding the primitives with a call to the AcGiSubEntityTraits function setSelectionMarker(), specifying a marker number unique to the entity object. Your implementation of getSubentPathsAtGsMarker() will associate the appropriate primitives with a given marker, based on how you set your markers.
kColorByBlock = 0; kRed = 1; kYellow = 2; kGreen = 3; kCyan = 4; kBlue = 5; kMagenta = 6; kWhite = 7; kColorByLayer = 256;
// Linetype // static const char* const kNoLinetyping = "CONTINUOUS"; static const char* const kLinetypeByLayer = "BYLAYER"; static const char* const kLinetypeByBlock = "BYBLOCK"; // Layer // static const char* const kLayerZero = "0";
NOTE Constant kWhite is white unless it conflicts with the background color,
in which case it becomes black so that it remains visible. If you assign color by block (setColor(0)) or color by layer (setColor(256)), youll need to query the block or layer for the actual color value.
728
Chapter 29
729
// Force the current color to use current layer's color. // pW->subEntityTraits().setColor(kColorByLayer); // Force current line type to DASHDOT. If // DASHDOT is not loaded, the current linetype // will still be in effect. // AcDbObjectId dashdotId; if (getLinetypeIdFromString("DASHDOT", dashdotId) == Acad::eOk) { pW->subEntityTraits().setLineType(dashdotId); } // Force current layer to "MY_LAYER". If // "MY_LAYER" is not loaded, the current layer // will still be in effect. // AcDbObjectId layerId; if (getLayerIdFromString("MY_LAYER", layerId) == Acad::eOk) { pW->subEntityTraits().setLayer(layerId); } // Draw a DASHDOT xline in "MY_LAYER"'s color. // pW->geometry().xline(pVerts[0], pVerts[2]); delete [] pVerts; return Adesk::kTrue; } // A useful function that gets the linetype ID from the // linetype's name; the name must be in upper case. // static Acad::ErrorStatus getLinetypeIdFromString(const char* str, AcDbObjectId& id) { Acad::ErrorStatus err; // Get the table of currently loaded linetypes. // AcDbLinetypeTable *pLinetypeTable; err = acdbHostApplicationServices()->workingDatabase() ->getSymbolTable(pLinetypeTable, AcDb::kForRead); if (err != Acad::eOk) return err;
730
Chapter 29
// Get the ID of the linetype with the name that // str contains. // err = pLinetypeTable->getAt(str, id, Adesk::kTrue); pLinetypeTable->close(); return err; } // A useful function that gets the layer ID from the // layer's name; the layer name must be in upper case. // static Acad::ErrorStatus getLayerIdFromString(const char* str, AcDbObjectId& id) { Acad::ErrorStatus err; // Get the table of currently loaded layers. // AcDbLayerTable *pLayerTable; err = acdbHostApplicationServices()->workingDatabase() ->getSymbolTable(pLayerTable, AcDb::kForRead); if (err != Acad::eOk) return err; // Get the ID of the layer with the name that str // contains. // err = pLayerTable->getAt(str, id, Adesk::kTrue); pLayerTable->close(); return err; }
Primitives
With mesh and shell primitives, you can specify traits for edges, faces, or vertices in addition to the basic geometry. The following sections illustrate the use of these primitives.
Primitives
731
Mesh
A mesh is an efficient way to store a parametrically rectangular grid of vertices. The geometry for a mesh is specified as the number of rows, the number of columns, and a list of vertices, in row-order:
virtual Adesk::Boolean AcGiWorldGeometry::mesh( const Adesk::UInt32 rows, const Adesk::UInt32 columns, const AcGePoint3d* pVertexList, const AcGiEdgeData* pEdgeData = NULL, const AcGiFaceData* pFaceData = NULL, const AcGiVertexData* pVertexData = NULL) const = 0;
The mesh() function has three optional parameters for attaching property data to edges, faces, or vertices. For edges in the mesh, you can attach color, layer, linetype, GS marker, and visibility properties. For example, you could use AcGiEdgeData::setColors() to attach a different color to each edge of the mesh. In the color list, first list the colors for all the row edges, then the colors for all the column edges. The following figure shows the ordering of edge property data for a sample mesh:
0 1 2
12 3
15 4
18 5
21
13 6
16 7
19 8
22
14 9
17 10
20 11
23
732
Chapter 29
The following sample code creates a mesh and assigns colors using edge data and face data. It constructs a four-by-four mesh with cyan rows and green columns.
Adesk::Boolean AsdkMeshSamp::worldDraw(AcGiWorldDraw* pW) { Adesk::UInt32 i, j, k; Adesk::UInt32 numRows = 4; Adesk::UInt32 numCols = 4; AcGePoint3d *pVerts = new AcGePoint3d[numRows * numCols]; for (k = 0, i = 0; i < numRows; i++) { for (j = 0; j < numCols; j++, k++) { pVerts[k].x = (double)j; pVerts[k].y = (double)i; pVerts[k].z = 0.; } } // Construct an array of colors to be applied to each // edge of the mesh. In this example, the rows are cyan and // the columns are green. // AcGiEdgeData edgeInfo; Adesk::UInt32 numRowEdges = numRows * (numCols - 1); Adesk::UInt32 numColEdges = (numRows - 1) * numCols; Adesk::UInt32 numEdges = numRowEdges + numColEdges; short *pEdgeColorArray = new short[numEdges]; for (i = 0; i < numEdges; i++) { pEdgeColorArray[i] = i < numRowEdges ? kCyan : kGreen; } edgeInfo.setColors(pEdgeColorArray); // Make the first face transparent and the rest // different colors. // Adesk::UInt32 numFaces = (numRows - 1) * (numCols - 1); Adesk::UInt8 *pFaceVisArray = new Adesk::UInt8[numFaces]; short *pFaceColorArray = new short[numFaces]; AcGiFaceData faceInfo; faceInfo.setVisibility(pFaceVisArray); for (i = 0; i < numFaces; i++) { pFaceVisArray[i] = i ? kAcGiVisible : kAcGiInvisible; pFaceColorArray[i] = (short)(i + 1); } faceInfo.setColors(pFaceColorArray);
Primitives
733
// If the fill type is kAcGiFillAlways, then a shell, // mesh, or polygon will be interpreted as faces; // otherwise, they will be interpreted as edges. // // Output mesh as faces. // pW->subEntityTraits().setFillType(kAcGiFillAlways); pW->geometry().mesh(numRows, numCols, pVerts, NULL, &faceInfo); // Output mesh as edges over the faces. // pW->subEntityTraits().setFillType(kAcGiFillNever); pW->geometry().mesh(numRows, numCols, pVerts, &edgeInfo); delete [] pVerts; delete [] pEdgeColorArray; delete [] pFaceColorArray; delete [] pFaceVisArray; return Adesk::kTrue; }
For faces in a mesh, you can attach color, layer, GS marker, normal, and visibility traits. To assign properties to faces in a mesh, you list the values for the faces in row-order, as indicated by the following figure:
Vertex data for the mesh is listed in the same order as in the vertex list. Properties that can be set with AcGiVertexData are normals and orientation.
Visibility
The AcGiEdgeData and AcGiFaceData classes allow you to specify the visibility type for the edges or faces in a mesh or shell primitive. There must be
734
Chapter 29
exactly one visibility entry in the array for each edge or face in the primitive. Passing in an array of an incorrect size causes unpredictable results. The visibility type for edges and faces, AcGiVisibility, can have one of the following values:
I kAcGiInvisible I kAcGiVisible I kAcGiSilhouette
If the surface is not curved, or the edge is not required for viewing purposes, specify kAcGiInvisible. For hard edges of a surface or visible creases, specify kAcGiVisible. For edges or faces that you can see from certain viewpoints, specify kAcGiSilhouette. The silhouette visibility type is recognized only by the HIDE command; otherwise, it is interpreted as kAcGiVisible. For example, in the solid cylinder shown below, the edges that form the rims of the cylinder are visible edges. The latitudinal edges are invisible edges, since they are never used for viewing purposes. The longitudinal edges are silhouette edges, since they are used when the cylinder is viewed from certain angles.
Shell
A shell is a list of faces that might be connected and can have holes in them. The shell is specified by the number of unique vertices, a list of vertices (pVertexList), the number of faces (faceListSize), and a face list, which consists of the number of points in a given face followed by the index in the vertex list of each vertex for that face. The signature for the shell() function is:
virtual Adesk::Boolean AcGiWorldGeometry::shell( const Adesk::UInt32 const AcGePoint3d* const Adesk::UInt32 const Adesk::Int32* const AcGiEdgeData* const AcGiFaceData* const AcGiVertexData* const struct resbuf*
nbVertex, pVertexList, faceListSize, pFaceList, pEdgeData = NULL, pFaceData = NULL, pVertexData = NULL pResBuf = NULL) const = 0;
Primitives
735
A negative vertex count indicates a hole in the shell. Holes must be in the same plane as the face in which they reside. The holes must not touch each other and must be completely inside the containing face. The shell() function is a costly operation because it requires the use of a triangulator to break the containing face and the holes down into component triangles. AcGi polygons and shells with faces of five or more sides are also broken down into triangles before being sent to be displayed. Having the AcGi triangulate a polygon or shell face can be costly in terms of memory and speed, so its recommended you use three- or four-sided faces in shells to build up faces or polygons with five or more sides. That way, the primitive will not be put through the slow triangulator step.
NOTE The triangulator is used only on polygons of five sides or more, shell
faces of five sides or more, shell faces with holes, and filled text. Vertices in a given face must be coplanar. There is no implied connectivity between faces. Edge data for a shell is listed in the order implied by the face list. For example, in the first face, vertex0 to vertex1 specifies the first edge, vertex1 to vertex2 specifies the second edge, and so on until the last vertex of the face, which connects to the first vertex, as shown below.
0
If the same edge is used in two different faces, properties may conflict. In such cases, you can set one of the edges to be invisible or make the properties match for each edge. The order of face data, if present, follows the ordering of the face list for the shell.
736
Chapter 29
The following is an example of a shell with color data attached to edges and faces and visibility data attached to edges. The shell is composed of two triangles in different planes that share a common edge. The common edge has silhouette visibility. This means that when the HIDE command is in effect and the AutoCAD variable DISPSILH equals 1 (display silhouettes is on), the common edge between the faces is drawn only if both faces in the viewport are on the same side of the common edge. In this case, one face is behind the other, so it is not drawn:
Adesk::Boolean AsdkShellSamp::worldDraw(AcGiWorldDraw* pW) { // Fill the faces with the current color. // pW->subEntityTraits().setFillType(kAcGiFillAlways); // Create vertices. // Adesk::UInt32 numVerts = 4; AcGePoint3d *pVerts = new AcGePoint3d[numVerts]; pVerts[0] = AcGePoint3d(0.0, 0.0, 0.0); pVerts[1] = AcGePoint3d(0.0, 1.0, 0.0); pVerts[2] = AcGePoint3d(1.0, 1.0, 0.0); pVerts[3] = AcGePoint3d(1.0, 0.0, 2.0); // Create two faces. // Adesk::UInt32 faceListSize = 8; Adesk::Int32 *pFaceList = new Adesk::Int32[faceListSize]; // Assign vertices for face 1. // pFaceList[0] = 3; // Three vertices in the face pFaceList[1] = 0; // pVerts[0] pFaceList[2] = 1; // pVerts[1] pFaceList[3] = 2; // pVerts[2] // Assign vertices for face 2. // pFaceList[4] = 3; // Three vertices in the face pFaceList[5] = 0; // pVerts[0] pFaceList[6] = 2; // pVerts[2] pFaceList[7] = 3; // pVerts[3]
Primitives
737
// Apply colors to edges. // AcGiEdgeData edgeData; int numEdges = 6; short *pEdgeColorArray = new short[numEdges]; pEdgeColorArray[0] = kRed; pEdgeColorArray[1] = kYellow; pEdgeColorArray[2] = kGreen; pEdgeColorArray[3] = kCyan; pEdgeColorArray[4] = kBlue; pEdgeColorArray[5] = kMagenta; edgeData.setColors(pEdgeColorArray); // Apply visibility to edges and make the common edge // between two faces have silhouette visibility during // the HIDE command with AutoCAD variable DISPSILH = 1. // Adesk::UInt8 *pEdgeVisArray = new Adesk::UInt8[numEdges]; edgeData.setVisibility(pEdgeVisArray); pEdgeVisArray[0] = kAcGiVisible; pEdgeVisArray[1] = kAcGiVisible; pEdgeVisArray[2] = kAcGiSilhouette; pEdgeVisArray[3] = kAcGiSilhouette; pEdgeVisArray[4] = kAcGiVisible; pEdgeVisArray[5] = kAcGiVisible; // Apply colors to faces. // AcGiFaceData faceData; int numFaces = 2; short *pFaceColorArray = new short[numFaces]; pFaceColorArray[0] = kBlue; pFaceColorArray[1] = kRed; faceData.setColors(pFaceColorArray); pW->geometry().shell(numVerts, pVerts, faceListSize, pFaceList, &edgeData, &faceData); delete [] pVerts; delete [] pFaceList; delete [] pEdgeColorArray; delete [] pFaceColorArray; return Adesk::kTrue; }
An AcGiVertexData object contains a single flag that specifies how vertices in a shell are ordered. This flag is set and queried with the following functions:
virtual void AcGiVertexData::setOrientationFlag( AcGiOrientationType oflag);
738
Chapter 29
This flag is not used for meshes because the ordering of vertices specifying a mesh is fixed. Values for the flag are
I kAcGiClockwise I kAcGiCounterClockwise I kAcGiNoOrientation
The orientation of vertices in a shells face list indicates the visible side of the face. For example, if the vertices are specified as clockwise and the vertices for a given face are listed in clockwise order, then that face is visible. In this case, faces with vertices in counterclockwise order are invisible.
Arc
The circularArc() function has two forms:
virtual Adesk::Boolean AcGiWorldGeometry::circularArc( const AcGePoint3d& center, const double radius, const AcGeVector3d& normal, const AcGeVector3d& startVector, const double sweepAngle, const AcGiArcType arcType = kAcGiArcSimple) const = 0; virtual Adesk::Boolean AcGiWorldGeometry::circularArc( const AcGePoint3d& start, const AcGePoint3d& point, const AcGePoint3d& end, const AcGiArcType arcType = kAcGiArcSimple) const = 0;
The arc type variable, AcGiArcType, can have one of the following values:
I kAcGiArcSimple I kAcGiArcSector I kAcGiArcChord
The arc itself, which is not fillable The area bounded by the arc and its center of curvature The area bounded by the arc and its end points
Simple Arc
Arc Sector
Arc Chord
Primitives
739
Polyline
The pline() function allows a custom entity to draw graphics primitives using an AcDbPolyline as a template:
virtual Adesk::Boolean pline( const AcDbPolyline& lwBuf, Adesk::UInt32 fromIndex = 0, Adesk::UInt32 numSegs = 0) const; AcDbPolylines are multisegmented and support straight and curved segments with or without width. Using pline() provides the ability to generate
contiguous straight and curved segments with width. None of the other AcGi primitive functions support width, so without using pline() it would be necessary to generate many parallel arc and line segments to simulate a filled arc or line segment with width. This is inefficient, and proper display is dependent upon the view (magnification).
Text
The example in this section shows use of the AcGiTextStyle class. It draws a rectangle around a piece of AcGi text that can be oriented and located anywhere in space. The normal and direction vectors of the text must be perpendicular to each other. If youre unsure of the directions, consider the direction to be along the X axis and the normal along the Z axis in a right-handed coordinate system. Calculate the Y axis from these. Then the cross product of the Y axis to Z axis will give you the normal planes interpretation of the direction. Be sure that the direction is not aligned with the normal, or you will not have a direction with respect to the normal. The AcGiTextStyle::loadStyleRec() function loads a font if it is not already loaded. (This function does not load an ACAD STYLE.) Its return values are as follows: 0x10 0x08 0x04 0x02 0x01 Another file (not FONTALT) opened in place of BigFont file name Another file (not FONTALT) opened in place of file name BigFont file name failed to be loaded File name failed to be loaded Files opened as called for
Text can be scaled in a number of ways. Use AcGiTextStyle::setTextSize() to scale the width and height of the text at the same time. Use setXScale()
740
Chapter 29
to scale the width of the text. Use setTrackingPercent() to specify how the characters of a particular font are placed next to each other. If you specify a value of 1.0, the spacing does not change; if you specify less than 1.0, the characters will squeeze together; and if its more than 1.0, the characters will be farther apart. This example sets the tracking percent to a value of .80. The AcGiTextStyle::extents() function returns the world coordinate size of the texts bounding box. If the penups argument is kTrue, then any undrawn pen moves made while the user was drawing the text will be included in the bounding box. The raw option tells the calculation to ignore escape code processing (so that %%% would not be interpreted as a single percent sign but as three percent signs). The following example draws text and then draws a bounding box around a portion of the text.
Adesk::Boolean AsdkTextStyleSamp::worldDraw(AcGiWorldDraw* pW) { AcGePoint3d pos(4.0, 4.0, 0.0); AcGeVector3d norm(0.0, 0.0, 1.0); AcGeVector3d dir(-1.0, -0.2, 0.0); char *pStr = "This is a percent, '%%%'."; int len = strlen(pStr); AcGiTextStyle style; AcGeVector3d vec = norm; vec = vec.crossProduct(dir); dir = vec.crossProduct(norm); style.setFileName("txt.shx"); style.setBigFontFileName(""); int status; if (!((status = style.loadStyleRec()) & 1)) pStr = "Font not found."; pW->geometry().text(pos, norm, dir, pStr, len, Adesk::kFalse, style); pos.y += 2.0; style.setTrackingPercent(80.0); style.setObliquingAngle(10.0); AcGePoint2d ext = style.extents(pStr, Adesk::kFalse, strlen(pStr), Adesk::kFalse); pW->geometry().text(pos, norm, dir, pStr, len, Adesk::kFalse, style);
Primitives
741
// // // // //
Draw a rectangle around the last text drawn. First, create a polyline the size of the bounding box. Then, transform it to the correct orientation, and then to the location of the text.
// Compute the matrix that orients the box. // AcGeMatrix3d textMat; norm.normalize(); dir.normalize(); AcGeVector3d yAxis = norm; yAxis = yAxis.crossProduct(dir); yAxis.normalize(); textMat.setCoordSystem(AcGePoint3d(0.0, 0.0, 0.0), dir, yAxis, norm); // Create the bounding box and enlarge it a little. // double offset = ext.y / 2.0; AcGePoint3d verts[5]; verts[0] = verts[4] = AcGePoint3d(-offset, -offset, 0.0); verts[1] = AcGePoint3d(ext.x + offset, -offset, 0.0); verts[2] = AcGePoint3d(ext.x + offset, ext.y + offset, 0.0); verts[3] = AcGePoint3d(-offset, ext.y + offset, 0.0); // Orient and then translate each point in the // bounding box. // for (int i = 0; i < 5; i++) { verts[i].transformBy(textMat); verts[i].x += pos.x; verts[i].y += pos.y; verts[i].z += pos.z; } pW->geometry().polyline(5, verts); return Adesk::kTrue; }
742
Chapter 29
The following functions make use of the AcGiTextStyle and AcDbTextStyleTableRecord names:
Acad::ErrorStatus fromAcDbTextStyle( AcGiTextStyle& textStyle, const char* AcDbStyleName); Acad::ErrorStatus toAcDbTextStyle( AcGiTextStyle& textStyle); Acad::ErrorStatus toAcDbTextStyle( AcGiTextStyle& textStyle, const char* AcDbStyleName);
When copying data to or from an AcDbTextStyleTableRecord that has been specified by name, the AcGiTextStyle objects name is set to match the name of the AcDbTextStyleTableRecord. If no record is found when copying to an AcDbTextStyleTableRecord specified by name, then one is created. When copying from an AcGiTextStyle to an AcDbTextStyleTableRecord and the name of the AcGiTextStyle is used as the name of the AcDbTextStyleTableRecord, if the AcGiTextStyle does not have a name, a unique name is generated and used as the name for the AcGiTextStyle and AcDbTextStyleTableRecord objects. The following functions are similar to the previous functions, except that they have an AcDbObjectId argument used for the objectId of the AcDbTextStyleTableRecord that the data has been copied into.
Acad::ErrorStatus toAcDbTextStyle( AcGiTextStyle& textStyle, AcDbObjectId& AcDbStyleId); Acad::ErrorStatus toAcDbTextStyle( AcGiTextStyle& textStyle, const char* AcDbStyleName, AcDbObjectId& AcDbStyleId);
Primitives
743
WARNING! Any drawables passed into draw() must have a lifetime equal to
or exceeding that of the outer object. This is required because the graphics of a drawable might be cached in an AcGsNode attached to the drawable. During display the graphics system might go back to get this cache, and if the object has been destroyed, a runtime error will occur.
Tessellation
Curves and curved surfaces need to be tessellatedbroken up into lines and polygonsin order to be displayed. The degree of tessellation determines how accurate the displayed curve will be (how close it will approximate the mathematical true curve) and how much performance overhead is required to generate the graphics for a curve. A very small circle may require only a single pixel to display it. A large circle may require hundreds of small line segments to be calculated and displayed to create a smooth appearance.
744
Chapter 29
The deviation() functions provided by the AcGiWorldDraw and AcGiViewportDraw classes return the deviation, which is the allowable maximum difference in world space between a true mathematical surface and the tessellated surface, as shown in the following figure:
tessellated surface
Access to this value allows custom entities to tune their tessellation to the
VIEWERS commands zoom percent option, which is set by the user. The
result is that custom entities are tessellated to relatively the same smoothness as built-in entities. The deviation() function returns the suggested maximum deviation in world space, given the type of deviation to calculate and a point in world space for perspective scaling if required. The signature for the deviation() function is
virtual double AcGiWorldDraw::deviation( AcGiDeviationType devType, const AcGePoint3d&) const = 0;
(for surfaces; formula for calculating this deviation uses the value of the FACETRES system variable)
Tessellation
745
Isolines
An isoline is used to give a visual clue to the shape of an object. The AcGiWorldDraw::isolines() function allows an entity to display the same number of isolines per surface as specified by the user. This value is an integer between 0 and 2047. The default number of isolines is 4. The AcGiViewportDraw class provides an analogous function:
virtual Adesk::UInt32 AcGiWorldDraw::numberOfIsolines() const;
Transformations
The graphics pipeline can apply three possible transformations to an entity:
I I I
The entitys block transformations The viewports view transformation The perspective transformation (if perspective is enabled from DVIEW)
Each transformation produces a new type of coordinates, as shown in the following figure. If not in perspective mode, eye and display coordinates are identical.
Model coordinates
World coordinates
Eye coordinates
*
Perspective transform
Display coordinates *Front and back clipping are performed here if specified
746
Chapter 29
For the REGEN, HIDE, and SHADE commands, the entitys world coordinates are sent through the graphics pipeline shown in the figure above. The view transformation specifies a particular view of the world coordinates, analogous to viewing a scene with a camera. The camera has a location in world space and a particular orientation toward the world coordinate scene. When the view transformation is complete, world coordinates are transformed to eye coordinates, looking down the Z axis of the camera. If perspective is enabled, the eye coordinates are transformed to display coordinates. This transformation involves division according to how far away something is from the camera, so that objects farther away from the camera appear smaller than objects closer to the camera. The following sections discuss these coordinate systems in greater detail.
Transformations
747
Transformation Examples
The AcGiViewport class provides functions that give you access to the graphics pipeline, allowing you to apply each transformation explicitly and perform the mathematics yourself. If you are manipulating entities in the graphics pipeline yourself, you use different forms of the AcGi polygon and polyline depending on where you are in the graphics pipeline. The AcGiViewportGeometry class provides three forms for polygons and polylines, in model, eye, and display coordinates. Normally, you would use the polyline() and polygon() functions, which require model coordinates. Use polylineEye() and polygonEye() if you are going to work with eye coordinates, as shown in Examples 1 and 2. Use polygonDc() and polylineDc() if you are working with display coordinates. The following sections contain four examples. The first example draws the same entity using model, eye, and display coordinates. Its main purpose is to demonstrate how to apply each transformation in the graphics pipeline. The second example illustrates working with eye coordinates to determine the front and back faces of a pyramid. The third example illustrates working with display coordinates to draw an entity in a size relative to the size of the current window. The fourth example shows how to determine the polyline with the fewest segments that is visually indistinguishable from one with more segments.
748
Chapter 29
Transformations
749
750
Chapter 29
Adesk::Boolean AsdkViewGeomSamp::worldDraw(AcGiWorldDraw* pW) { // Draw a pyramid. // // If this is the regular AutoCAD DISPLAY mode... // if (pW->regenType() == kAcGiStandardDisplay) { // From each viewport's vantage point, figure out // which sides of the pyramid are visible, // then make the visible ones yellow and the hidden // ones blue. // // Set the extents of the pyramid here because // AcGiViewportGeometry's polylineEye() doesn't // set extents. // for (Adesk::UInt32 i = 0; i < mNumVerts; i++) { AcGePoint3d pt[2]; pt[0] = mVerts[i]; pt[1] = mVerts[(i + 1) % mNumVerts]; pW->geometry().setExtents(pt); } return Adesk::kFalse; // Call viewport draws. } // Otherwise, give HIDE, SHADE, RENDER, or proxy graphics // a pyramid with filled faces. // const Adesk::UInt32 faceListSize = 16; static Adesk::Int32 faceList[faceListSize] = { 3, 0, 1, 2, 3, 0, 2, 3, 3, 0, 3, 1, 3, 1, 2, 3 }; pW->geometry().shell(mNumVerts, mVerts, faceListSize, faceList); return Adesk::kTrue; // Do not call viewportDraw. }
Transformations
751
void AsdkViewGeomSamp::viewportDraw(AcGiViewportDraw* pV) { // For this viewport, draw a pyramid with yellow // visible lines and blue hidden lines. // // Get this viewport's net transform. This transform // includes this entity's block transforms and this // viewport's view transform; it does not include the // perspective transform if we're in perspective // mode; that currently has to be applied separately // when in perspective mode. // AcGeMatrix3d modelToEyeMat; pV->viewport().getModelToEyeTransform(modelToEyeMat); // Get the pyramid's vertices. // AcGePoint3d A = mVerts[0]; AcGePoint3d B = mVerts[1]; AcGePoint3d C = mVerts[2]; AcGePoint3d D = mVerts[3]; // Convert them to the viewport's eye coordinates. // A.transformBy(modelToEyeMat); B.transformBy(modelToEyeMat); C.transformBy(modelToEyeMat); D.transformBy(modelToEyeMat); // Save the // AcGePoint3d AcGePoint3d AcGePoint3d AcGePoint3d eye coordinates. AEye BEye CEye DEye = = = = A; B; C; D;
// Perform the perspective transform if necessary. // if (pV->viewport().isPerspective()) { pV->viewport().doPerspective(A); pV->viewport().doPerspective(B); pV->viewport().doPerspective(C); pV->viewport().doPerspective(D); }
752
Chapter 29
// From that view, figure out which faces are // facing the viewport and which are not. // int which_faces; which_faces = ((C - A).crossProduct(B - A)).z > 0.0 ? 1 : 0; which_faces |= ((D - A).crossProduct(C - A)).z > 0.0 ? 2 : 0; which_faces |= ((B - A).crossProduct(D - A)).z > 0.0 ? 4 : 0; which_faces |= ((B - D).crossProduct(C - D)).z > 0.0 ? 8 : 0; // Those edges that meet between two faces that are // facing away from the viewport will be hidden edges, // so draw them blue; otherwise, they are visible // edges. (This example is incomplete, as the test is // indeterminate when the face is edge-on to the // screen -- neither facing away nor toward the screen.) // Draw the six edges connecting the vertices using eye // coordinate geometry that can be clipped back and front. // AcGePoint3d verts[2]; Adesk::UInt16 color; // AB color = which_faces & 0x5 ? kYellow : kBlue; pV->subEntityTraits().setColor(color); verts[0] = AEye; verts[1] = BEye; pV->geometry().polylineEye(2, verts); // AC color = which_faces & 0x3 ? kYellow : kBlue; pV->subEntityTraits().setColor(color); verts[0] = AEye; verts[1] = CEye; pV->geometry().polylineEye(2, verts); // AD color = which_faces & 0x6 ? kYellow : kBlue; pV->subEntityTraits().setColor(color); verts[0] = AEye; verts[1] = DEye; pV->geometry().polylineEye(2, verts); // CD color = which_faces & 0xa ? kYellow : kBlue; pV->subEntityTraits().setColor(color); verts[0] = CEye; verts[1] = DEye; pV->geometry().polylineEye(2, verts);
Transformations
753
// DB color = which_faces & 0xc ? kYellow : kBlue; pV->subEntityTraits().setColor(color); verts[0] = DEye; verts[1] = BEye; pV->geometry().polylineEye(2, verts); // BC color = which_faces & 0x9 ? kYellow : kBlue; pV->subEntityTraits().setColor(color); verts[0] = BEye; verts[1] = CEye; pV->geometry().polylineEye(2, verts); }
754
Chapter 29
// Create a unit square. // const int num_verts = 5; AcGePoint3d verts[num_verts]; for (int i = 0; i < num_verts; i++) { verts[i].x = xcenter; verts[i].y = ycenter; verts[i].z = 0.0; } verts[0].x verts[0].y verts[1].x verts[1].y verts[2].x verts[2].y verts[3].x verts[3].y verts[4] = -= half_xsize; += half_ysize; += half_xsize; += half_ysize; += half_xsize; -= half_ysize; -= half_xsize; -= half_ysize; verts[0];
Transformations
755
not be able to differentiate the individual line segments that make up the circle because the visual differences are less than a pixel.
y
angle x
Adesk::Boolean AsdkTesselateSamp::worldDraw(AcGiWorldDraw *pW) { // Draw a red 1x1 drawing-unit square centered at the // world coordinate origin and parallel to the XY-plane. // const Adesk::UInt32 num_pts = 5; AcGePoint3d verts[num_pts]; verts[0] = verts[4] = AcGePoint3d(-0.5, -0.5, 0.0); verts[1] = AcGePoint3d( 0.5, -0.5, 0.0); verts[2] = AcGePoint3d( 0.5, 0.5, 0.0); verts[3] = AcGePoint3d(-0.5, 0.5, 0.0); pW->subEntityTraits().setColor(kRed); pW->geometry().polyline(num_pts, verts); // If regenType is kAcGiSaveWorldDrawForProxy, return // Adesk::kTrue, otherwise return Adesk::kFalse to trigger // calls to viewportDraw(). // return (pW->regenType() == kAcGiSaveWorldDrawForProxy); } void AsdkTesselateSamp::viewportDraw(AcGiViewportDraw *pV) { static double two_pi = atan(1.0) * 8.0; // Get the number of pixels on the X- and Y-edges of // a unit square centered at (0.0, 0.0, 0.0), in // world coordinates. // AcGePoint3d center(0.0, 0.0, 0.0); AcGePoint2d area; pV->viewport().getNumPixelsInUnitSquare(center, area);
756
Chapter 29
// // // // if
If the area values are negative, then we are in perspective mode and the center is too close or in back of the viewport. (area.x > 0.0) { // Print out the number of pixels along the // Y-axis of the unit square used in // getNumPixelsInUnitSquare. // AcGeVector3d norm(0.0, 0.0, 1.0); AcGeVector3d dir(1.0, 0.0, 0.0); char buf[100]; sprintf(buf, "%7.3lf", area.y); pV->geometry().text(center, norm, dir, 1.0, 1.0, 0.0, buf); // Draw a circle that depends on how big the circle // is in the viewport. This requires // figuring out the fewest number of segments needed // by a polyline so that it doesn't look segmented. // // The worldDraw() and viewportDraw() of // an entity in a viewport are only called during a // regen and not necessarily during a ZOOM or PAN. // The reason is that a regen produces something // akin to a very high resolution image internally, // which AutoCAD can zoom in or pan around. That is, // until you get too close to this image or any of // its edges, at which point a regen is internally // invoked for that viewport and a new internal // image is created (ready to be mildly zoomed and // panned). // double radius = 0.5; double half_pixel_hgt = 2.0 / area.x; // In WCS int num_segs = 8; double angle = two_pi / num_segs; if (half_pixel_hgt > radius / 2) { // The circle is approximately the same or less // than the size of a pixel. So, generate a very // small octagon. // num_segs = 8;
Transformations
757
} else { // Given a circle centered at the origin of a // given radius in the XY-plane, and given a // vertical line that intersects the X-axis at // 'radius - half a pixel', what is the angle // from the X-axis of a line segment from the // origin to the point where the vertical line // and the circle intersect? Two pi divided by // this angle gives you a minimum number of // segments needed by a polyline to look like // a circle and not be able to differentiate // the individual segments because the visual // differences are less than the size of a // pixel. (This is not the only way to figure // this out but it's sufficient.) // angle = acos((radius - 1.0 / (area.x / 2.0)) / radius); double d_num_segs = two_pi / angle; // // // // if Limit the number of segments from 8 to 128 and use whole numbers for this count.
(d_num_segs < 8.0) { num_segs = 8; } else if (d_num_segs > 128.0) { num_segs = 128; } else { num_segs = (int)d_num_segs; } } // Calculate the vertices of the polyline from the // start, around the circle, and back to the start // to close the polyline. // angle = 0.0; double angle_inc = two_pi / (double)num_segs; AcGePoint3d* verts = new AcGePoint3d[num_segs + 1]; for (int i = 0; i <= num_segs; i++, angle += angle_inc) { verts[i].x = center.x + radius * cos(angle); verts[i].y = center.y + radius * sin(angle); verts[i].z = center.z; } pV->geometry().polyline(num_segs + 1, verts); delete [] verts; } }
758
Chapter 29
Background
Clip boundaries are closed, non-self-intersecting, concave 2D polygons. Optional front and back Z clipping values can be assigned. The clip boundary is expressed in an arbitrary coordinate system relative to the objects being clipped. In AutoCAD, when the user defines a clipping boundary for a block, the view direction and twist of the current view are used to define the coordinate system for the clip boundary. This might be the same as the coordinate system of the block reference being clipped. This is reflected in the API by the provision of a transformation to the clipping space from the block reference system:
Before Clipping
After Clipping
Clip boundaries can be nested. A compound object can define a clipping boundary, and the objects that it contains can also define boundaries for their internal geometry. In this case, the nested geometry is first clipped against its parents boundary and any resultant fragments are then clipped against the clip boundary of the outer block.
759
Since this clipping is a complex operation, some AcGi implementations might not support it fully. In this case, the AcGi implementation may return false from pushClipBoundary(), and you should not call popClipBoundary().
760
Chapter 29
30
In this chapter
I Overview of the AcGe Library I Using Basic Geometry Types I Using the Line and Plane
This section discusses the main uses of the AcGe library, which provides a number of classes for representing 2D and 3D geometry. This library is intended for use by any Autodesk application and is frequently used by the AcDb and AcGi libraries in ObjectARX .
Classes
I Parametric Geometry I Special Evaluation Classes I Persistent AcGe Entities
761
762
Chapter 30
AcGeBoundBlock2d AcGeClipBoundary2d AcGeCurve2d AcGeCircArc2d AcGeCompositeCurve2d AcGeEllipArc2d AcGeExternalCurve2d AcGeLinearEnt2d AcGeLine2d AcGeLineSeg2d AcGeRay2d AcGeOffsetCurve2d AcGeSplineEnt2d AcGeCubicSplineCurve2d AcGeNurbCurve2d AcGePolyline2d AcGeCurveCurveInt2d AcGePointEnt2d AcGePointOnCurve2d AcGePosition2d AcGeCurveBoundary AcGe AcGeContext AcGeDwgIO AcGeDxfIO AcGeFileIO AcGeFiler AcGeInterval AcGeKnotVector AcGeLibVersion AcGeMatrix2d AcGeMatrix3d AcGePoint2d AcAxPoint2d AcGePoint3d AcAxPoint3d AcGeScale2d AcGeScale3d AcGeTol AcGeVector2d AcGeVector3d
AcGeBoundBlock3d AcGeCurve3d AcGeCircArc3de AcGeCompositeCurve3d AcGeEllipArc3e AcGeExternalCurve3d AcGeLinearEnt3d AcGeLine3d AcGeLineSeg3d AcGeRay3d AcGeMatrix3d AcGeOffsetCurve3d AcGeSplineEnt3d AcGeCubicSplineCurve3d AcGeNurbCurve3d AcGePolyline3d AcGeAugPolyline3d AcGeCurveCurveInt3d AcGeCurveSurfInt AcGePointEnt3d AcGePointOnCurve3d AcGePointOnSurface AcGePosition3d AcGeSurfSurfInt AcGeSurface AcGeCone AcGeCylinder AcGeExternalBoundedSurface AcGeExternalSurface AcGeNurbSurface AcGeOffsetSurface AcGePlanarEnt AcGeBoundedPlanet AcGePlane AcGeSphere AcGeTorus
763
The AcGe library provides both simple and complex geometry classes. Simple linear algebra classes include the point, vector, matrix, 2D and 3D linear entity classes, and planar entity classes. Complex classes include curve classes, such as spline entity, and surface classes, such as NURBS surfaces. The class hierarchy offers separate classes for 2D and 3D geometry. This simplifies programming by clearly distinguishing 2D parametric-space geometry from 3D modeling-space geometry. Because of this distinction, you cannot inadvertently mix 2D and 3D entities in the same operation. The library includes a number of basic types, such as AcGePoint3d, AcGeVector3d, and AcGeMatrix3d, that have public data members for fast and efficient access. These simple classes are commonly used by other libraries as well as by the AcGe classes derived from AcGeEntity2d and AcGeEntity3d. Runtime type checking is provided for all classes derived from AcGeEntity2d and AcGeEntity3d. Each class provides a type() function that returns the objects class and an isKindOf() function that returns whether the object is of a particular class (or a class derived from that class). Two entities are considered equal if they are of the same type and represent the same point set. Curves and surfaces are considered equal only if their parameterization is the same.
geline2d.h
gepnt3d.h gemat3d.h
764
Chapter 30
geline3d.h
geplane.h
gegbl.h
computes an arbitrary vector that is perpendicular to it. You can substitute your own function for the given function.
Tolerances
Many methods accept a tolerance value as one of their parameters. This value is of the AcGeTol class and always has a default value, as defined in AcGeContext::gTol. Functions such as isClosed() and isPlanar() calculate whether the start points and endpoints are within the defined tolerance before returning a Boolean value. You can change the tolerance for one particular function call, or you can change the global tolerance value. The AcGeTol class provides two functions for setting the tolerance for points and vectors:
void setEqualPoint(double); void setEqualVector(double);
The AcGeTol class also provides two functions for obtaining the tolerance for points and vectors:
double double equalPoint() const; equalVector() const;
765
The equalPoint and equalVector tolerance values are used as follows: Two points, p1 and p2, are equal if (p1 - p2).length() <= equalPoint
I
Two vectors, v1 and v2, are equal if (v1 - v2).length() <= equalVector
I I
Two vectors, v1 and v2, are parallel if (v1/v1.length() - v2/v2.length()).length() < equalVector OR (v1/v1.length() + v2/v2.length()).length() < equalVector Two vectors, v1 and v2, are perpendicular if abs((v1.dotProduct(v2))/(v1.length()*v2.length())) <= equalVector
I I I
Two lines or rays are parallel (perpendicular) if their directional vectors are parallel (perpendicular) Two lines are equal if the points at parameter 0 are equal and their directions are equal
NOTE These rules mean that two lines are close to each other as point sets in
the part of the modeling space of diameter diam only if the tolerance equalVector is set tighter than equalPoint/diam.
The point and vector classes provide +, +=, -, and -= operators. These operators allow points and vectors to be used in much the same way as built-in
766
Chapter 30
types, such as doubles and integers. The following are examples of adding and subtracting points and vectors:
p2 p1 p3 v3 v1 v3 = p1 + v1; += v1; -= v1; = v1 + v2; += v2; = v1 - v2; // // // // // // Set p2 to sum of p1 and v1. Add v1 to p1. Subtract v1 from p3. Set v3 to sum of v1 and v2. Add v2 to v1. Set v3 to difference of v1 and v2.
There is no + operator for adding two points; however, a point can be converted to a vector, which can then be added to another point:
p1 += p2.asVector();
The point and vector classes contain a number of query functions for computing distances and lengths:
double len = v2.length(); len = p1.distanceTo(p2); // Length of v2. // Distance from p1 to p2.
The following function is very useful for computing the angle between two 3D vectors. The following returns the angle between v1 and v2 where the angle is taken to be counterclockwise about v3 (v3 is assumed to be perpendicular to v1 and v2):
angle = v1.angleTo(v2,v3);
The following functions return a Boolean value (TRUE or FALSE) and may be used inside if statements:
if (v1.isZeroLength()) if (v1.isParallelTo(v2)) if (v1.isPerpendicularTo(v2))
The vector class contains functions for the usual vector operations:
len = v1.dotProduct(v2); v3 = v1.crossProduct(v2);
The default constructor for a matrix initializes the matrix to the identity matrix:
AcGeMatrix3d mat1, mat2, mat3;
767
The following rotates p3 90 degrees about the line defined by p1 and v1:
mat1.setToRotation ( kPi/2.0, v1, p1 ); p3 = mat1 * p2;
The following tests whether a matrix contains equal scaling in all three coordinates (that is, it does not change the shape of any entity to which it is applied):
if (mat.isUniScaledOrtho())
The above constructor for line1 constructs a line through p1 in the direction of v1. The constructor for plane1 constructs a plane through p1 and normal to v1. Thus, line1 is perpendicular to plane1. The following functions return the line or plane definition:
p1 v1 p1 v1 = = = = line1.pointOnLine(); line1.direction(); plane1.pointOnPlane(); plane1.normal(); // // // // Arbitrary point on line. Direction vector of line. Arbitrary point on plane. Normal vector of plane.
768
Chapter 30
The following functions return the closest point on the line or plane to the point p1:
p2 = line1.closestPointTo(p1); p2 = plane1.closestPointTo(p1);
The following functions return the distance between a point and line or plane (these distances will be the same as the distances between p1 and p2 above):
double len = line1.distanceTo(p1); len = plane1.distanceTo(p1);
The following functions return a Boolean value (TRUE or FALSE) and may be used inside an if statement. The first two test if the point p1 lies on line1 or plane1, and the third tests if line1 lies on plane1:
if (line1.isOn(p1)) if (plane1.isOn(p1)) if (line1.isOn(plane1))
The following functions test if lines or planes are parallel, perpendicular, or coincident:
if if if if if if if if (line1.isParallelTo(line2)) (line1.isParallelTo(plane1)) (line1.isPerpendicularTo(line2)) (line1.isPerpendicularTo(plane1)) (line1.isColinearTo(line2)) (plane1.isParallelTo(plane2)) (plane1.isPerpendicularTo(plane2)) (plane1.isCoplanarTo(plane2))
Parametric Geometry
The following sections discuss working with parametric geometry.
Curves
Curves and surfaces in the AcGe library are parametric. A curve is the result of mapping an interval of the real line into 2D or 3D modeling space using an evaluator function with one argument, such as f(u). Similarly, a surface is a mapping from a 2D domain into 3D modeling space using an evaluator function based on two arguments for example, f(u, v). Each 2D and 3D curve
Parametric Geometry
769
class has a getInterval() function that returns the parametric interval. This function has two forms: the first returns the interval; the second returns the interval as well as the start point and endpoint of the curve.
NOTE If the interval is unbounded in either direction, the start points and
endpoints do not have meaning.
Characteristics
Curves have the following characteristics:
I I I I I
The orientation of a curve is determined by the direction in which its parameter increases. You can use the AcGeCurve2d::reverseParam() or AcGeCurve3d::reverseParam() function to reverse the orientation of a curve. Some curves are periodic, which means that they repeat themselves after a certain interval. For example, the period of a circle is 2pi. Use these functions to determine whether a curve is periodic:
Adesk::Boolean AcGeCurve2d::isPeriodic(double& period) const; Adesk::Boolean AcGeCurve3d::isPeriodic(double& period) const;
A closed curve has start points and endpoints that are the same. Curves can be either closed or open. Use these functions to determine whether a curve is closed:
Adesk::Boolean AcGeCurve2d::isClosed( const AcGeTol&= AcGeContext::gTol) const; Adesk::Boolean AcGeCurve3d::isClosed( const AcGeTol&= AcGeContext::gTol) const;
770
Chapter 30
A 3D curve can be planar (meaning that all of its points reside in the same plane) or nonplanar. Use this function to determine whether a 3D curve is planar:
Adesk::Boolean AcGeCurve3d::isPlanar( AcGePlane&, const AcGeTol&=AcGeContext::gTol) const;
Given two parameter values, you can obtain the length of the curve between these two values using the following functions:
double AcGeCurve2d::length( double fromParam, double toParam, double=AcGeContext::gTol.equalPoint()) const; double AcGeCurve3d::length( double fromParam, double toParam, double=AcGeContext::gTol.equalPoint()) const;
You can use the AcGeCurve2d::evalPoint() and AcGeCurve3d::evalPoint() functions to obtain the model space point that corresponds to a given parametric value. If your application performs evaluation frequently, youll probably find the AcGePointOnCurve3d and AcGePointOnCurve2d classes more efficient (see Special Evaluation Classes on page 774). The curve functions for evaluating points are as follows:
AcGePoint2d AcGeCurve2d::evalPoint(double param) const; AcGePoint2d AcGeCurve2d::evalPoint( double param, int numDeriv, AcGeVector2dArray& derivArray) const; AcGePoint3d AcGeCurve3d::evalPoint(double param) const; AcGePoint3d AcGeCurve3d::evalPoint( double param, int numDeriv, AcGeVector3dArray& derivArray) const;
Degeneracy
Certain operations can result in the creation of degenerate entities. Degenerate means that, although the resulting object belongs to a particular class, its geometry may no longer conform to the requirements of that class. For example, if you begin with a circular arc and then set its start angle equal to its end angle, you actually have a point instead of a circular arc.
Parametric Geometry
771
Geometrically, the object is a point, but its runtime type is still a circular arc. You can use one of the isDegenerate() functions to determine whether the object is degenerate. The first version of each pair of functions returns the type. The second version returns a nondegenerate object of a different runtime type. In the previous example, it would return a point:
Adesk::Boolean AcGeCurve2d::isDegenerate( AcGe::EntityId& degenerateType, const AcGeTol&=AcGeContext::gTol) const; Adesk::Boolean AcGeCurve2d::isDegenerate( AcGeEntity2d*& pConvertedEntity, const AcGeTol&=AcGeContext::gTol) const; Adesk::Boolean AcGeCurve3d::isDegenerate( AcGe::EntityId& degenerateType, const AcGeTol&=AcGeContext::gTol) const; Adesk::Boolean AcGeCurve3d::isDegenerate( AcGeEntity3d*& pConvertedEntity, const AcGeTol&=AcGeContext::gTol) const;
Surfaces
The orientation of a surface partially determines its evaluated normal vectors. A parametric surface has two parameters, u and v, each representing the direction of the parametric lines on the surface. If you take the cross-product of the u tangent vector and the v tangent vector at the same point, you obtain a vector that is normal to the surface. This vector is the natural normal of the surface at that point. You can reverse the orientation of a surface by calling the following function:
AcGeSurface& AcGeSurface::reverseNormal()
The surface evaluator returns either the natural normal or its inverse, depending on whether reverseNormal() has been called an even or odd number of times. The following function returns a value of TRUE if the orientation of the surface is opposite to the natural orientation:
Adesk::Boolean AcGeSurface::isNormalReversed() const
772
Chapter 30
This example constructs a circle and projects it onto the XY plane. The type of the projected entity is then checked to see what type of entity it was projected into:
AcGePlane AcGePoint3d AcGeVector3d AcGeCircArc3d AcGeEntity3d plane; // Constructs XY-plane. p1(2,3,5); v1(1,1,1); circ (p1, v1, 2.0); *projectedEntity = circ.project(plane,v1);
if (projectedEntity->type() == AcGe::kEllipArc3d) ... else if (projectedEntity->type() == AcGe::kCircArc3d) ... else if (projectedEntity->type() == AcGe::kLineSeg3d) ...
The following example constructs a NURBS curve and finds the closest point on the curve to the point p1. The closest point is returned as an AcGePointOnCurve3d object from which the coordinates and parameter value are obtained:
AcGeKnotVector AcGePoint3dArray AcGePointOnCurve3d AcGePoint3d knots; cntrlPnts, pntOnCrv; p1(1,3,2);
knots.append (0.0); knots.append (0.0); knots.append (0.0); knots.append (0.0); knots.append (1.0); knots.append (1.0); knots.append (1.0); knots.append (1.0); cntrlPnts.append (AcGePoint3d(0,0,0)); cntrlPnts.append (AcGePoint3d(1,1,0)); cntrlPnts.append (AcGePoint3d(2,1,0)); cntrlPnts.append (AcGePoint3d(3,0,0)); AcGeNurbCurve3d nurb (3, knots, cntrlPnts); nurb.getClosestPointTo(p1,pntOnCrv); p2 = pntOnCrv.point(); double param = pntOnCrv.parameter();
Parametric Geometry
773
They encapsulate all the geometric information about a particular point on a curve or surface such as parameter value, model space coordinates, derivatives, and curvature. They provide an interface to the curve and surface evaluators that is simpler and more efficient than the traditional evaluator interface of most CAD systems.
The public interface to the AcGePointOnCurve2d, AcGePointOnCurve3d, and AcGePointOnSurface classes is identical except for minor differences in the member function names. For example, the AcGePointOnCurve3d class contains the function deriv(), which returns the derivative vector, while the AcGePointOnSurface class contains two functions, uDeriv() and vDeriv(),
774
Chapter 30
to return the u and v partial derivatives. The remainder of this section describes how to use the AcGePointOnSurface class, but this description applies to the AcGePointOnCurve2d and AcGePointOnCurve3d classes as well, because their interface is very similar to that of the AcGePointOnSurface class. To use the AcGePointOnSurface class to evaluate points and derivatives, you must specify which surface is to be evaluated and the parameter value at which the evaluation is to be done. The following two member functions set the surface and parameter value of an AcGePointOnSurface object:
AcGePointOnSurface& AcGePointOnSurface& setSurface (const AcGeSurface&); setParameter (const AcGePoint2d&);
After you call setSurface(), all subsequent evaluations are performed on that surface until you call setSurface() again for a different surface. Similarly, after you call setParameter(), all subsequent query functions return information pertaining to that parameter value until setParameter() is called again for a different parameter value. For example, consider if srf is an AcGeSurface object, param is an AcGePoint2d object, and pntOnSrf is an AcGePointOnSurface object, then the following code evaluates the point and first derivatives on srf at the parameter value param:
pntOnSrf.setSurface (srf); pntOnSrf.setParameter (param); AcGePoint3d pnt3d = pntOnSrf.point(); AcGeVector3d uFirstPartial = pntOnSrf.uDeriv(1), vFirstPartial = pntOnSrf.vDeriv(1);
In practice, you rarely, if ever, call setSurface() or setParameter() directly. Instead you call these functions indirectly through member functions of the AcGePointOnSurface class. For example, the point() function, which returns the model space point at a particular parameter value, has three different signatures:
AcGePoint3d AcGePoint3d point () const;
The first signature takes no parameters and assumes that the surface and parameter value have already been set by previous calls to setSurface() and setParameter(). The second signature assumes that the surface has already been set by a previous call to setSurface(), but it calls setParameter(param) to set the parameter value before evaluating. The third signature calls setSurface(srf) and setParameter(param) to set the surface and parameter value before evaluating. Only the first member function is declared as const;
775
the other two modify the object by setting the surface and/or parameter value. The direct calls to setSurface() and setParameter() can now be removed from the previous code as follows:
AcGePoint3d AcGeVector3d pnt3d = pntOnSrf.point ( srf, param ); uFirstPartial = pntOnSrf.uDeriv(1), vFirstPartial = pntOnSrf.vDeriv(1);
The first statement causes setSurface(srf) and setParameter(param) to be called before the evaluation is performed. Subsequent evaluations are performed on the same surface and at the same parameter value until setSurface() or setParameter() is called again, either directly or indirectly. Therefore, the second statement does not need to respecify either the srf or param arguments. All the evaluation functions of the AcGePointOnSurface class follow the same pattern of having three different signatures:
AcGeVector3d AcGeVector3d uDeriv (int order) const;
AcGeVector3d uDeriv ( int order, const AcGeSurface& srf, const AcGePoint2d& param); AcGeVector3d AcGeVector3d vDeriv (int order) const;
AcGeVector3d vDeriv ( int order, const AcGeSurface& srf, const AcGePoint2d& param); AcGeVector3d AcGeVector3d mixedPartial () const;
AcGeVector3d mixedPartial ( const AcGeSurface& srf, const AcGePoint2d& param); AcGeVector3d AcGeVector3d normal () const;
776
Chapter 30
When using the first constructor, you do not specify a surface or parameter value. Presumably, you set the surface and parameter value before the first evaluation. To prevent the construction of an uninitialized object, the first constructor sets the surface to AcGePlane::kXYPlane, which is just the XY plane, and sets the parameter value to the default value (0,0). The second constructor calls setSurface(srf) and sets the parameter value to the default value of (0,0). The third constructor calls setSurface(srf) and setParameter(param). The second constructor is especially useful in functions in which a surface is passed in as an argument:
void func (const AcGeSurface& srf) { AcGePointOnSurface pntOnSrf (srf); . . . }
The constructor calls setSurface(srf) so that all subsequent evaluations in this function are performed on srf. Because the AcGePointOnSurface class encapsulates both the parametric and model space information about a particular point on a surface, it is useful for functions that need to return information about one or more distinct points on a surface. For instance, the AcGeSurface class contains the member function:
void getClosestPointTo ( const AcGePoint3d& pnt3d, AcGePointOnSurface& closestPoint, const AcGeTol& = AcGeContext::gTol) const;
This function returns the closest point on the surface to the input point pnt3d. The closest point is returned as an AcGePointOnSurface object, which contains the parameter value, model space point, and other information about that particular point on the surface. All functions in the AcGe library that return an AcGePointOnSurface object as an output argument (nonconst) have already called setSurface() and setParameter() for that argument. Therefore, after calling such a function, you do not need to reset the surface or parameter value. For example, the following code obtains the parameter value, model space point, and first derivatives of the closest point on the surface srf to the point pnt3d:
// Compute the closest point on the surface to pnt3d. AcGePointOnSurface closestPoint; srf.getClosestPointTo (pnt3d, closestPoint);
777
// Get parameter value, model space point, and first derivative // vectors of closest point. AcGePoint2d param = closestPoint.parameter(); AcGePoint3d pnt3d = closestPoint.point(); AcGeVector3d uFirstPartial = closestPoint.uDeriv(1), vFirstPartial = closestPoint.vDeriv(1);
None of the calls to point(), uDeriv(), or vDeriv() needs to specify the surface or parameter value, because they were already set by getClosestPointTo(). In general, setSurface() and setParameter() should not be called unless you explicitly intend to change the surface or parameter value of the AcGePointOnSurface object. For example, the first statement in the following code indirectly calls setSurface() and setParameter(). The second and third statements are inefficient because they make unnecessary calls to setSurface() and setParameter(), using the exact same arguments as the first statement.
AcGePoint3d AcGeVector3d AcGeVector3d pnt3d = pntOnSrf.point (srf, param); uFirstPartial = pntOnSrf.uDeriv (1, srf, param); vFirstPartial = pntOnSrf.uDeriv (1, param);
The AcGePointOnCurve2d, AcGePointOnCurve3d, and AcGePointOnSurface classes not only provide a way to encapsulate the parameter space and model space information of a point on a curve or surface, they also provide a simpler and more natural interface to the curve and surface evaluators than the traditional evaluators. A typical C-style surface evaluator looks something like the following:
void evaluate ( int numDeriv, double u, double v, Point& pnt, Vector[] derivArray);
Here, you specify the parameter value (the parameter value of a surface is the 2D point whose coordinates are u, v) and request how many derivatives are to be returned. The evaluator then computes the point and requested derivatives at the specified parameter value. If requesting derivatives, you must know the order in which they are returned. For example, is the mixed partial stored in the fourth or fifth element of the array? You must also make sure that you do not pass in an array that is too small, or else memory overwrite will occur. This can be a problem when the evaluator is originally called for zero derivatives or one derivative (with an array size of 2 for derivArray) and is later changed to return two derivatives. If you forget to increase the size of derivArray, then memory overwrite occurs because the evaluator returns
778
Chapter 30
five derivative vectors (two first derivatives and three second derivatives) into an array that can only hold two vectors. With the AcGePointOnSurface class, you request point, derivative, and normal information in a simple fashion using the point(), uDeriv(), vDeriv(), mixedPartial(), and normal() functions. The names of these functions indicate clearly which values they are returning, and there is no danger of memory overwrite. You do not have to index into an array to obtain derivative vectors and run the risk of making a mistake and using the wrong index for one or more of the vectors. The AcGePointOnSurface class provides an interface to the surface evaluator, which results in simpler code that is also more readable and understandable to other programmers. In addition to providing a simpler and more natural interface to the curve and surface evaluators, the AcGePointOnCurve2d, AcGePointOnCurve3d, and AcGePointOnSurface classes provide a more efficient interface as well over the traditional evaluators. This is because each of these classes contains a pointer to a data area that can be used by the evaluators to store information between evaluations. For instance, the NURBS evaluator uses this area to store power basis matrices, which are not stored as part of the surface definition. By using this data area, the evaluators can avoid recomputing the same data that was computed in a previous evaluation and thus operate more efficiently. This data is not part of the curve or surface classes because evaluations might take place in more than one area in an alternating way, which would result in inefficient loss of the local evaluation data in switching context. This data area also allows the evaluators to be much more efficient when a transformation has been applied to the AcGePointOnSurface object. If the transformBy() function is invoked on an AcGePointOnSurface object, it causes subsequent evaluations to be transformed by the specified transformation without actually transforming the underlying surface. This means that the evaluators must apply the transformation to each point, derivative, and normal vector that they compute. By using the data area of the AcGePointOnSurface object, the evaluators can avoid having actually to apply this transformation for each evaluation. For instance, the AcGePlane class contains the data members mPoint, mUAxis, and mVAxis, which define the origin and axes of the plane. The AcGePlane evaluator evaluates a point with the following statement:
AcGePoint3d pnt3d = mPoint + param.x * mUAxis + param.y * mVAxis;
If transformBy() has been called for the AcGePointOnSurface object, then this transformation must be applied to pnt3d before it is returned to the caller. The evaluator can avoid the expense of a matrix multiply by storing
779
the transformed mPoint, mUAxis, and mVAxis in the AcGePointOnSurface data area. Then the above statement will evaluate the point in the transformed location without the extra expense of a matrix multiply. This is an especially useful ability in applications such as assembly modeling, where curves and surfaces have been transformed into assembly space by a positioning transformation.
By passing pntOnSrf to func2, the evaluator can continue to use the same data area that was used for all the evaluations in func1. If func1 does not pass the AcGePointOnSurface object to func2, then func2 must declare a new AcGePointOnSurface object, which will create a new data area and recompute data that was computed in func1. The following code executes correctly; however, it is less efficient than the previous code:
void func1 (const AcGeSurface& srf) { AcGePointOnSurface pntOnSrf (srf); ... func2 (srf); ... }
780
Chapter 30
void func2 (const AcGeSurface& srf) { AcGePointOnSurface pntOnSrf (srf); . . // Evaluate some points and derivatives, using new // pntOnSrf declared above. . }
Reusing the same AcGePointOnSurface object is important for evaluatorintensive applications, such as surface-surface intersectors or finite-element mesh generators. In the case of a surface-surface intersector, the top-level function should declare two AcGePointOnSurface objects (one for each surface) and pass these objects down through all of the lower-level routines. In this way, the application obtains maximum use of data that is saved between evaluations and obtains the maximum efficiency from its surface evaluators. To obtain the best use of the AcGePointOnCurve2d, AcGePointOnCurve3d, and AcGePointOnSurface classes, a large number of these objects should never be in scope at the same time for the same curve or surface. In most situations, only one of these objects should be in scope for a particular curve or surface.
to be implemented.
AcGeLibVersion encapsulates the version of AcGe. It is maintained by the system. The user of AcGe keeps track of the version of AcGe being used through the global variable, AcGe::gLibVersion. All writes and reads of AcGe entities are performed in the context of the version of AcGe being used. Typically, the user must write AcGe::gLibVersion to the file before writing any other AcGe entity (correspondingly, it would be the first AcGe object read from a file in a
subsequent read). The following functions are used to write and read this object (also see the following discussion of the AcGeFileIO class):
Acad::ErrorStatus outFields (AcGeFiler*, const AcGeLibVersion&) Acad::ErrorStatus inFields (AcGeFiler*, AcGeLibVersion&)
781
The file I/O functions of AcGe entities are scoped within the AcGeFileIO class. These are a collection of static functions for reading and writing of AcGe entities.
readBoolean(Adesk::Boolean*); writeBoolean(Adesk::Boolean); readBool(bool*); writeBool(bool); readChar(char*); writeChar(char); readShort(short*); writeShort(short); readLong(long*); writeLong(long); readUChar(unsigned char*); writeUChar(unsigned char); readUShort(unsigned short*); writeUShort(unsigned short); readULong(unsigned long*); writeULong(unsigned long); readDouble(double*); writeDouble(double); readPoint2d(AcGePoint2d*); writePoint2d(const AcGePoint2d&); readPoint3d(AcGePoint3d*); writePoint3d(const AcGePoint3d&); readVector2d(AcGeVector2d*); writeVector2d(const AcGeVector2d&); readVector3d(AcGeVector3d*); writeVector3d(const AcGeVector3d&);
782
Chapter 30
(AcDbDwgFiler*); ();
mpFiler;
// Inline methods. // inline AcGeDwgFiler::AcGeDwgFiler(AcDbDwgFiler* filer) : mpFiler(filer) {} inline AcGeDwgFiler& AcGeDwgFiler::setDwgFiler(AcDbDwgFiler* filer) { mpFiler = filer; return *this; } inline AcDbDwgFiler* AcGeDwgFiler::dwgFiler() { return mpFiler; }
The next code fragment illustrates the implementation of a few functions. Other functions are implemented in the same manner:
Acad::ErrorStatus AcGeDwgFiler::readBoolean(Adesk::Boolean* data) { return mpFiler ? mpFiler->readBoolean(data) : Acad::eNoDatabase; } Acad::ErrorStatus AcGeDwgFiler::writeBoolean(Adesk::Boolean data) { return mpFiler ? mpFiler->writeBoolean(data) : Acad::eNoDatabase; }
The next example illustrates a persistent class that uses AcGeExternalSurface. The code after the class declaration illustrates reading and writing of the external surface class. In particular, note that AcGe::gLibVersion is written out first and subsequently read first prior to writing or reading of the external surface class:
class class class class AcGeExternalCurve2d; AcGeExternalCurve3d; AcGeExternalBoundedSurface; AcGeExternalSurface;
783
class AcGePersistentXEnt : public AcDbObject { public: ACRX_DECLARE_MEMBERS (AcGePersistentXEnt); AcGePersistentXEnt (); ~AcGePersistentXEnt (); Acad::ErrorStatus dwgOutFields (AcDbDwgFiler*) const; Acad::ErrorStatus dwgInFields (AcDbDwgFiler*); AcGeExternalSurface* mpXSrf; };
784
Chapter 30
// Only interested in a file filer. // if (filer->filerType() != AcDb::kFileFiler) return stat; AcGeDwgFiler geDwgFiler(filer); AcGeLibVersion gelibVersion; if ((stat = AcGeFileIO::inFields(&geDwgFiler, gelibVersion)) != Acad::eOk) return stat; acutPrintf("\n... Reading External Surface\n"); mpXSrf = new AcGeExternalSurface; ADS_ASSERT(mpXSrf); if ((stat = AcGeFileIO::inFields(&geDwgFiler, *mpXSrf, gelibVersion)) != Acad::eOk) return stat; return stat; }
785
786
31
In this chapter
I Overview of the AcBr Library I Domain I Limitations I Class Hierarchy I Topological Objects I AcBr Class Descriptions I Enumerated Types I Building an Application
This section shows how to use the AcBr library (acbr15.dll) to access topological, geometric, and analytic data contained in certain AutoCAD entities, such as solids, bodies, and regions (that is, objects of class AcDb3dSolid, AcDbBody, and AcDbRegion), and myriad derived types (for example, objects of class AcDbPart,
AcAsSurface,
the purpose of brevity, this section refers to all of these objects collectively as solids.
787
I I
represents a solid; it encloses one or more volumes. represents a planar surface; it might contain multiple coplanar surfaces. AcDbBody is the concrete base class for all boundary representation objects not covered by AcDb3dSolid or AcDbRegion, including derived types defined by Autodesk Mechanical Desktop and client applications. AcDbPart represents a solid or sheet body in the context of an assembly or feature in Autodesk Mechanical Desktop. AcAsSurface represents a single surface as a sheet body in Autodesk Mechanical Desktop.
The AcBr library provides read-only access to a subset of modeling data contained in AutoCAD solids. These solids are not required to be database active, and may be created in any of the following ways:
I I I I I
AutoCAD object creation commands (such as SPHERE), or equivalent AutoLISP scripts. Autodesk Mechanical Desktop object creation commands (such as ADREVOLVE), or equivalent AutoLISP scripts. Invocation of the AutoCAD EXPLODE command on a part or assembly in Autodesk Mechanical Desktop. File importation using OPEN, DXFIN, ACISIN, ADSATIN, VDAFSIN, STEPIN, AMIDFIN, or IGESIN. Programmatic instantiation of primitives using
AcDb3dSolid::createFrustum(), AcDb3dSolid::createBox(), AcDb3dSolid::createWedge(), AcDb3dSolid::createSphere(), AcDb3dSolid::createTorus(), AcDbRegion::createFromCurves().
Transferring entity or subentity data into your application for display, analysis, or manipulation. Locating particular features of interest in the solid and querying for associated data, such as geometry. Transferring entity data to another modeling system (that is, data exchange). Meshing the surface data in the solid for display, analysis, or manipulation. Supporting analysis (such as point and line containment, bounding blocks, and mass properties).
788
Chapter 31
Domain
AutoCAD solids are boundary representations (often referred to as B-rep models), consisting of a collection of topological connectivity objects and associated geometric boundary objects. The topological objects are defined in the AcBr library and are described later in this section, whereas the geometric objects are defined in the AcGe library. Objects defined or generated by the AcBr library reside in three-dimensional Euclidean model space (E3). The only exceptions are geometric objects defined in the two-dimensional parameter space of a surface (such as parameter curves and parameter points). In general, only the 2-manifold topological domain is supported by the AcBr library. Singularities (which are geometric degeneracies) are supported in order to represent the apex of a cone, but wire bodies and mixed dimensionality solids (which may include dangling wires and faces) are not supported; nor can they be realized in AutoCAD. The general nonmanifold domain is a superset of the 2-manifold domain, and allows distinct solid volumes to touch at single points, curves, or faces; and allows any combination of wireframe, sheet, and solid objects. The following nonmanifold objects are supported by AutoCAD and the AcBr library:
I I
Two 2-manifold solids united along a shared edge or vertex An AcDbBody object containing a single face
A topological object may be unbounded (that is, it may have no lower dimensional bounding topology) only in the following cases:
I
A closed surface, which is intrinsically bounded in both the u and v directions (such as a full torus or sphere), is represented by a face that has no loop boundaries. A closed curve, which is intrinsically bounded (such as a full circle or ellipse), is represented by an edge that has coincident start and end vertices.
Domain
789
Limitations
Certain operations cannot support nonuniform scaling. This includes all functions that return an external curve or surface (including NURBS surfaces). The entire chain of transforms from the subentity path is cached at the time that an AcBr objects subentity path is set (for efficiency reasons). If a block reference is moved, it will point to a new transform matrix but the AcBr object will not know that its cached transform is out of date. If an insert is changed to refer to a different AutoCAD entity, the subentity path simply no longer has relevance and should be updated to reflect the new entity reference before being used to reinitialize all relevant AcBr objects. Singularities (such as the apex of a cone) map to edges in AutoCAD and thus can be used to initialize an AcBrEdge for the express purpose of querying for the vertex, but cannot be queried for curve geometry or used to set an AcBrLoopEdgeTraverser. They can also be accessed using an AcBrLoopVertexTraverser, as a singularity corresponds to a single loop boundary of a face. Just as with AcDbObject pointers, AcBr objects cannot be used once the AutoCAD database object has been closed in the database or goes out of scope; they are not persistent. Any change to the database object will be flagged as an eBrepChanged error, unless the validation level has been set to ignore database changes. An out-of-scope or closed database object will generally cause Acad::eNotInDatabase to be returned.
790
Chapter 31
Class Hierarchy
The AcBr class hierarchy is a subset of the ObjectARX class hierarchy, and defines the following classes:
AcBrEntity AcBrBrep AcBrComplex AcBrEdge AcBrFace AcBrLoop AcBrShell AcBrVertex AcBrMeshControl AcBrMesh2dControl AcBrMeshEntity AcBrElement AcBrElement2d AcBrNode AcBrTraverser AcBrBrepComplexTraverser AcBrBrepEdgeTraverser AcBrBrepFaceTraverser AcBrBrepShellTraverser AcBrBrepVertexTraverser AcBrBrepShellTraverser AcBrBrepVertexTraverser AcBrComplexTraverser AcBrEdgeLoopTraverser AcBrElement2dNodeTraverser AcBrFaceLoopTraverser AcBrLoopEdgeTraverser AcBrLoopVertexTraverser AcBrMesh2dElement2dTraverser AcBrShellFaceTraverser AcBrVertexEdgeTraverser AcBrVertexLoopTraverser AcBrHit AcBrHitPath AcBrMesh2dFilter
Note that AcBr objects are not derived from AcDbObject, and therefore cannot be registered with the AutoCAD database.
Class Hierarchy
791
Topological Objects
Topological objects are either primary or secondary, depending on whether they are bound to a specific topological dimension. Primary topological objects are used to cover an evaluated model space completely. They are defined in terms of point sets and are also referred to as n-simplexes, where n is their topological dimension. A 0-simplex is a vertex, a 1-simplex is an edge, a 2-simplex is a face, and a 3-simplex is a complex. They do not include their boundaries, but they can be bounded by simplexes of any lower dimension. The primary topological objects are the following: Complex A connected topologically three-dimensional region of points R3 in E3. It is a volume constructed out of vertices, faces, and edges. A complex is usually bounded by one or more shells. A connected topologically two-dimensional region of points R2 in E3. It is a bounded, orientable subset of a surface on a shell boundary of a complex. A face is usually bounded by one or more loops. A connected topologically one-dimensional region of points R1 in E3. It is a bounded, orientable subset of a curve on a loop boundary of a face. An edge is usually bounded by one or two vertices. A connected topologically zero-dimensional region of points R0 in E3. It is a single point on a face. A vertex is bounded only by itself.
Face
Edge
Vertex
The geometry returned by each of these primary topological objects can be queried further using the Autodesk Geometry Library. Secondary topological objects are connected collections of primary topological objects, and are not necessarily bound to a specific topological dimension. They represent the boundary mapping from a higher-dimension simplex to a set of lower-dimension simplexes that define a connected part of its boundary. Each primary topological object belongs to at least one secondary topological object.
792
Chapter 31
The secondary topological objects are the following: Brep A collection of everything in an evaluated space; that is, a collection of all of the primary and other associated secondary topological objects for a unique E3. At the very least, this collection must contain a single complex. An unordered collection of faces that bound a complex. At the very least, this collection must contain a single face. There may be at most one exterior shell, and there must be an exterior shell for there to be interior shells (voids). An ordered collection of edges and vertices that form the connected boundaries of a face, which may consist of a single vertex (for a singularity, such as the apex of a cone) or an ordered connected sequence of edges. There may be at most one exterior loop, and there must be an exterior loop for there to be interior loops (holes).
Shell
Loop
Topological Objects
793
Global Searches
Global traversers (such as AcBrBrepFaceTraverser and AcBrBrepEdgeTraverser) provide the ability to traverse all of the topological objects in the solid (complexes, shells, faces, edges, vertices).
794
Chapter 31
AcBrBrepShellTraverser
AcBrBrepFaceTraverser
AcBrBrepEdgeTraverser
AcBrBrepVertexTraverser
AcBrShellFaceTraverser
AcBrFaceLoopTraverser
AcBrLoopEdgeTraverser
AcBrLoopVertexTraverser
AcBrVertexLoopTraverser
AcBrVertexEdgeTraverser
AcBrEdgeLoopTraverser
Topological Objects
795
An edge can have at most two vertices. These are exposed by explicit functions in the AcBrEdge class (see the next section), as a traverser would be wasteful for such a trivial adjacency. A loop can have many vertices, but may have as few as one (in the case of a single edge, or in the case of singularity, where there is no edge geometry, such as the apex of a cone). The LoopVertex traversal covers both the general list of vertex boundaries on a face as well as singularities. This list may be more economical than dumping the edges on a loop, if the only thing of interest is the point geometry for the face boundary. This class defines the functions that are related to the radial ordering of faces that share a common edge. In order to provide the tightest coupling with edge list traversals (AcBrLoopEdgeTraverser), the face is represented by its loop boundary at the shared edge. The setEdgeAndLoop() function sets the edge owner and the loop starting position. The setEdge() function sets the edge owner and the loop starting position. The loop position cannot be set separately from the edge, as radial traversals should be tightly coupled with facecontextual edge lists (that is, AcBrLoopEdgeTraverser).
EdgeLoop traversal
VertexLoop traversal
This class defines the functions that are related to the radial ordering of faces that share a common vertex. To provide the tightest coupling with edge list traversals (AcBrLoopEdgeTraverser), the face is represented by its loop boundary at the shared vertex. The setVertexAndLoop() function sets the vertex owner and the loop starting position. The setVertex() function sets the vertex owner and sets the loop starting position. The loop position cannot be set separately from the vertex, as radial traversals should be tightly coupled with face-contextual vertex lists (that is, AcBrLoopVertexTraverser).
796
Chapter 31
AcBrElement2dNodeTraverser
MeshElement traversal
This class defines the functions that are pertinent to a 2D element. It is used to seed a downward hierarchical traversal of a 2D mesh, or to traverse all of the unique 2D elements and nodes in a 2D mesh. This class defines the functions that are pertinent to a node in the context of a 2D element. It is used to get access to node data and the geometry of the original surface, such as surface normals and UV parameter pairs. Nodes are used by more than one mesh element and may be associated with more than one surface since there is node sharing at the common boundaries of the original surfaces.
ElementNode traversal
797
Entity Classes
Boundary representation objects are typically built using a default AcBr constructor and then initialized either with a set() function or with a traverser and one of its get* functions. All AcBr classes support copy constructors; assignment operators;
isEqualTo(), isNull(), and set() and get() semantics; and other functions
Containment Classes
Containment objects are never built directly by the user. They are returned by line containment queries on entities derived from AcBrEntity. The AcBrHit class is a containment class.
Mesh Classes
Mesh objects are never built directly by the user, except where noted in the ObjectARX Reference. They are returned by mesh traversal queries. The mesh classes include the following:
I I I I I I I I I
798
Chapter 31
Traverser Classes
Traverser objects are typically built using a default AcBrTraverser* constructor and then initializing with one of the set* functions. Note that the list owner must be set before the list position can be set independently, to provide context. All classes derived from AcBrTraverser support copy constructors, assignment operators, isEqualTo(), and isNull(), along with general traversal functions. The initializer functions are semantically bound to the AcBr types appropriate to the specific traverser (that is, the two types contained in the derived traverser class name, such as AcBrBrep and AcBrEdge for AcBrBrepEdgeTraverser). All initializer functions reset the criteria for next() and done(). They fall into the general algorithmic categories as follows:
I setListOwnerAndCurrentPosition
from another traverser, using its list owner as current position and its current position as list owner (that is, swap the list owner and current position). This algorithm is only valid for mapping between associated traversers such as AcBrLoopEdgeTraverser and AcBrEdgeLoopTraverser. setListOwnerAndCurrentPosition from an AcBr object, using it as the current position and its owner as the list owner. This algorithm is only valid in cases where the list owner is unambiguous, such as the shell owner of a face on setting AcBrShellFaceTraverser. setListOwner from another traverser, using its current position as list owner and defaulting the current position to the first position in the new adjacency list. This algorithm is only valid for setting downward hierarchical traversers using another downward hierarchical traverser from the next level up (such as using an AcBrShellFaceTraverser to initialize an AcBrFaceLoopTraverser), or for setting upward hierarchical traversers using another upward hierarchical traverser from the next level down (such as using an AcBrVertexEdgeTraverser to initialize an AcBrEdgeLoopTraverser). setListOwner from an AcBr object, using it as the list owner and defaulting the current position to the first position in the new adjacency list. This algorithm is valid for all traverser types. setCurrentPosition from an AcBr object, using it as the current position in an already established list. This algorithm is valid for most traverser types but requires that the list owner has already been set previously.
799
AcBrTraverser AcBrBrepComplexTraverser AcBrBrepShellTraverser AcBrBrepFaceTraverser AcBrBrepEdgeTraverser AcBrBrepVertexTraverser AcBrComplexShellTraverser AcBrShellFaceTraverser AcBrFaceLoopTraverser AcBrLoopEdgeTraverser AcBrLoopVertexTraverser AcBrVertexLoopTraverser AcBrVertexEdgeTraverser AcBrEdgeLoopTraverser AcBrMesh2dElement2dTraverser AcBrElement2dNodeTraverser
Enumerated Types
The AcBr struct contains enumerated types that are unique to the AcBr library and that are used as return codes or on the argument list of local class functions. The various enum fields are described in the following sections.
Validation Level
The AcBr::ValidationLevel enum sets the level of validation for an AcBr object. If kFullValidation (the default upon object instantiation) is specified, every function that accesses the brep topology (directly or indirectly) first checks the associated AutoCAD database object to see if it has been modified since the AcBr object was last set. This is an expensive operation, but it guarantees all brep data is within scope. If kNoValidation is specified, the database is not checked unless it is critical to the completion of the functions
800
Chapter 31
tasks. This is more efficient but does not guarantee the brep data is within scope. Setting the validation level on an object-by-object basis prevents any collisions between applications loaded simultaneously.
ShellType
The AcBr::ShellType enum classifies a shell as interior, exterior, and so on. Peripheral shells are returned as kShellExterior, and there is only one such shell per brep or region (by industry convention). Voids are returned as kShellInterior, and there may be several per brep or complex (providing there is also an exterior shell).
LoopType
The AcBr::LoopType enum classifies a loop as interior, exterior, and so on. Peripheral loops are kLoopExterior, and there is only one such loop per face (by industry convention). Holes are returned as kLoopInterior, and there may be several per face (providing there is also an exterior loop). Cones and cylinders (whether with an elliptical or circular base) have at least two base loops (if they are complete in both u and v), which are returned as kLoopWinding as opposed to kLoopExterior due to the restriction of there being just one exterior loop (along with the fact that neither base loop is a hole). Singularities (such as the apex of a cone) are returned as kLoopUnclassified. All loops on spheric and toric surfaces, as well as closed periodic NURBS, return kLoopUnclassified.
Building an Application
An application that uses the AcBr library must have the library file acbr15.dll available to link against. More importantly, the library is needed to ensure proper registration of the AcBr classes with ObjectARX at runtime.
Building an Application
801
Therefore it is important that acbr15.dll be explicitly loaded by the application, if it has not already been loaded by the modeler or another application. The best way to ensure this is to use acrxDynamicLoader() and acrxClassDictionary() to load acbr15.dll and to check if it has been loaded. The following code fragment provides an example:
AcRx::AppRetCode acrxEntryPoint(AcRx::AppMsgCode msg, void* pkt) { switch (msg) { case AcRx::kInitAppMsg: if (!acrxClassDictionary->at("AcBrEntity")) { acrxDynamicLinker->loadModule("acbr15.dll", 1); acutPrintf("\n acbr15 loaded \n"); } acedRegCmds->addCommand( "MY_APP", "MY_CMD", "MY_CMD", ACRX_CMD_MODAL, &myCmdImp); acrxUnlockApplication(pkt); // try to allow unloading break; case AcRx::kUnloadAppMsg: acedRegCmds->removeGroup("MY_APP"); break; default: break; } return AcRx::kRetOK; }
802
Chapter 31
Index
.arx file extension, 692 .dbx file extension, 692 3D geometry, 766
A
abortDeepClone() function, 502 abortInsert() function, 505 abortTransaction() function, 453 abortWblock() function, 503 AC_DECLARE_EXTENSION_MODULE macro, 171 AC_IMPLEMENT_EXTENSION_MODULE macro, 171, 188 acad.lib library, 7 acad.unt file, 272 ACAD_PROXY_ENTITY type, 391 ACAD_PROXY_OBJECT type, 391 AcApDocManager class, 417 setDefaultFormatForSave() function, 62 AcApDocManagerReactor class, 418 AcApDocument class, 61, 417 formatForSave() function, 62 AcApDocumentIterator class, 417 AcApLayoutManager class, 158 AcApLongTransactionManager class, 70 AcApLongTransactionReactor class, 70 AcApProfileManagerReactor class, 585 AcAxOleLinkManager class, 630 AcBr library class hierarchy, 791 containment classes, 798
AcBr library (continued) entity classes, 798 enumerated types, 800 mesh classes, 798 traverser classes, 799 accessing AutoLISP variables, 252 current document and related objects, 428 databases with noncurrent documents, 428 symbol tables, 245 system variables, 251 AcDb library, 10 class hierarchy, 11 acdb15.lib library, 7 AcDb2dPolyline class, 136 setElevation() function, 136 AcDb2dVertex class, 136 acdbAngToF() function, 269 acdbAngToS() function, 269 AcDbBlockTable class, 145 AcDbBlockTableRecord class, 98 appendAcDbEntity() function, 85 AcDbBlockTableRecordIterator class, 150 AcDbCircle class, creating in AutoCAD, 21 AcDbCompositeFilteredBlockIterator class, 78 AcDbCurve class, 136 overriding functions, 296 AcDbDatabase class, 396 deep clone operation, 465, 469 deepCloneObjects() function, 466 dwgFileWasSavedByAutodeskSoftware() function, 80 external reference pre- and post-processing, 75
803
AcDbDatabase class (continued) insert() function, 65, 466 restoreForwardingXrefSymbols() function, 76 restoreOriginalXrefSymbols() function, 76 wblock() function, 63 wblockCloneObjects() function, 70 xrefBlockId() function, 76 AcDbDatabaseReactor class, 396, 398 notification events, 400 AcDbDatabaseSummaryInfo class, 79 custom fields, 80 predefined fields, 79 AcDbDictionary class, 142 AcDbDimStyleTable class, 145 acdbDisToF() function, 269 acdbDxfOutAsR14() function, 62 acdbEntDel() function, 219, 226 acdbEntGet() function, 226, 237 acdbEntGetX() function, 237, 240, 281 AcDbEntity class, 98, 358, 359, 366 acedSSGet() function, 110 acedSSNameX() function, 110 colorIndex() function, 101 default protocol extension class and, 374 deriving a custom entity class, 386 deriving a custom entity class from, 353 extending entity functionality, 374 using AcEdJig, 374386 deriving entities from, 353 draw() function, 104 explode() function, 104, 122, 124 functions rarely overridden, 356 functions usually overridden, 355 getEcs() function, 135 getGeomExtents() function, 104 getGripPoints() function, 104 getGsMarkersAtSubentPath() function, 105 getOsnapPoints() function, 104, 105, 106 getStretchPoints() function, 104 getSubentPathsAtGsMarker() function, 105 getTransformedCopy() function, 104, 106 graphics generation and, 453 Graphics System Markers, 108 highlight() function, 105, 110 intersectWith() function, 104, 106, 107 layer() function, 103 layerId() function, 104 linetype() function, 102 linetypeScale() function, 102 list() function, 104 moveGripPointsAt() function, 104 moveStretchPointsAt() function, 104 proxy object class derived from, 390 setColorIndex() function, 101 setLayer() function, 103 setLinetype() function, 102
setLinetypeScale() function, 102 setVisibility() function, 103 subentPtr() function, 105, 111 transformation functions, 366 transformBy() function, 104, 106 viewportDraw() function, 104 visibility() function, 103 worldDraw() function, 104 AcDbEntityReactor class, 398 acdbEntLast() function, 251 acdbEntMake() function, 146, 230, 231, 234 acdbEntMod() function, 229, 230, 236 acdbEntNext() function, 217, 250 acdbEntUpd() function, 236 acdbFail() function, 525 AcDbFilter class, 78 AcDbFilteredBlockIterator class, 78 AcDbFullSubentPath class, 109 acdbGetSummaryInfo() function, 80 acdbGetSummaryInfoManager() function, 80 AcDbGroup class newIterator() function, 152 setColor() function, 152 setLayer() function, 152 setLinetype() function, 152 setVisibility() function, 152 acdbHandEnt() function, 218, 226, 242 AcDbHostApplicationServices class, 694 AcDbHyperlink class, 138 AcDbHyperlinkCollection class, 138 AcDbHyperlinkPE class, 138 AcDbIdMapping class, 506 AcDbIndex class, 78 AcDbIndexFilterManager namespace, 78 acdbInters() function, 256 AcDbIsPersistentReactor class, 404 acdbIsPersistentReactor() function, 404 AcDbLayerTable class, 145 AcDbLayerTableRecord class, 147 creating and modifying, 148 AcDbLayout class, 155, 158 AcDbLayoutManager class, 158 AcDbLayoutManagerReactor class, 158 AcDbLine class, creating in AutoCAD, 21 AcDbLinetypeTable class, 145 AcDbLongTransaction class, 70 AcDbLongTransWorkSetIterator class, 70 AcDbMatchProperties class, 514 as protocol extension class, 374 AcDbMline class, 155 AcDbMlineStyle class, 155 AcDbObject class, 82, 396, 397 addReactor() function, 399 custom notification and, 400 deep cloning with, 465 deriving from, 293 erase() function, 93
804
Index
AcDbObject class (continued) new() function, 84 notification events, 400 overriding functions, 294 proxy object class derived from, 390 reactors, 397, 404 setXData() function, 86 undo mechanism and, 453 wblockClone() function, 466 xData() function, 86 AcDbObjectId class, 467 AcDbObjectIds, translating ads_names to, 83 AcDbObjectReactor class, 396, 398 notification events, timing of, 412 functions, 399, 400 transient reactors derived from, 404 acdbOpenObject() function, 82 with close() function, 452 AcDbPlotSettings class, 158 AcDbPlotSettingsValidator class, 158 AcDbProxyEntity class, 390 AcDbProxyObject class, 390, 393 acdbPutSummaryInfo() function, 80 acdbRegApp() function, 240 AcDbRegAppTable class, 145 acdbRToS() function, 269 acdbSaveAsR13() function, 63 acdbSaveAsR14() function, 63 AcDbSummaryInfoManager class, 80 AcDbSummaryInfoReactor class, 80 acdbTblNext() function, 245 acdbTblSearch() function, 245, 246 AcDbTextStyleTable class, 145 AcDbTextStyleTableRecord class, 467 associating with AcGiTextStyle, 742 AcDbTransactionManager class, 698 undo mechanism and, 453 AcDbUCSTable class, 145 AcDbViewportTable class, 145 AcDbViewTable class, 145 AcDbWblockCloneFiler class, 486 acdbXdRoom() function, 242 acdbXdSize() function, 242 AcDbXML extending the AcDbXML schema, 598 overview, 596 schema, 596 AcDbXmlObjectProtocol, 599 AcDbXrecord class, 160 AcDbXrefFileLock class, 76 acdbXrefReload() function, 76 AcEd library, 9 class hierarchy, 10 acedAlert() function, 525 acedapi.lib library, 7 acedArxLoaded() function, 531
acedArxUnload() function, 531 acedCmd() function, 248, 249 acedCommand() function, 248, 249 acedDefun() function, 522, 525 acedDragGen() function, 214, 261, 266, 533 acedEntSel() function, 216, 218, 261 acedFindFile() function, 253 acedGetArgs() function, 524 acedGetDist() function, 261 acedGetFileD() function, 254 acedGetFunCode() function, 524 acedGetInput() function, 234 acedGetKword() function, 261 acedGetPoint() function, 262 acedGetReal() function, 266 acedGetString() function, 261 acedGetSym() function, 252, 524 acedGetVar() function, 251 acedGraphScr() function, 278 acedGrDraw() function, 278 acedGrRead() function, 278 acedGrText() function, 278 acedGrVecs() function, 214, 533 acedInitGet() control bits, 263 acedInitGet() function, 261, 262, 266 AcEdInputContextReactor class, 566 AcEdInputPointManager class, 565 acedInvoke() function, 526 AcEditorReactor class, 75, 397, 398, 400, 502 notification functions, 400 transactions and, 451 AcEdJig class, 374, 374386 adding entity to the database, 381 deriving from, 354, 375 drag loop, 376, 377 drag() function, 375 general steps for using, 375 implementing sampler(), update(), and entity() functions, 378381 cursor types (table), 378 display prompt, 378 keyword list, 378 user input controls (list), 379 sample code, 381386 setting up parameters for drag sequence, 376 acedMenuCmd function(), 277 acedNEntSel() function, 110, 216, 219, 261 acedNEntSelP() function, 110, 216, 261, 533 acedOsnap() function, 254, 533 acedPrompt() function, 276 acedPutSym() function, 252, 524 acedRedraw() function, 278 acedRegFunc() function, 527 acedSetVar() function, 251 acedSSAdd() function, 211, 217 acedSSDel() function, 211
Index
805
acedSSFree() function, 205 acedSSGet() function, 110, 202 DXF group codes, 208 example, 203 selection set options, 203 acedSSLength() function, 212 acedSSMemb() function, 212 acedSSName() function, 212 example, 213 acedSSNameX() function, 110 acedSSXform() function example, 213 acedTablet() function, 279 acedTextBox() function, 257 acedTextPage() function, 278 acedTextScr() function, 278 acedTrans() function, 274 acedUsrBrk() function, 267 acedVports() function, 255 acedXformSS() function, 213, 533 AcGe library, 12, 761 class hierarchy, 13, 763 acge15.lib library, 8 AcGeFileIO class, 781 AcGeFiler class, 781 AcGeLibVersion class, 781 AcGeTol class, 765 AcGi library, 11, 719 API for ObjectDBX, 699 class hierarchy, 12, 722 acgiapi.lib library, 8 AcGiDrawable class, 720 AcGiEdgeData class, 734 AcGiFaceData class, 734 AcGiFillType, 727 AcGiSubEntityTraits class, 358, 359, 726 setting attribute values, 358 AcGiTextStyle class, 740 associating with AcDbTextStyleTableRecord, 742 AcGiViewport class, 359 AcGiViewportGeometry class, 359 AcGiWorldDraw class, 358 AcGiWorldGeometry class, 358 AcGix API, 700 AcGixSimpleView class, 702 AcGixWhipView class, 704 AutoCAD features not supported, 701 getDatabaseMutex() function, 703 releaseDatabaseMutex() function, 703 representing TrueType fonts in 3D space, 702 SimpleView, 702 TrueType font elaboration, 701 using the database mutex, 703 ViewAcDb viewer, 704 WhipView, 704
acgixAllocateWhipView() function, 704 AcGixSimpleView class, 702 AcGixWhipView class, 704 acProfileManagerPtr() function, 584 acquireAngle() function, 379 acquireDist() function, 379 acquirePoint() function, 379 acquireXXX() function, 379 AcRx library, 8 class hierarchy, 9 ACRX_DECLARE_MEMBERS macro, 288 ACRX_DXF_DEFINE_MEMBERS macro, 390, 392 ACRX_NO_CONS_DEFINE_MEMBERS macro, 289 ACRX_X_CALL macro, 513 acrx15.lib library, 7 acrxAbort() function, 525 AcRxDictionary class, 8 AcRxDLinkerReactor class, 398 acrxEntryPoint() function, 34, 185, 186 AcRxObject class, 8, 396 cloning with, 467 function overriding, 296 acrxProductKey() function, 665 active document, 418 ActiveX Automation, 613 AcAxOleLinkManager class, 630 accessing COM interfaces from ObjectARX, 615 adding custom objects and entities to object model, 644 adding functionality to object model, 642 additional requirements for COM Objects, 633 ATL templates, 634 ATL templates provided by Autodesk, 635 AutoCAD implementation, 628 axtempl.h file, 635 building and registering a COM DLL, 649 creating a registry file, 637 creating the COM Object, 632 document locking, 636 exposing Automation functionality, 639 IAcadBaseObject interface, 629 implementation of Automation objects, 633 interacting with AutoCAD, 635 MFC access to AutoCAD ActiveX Automation, 615 non-MFC access to AutoCAD ActiveX Automation, 621 registry layout for COM objects, 632 relationship between AcDbObjects and Automation objects, 628 setting up an ATL project file, 640 writing a COM wrapper, 642
806
Index
ActiveX Automation (continued) AcTransaction class, 449, 698 for newly created objects, 452 for obtaining object pointers from object IDs, 451 open and close mechanism with, 453 AcTransactionManager class, 698 flushGraphics() function, 453 for newly created objects, 452 graphics generation and, 453 nesting transactions with, 449 obtaining, 449 queueForGraphicsFlush() function, 453 AcTransactionReactor class, 398, 698 actrTransactionManager macro, 449 AcUi library button classes, 183 CAdUiPickButton class, 184 CAdUiSelectButton class, 184 combo box controls, 179 CAcUiAngleComboBox class, 179 CAcUiNumericComboBox class, 180 CAcUiStringComboBox class, 180 CAcUiSymbolComboBox class, 180 control bar classes, 176 CAcUiDockControlBar class, 177 dialog classes, 175 CAcUiAlertDialog class, 176 CAcUiDialog class, 175 CAcUiFileDialog class, 176 CAcUiTabChildDialog class, 176 CAcUiTabMainDialog class, 175 edit controls, 177 CAcUiAngleEdit class, 178 CAcUiEdit class, 177 CAcUiHeaderCtrl class, 179 CAcUiListBox class, 178 CAcUiListCtrl class, 179 CAcUiNumericEdit class, 178 CAcUiStringEdit class, 178 CAcUiSymbolEdit class, 178 hierarchy, 173 most recently used combo boxes, 180 CAcUiArrowHeadComboBox class, 181 CAcUiColorComboBox class, 181 CAcUiLineWeightComboBox class, 181 CAcUiMRUComboBox class, 180 CAcUiMRUListBox class, 182 CAcUiPlotStyleNamesComboBox class, 182 CAcUiPlotStyleTablesComboBox class, 182 overview, 172 acui.h file, 172 acutAngle() function, 256
acutBuildList() function, 204, 249, 549 acutCvUnit() function, 272 acutDistance() function, 256 acutGetVar() function, 584 acutPolar() function, 256 acutPrintf() function, 276 acutRelRb() function, 547 acutWcMatch() function, 281, 282 acutWcMatchEx() function, 282 ADC See AutoCAD DesignCenter API AddExtendedTabs() function, 185 adding entities to the database, 381 object-specific data, 85 addPersistentReactor() function, 398, 404 AddTab() function, 185, 186 addX() function, 512 ADS functions, 520 ads_binary structure, 552 ads_command() function, 450 ads_matrix data type, 213 ads_name, 202 translating AcDbObjectIds to, 83 ads_point data type, 532 ads_point_set() macro, 532 ads_real data type, 532 adscodes.h file, 168 AdUi library button classes, 182 CAdUiBitmapButton class, 183 CAdUiBitmapStatic class, 183 CAdUiDropSite class, 183 CAdUiOwnerDrawButton class, 182 CAdUiToolButton class, 183 combo box controls, 179 CAdUiComboBox class, 179 control bar classes, 176 CAdUiDockControlBar class, 176 dialog classes, 174 CAdUiBaseDialog class, 174 CAdUiDialog class, 175 CAdUiFileDialog class, 175 CAdUiTabChildDialog class, 175 CAdUiTabMainDialog class, 175 edit controls, 177 CAdUiEdit class, 177 hierarchy, 173 messages, 173 overview, 172 tab extensibility, 176 CAdUiTab class, 176 CAdUiTabExtensionManager class, 176 tip windows, 174 CAdUiDrawTextTip class, 174
Index
807
AdUi library (continued) CAdUiTextTip class, 174 CAdUiTipWindow class, 174 adui.h file, 172 AFX_EXTENSION_MODULE, 170 AfxSetResourceHandle() function, 170 AIG See Application Interoperability Guidelines angle conversions, 269 examples, 271 angles, between 3D vectors, 767 anonymous blocks, 234 append() function, 381 appendAcDbEntity() function, 496 Application Interoperability Guidelines (AIG), 6 application names registering, 240 registering, example, 240 applications basic example, 37 communicating between, 526 configuration, 583 creating, 27, 28 debugging with dynamic MFC, 169 execution context, 435 external, 530 initializing, 35 interoperability, 6 listing loaded, 42, 51 loading, 41, 52 MDI requirements, 423 messages sent to ObjectARX, 29 proxy objects and, 390, 394 reactors in, 396 result codes, 527 running from AutoLISP, 53 sequence of events in, 33 unloading, 36, 42, 52, 394, 513 unlocking, 42 using protocol extension functionality, 513 Windows system registry entries in, 44 applyPartialUndo() function, 328, 329 arc primitive, 739 argument lists in AutoLISP and C, 520 ARX command, 51 AsdkPoly class overriding intersectWith() function, 368 transformation functions and, 366 with grip points, 363 assertNotifyEnabled() function, 298 assertReadEnabled() function, 298, 301, 304 assertWriteEnabled() function, 298, 301, 304, 327, 330, 412 assoc AutoLISP function, 229 associating AcDbTextStyleTableRecord with
AcGiTextStyle, 742 associating (continued) hyperlinks with entities, 138 association list, 228 asynchronous interaction from an automation client, 635 ATL setting up an ATL project file, 640 templates, 634 AttachInstance() function, 170 attribute values, 358 auditing extended data handles, 242 AutoCAD AutoCAD DesignCenter API, 664 classes, protocol extension and, 509 cloning phase, 475 commands boundaries in transactions, 450 using deep clone and wblock clone, 474 using wblock clone, 474 creating circle entities in, 21 creating entry points for, 34 creating group entities in, 22 creating instances of entities, 124 creating layer entities in, 21 creating line entities in, 21 creating objects in, 20 creating registry subkeys and values, 46 database overview, 18 demand loading features, for proxy objects, 390 ERRNO system variable, 525 features that use COM, 614 key components, 18 proxy entity messages in, 391 responding to messages from, 29 translation phase, 475 undo in transactions, 453 AutoCAD command functions, 248 AutoCAD DesignCenter API, 664 applications registry branch example, 666 applications registry key, 665 CLASSID registration, 667 custom content example, 670 extensions registry branch example, 667 extensions registry key, 666 IAcDcContentBrowser interface, 664 IAcDcContentBrowser interface functions, 668 IAcDcContentFinder interface, 665 IAcDcContentFinderSite interface, 664 IAcDcContentView interface, 664 IAcDcContentView interface functions, 669 IAcPostDrop interface, 665 implementing interfaces, 668
808
Index
AutoCAD DesignCenter API (continued) providing custom content, 670 registry requirements, 665 using the acrxProductKey() function, 665 AutoCAD Release 12 saving to DWG file, 359 AutoLISP comparison of AutoLISP calls to ObjectARX calls, 520 defining functions, 522 expressions in user input, 262 functions (assoc), 229 (command), 248 (entget), 240 (vports), 255 returning values to, 268 linked lists, 549 running applications from, 53 variables accessing from ObjectARX, 252 setting to nil, 253 automatic undo, 327 automation See ActiveX Automation, 615 axtempl.h file, 635, 642, 645
C
CAcExtensionModule class, 170 DLL initialization and termination, 170 example, 171 resource tracking, 170 CAcModuleResourceOverride class, 170 example, 171 CAcUiAlertDialog class, 176 CAcUiAngleComboBox class, 179 CAcUiAngleEdit class, 178 CAcUiArrowHeadComboBox class, 181 CAcUiColorComboBox class, 181 CAcUiDialog class, 175 CAcUiDockControlBar class, 177 CAcUiEdit class, 177 CAcUiFileDialog class, 176 CAcUiLineWeightComboBox class, 181 CAcUiMRUComboBox class, 180 CAcUiMRUListBox class, 182 CAcUiNumericComboBox class, 180 CAcUiNumericEdit class, 178 CAcUiPickButton class, 184 CAcUiPlotStyleNamesComboBox class, 182 CAcUiPlotStyleTablesComboBox class, 182 CAcUiSelectButton class, 184 CAcUiStringComboBox class, 180 CAcUiStringEdit class, 178 CAcUiSymbolComboBox class, 180 CAcUiSymbolEdit class, 178 CAcUiTabChildDialog class, 176 CAcUiTabMainDialog class, 175 CAdUiBaseDialog class, 174 CAdUiBitmapButton class, 183 CAdUiBitmapStatic class, 183 CAdUiComboBox class, 179 CAdUiDialog class, 175 CAdUiDockControlBar class, 176 CAdUiDrawTipText class, 174 CAdUiDropSite class, 183 CAdUiEdit class, 177 CAdUiFileDialog class, 175 CAdUiHeaderCtrl class, 179 CAdUiListBox class, 178 CAdUiListCtrl class, 179 CAdUiOwnerDrawButton class, 182 CAdUiTab class, 176 CAdUiTabChildDialog class, 175 CAdUiTabExtensionManager class, 176 CAdUiTabMainDialog class, 175 CAdUiTextTip class, 174 CAdUiTipWindow class, 174 CAdUiToolButton class, 183
B
beginClose() function, 400 beginDeepClone() function, 487, 502, 503, 505 beginDeepCloneXlation() function, 489, 502, 503, 505 ID map (table), 489, 502 notification functions, 505 beginDxfIn() function, 400 beginInsert() function, 505 beginSave() function, 400 beginWblock() function, 488, 503 BHATCH command, 373 blanking out, 278 BLOCK command, 146 cloning application, 474 block references with attributes, creating, 128 block table, 145, 146 block table records appending function to, 381 creating, 125 creating, with attribute definitions, 126 iterating through, 131 blocks, anonymous, 234 boundaries of transactions, 450 boundary representation library See AcBr library bounding box for text, 257 building an ownership hierarchy, 315
Index
809
calibrating tablets, 279 cancel() function, 331, 413, 453 cancelled() function, 399, 412, 413 CECOLOR system variable, 66 CELTSCALE system variable, 66, 102 CELTYPE system variable, 101 channels DesignXML, 596 character conversion and testing functions, 273 checkIn() function, 324 checkOut() function, 324 circularArc() function, 739 class descriptor objects, 287 class hierarchy AcBr, 791 AcDb, 11 AcEd, 10 AcGe, 13, 763 AcGi, 12, 722 AcRx, 9 AcUi, 173 AdUi, 173 classes AcApDocManager, 417 AcApDocManagerReactor, 418 AcApDocument, 61 AcApDocumentIterator, 417 AcApLayoutManager, 158 AcApLongTransactionManager, 70 AcApLongTransactionReactor, 70 AcApProfileManager, 584 AcApProfileManagerReactor, 585 AcBr containment, 798 AcBr entity, 798 AcBr mesh, 798 AcBr traverser, 799 AcDb2dPolyline, 136, 286 AcDb2dVertex, 136, 286 AcDb3dPolyline, 286 AcDb3dPolylineVertex, 286 AcDbBlockBegin, 286 AcDbBlockEnd, 286 AcDbBlockTable, 145 AcDbBlockTableRecordIterator, 150 AcDbCompositeFilteredBlockIterator, 78 AcDbCurve, 136, 286 AcDbDatabaseReactor, 286 AcDbDatabaseSummaryInfo, 79 AcDbDimension, 286 AcDbDimStyleTable, 145 AcDbEntity, 98, 286, 366 AcDbEntityReactor, 286 AcDbFaceRecord, 287 AcDbFilter, 78 AcDbFilteredBlockIterator, 78 AcDbFullSubentPath, 109 AcDbHostApplicationServices, 694
classes (continued) AcDbHyperlink, 138 AcDbHyperlinkCollection, 138 AcDbIndex, 78 AcDbLayerTable, 145 AcDbLayerTableRecord, 147 AcDbLayout, 155, 158 AcDbLayoutManager, 158 AcDbLayoutManagerReactor, 158 AcDbLinetypeTable, 145 AcDbLongTransaction, 70 AcDbLongTransWorkSetIterator, 70 AcDbMatchProperties, 514 AcDbMInsertBlock, 287 AcDbMline, 155 AcDbMlineStyle, 155 AcDbObject, 286 AcDbObjectId, 467 AcDbObjectReactor, 286 AcDbPlotSettings, 158 AcDbPlotSettingsValidator, 158 AcDbPolyFaceMesh, 287 AcDbPolyFaceMeshVertex, 287 AcDbPolygonMesh, 287 AcDbPolygonMeshVertex, 287 AcDbRegAppTable, 145 AcDbSequenceEnd, 286 AcDbSummaryInfoManager, 80 AcDbSummaryInfoReactor, 80 AcDbSymbolTable, 286 AcDbTextStyleTable, 145 AcDbTransactionManager, 698 AcDbUCSTable, 145 AcDbVertex, 287 AcDbViewport, 287 AcDbViewportTable, 145 AcDbViewTable, 145 AcDbXrecord, 160 AcDbXrefFileLock, 76 AcDbXxxDimension, 286 AcEdInputContextReactor, 566 AcEdInputPointManager, 565 AcEditorReactor, 286, 400, 502 AcEditorReactor class, 75 AcEdJig, 286, 374 AcGeFileIO, 781 AcGeFiler, 781 AcGeLibVersion, 781 AcGeTol, 765 AcGiDrawable, 720 AcGiEdgeData, 734 AcGiFaceData, 734 AcGiSubEntityTraits, 726 AcGiTextStyle, 740 AcRxDictionary, 8 AcRxObject, 8, 286 AcRxService, 286
810
Index
classes (continued) AcTransaction, 698 AcTransactionManager, 698 AcTransactionReactor, 286, 698 CAcExtensionModule, 170 CAcModuleResourceOverride, 170 CAcUiAlertDialog, 176 CAcUiAngleComboBox, 179 CAcUiAngleEdit, 178 CAcUiArrowHeadComboBox, 181 CAcUiColorComboBox, 181 CAcUiDialog, 175 CAcUiDockControlBar, 177 CAcUiEdit, 177 CAcUiFileDialog, 176 CAcUiLineWeightComboBox, 181 CAcUiMRUComboBox, 180 CAcUiMRUListBox, 182 CAcUiNumericComboBox, 180 CAcUiNumericEdit, 178 CAcUiPickButton, 184 CAcUiPlotStyleNamesComboBox, 182 CAcUiPlotStyleTablesComboBox, 182 CAcUiSelectButton, 184 CAcUiStringComboBox, 180 CAcUiStringEdit, 178 CAcUiSymbolComboBox, 180 CAcUiSymbolEdit, 178 CAcUiTabChildDialog, 176 CAcUiTabMainDialog, 175 CAdUiBaseDialog, 174 CAdUiBitmapButton, 183 CAdUiBitmapStatic, 183 CAdUiComboBox, 179 CAdUiDialog, 175 CAdUiDockControlBar, 176 CAdUiDrawTipText, 174 CAdUiDropSite, 183 CAdUiEdit, 177 CAdUiFileDialog, 175 CAdUiHeaderCtrl, 179 CAdUiListBox, 178 CAdUiListCtrl, 179 CAdUiOwnerDrawButton, 182 CAdUiTab, 176 CAdUiTabChildDialog, 175 CAdUiTabExtensionManager, 176 CAdUiTabMainDialog, 175 CAdUiTextTip, 174 CAdUiTipWindow, 174 CAdUiToolButton, 183 containers hierarchy, 143 control bar, 176 CPropertyPage, 176 CPropertySheet, 176 creating custom, 29 creating MFC dialog, 190
classes (continued) custom class derivation, 286 custom class function declaration, 288 declaring and defining protocol extensions, 510 document management, 417 evaluation, 773 implementing deepClone() for custom classes, 474 initialization function, 291 line, 768 matrix, 766 plane, 768 point, 766 reactors, 396 renaming, 350 runtime class identification, 287 vector, 766 version support, 346 clear_reactors() function, 401 clip boundaries in AcGi, 759 clone() function, 467 versus deepClone() function, 467 cloning deep, 465 versus deep cloning, 467 deep clone types and duplicate record cloning, 71 editor reactor notification functions, 502 exploding and, 475 ID map, 468 inserting, 502 key concepts, 467 objects from different owners, 471 overriding the deepClone() function, 482 overriding the wblockClone() function, 486 in transformation functions, 366 translating, 468 using appendAcDbEntity(), 496 close() function, 331, 413, 414 for newly created objects, 452 object pointers and, 451 open and close mechanism with, 453 closing ObjectARX objects, 25 CMDACT system variable, 567, 568 collections of hyperlinks, 138 colors entity, 100 values, 66 COM accessing COM interfaces from ObjectARX, 615 AutoCAD features that use COM, 614 building and registering a COM DLL, 649 eTransmit interfaces, 683 See alsoActiveX Automation, 613 combo box controls, 179
Index
811
command AutoLISP function, 248 command line prompt, 378 command stack, 38 commandEnded() function, 451, 503 commands ARX, 51 AutoCAD, for cloning applications, 474 BLOCK, 146 boundaries (AutoCAD) in transactions, 450 displaying names in command groups, 52 EXPLODE, 124 global versus local names, 40 group names, 38 INSERT, 146 LAYER, 147 lookup order, 40 modal, 41 multi-document, 432 nonreentrant, 431 not used in transactions, 451 registering new, 38 transparent versus modal, 41 commandWillStart() function, 451 commit-time guidelines, 452 notification, 412 common characteristics of global functions, 520 common entity properties, 100 communicating between applications, 526 comparison ObjectARX calls and AutoLISP calls, 520 symbol tables and dictionaries, 142 compatibility levels for SDI and MDI, 423 complex entities, 98, 133, 220, 231 component codes for entities, 229 computing points on parametric curves, 774 conditional filtering, 210 consistency checks, 76 constructing tab dialogs, 185 container objects, 141 context data, 224 context events, input, 566 context help, 174 control bar classes, 176 controlling graphics and text screens, 278 controlling long transactions, 70 controlling the display, 276 controls creating, 190 user input, 379 conversions angles, 269 coordinate systems, 274 numbers, 269 strings, 269 units, 272
coordinate systems accessing, 135 conversions, 274 descriptions, 274 specifying, 274 transformation example, 276 transformations, 274 copied() function, 399, 412 COPY command, 474 copying arrays of entities to create databases, 64 from entity to entity, 374 named blocks to create databases, 64 CPROFILE system variable, 584 CPropertyPage class, 176 CPropertySheet class, 176 createObjs() function, 315 createXrecord() function, 161 creating AcDbLayerTableRecord example, 148 application registry keys and values, 47 applications, 27, 28 block table records, 125 with attribute definitions, 126 circle entities in AutoCAD, 21 class descriptor objects, 287 classes and controls, 190 complex entities, 133 custom classes, 29 custom entities, 354 databases from existing databases, 63 databases with entities, 64 dialog handlers, 192 dictionaries, 156 entities, 23 entry points for AutoCAD, 34 instances of AutoCAD entities, 124 layer entities in AutoCAD, 21 layer groups in AutoCAD, 22 layers, 24 line entities in AutoCAD, 21 MFC dialog using App Studio, 189 objects in AutoCAD, 20 objects in ObjectARX, 23 objects of protocol extension classes, 512 ownership connection, 313 proxies, 391 registry file (.reg) for custom object COM server, 637 selection sets, 202 simple entities, 124 skeletons, 187 Windows system registry subkeys and values, 46 creating a database, 60 ctype.h file, 273 current database, accessing, 19
812
Index
current document accessing, 428 setting, 428 current save format, 62 cursor specified types with AcEdJig, 374 types (table), 378 curves AcDbCurve class, 136 AcGe functions, 769 computing points on, 774 functions, 136 tessellating, 744 custom classes creating, 29 overriding AcDbCurve functions, 294 overriding AcDbObject functions, 294 overriding AcRxObject functions, 294 custom entities adding, using AcEdJig, 374 deriving, 354 deriving from AcDbEntity, 353, 359, 360, 374 extending entity functionality, 374 intersecting with other entities, 373 stretch points, 365 transformation functions, 366 custom notifications, 400 custom object snap modes, 556 custom objects derivation, 286 developing object enablers for, 590 LongTransaction issues, 324 proxy objects and, 390, 391, 394 supporting XML, 597
D
data class version numbers, 350 class version support, 347 dynamically allocated, 544 instances per document, 416 per-document data, 424 data types acad_proxy_entity, 391 acad_proxy_object, 391 ads_matrix, 213 ads_name, 202 runtime identification mechanism, 285 database operations, 59 databases accessing with noncurrent documents, 428 adding entities to, 381 closing objects, 82 color values, 66 components of, 18
databases (continued) creating, 60 creating by copying arrays of entities , 64 creating by copying named blocks, 64 creating databases with entities, 64 creating empty, 20 creating from existing databases, 63 current, 19 dependencies, finding, 404 document-independent databases, 438 essential objects, 20 example of operations, 67 initial values, 60 inserting, 65 layer values, 67 linetype scale values, 66 linetype values, 66 merging, 65 multiple, 19 notification events, 400 objects overview, 82 opening objects, 82 overview, 18 ownership of objects, 85 ownership structure for entities, 99 populating, 60 primer, 17 reactors, using, 396 resolving conflicts while inserting, 65 resolving conflicts while merging, 65 restoring in external references, 76 saving, 61 setting color values, 66 setting current values, 66 setting linetype scale values, 66 setting linetype values, 66 startup values, 60 wblock cloning, 486 dbapserv.h. file, 694 DCS See display coordinate system, 748 debugging applications with dynamic MFC, 169 deep cloning, 465 calls, 503 cloning objects from different owners, 471 filing, 467 functions, 465 implementing AutoCAD commands for, 474 cloning phase, 475, 482 filers for, 482 insert operation, 484 translation phase, 475, 482 in long transactions, 70 key concepts, 467 ownership, 466, 467 typical operation, 469
Index
813
deep cloning (continued) using clone() versus deepClone(), 467 wblock functions and, 465, 466 deepClone() function, 324, 467, 474, 482, 497 AutoCAD commands using, 474 versus clone() function, 467 cloning phase, 475 overriding, 482 translation phase, 475 deepCloneContext() function, 506 deepCloneObjects() function, 465, 468, 469 implementing, 474 default file format considerations, 62 settings, 61 definition data, retrieving, 230 degeneracy of entities, 771 deleteAcRxClass() function, 394 deleting extended data, 238 objects, 84 demand loading, 43 for ObjectDBX, 706 on AutoCAD start-up, 50 on command, 50 on detection of custom objects, 49 DEMANDLOAD system variable, 48 deriving custom classes, 286 custom entities, 354 new classes from AcEdJig, 375 DesignCenter See AutoCAD DesignCenter API DesignXML DesignXML APIs, 595 overview, 596 precision, 597 reading and writing DesignXML files, 609 schema, 596 deviation() function, 745 dialog boxes data persistency, 184 naming conventions, 184 dialog classes AcUi, 175 AdUi, 174 dictionaries, 142, 152 adding and deleting entries, 152 comparison with symbol tables, 142 creating, example, 156 extension, 88 group, 26, 152 layout, 155 Mline style, 155 named object, 478 names, 144 setAt() function, 152
dimstyle table, 145 directory tree, ObjectARX, 14 disableSystemCursorGraphics() function, 565 display control, 276 display coordinate system (DCS), 748 definition, 275 display prompt for drag sequence with AcEdJig, 376 setting, 378 displaying command names, 52 entity changes, 236 proxy entities, 392 displaying menus example, 277 functions, 277 displaying text functions, 276 size limits, 276 DLLs initializing, 170 terminating, 170 docking system, 176 document management classes, 417 documentation online, 6 document-independent databases, 438 documents accessing current, 428 disabling switching, 434 events, 430 execution context, 416 locking, 417 locking for Automation requests, 636 object, 417 downgradeOpen() function, 86 drag sequences controlling, with AcEdJig, 374 drag loop, 376 drag loop, illustrated, 377 implementing, with AcEdJig, 375 limitations on, 380 setting display prompt, 376 drag() function, 375 dragging, 381 dragging selection sets, 266 draw() function, 104, 453 drawables, 744 drawing ancillary data, 79 drawings save functions, 63 summary information, 79 transactions and, 453 DrawTips, 174 dummy entities, 244 DWG files, 299 from earlier releases, 715
814
Index
dwgIn() function, 301, 467 dwgInFields() function, 301, 327, 350 dwgOut() function, 300, 467 dwgOutFields() function, 301, 327, 330 DXF files, 299 DXF group codes, 540 extended data, 238 handle value, 242 in acedSSGet(), 208 ranges, 304 sentinel codes, 229, 237 xrecords, 160, 243 dxfIn() function, 301 dxfInComplete() function, 400 dxfInFields() function, 306, 350 dxfOut() function, 301 dxfOutFields() function, 304 dynamic properties and Object Property Manager, 659 dynamically allocated data, 544
E
ECS See Eye Coordinate System edit controls, 177 editing proxy entities, 392 editor reactor, 400 notification functions, 502 eInProcessOfCommitting message, 413, 452 ellipse, creating (example), 381386 empty databases, 20 enable GraphicsFlush() function, 453 enableSystemCursorGraphics() function, 565 endCalledOnOutermostTransaction() function, 452, 454 endDeepClone() function, 478, 502, 503 endInsert() function, 505 endWblock() function, 503 entget AutoLISP function, 240 entities AcDbBlockTableRecord, 98 adding to the database, 381 assigning extended data, 244 associating hyperlinks, 138 colors, 100 complex, 98, 133, 220, 231 component codes, 229 context, 219 context data, 224 coordinate transformation data, 220 creating, 23 creating instances of AutoCAD entities, 124 data functions, 226 defined, 98 definition data, 226 degeneracy, 771
entities (continued) displaying changes, 236 dummies, 244 exploding, 122 extending entity functionality, 374 extending functionality, 374 forced entity picking, 566 intersecting with other entities, 373 layer, 103 linetype, 101 linetype scale, 102 linetype scale in paper space, 103 linked result buffers, 224 name volatility, 202 names, 202, 216 native, 373 nested entities, 224 nested in block references, 219 object snap point functions, 360 ownership, 98, 244 properties, common, 100 proxies, 389 purging, 226 recovering, 219 regenerating, 103, 237 restoring color, 229 restoring linetype, 229 saving, 359 setting entity traits, 726 snap points, 105 stretch points, 365 subentity traits, 726 transformation example, 225 transformation functions, 366 translation example, 221 using AcEdJig, 374386 adding the entity to the database, 381 deriving a new class from AcEdJig, 375 drag loop, 376 general steps for using AcEdJig, 375 implementing sampler(), update(), and entity() functions, 378 sample code, 381386 setting up parameters for drag sequence, 376 using handles, 218 visibility, 103 entity coordinate system definition, 275 entity() function, 376 implementing, 378, 381 overriding, 375 returning points to entity with, 381 entry points, creating for AutoCAD, 34 enumerated types, AcBr, 800 erase() function, 93, 331 erased() function, 399, 413
Index
815
erasing objects, 93 error codes eWasErased, 94 when opening objects, 84 error handling, 54 AutoCAD system variable ERRNO, 525 filing objects to DXF and DWG files, 301 in selection sets, 216 invoked functions, 530 ObjectARX, 525 RTERROR, 207 essential database objects, 20 eTransmit API, 683 example, 685 extracting the IDL file, 684 using, 684 evaluating external functions, 524 evaluation classes, 773 events document, 430 immediate events, 412 input context events, 566 notification, 396, 400 sequence of, in applications, 33 time-commit events, 412 eWasErased error code, 94 eWasNotifying message, 451 eWasNotOpenForWrite message, 413 example schema, 598 XML object enabler application interface, 600 XML schema, 598 xmlIn(), 606 xmlOut(), 603 examples accessing AutoLISP variables, 252 AcDbLayerTableRecord creating and modifying, 148 acedCmd(), 250 acedCommand(), 249 acedFindFile(), 254 acedGetFileD(), 254 acedGetInput(), 265 acedGetSym(), 252 acedInitGet(), 263, 264 acedOsnap, 254 acedPutSym(), 253 acedSSGet(), 203 acedSSName() usage, 213 acedSSXform() usage, 213 acedTextBox(), 258 AcGe persistency, 782 acutBuildList(), 250 adding code to handlers, 192 adding members to selection sets, 217 angle conversions, 271
examples (continued) anonymous blocks, 234 application name registration, 240 AutoCAD DesignCenter API applications registry branch, 666 AutoCAD DesignCenter API custom content, 670 AutoCAD DesignCenter API extensions registry branch, 667 basic application, 37 building object dependencies, 406 CAcExtensionModule class, 171 CAcModuleResourceOverride class, 171 calling external functions, 526 checking entities in and out of a database, 71 class data and xdata version support, 350 class version support, 348, 349 clip boundary, 760 cloning and translation, 475 cloning objects from different owners, 471 COM ActiveX Automation access using MFC, 615 COM ActiveX Automation access without MFC, 621 constructing an extensible custom tab dialog, 185 coordinate system transformation, 276 creating a mesh, 733 creating an application skeleton, 187 creating an MFC dialog using App Studio, 189 creating block table records, 126 creating classes and controls, 190 creating complex entities, 133, 231 creating custom entities, 381 creating dialog handlers, 192 creating dictionaries, 156 creating simple AutoCAD entities, 124 curve functions, 137 custom object classes, 341 custom object header file, 341 custom object snap mode, 560 custom object source file, 342 database operations, 67 database reactors, 401 deep clone operation, 469 deepClone() function, 497 definition data retrieval, 230 definition data retrieving and printing, 226 deleting extended data, 238 displaying entity changes, 236 displaying menus, 277 dragging selected objects, 266 dwgInFields() function, 303 dwgOutFields() function, 302 DXF group code handling, 541
816
Index
examples (continued) dxfInFields() function with order dependence, 310 dxfInFields() with order independence, 307 dxfOutFields() function, 306 entity handles, 219 entity names, 213 entity transformation, 225 entity translation, 221 eTransmit API, 685 extending built-in tab dialogs, 186 extension dictionary global functions, 91 file dialog box, 254 file search, 254 finding persistent reactors, 404 finding viewports, 246 geometric functions, 256 gripping and transforming objects, 363 group and group dictionary functions, 153 handling hard references to AcDbEntities during wblockClone(), 500, 501 highlighting a subentity, 111 highlighting nested block references, 115 hyperlinks, 139 input context events, 568 input options, 263 input point filter and monitor, 575 inserting blocks with references into drawings, 128 interacting with AutoCAD using ActiveX Automation, 636 intersecting functions, 368 iterating over dictionary entries, 157 iterating through block table records, 131 iterating through vertices in a polyline, 134 keywords comparison, 265 linked lists for AutoLISP, 549 linking result buffers, 544 long transactions, 71 LongTransaction, 325 matrix composition, 216 matrix multiplication, 222 MDI-Aware example application, 439 message map, 168 named object dictionary, 479 nested transactions, 455 numeric conversions, 271 object snap, 254 overriding resources, 170 overriding subsidiary functions, 331 overriding the deepClone() function, 482 overriding the wblockClone() function, 486 ownership hierarchy, 316 pausing for input in commands, 250 pick points in commands, 251 profile manager, 586 protocol extension, 514
examples (continued) protocol extension class for custom object snaps, 557 reactor adding behavior to a WBLOCK command, 505 reading a drawing, 67 registry file (.reg) for custom object COM server, 637 resbuf, 206, 207 retrieving blocks, 245 retrieving extended data for specific applications, 241 returning a value to AutoLISP, 269 reversing damage to the graphics screen, 279 sample application using the AcBr library, 802 saving a drawing, 67 scaling selections, 213 searching for files, 254 searching resbufs for specific DXF codes, 229 selecting using conditional tests, 211 selecting using extended data, 209 selecting using relational tests, 210 set an AutoLISP variable to nil, 253 setting AutoLISP variables, 253 setting entity names, 537 setting the Model Space viewport for ObjectDBX, 713 setting up an ATL project file for a COM wrapper, 640 shell with color and visibility, 737 simple block table records, 125 snap mode, 361 string conversions, 270 system variable access, 251 tablet calibration, 279 text coordinates, 258 text with bounding box, 741 transformation, 748 transformation functions, 366 transforming text boxes, 260 unit conversion, 272 user input, 264 user input break or cancel, 267 using a transient editor reactor, 506 using AcGi, 729 using appendAcDbEntity() during cloning, 496 using the AcBr library, 802 viewport descriptors, 255 wblockClone() function, 498 wild-card comparisons, 281 xrecord functions, 161 execution contexts application, 435
Index
817
execution contexts (continued) document, 416 EXPLODE command, 124, 373 cloning application, 475 explode() function, 104, 122, 124, 373 exploding entities, 122, 373 extended data application names, 240 assigning to entities, 244 auditing handles, 242 deleting, 238 DXF group codes, 238 filtering for, 208 handle translation, 243 limit in size, 242 managing memory use, 242 notes, 237 organization, 238 protecting application data, 241 retrieving, 240 sentinel code, 237 using handles, 242 versus xrecords, 244 See also xdata extended entity data (EED or xdata) See xdata extending entity functionality, 374 extension dictionaries, overview, 88 external functions, 522 external references block table records, 76 clipped, 77 consistency checks, 76 contents, 74 file locks, 76 overview, 74 pre- and post-processing, 75 reloading, 76 restoring databases, 76 restoring resolved symbols, 76 separate databases, 74 Eye Coordinate System (ECS), 747
F
F1 help, 174 figures cloning ownership connections, 467 coordinate transformations, 747 creating circle entities in AutoCAD, 21 creating group entities in AutoCAD, 22 creating layer entities in AutoCAD, 21 creating line entities in AutoCAD, 21 input point filtering and monitoring, 573 key components of AutoCAD databases, 18 mesh edge ordering, 732 mesh face properties, 734
figures (continued) ownership references, 310 points returned by acedTextBox(), 257 regenerating a drawing, 721 sequence of events in application, 33 file locks external references, 76 file searching, 253 filers, for deep cloning, 482, 486 files DWG, 299, 391 DXF, 299, 391 formats for saving drawings, 61 locking, 76 register.dbx, 591 saving objects to files, 299 setting the default formats, 61 filing objects, 94 filing, and cloning, 467 fill type, primitive, 727 filtering conditional, 210 extended data, 208 multiple properties, 207 relational tests, 210 selection sets, 205 wild-card patterns, 208 filters chaining input point filters, 574 input point, 573 finding viewports example, 246 flushGraphics() function, 453 forced entity picking, 566 funcload() function, 527 functions abortDeepClone(), 502 abortInsert(), 505 abortWblock(), 503 accessing AutoLISP variables, 252 acdbAngToF(), 269 acdbAngToS(), 269 acdbDisToF(), 269 acdbDxfOutAsR14(), 62 acdbEntDel(), 219, 226 acdbEntGet(), 226, 237 acdbEntGetX(), 237, 240, 281 acdbEntLast(), 251 acdbEntMake(), 146, 230, 231, 234 acdbEntMod(), 229, 230, 236 acdbEntNext(), 217, 250 acdbEntUpd(), 236 acdbFail(), 525 acdbGetSummaryInfo(), 80 acdbGetSummaryInfoManager(), 80 acdbHandEnt(), 218, 226, 242 acdbInters(), 256 acdbIsPersistentReactor(), 404
818
Index
functions (continued) acdbOpenObject(), 82 acdbPutSummaryInfo(), 80 acdbRegApp(), 240 acdbRToS(), 269 acdbSaveAsR13(), 63 acdbSaveAsR14(), 63 acdbTblNext(), 245 acdbTblSearch(), 245, 246 acdbXdRoom(), 242 acdbXdSize(), 242 acdbXrefReload(), 76 acedAlert(), 525 acedArxLoaded(), 531 acedArxUnload(), 531 acedCmd(), 248, 249 acedCommand(), 248 acedDefun(), 522, 525 acedDragGen(), 214, 261, 266, 533 acedEntSel(), 216, 218, 261 acedFindFile(), 253 acedGetArgs(), 524 acedGetDist(), 261 acedGetFileD(), 254 acedGetFunCode(), 524 acedGetInput(), 234 acedGetKword(), 261 acedGetPoint(), 262 acedGetReal(), 266 acedGetString(), 261 acedGetSym(), 252, 524 acedGetVar(), 251 acedGraphScr(), 278 acedGrDraw(), 278 acedGrRead(), 278 acedGrText(), 278 acedGrVecs(), 214, 533 acedInitGet(), 261, 262, 266 acedInvoke(), 526 acedMenuCmd(), 277 acedNEntSel(), 110, 216, 219, 261 acedNEntSelP(), 110, 216, 261, 533 acedOsnap(), 254, 533 acedPrompt(), 276 acedPutSym(), 252, 524 acedRedraw(), 278 acedRegFunc(), 527 acedSetVar(), 251 acedSSAdd(), 217 acedSSAddl(), 211 acedSSDel(), 211 acedSSFree(), 205 acedSSGet(), 110, 202 acedSSLength(), 212 acedSSMemb(), 212 acedSSName(), 212 acedSSNameX(), 110
functions (continued) acedTablet(), 279 acedTextBox(), 257 acedTextPage(), 278 acedTextScr(), 278 acedTrans(), 274 acedUsrBrk(), 267 acedVports(), 255 acedXformSS(), 213, 533 acgixAllocateWhipView(), 704 acProfileManagerPtr(), 584 acquirePoint(), 379 acrxAbort(), 525 acrxEntryPoint(), 34, 185, 186 acrxProductKey(), 665 acutAngle(), 256 acutBuildList(), 204, 249, 549 acutCvUnit(), 272 acutDistance(), 256 acutGetVar(), 584 acutPolar(), 256 acutPrintf(), 276 acutRelRb(), 547 acutWcMatch(), 281, 282 acutWcMatchEx(), 282 AddExtendedTabs(), 185 AddTab(), 185, 186 addX(), 512 AfxGetResourceHandle(), 170 angle conversion, 269 appendAcDbEntity(), 85, 496 applyPartialUndo(), 328, 329 assertNotifyEnabled(), 298 assertReadEnabled(), 298, 301, 304 assertWriteEnabled(), 298, 301, 304, 327, 330 AttachInstance(), 170 AutoCAD commands, 248 AutoCAD services, 248 beginClose(), 400 beginDeepClone(), 502 beginDeepCloneXlation(), 502 beginDxfIn(), 400 beginInsert(), 505 beginSave(), 400 beginWblock(), 503 cancel(), 331 cast(), 287 character conversion and testing, 273 checkIn(), 324 checkOut(), 324 circularArc(), 739 clone(), 467 close(), 331 colorIndex(), 101 common characteristics of library functions, 520
Index
819
functions (continued) conversion utilities, 269 coordinate system conversion, 274 createObjs(), 315 createXrecord(), 161 deepClone(), 324, 467, 474, 482, 497 deepCloneObjects(), 466, 468 deleteAcRxClass(), 394 desc(), 287 deviation(), 745 disableSystemCursorGraphics(), 565 displaying menus, 277 displaying text, 276 downgradeOpen(), 86 drag(), 375 draw(), 104 dwgFileWasSavedByAutodeskSoftware(), 80 dwgIn(), 301, 467 dwgInFields(), 301, 327, 350 dwgOut(), 300 dwgOutFields(), 301, 327, 330 dxfIn(), 301 dxfInComplete(), 400 dxfInFields(), 306, 350 dxfOut(), 301 dxfOutFields(), 304 enableSystemCursorGraphics(), 565 endCalledOnOutermostTransaction(), 454 endDeepClone(), 502 endInsert(), 505 endWblock(), 503 entity(), 378 erase(), 93, 331 executing AutoCAD commands, 248 explode(), 104, 122, 124, 373 external, 522 file search, 253 flushGraphics(), 453 for drawing primitives, 358 formatForSave(), 62 funcload(), 527 geometric utilities, 256 getAt(), 145 getDatabaseMutex(), 703 GetDialogName(), 186 getEcs(), 135 getFilerStatus(), 301 getGeomExtents(), 104 getGripPoints(), 104, 362 getGsMarkersAtSubentPath(), 105 GetObjectId(), 630 getOsnapInfo(), 557 getOsnapPoints(), 104, 105, 106, 360 getStretchPoints(), 104, 365 getSubentPathsAtGsMarker(), 105, 110 getTransformedCopy(), 104, 106, 366 highlight(), 105, 110
functions (continued) insert(), 65, 466 intersectWith(), 104, 106, 107, 368, 373 isA(), 287 isKindOf(), 287 layer(), 103 linetype(), 102 linetypeScale(), 102 list(), 104 listXrecord(), 161 mesh(), 732 moveGripPoints(), 363, 365 moveGripPointsAt(), 104 moveStretchPointsAt(), 104 new(), 84 newIterator(), 150, 152 numeric conversion, 269 object snap, 254 ON_MESSAGE(), 168 OnInitDialog(), 185, 198 OnModified(), 630 open(), 331 otherInsert(), 505 otherWblock(), 503 overriding AcDbCurve functions, 294 overriding AcDbObject functions, 294 overriding AcRxObject functions, 294 passing pick points, 250 pausing for user input, 250 pline(), 740 position(), 136 PostNcDestroy(), 185 printdxf(), 226, 228 purge(), 326 queueForGraphicsFlush(), 453 rbChain(), 160 releaseDatabaseMutex(), 703 restoreForwardingXrefSymbols(), 76 restoreOriginalXrefSymbols(), 76 return values and results for global functions, 521 returning values to AutoLISP, 268 rxInit(), 291 sampler(), 376, 378 saveComplete(), 400 setAt(), 94, 152 setAttributes(), 720, 722 setColor(), 152 setColorIndex(), 101 setDefaultFormatForSave(), 62 SetDialogName(), 185 SetDirty(), 185 setElevation(), 136 setFromRbChain(), 160 setHighlight(), 152 setLayer(), 103, 152 setLinetype(), 102, 152
820
Index
functions (continued) setLinetypeScale(), 102 SetObjectId(), 630 setPosition(), 136 setVisibility(), 103, 152 setXData(), 86 string conversion, 269 subCancel(), 331 subClose(), 331 subentPtr(), 105, 111 subErase(), 331 subOpen(), 331 system variables, 251 text utility box, 257 traits, 358 transactionAborted(), 454 transactionEnded(), 454 transactionStarted(), 454 transformation, 366 transformBy(), 104, 106, 366, 393 unit conversion, 272 update(), 378 user input, 260 vertexPosition(), 136 viewport descriptors, 255 viewportDraw(), 104, 720, 724 visibility(), 103 wblock(), 63 wblockClone(), 324, 466, 474, 486, 498, 500 wblockCloneObjects(), 70 workingDatabase(), 19 worldDraw(), 104, 720, 723 xData(), 86 xrefBlockId(), 76
getOsnapInfo() function, 557 getOsnapPoints() function, 104, 105, 360 overriding, 360 getStretchPoints() function, 104, 365 getSubentPathsAtGsMarker() function, 105, 110 getting user input, 260 getTransformedCopy() function, 104, 106, 366 global functions, 247 global utility functions, 519 glyphs, custom, 557 goodbye() function, 399, 412 graphics and text screens controlling, 278 low-level access, 278 reversing damage, 279 graphics generation, and transactions, 453 graphics interface library, 11, 719 Graphics System Markers, 727 example diagram, 108 graphicsModified() function, 413 grip points, 353 stretch points as subset of, 365 group codes See DXF group codes GROUP dictionary, 26, 152 group names for commands, 38 groups adding to group dictionaries, 26 assign properties to all members, 152 GS markers See Graphics System Markers
H
handlers creating dialog, 192 handles in extended data, 242 object, 19 requesting object, 83 translation, 243 handling external applications, 530 hard references to AcDbEntities during wblockClone(), 500 hard references in long transactions, 70 help context, 174 F1, 174 hierarchy container classes, 143 entity classes, 99 xrecords, 244 See also class hierarchy highlight() function, 105, 110 highlighting nested block references, 115
G
geometric utilities, 256 geometry 3D, 766 basic types, 766 library, 761 parametric, 769 getAt() function, 145 getDatabaseMutex() function, 703 GetDialogName() function, 186 getEcs() function, 135 getFilerStatus() function, 301 getGeomExtents() function, 104 getGripPoints() function, 104, 362, 363, 365 getGsMarkersAtSubentPath() function, 105 getObject() function, 449, 451 for newly created objects, 452 in open mode, 451 open and close mechanism with, 453 GetObjectId() function, 630
Index
821
highlighting (continued) subentities, 109 hyperlinks AcDbHyperlink class, 138 associating with entities, 138 collections, 138 example, 139 nested, 138 objects, 138
I
IAcadBaseObject interface, 629 Clone() function, 630 GetClassId() function, 630 GetObjectId() function, 630 NullObjectId() function, 630 SetObjectId() function, 630 IAcadBaseObject interface interface OnModified() function, 630 IAcDcContentBrowser interface, 664, 668 IAcDcContentFinder interface, 665 IAcDcContentFinderSite interface, 664 IAcDcContentView interface, 664, 669 IAcPostDrop interface, 665 ICategorizeProperties interface, 653 ID maps cloning, 468 for ddp cloning (table), 489, 502 use with inserting, 502 identity matrix, 223 IdMap, 324 IdPair, 324 IDynamicProperty interface, 659, 660 implementing automation objects, 633 deepClone() for custom classes, 474 DWG filing functions, 301 DXF filing functions, 304 grip point function, 362 interfaces for AutoCAD DesignCenter, 668 member functions, 298 protocol extension, 510 snap point functions, 360 static Object Property Manager interfaces, 654 stretch point functions, 365 include directory, ObjectARX, 15 indexing and filtering block iteration, 78 defining a query, 78 interfaces provided, 77 list of classes, 77 overview, 77 processing a query, 78
indexing and filtering (continued) querying the database, 78 registering, 77 updating, 77 initial items in a database, 60 initializing applications, 35 classes, 291 input context events, 566 input controls user, 379 values (list), 380 input options for user input functions, 263 input point filtering, 573 management, 565 manager class, 565 monitoring, 573 processing, 555 INSERT command, 146 cloning application, 474 inserting, 502 blocks with references into drawings, 128 databases, 65 resolving conflicts, 65 installation, modifying Windows system registry at, 45 installing ObjectARX, 14 interacting with multiple documents, 427 with other environments, 5 interactive output, 276 interoperability of applications, 6 intersecting for points, 106 with other entities, 373 with custom entities, 373 with native entities, 373 intersectWith() function, 104, 106, 107, 361, 368, 373 IOPMPropertyExpander interface, 654 IOPMPropertyExtension interface, 653 IPerPropertyBrowsing interface, 653 IPropertyManager interface, 659 isolines, 746 iterating over dictionary entries, 157 over open documents, 417 over tables, 150 through block table records, 131 through vertices in a polyline, 134 iterators, 150 ITransmittalAddFileNotificationHandler interface, 684 ITransmittalFile interface, 684 ITransmittalOperation interface, 684
822
Index
K
kAllAllowedBits value, 393 kCfgMsg message, 30, 33 kColorChangeAllowed value, 393 kDependencyMsg message, 30, 32 kEdgeSubentType subentity, 110 kEndMsg message, 30, 33 kEraseAllowed value, 393 keyword comparison, 265 keyword lists, 262, 265, 378 keyword specifications, 265 keywords in user input, 261 keywords, custom object snap, 557 kFaceSubentType subentity, 110 kForNotify mode, 84 kForRead mode, 83 kForWrite mode, 83 kInitAppMsg message, 29, 31 kInvkSubrMsg message, 30, 32 kLayerChangeAllowed value, 393 kLinetypeChangeAllowed value, 393 kLinetypeScaleChangeAllowed value, 393 kLoadDwgMsg message, 29, 32 kNoDependencyMsg message, 30, 32 kNoOperation value, 393 kOleUnloadAppMsg message, 31, 32 kPreQuitMsg message, 30, 32 kQuitMsg message, 30, 33 kSaveDwg message, 249 kSaveMsg message, 29, 33 kTransformAllowed value, 393 kUnloadAppMsg message, 29, 31 kUnloadDwgMsg message, 29, 32 kVisibilityChangeAllowed value, 393
L
last saved by Autodesk software, 80 LAYER command, 147 layer table, 145, 147 layers creating, 24 database values, 67 entity, 103 layout dictionary, 155 layout manager, 159 layouts dictionary, 158 model space, 157 ObjectARX classes, 158 paper space, 157 lengthy operations, 267 libacbr.dll file, 787 libraries acad.lib, 7 AcBr
libraries (continued) AcDb, 10 acdb15.lib, 7 AcEd, 9 acedapi.lib, 7 AcGe, 12, 761 acge15.lib, 8 AcGi, 11, 719 acgiapi.lib, 8 AcRx, 8 acrx15.lib, 7 adui15.lib, 172 directory, ObjectARX, 15 required ObjectARX, 7 rxapi.lib, 7 search path, 42 limit of size for extended data, 242 line classes, 768 linear algebra operations, 768 linetype entity, 101 scale, 66, 102 table, 145 values, 66 linked lists, 538 AutoLISP, 549 command and function invocation lists, 552 creating, 547 deleting, 547 dynamically allocated data, 544 entity lists with DXF codes, 551 nested, 549 linking VC++ project settings, 169 with MFC, 168 with ObjectARX libraries, 7 LIST command, proxy entity messages with, 391 list() function, 104 lists keyword, 265, 378 loaded applications, 42 listXrecord() functions, 161 Live Enabler technology, 590 loading applications, 41, 52 demand, 43 localization for ObjectDBX, 696 locking documents, 417 documents for Automation requests, 636 explicit document locking, 425 logo compliance, 6 logo program for ObjectARX, 6 long transactions controlling, 70 deep cloning, 70
Index
823
long transactions (continued) example, 71 hard references, 70 notifications, 70 overview, 69 read-only access, 70 starting, 70 tracking, 70 LongTransaction issues for custom objects, 324 LongTransactionManager, 324 lookup order, commands, 40 LTSCALE system variable, 66
M
macros AC_DECLARE_EXTENSION_MODULE, 171 AC_IMPLEMENT_EXTENSION_MODULE, 171, 188 ACRX_DECLARE_MEMBERS, 288 ACRX_DXF_DEFINE_MEMBERS, 390, 392 ACRX_NO_CONS_DEFINE_MEMBERS, 289 ACRX_X_CALL, 513 actrTransactionManager, 449 ads_point_set(), 532 cast(), 287 class implementation, 289 custom class function declaration, 288 desc(), 287 isA(), 287 isKindof(), 287 managing applications with Windows system registry, 51 masking. See filtering MATCH command protocol extension for, 514 MATCHPROP command, 374 matrix classes, 766 composition, 216 identity, 223 multiplication, 222 transformation, 533 MCS. See model coordinate system MDI See multiple document interface measuring text strings, 258 memory management for extended data, 242 memory requirements for global functions, 521 merging databases, 65 mesh primitives, 732 face and edge visibility, 734 traversers, 797 mesh() function, 732 message map example, 168
messages application reactions to AutoCAD messages, 31 kCfgMsg, 30, 33 kDependencyMsg, 30, 32 kEndMsg, 30, 33 kInitAppMsg, 29, 31 kInvkSubrMsg, 30, 32 kLoadDwgMsg, 29, 32 kNoDependencyMsg, 30, 32 kOleUnloadAppMsg, 31, 32 kPreQuitMsg, 30, 32 kQuitMsg, 30, 33 kSaveMsg, 29, 33, 249 kUnloadApMsg, 31 kUnloadAppMsg, 29 kUnloadDwgMsg, 29, 32 only responded to by applications using ActiveX, 31 responding to AutoCAD, 29 sent only if AutoLISP function registered, 30 sequence of, 33 to applications that have registered services, 30 WM_ACAD_KEEPFOCUS, 168 MFC See Microsoft Foundation Classes Microsoft Cabinet SDK, 591 Microsoft Component Object Model (COM) See COM, 613 Microsoft Foundation Classes access to AutoCAD ActiveX Automation, 615 and modeless dialog boxes, 168 debugging ObjectARX applications, 169 dynamic linking, 168, 169 modeless dialog boxes, 168 resource management, 169 topics, 167 user interface support, 171 user-interface support, 171 Microsoft Visual C++ project settings for dynamically linked MFC, 169 using AdUi and AcUi with AppWizard, 187 MIRROR command, cloning application, 474 mixing the transaction model with the open and close mechanism, 453 Mline style dictionary, 155 modal commands, 41 model coordinate system (MCS), 220, 747 model coordinates, transforming to world coordinates, 220 model space adding entities to, 21 layouts, 157 versus paper space, 21
824
Index
modeless dialog boxes focus, 168 MFC, 168 modes for opening objects, 83 kForNotify, 84 kForRead, 83 kForWrite, 83 modified() function, 396, 399, 413 modifiedXData() function, 399, 413 modifying Windows system registry at installation, 45 modifyUndone() function, 399, 413 monitoring input points, 573 most recently used combo boxes, 180 moveGripPoints() function, 363, 365 moveGripPointsAt() function, 104, 363, 365 signature, 363 moveStretchPointsAt() function, 104, 365 multi-document commands, 432 multiple databases, 19 multiple document interface (MDI), 415 application-specific data, 430 compatibility levels, 423 database undo, 437 MDI-Aware compatibility, 423 MDI-Capable compatibility, 426 MDI-Enhanced compatibility, 427 minimum requirements, 423 SDI-Only compatibility, 423 terminology, 418 transaction management, 437 multiple documents, interacting, 427
notification (continued) object reactors for, 404 obtaining ID of, 404 types of, 397 overview, 396 reactor classes for, 396 reactor lists, 396 reactors for, 398 use guidelines, 414 using, 396 numActiveTransactions() function, 413 numbers, real, 532 numeric conversions, 269 examples, 271
O
object enablers, 589 creating a self-extracting package, 591 developing, 590 populating the registry, 591 testing, 592 uploading, 594 using Live Enabler technology, 590 XML, 599 object IDs commit-time and, 452 for named object dictionaries, 478 obtaining, 19 obtaining object pointers from, 448, 451 ownership, for cloning, 467 object pointers in nesting transactions, 449 obtaining in transactions, 448, 451 with getObject() functions, 451 Object Property Manager API, 613, 651 adding properties for OPM, 654 AutoCAD COM Implementation, 651 categorizing properties for OPM, 656 dynamic properties, 659 ICategorizeProperties interface, 653 IDynamicProperty interface, 659, 660 implementing static OPM interfaces, 654 IOPMPropertyExpander interface, 654 IOPMPropertyExtension interface, 653 IPerPropertyBrowsing interface, 653 IPropertyManager interface, 659 static COM interfaces, 652 object snap, 254 object snap manager, 556 object snap points, 105 for intrinsic entity functions, 360, 353 objectAppended() function, 401 ObjectARX common characteristics of global functions, 520 communication between applications, 526
N
named object dictionary, 142, 243, 478 defaults, 20 names global versus local command names, 40 symbol table records and dictionaries, 144 symbol tables, 246 nested block reference highlighting, 115 entities, 224 hyperlinks, 138 transactions, 449 newIterator() function, 150 nonreentrant commands, 431 notification custom, 400 document events, 430 for AcDbObject and database, 400 functions, 396 immediate, 412 immediate versus commit-time events, 412 in long transactions, 70
Index
825
ObjectARX (continued) comparison of global function calls to AutoLISP calls, 520 creating objects in, 23 creating the MFC application skeleton, 187 defining AutoLISP functions, 522 deriving custom classes, 286 directory tree, 14 DXF group codes, 540 dynamically allocated linked lists of result buffers, 544 entity names, 536 error handling for global functions, 525 extension dictionary example, 89 extension dictionary global functions example, 91 external application handling, 530 global function argument lists, 520 global function memory requirements, 521 global function return values, 521 global utility functions, 519 include directory, 15 installing, 14 library directory, 15 logo program, 6 macros object reactor classes and, 404 reactor classes and, 399 opening and closing objects, 25 overview, 3 result buffers, 538 result type codes, 539 samples directory, 15 selection set names, 536 types, 531 user input control bit codes, 544 user interface, 171 using MFC with applications, 168 values, 531 variables, 531 wizard, 28 objectClosed() function, 399, 413 ObjectDBX, 691 AcDbHostApplicationServices class, 694 AcDbTransactionManager class, 698 AcEditorReactor notifications, 696 AcGi API, 699 AcGix API, 700 AcGixSimpleView class, 702 AcGixWhipView class, 704 active viewports in Model Space, 713 AcTransaction class, 698 AcTransactionManager class, 698 AcTransactionReactor class, 698 application services class, 694 creating a viewer, 699 custom classes, 286
ObjectDBX (continued) demand loading, 706 DesignXML, 609 differences from ObjectARX, 695 DWG files from earlier releases, 715 extended entity data (EED or xdata), 716 getDatabaseMutex() function, 703 getting started and using, 693 host application, 692 insert() function, 713 installing the libraries for your application, 707 libraries, 692 localization, 696 overview, 692 raster images, 717 releaseDatabaseMutex() function, 703 representing TrueType fonts in 3D space, 702 SimpleView, 702 tips and techniques, 710 transaction management, 698 TrueType font elaboration, 701 user interface and database access, 692 using the database mutex, 703 ViewAcDb viewer, 704 viewports, 714 WhipVIew, 704 XMX files, 696 objectErased() function, 401 objectModified() function, 401 objects AcApDocManager, 417 AcApDocument, 417 AcDbDictionary, 142 AcDbHyperlink, 138 AcDbLayout, 155 AcDbObject, 82 application-specific document objects, 430 closing, 82 container, 141 creating in AutoCAD, 20 creating in ObjectARX, 23 creating of protocol extension classes, 512 deleting, 84 document, 417 erasing, 93 error codes during opening, 84 exploding, cloning and, 475 filing, 94 handles, 19 implementation of automation, 633 newly created, and transactions, 452 obtaining pointers to, in transactions, 451 open modes, 83 opening, 82 opening and closing process diagram, 82
826
Index
objects (continued) overview, 82 ownership, 85 proxies, 389 references, 312 relationships between, 467 requesting handles, 83 snap modes, 105 snap modes, custom, 556 snap points, 105 solid, 793 using drawables in objects, 744 version support, 346 xrecord, 243 object-specific data adding, 85 objList space objects, 471 obtaining object IDs, 19 ON_MESSAGE() function, 168 OnInitDialog() function, 185, 198 online documentation, 6 OnModified() function, 630 open() function, 331 openedForModify() function, 399, 412 opening ObjectARX objects, 25 operations databases, 59 linear algebra, 768 wblock, 63 OPM See Object Property Manager API, 651 order dependence, 306 organization of extended data, 238 origin point of text, 257 otherInsert() function, 505 otherWblock() function, 503 overriding AcDbEntity functions, 354, 355, 356 common entity functions, 357 getOsnapPoints() function, 360 resources, 170 saveAs() function, 359 viewportDraw() function, 358 worldDraw() function, 354, 357, 358 overview of ObjectARX, 3 ownership and cloning, 467, 487 building a hierarchy, 315 cloning objects from different owners, 471 entities, 98 hard, 315, 467, 486 in named object dictionaries, 478 objects, 85 of entities, 244 references, 313 soft, 315, 467 structure for database entities, 99 types of cloning and, 467
P
paper space layouts, 157 linetype scale, 103 versus model space, 21 paper space display coordinate system definition, 275 parametric geometry, 769 partial undo, 328 paths library search, 42 subentity, 109 pause symbol, 250 pausing for user input in commands, 250 per-document data, 424 persistency of AcGe entities, 781 of dialog data, 184 pick points example in commands, 251 passing to commands, 250 picking forced entity, 566 plane classes, 768 pline() function, 740 plot settings, 159 point classes, 766 pointers AcEdJig and, 374 hard, 323, 467, 486 illustrated, 488 obtaining pointers to objects in transactions, 451 soft, 323, 467 points, 532 polygonDc() functions, 359 polygonEye() functions, 359 polygons primitives for drawing, 359 stretching, 363 polyline primitive, 740 polylineDc() functions, 359 polylineEye() functions, 359 polylines primitives for drawing, 359 scaling AsdkPoly and, 366 populating databases, 60 position() function, 136 PostNcDestroy() function, 185 pOwner parameter, 487 primitives, 731 arc, 739 defined, 358 functions for drawing, 358, 359 mesh, 732 polyline, 740
Index
827
primitives (continued) shell, 735 text, 740 printdxf() function, 226, 228 printing text on screen, 276 profile manager, 584 prompt line input, 261 prompting for input, 260 properties, common entity, 100, 726 protecting application extended data, 241 protocol extension, 509 AcDbXmlObjectProtocol, 599 class descriptor objects, 512 structure, 512 defining classes, 510 for custom object snaps, 557 implementing, 510 default class for, 513 for MATCH command, 514 unloading the application for, 513 MATCH command, 514 registering classes of, 511 using in applications, 513 proxy objects for entities and objects, 389 creating, 391 custom objects and, 390, 391, 394 displaying proxy entities, 392 editing proxy entities, 392 entities displaying, 392 editing, 392 modification of, 390 object life cycle, 390 unloading application for, 394 user encounters with, 391 using proxy objects, 391 PROXYGRAPHICS system variable, 392 PROXYSHOW system variable, 392 PSLTSCALE system variable, 66, 103 purge() function, 326 purging entities, 226, 326
Q
queueForGraphicsFlush() function, 453 quiescent, definition, 421
R
raster images in ObjectDBX, 717 rbChain() function, 160 RDS See registered developer symbol reactors, 396, 454, 505 AcDbObject and database notification events, 400
reactors (continued) classes, 396 custom notifications and, 400 database, using, 396 document, 418 editor deep cloning and, 465 using, 396 lists, 396 object, 397 building in object dependencies with (example), 406 ending transactions, 413 immediate versus commit-time events, 412 modifying, 413 obtaining ID of, 404 persistent, 397, 404 read-only state of, 413 transient, 397, 404 types of, 397 using, 396, 404 ObjectARX macros and, 399 obtaining the ObjectID, 404 persistent, 397 deriving, 404 transaction, 454 using, 396, 398 reading a drawing example, 67 read-only access in long transactions, 70 real numbers, 532 reappended() function, 399, 400, 413 recovering deleted entities, 219 redo, 327 redrawing the graphics screen, 278 references handling hard references to AcDbEntities during wblockClone(), 500 objects, 312 ownership, 313 pointer, 323 regapp table, 145 regen type See regeneration type regenerating drawings, 103, 720 entities, 237 regeneration type, viewport, 725 register.dbx file, 591 registered developer symbol (RDS), 7, 38, 40, 142 registering application names, 240 as MDI-Aware, 426 new commands, 38 protocol extension classes, 511
828
Index
registry See Windows system registry relational tests in filtering, 210 relationship between DuplicateRecordCloning and DeepCloneType values, 71 relationship between symbol table and deep clone types, 71 releaseDatabaseMutex() function, 703 reloading external references, 76 removing Windows system registry information, 48 renaming classes, 350 requesting, object handles, 83 requirements ObjectARX libraries, 7 software and hardware, 14 resbuf, 538, 544 example, 206, 207 value types, 206 resource management detaching resources, 188 explicit setting, 170 overriding resources, 170 overview, 169 switching resources, 170 responding to AutoCAD messages, 29 restoring color or linetype, 229 restoring state, 329 restype, 539 result buffers, 538, 544 result type codes, 539 retrieving blocks example, 245 retrieving data interactively, 260 retrieving definition data example, 230 retrieving extended data, 240 retrieving extended data for specific applications example, 241 retrieving user input, 260 returning values to AutoLISP functions, 268 RSERR, 527 RSRSLT, 527 RT codes, 539 RTCAN, 262 RTERROR, 262 RTKWORD, 262 RTNONE, 262 RTNORM, 262, 521, 530 RTREJ, 262 running applications from AutoLISP, 53 runtime class identification, 287 type identification mechanism, 9, 285 rxapi.lib library, 7 rxboiler.h file, 286 rxInit() function, 291
S
sampler() function, 376 with drag loop, 376 implementing, 378381 obtaining angle, distance, or point with, 380 overriding, 375 samples See examples samples directory, ObjectARX, 15 saveAs() function overriding, 359 proxy entities and, 392 saveComplete() function, 400 saving considerations, 62 databases, 61 entities, 359 file formats for drawings, 61 global functions, 63 saving a drawing, example, 67 scaling interactive, 214 selection sets, 213 schemas guidelines for authoring, 598 screen input, 261 low-level access, 278 scrolling tabs, 184 SDI system variable, 422 SDI, See single drawing interface search for files by name, 253 selecting extended data selection sets creating, 202 deleting items, 211 error handling, 216 filtering, 205 freeing, 205 graphically dragging, 266 interactive scaling, 214 interactive transformation, 214 limitations, 205 manipulating, 211 name, 202 name volatility, 202 names, 536 options, 203 scaling, 213 transformation, 213 sentinel code extended data, 237 resbufs, 229 sequence of events in applications, 33 setAt() function, 94, 152 setAttributes() function, 720, 722
Index
829
setColor() function, 152 SetDialogName() function, 185 SetDirty() member function, 185 setDispPrompt() function, 375, 376, 378 setFromRbChain() function, 160 setHighlight() function, 152 setKeywordList() function, 375, 378 setLayer() function, 152 setLinetype() function, 152 SetObjectId() function, 630 setPosition() function, 136 setSpecialCursorType() function, 375, 378 setting current database values, 66 current document without activating, 429 database color values, 66 database linetype scale values, 66 database linetype values, 66 default file format, 61 setUserInputControls() function, 375, 379 setVisibility() function, 152 setXData() function, 86 sheep cloning See deep cloning shell primitive, 735 SimpleView, 702 single drawing interface (SDI), 422 compatibility with MDI, 423 single-screen AutoCAD installations, 278 size limits for extended data, 242 skeletons creating, 187 snap modes, 105 custom objects, 556 keywords, 557 snap points, 105 implementing, 360 solid objects, 793 specifying a coordinate system, 274 stack, command, 38 stacked tabs, 184 starting long transactions, 70 storing information in objects, 244 STRETCH command, 365 stretch point functions, 365 stretch points, 353, 365 in grip editing, 363 string comparisons ignoring case, 282 with wild-cards, 281 string conversions, 269 examples, 270 leading and trailing zeros, 269 precision, 269 units used, 269, 270 string globalization, 553
struct resbuf, 538 subCancel() function, 331 subClose() function, 331, 400 subentities highlighting, 109 kEdgeSubentType, 110 kFaceSubentType, 110 paths, 109 subentPtr() function, 105, 111 subErase() function, 331 subObjModified() function, 399, 413 subOpen() function, 331 subsidiary functions, 331 summary information for drawings, 79 surfaces, AcGe functions, 769 switching, disabling document, 434 symbol tables, 142 accessing, 245 comparison with dictionaries, 142 contents at startup, 60 defaults, 20 getAt() function, 145 interating over symbol tables, 150 names, 246 names in records, 144 VPORT, 246 system cursor, disabling, 566 system registry See Windows system registry system requirements, 14 system variables accessing, 251 APERTURE, 255 AUNITS, 269 AUPREC, 269 CECOLOR, 66 CELTSCALE, 66, 102 CELTYPE, 101 CMDACT, 567, 568 CPROFILE, 584 DEMANDLOAD, 48 DIMZIN, 269 example of accessing, 251 freeing string space, 251 LTSCALE, 66 LUNITS, 269 LUPREC, 269 PROXYGRAPHICS, 392 PROXYSHOW, 392 PSLTSCALE, 66, 103 SDI, 422 TABMODE, 279 UNITMODE, 270, 272 sysvars. See system variables
830
Index
T
tab dialogs constructing, 185 extending, 184 extending built-in, 185 extensibility, 176 scrolling tabs, 184 stacked tabs, 184 using, 184 tables AcApProfileManager class capabilities, 584 AcApProfileManagerReactor class notifications, 585 AcGe global identifiers and header files, 764 application reactions to AutoCAD messages, 31 arbitrary user input, 264 ATL-based templates, 635 character type functions, 273 command lock types, 425 CursorType values, 379 deep clone types and duplicate record cloning, 71 DXF group code ranges for object representation, 305 DXF group coderanges for xrecords, 160 entity colors, 100 error codes, when opening objects, 84 exploding entities, 122 IAcDcContentBrowser interface functions, 668 IAcDcContentView interface functions, 669 ID map for overriding the wblockClone() function examples, 489 input context events, 567 input options set by acedInitGet(), 263 mesh traversers, 797 message handlers, 192 messages sent to ObjectARX applications, 29, 30, 31 object snap modes, 105 ObjectDBX libraries, 694 proxy flags options, 392 result type codes, 539 return values for user-input functions, 262 save format, 61 SDI system variable values, 422 selection set options for acedSSGet(), 203 states for opening objects, 305 topological traversers, 795 user-input function summary, 261 value-return function summary, 268 XMX file types, 697 tablet calibration, 279 example, 279 normalization, 281
tablet calibration (continued) transformation matrix, 280 tessellation, 744 testing object enablers, 592 text bounding boxes, 257 text box utility, 257 text origin, 257 text primitive, 740 text strings globalization, 553 measurement, 258 text style table, 145 TextTips, 174 tip windows, 174 tolerances, 765 ToolTip string, custom object snaps, 557 ToolTips windows, 174 topological objects, 792 brep, 792 complex, 792 edge, 792 face, 792 loop, 792 shell, 792 vertex, 792 topological traversers, 794 topTransaction() function, 449 traits entity, 726 subentity, 726 transaction management for ObjectDBX, 698 in MDI environment, 437 transaction model commit-time guidelines in, 452 graphics generation and, 448, 453 mixing, with open and close mechanism, 448 newly created objects and, 452 obtaining pointers to objects in, 448, 451 reactors, 454 stack, 450 undo mechanism and, 448 transactionAborted() function, 454 transactionEnded() function, 413, 452, 454 transactions aborting or ending, 450 boundaries, 450 graphics generation, 453 handling newly created objects, 452 management, 447, 448 manager, 449 model, 448 nested, 449 obtaining pointers to objects, 451 reactors, 454 undo, 453
Index
831
transactionStarted() function, 454 transformation matrices, 533 transformation, coordinate, 746 transformBy() function, 104, 106, 366, 393 applying transform matrix to entity, 366, 393 for stretch mode, 363 transforming coordinate data, 220 coordinate systems, 274 functions, 106, 366 interactive, 214 selection sets, 213 transient reactors, 397 deriving, 404 translating AcDbObjectIds and ads_names, 83 and cloning, 468 transparent commands, 41 traversers mesh, 797 topological, 794 TrueType font elaboration for ObjectDBX, 701 types ads_point, 274
user input (continued) controls, 379 during lengthy operations, 267 from prompt line, 261 from the screen, 261 function return values, 262 functions, 260 ignoring breaks and cancels, 267 input options, 263 keywords, 261 low-level access, 278 pausing for in commands, 250 raw, 278 restricted input example, 263 retrieving, 260 user interface in ObjectARX, 171 initialization, 172 support, 171
V
value types resbufs, 206 values attributes, 358 cursor (table), 378 points, 532 user input controls (list), 380 vector classes, 766 Veritest, 6 version support class data, 350 class xdata, 350 classes, 346 objects, 346 using class versioning, 348 vertexPosition() function, 136 vertices in a polyline, iterating through, 134 view table, 145 ViewAcDb viewer, 704 viewers, 699 viewport descriptors, 255 viewport regeneration type, 725 viewport table, 145 viewportDraw() function, 104, 357, 720, 724 proxy entities and, 392 regenerating graphics with, 357 saving and, 360 view specificity with, 358 view-dependence with, 357 visibility entity, 103 visibility, mesh face and edge, 734 Visual C++. See Microsoft Visual C++ VPORT symbol table, 246 vports AutoLISP function, 255
U
UCS See user coordinate system UCS table, 145 unappended() function, 399, 400, 413 undo, 327 automatic, 327 in MDI environment, 437 partial, 328 transactions, 453 undrawing, 278 unit conversions, 272 example, 272 unloading applications, 36, 42, 52, 394, 513 unlocking applications, 42 update() function, 376 implementing, 378, 381 modifying entity with, 381 overriding, 375 user breaks, 267 user coordinate system (UCS), 274 user input arbitrary input example, 264 AutoLISP expressions, 262 breaks, 267 cancel, 267 cancel example, 267 control bit codes, 544 controlling function conditions, 262
832
Index
W
watch_db() function, 401 wblock cloning and filing, 467 cloning and ownership, 467 notification functions, 503 operations, 63 WBLOCK command, 505 cloning application, 475, 478 wblockClone() function, 324, 486, 498, 500 AutoCAD commands using, 474 deep cloning with, 465 pOwner parameter, 487 symbol table record and, 487 translation phase, 475 WCS. See World Coordinate System WhipView library, 704 wild-card character matching, 281 ignore case, 282 matching example, 281 patterns, 281 patterns in filters, 208 special characters, 281 Windows system registry application entries, 44 creating file for custom object COM server, 637 keys and values, creating at installation, 47 managing applications with, 51 modifying at application installation, 45 populating for Live Enabler technology, 591 removing information, 48 requirements for AutoCAD DesignCenter API, 665 subkeys and values, creating in AutoCAD, 46 user profiles, 184 wizard, ObjectARX, 28 WM_ACAD_KEEPFOCUS, 168
workingDatabase() function, 19 World Coordinate System (WCS), 220, 747 definition, 274 worldDraw() function, 104, 720, 723 custom entities displayed in, 357 overriding, 354, 357 proxy entities and, 392 regenerating graphics with, 357, 376 saving and, 360 supporting proxy entity graphics, 360 view-independence with, 358 write, open and close mechanism for, 448
X
xdata, 86 ads_binary structure, 552 class version numbers, 350 exclusive data types, 552 for ObjectDBX applications, 716 See also extended data xData() function, 86 XML DesignXML, overview of, 596 information sources, 596 object enablers, 599 schemas, guidelines for authoring, 598 supporting custom objects, 597 XML APIs, 595 xmlIn(), implementing, 605 xmlOut(), implementing, 603 XMX files for ObjectDBX, 696 xrecords, 159 DXF group codes, 243 example, 161 hierarchy, 244 objects, 243 versus extended data, 244 xrefs. See external references
Index
833