0% found this document useful (0 votes)
222 views

Advanced Programming Techniques Vol4

Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
222 views

Advanced Programming Techniques Vol4

Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 206

AutoCAD expert’s Visual LISP

Volume 4

Advanced Programming Techniques

Reinaldo N. Togores
AutoCAD expert’s Visual LISP.
Volume 4
Advanced Programming Techniques

Copyright © 2013 by Reinaldo N. Togores

All Rights Reserved. No part of this book may be reproduced or utilized in any form or by any means,
electronic or mechanical, including photocopying, recording, or by any information storage and
retrieval system, without permission in writing from the author.

All data, information and examples provided by this book are for educational purposes only. All
information is provided on an as-is basis. Although the author and publisher have made every effort
to ensure that the information in this book was correct at press time, the author and publisher do not
assume and hereby disclaim any liability to any party for any loss, damage, or disruption caused by
errors or omissions.

Autodesk, AutoCAD, AutoLISP, Visual LISP, DWG, the DWG logo, Revit, 3ds Max and Inventor ar
registered trademarks or trademarks of Autodesk, Inc., and/or its subsidiaries and/or affiliates in the
USA and other countries. Autodesk screen shots reprinted with the permission of Autodesk, Inc.
Contents
Volume 4.

P ART 5. ADVANCED P ROGRAMMING.


Chapter 21. Reacting to Events: Reactors.
21.1. The VLR functions.
21.2. Events that trigger a reactor.
21.3. Actions.
21.4. Tutorial: An application using Reactors.
21.5. Enabling persistent reactors functionality.
21.6. Summary.

Chapter 22. DCL: The Graphical User Interface.


22.1. The DCL language.
22.2. Programming a dialog in the Visual LISP Editor.
22.3. Tutorial: Dialog box for generating Parametric models.
22.4. Controlling the dialog.
22.5. Event callback functions.
22.6. Assignment of the callback functions.
22.7. Activating the Dialog Box.
22.8. Generating the Model.
22.9. Summary.

Chapter 23. Associating information to Graphic Objects.


23.1. Blocks with attributes.
23.2. Extended Entity Data (XDATA).
23.3. XRECORD objects and Dictionaries.
23.4. Sample Program: Data in Dictionaries.
23.5. LDATA Dictionaries.
23.6. Access to external databases.
23.7. Summary.

Chapter 24. Tables.


24.1. Fundamental Methods for working with TABLES.
24.2. Sample Program: Block attributes Table.
24.3. Summary.

Chapter 25. Visual LISP as ActiveX client.


25.1. Tutorial: From AutoCAD to Excel.
25.2. Writing in the Worksheet.
25.3. The Dialog box.
25.4. Project Structure.
25.5. Summary.

Chapter 26. VLX: The Visual LISP Executable.


26.1. Managing an Application.
26.2. The VLISP Project Manager.
26.3. Namespaces.
26.4. Creating the Application Module.
26.5. Summary.

Chapter 27. OpenDCL.


27.1. The OpenDCL project.
27.2. The OpenDCL development environment.
27.3. Form Types.
27.4. Control Property Wizard.
27.5. Tutorial: An application developed with OpenDCL.
27.6. Adding the necessary controls.
27.7. Distributing the Application.
27.8. Summary.
Part 5
Advanced Programming Topics
In this final part of the book we have grouped a number of topics that go beyond the creation of
graphic objects. These topics include:

Programming reactors that can trigger actions depending on the occurrence of certain events.
Creating graphical user interfaces for the management of our programs.
Adding alphanumeric information to the drawing, in most cases associated with graphic objects
and the creation of tables using that information within the drawing.
Managing other applications in the Windows environment using Visual LISP programs.
Creating Visual LISP compiled applications operating in their own namespace.

From the point of view of their relationship with the user and the environment in which they run we
can distinguish between three basic types of programs . These three types are usually known as
Sequential, Interactive and Event-driven programs.

The first computer programs were of the sequential type. These are programs that follow a predefined
order: Reading the required data, which must be structured in a fixed way, then performing the
calculations for which it was designed and returning the results usually as a printout or a file written
to a storage device. When running a sequential program the user simply waited while it did its work
until the command prompt returned, indicating that other program could then be run. Such programs
are also known as procedure-oriented programs.

When user intervention is required during program execution we refer to interactive programs. In an
interactive program the user provides data or selects which actions to execute by means of some kind
of communication interface such as the AutoCAD command line. The AutoCAD command that draws
polylines is a good example of this programming style. Initially the user can't do anything but pick a
point on the screen or type its coordinates. This done, he can choose between specifying the next
point or selecting the options for drawing an arc, closing the polyline, specify its width, etc. If
drawing an arc is selected, other options will be offered, such as specifying an angle, indicating the
arc's center, and so on. User participation is still restricted to a predefined order as to when to
intervene and the information that can then be entered or requested.

Event-driven programming is usually identified with programs that display a Graphic User Interface
(GUI) as those in Microsoft Windows, Mac OS X, and X Windows for desktop or laptop computers
and those used in mobile devices using operating systems such as Symbian, BlackBerry OS, Android
or the iPhone OS from Apple. All of them present different alternatives to choose from usually
represented by images (icons), and a range of visual widgets that support the actions necessary to
achieve the user's goals, displayed as a form that can be completed in whatever order the user sees
fit. Usually data are validated within the form, not allowing the action that it is designed for to run
until the data fitness requirements have been successfully attained. Each user action on a component
of this GUI raises an event that will be managed by an event handler that may execute a certain action
in response to the event.

But not only forms are capable of raising events. All of an application's objects are able to do so
including the application itself considered as one more object. Monitoring these events and setting the
appropriate callback functions to the events we have decided to listen to, we can automate operations
to a great extent. User interventions can be combined with actions that are triggered not by the user's
decision but by the way that decision has affected the environment in which the program runs.

Regarding alphanumeric information in the drawing, the topic of linking to external databases will not
be addressed in this book. It would merit a whole book. It is also an aspect that is closely linked to
highly specialized applications such as Geographic Information Systems (GIS), so AutoCAD in its
"vanilla" version would not be the ideal environment. Vertical applications such as AutoCAD MAP
are specifically aimed at this kind of work.
Chapter 21
Reacting to Events: Reactors
An event is an action that takes place in the environment in which we operate, in our case the
AutoCAD application or in a more general sense, the computer. Obviously, not all actions that may
occur while running an application deserve the same attention. The applications programmers are the
ones who decide what actions deserve to be taken into account. Visual LISP has access only to events
raised within AutoCAD.

A reactor is a special object that can be linked to another object in the drawing, even the drawing
editor itself. This reactor is responsible for notifying our program when the event we are interested in
is raised. If an entity linked to a reactor is modified, the notice of such modification can be used by
the program, for example, to modify in the same way (moving, scaling, etc.) other entities that have
been associated to the one that causes the event notification.

A reactor communicates with the application by calling a function that our program has associated
with that reactor. These types of functions are known as callbacks. These are normal functions
(although with certain limitations) that become callback functions when linked to a reactor’s events.

AutoCAD has increased the role of reactors within the application. Features like parametric design or
associative surfaces are implemented using the reactor technology.

21.1 The VLR functions.


The functions used for everything related to reactors are identified by the vlr- prefix. These
functions are part of the Visual LISP ActiveX extensions requiring a call to vl-load-com before
they can be used. A prefix search using Apropos reveals that there are 47 such functions. Although
addressing a topic so prolific in functions may seem a bit overwhelming, it’s actually not so difficult
once the basic mechanism is understood. This does not mean that their use is simple, and not even
justified. The abuse of reactors can slow down our system, especially in the case of reactors
triggered by editor events that may be continuously raised. It may even be the case that the effect of
other reactor triggers actions which in turn will trigger the original and so on, originating an endless
loop. And many things that in Release 2000 could only be implemented using reactors are now built
into the application’s standard features.

Reactor types.
When planning the implementation of a reactor, the events we want our program to react to should be
carefully studied. There are five types of reactors in AutoCAD. Each reactor responds to one or more
events. They are grouped in the Database, Document, Editor, Linker and Object categories.

Each category includes only one type of reactor except for the Editor category, which in Release 14
Visual LISP was a single reactor. This reactor, which is kept for compatibility reasons has been
divided into 14 separate reactors.

The vlr-types function returns a list of all available reactors and the vlr-reactors function
returns a list of those reactors belonging to a certain category or categories that are defined in the
drawing. Reactor types are identified by keywords preceded by a colon. The functions used to create
Reactor objects have the same name without the colon. Evaluating (vlr-types) we obtain the
list:
(:VLR-Linker-Reactor
:VLR-Editor-Reactor :VLR-AcDb-Reactor
:VLR-DocManager-Reactor :VLR-Command-Reactor
:VLR-Lisp-Reactor :VLR-DXF-Reactor :VLR-DWG-Reactor
:VLR-Insert-Reactor :VLR-Wblock-Reactor
:VLR-SysVar-Reactor :VLR-DeepClone-Reactor
:VLR-XREF-Reactor :VLR-Undo-Reactor :VLR-Window-Reactor
:VLR-Toolbar-Reactor :VLR-Mouse-Reactor
:VLR-Miscellaneous-Reactor :VLR-Object-Reactor)

The category these reactors belong to and the events that trigger them are summarized in Table 21.1.

There are many different events to which a reactor can be linked. The user can perform many different
actions, and it is up to the developer the choice of the ones to listen for. Having decided on the action
one wants to respond to, the appropriate reactor can be linked to that event and the callback function
coded.

21.2 Events that trigger a reactor.


To find out exactly which events can activate a reactor we can use the vlr-reaction-names
function. This function returns a list of the events that can activate the reactor whose name it receives
as argument. Following is a brief overview of the events each of the reactors answer and a brief
description of the actions that trigger it.

Database events.

Triggered when a change is made to the drawing database. The events that trigger this reactor are
shown in Table 21.2.

Table 21.1. Categories and reactor types.


DATABASE Reactor

::VLR-AcDb-Reactor Reacts to adding, modifying or deleting entities.

DOCUMENT Reactor
Reacts to opening, closing, creating or activating a drawing in a
::VLR-DocManager-Reactor
multi-document (MDI) environment.

EDITOR Reactor

::VLR-Editor-Reactor General editor reactor (obsolete).

::VLR-Command-Reactor Reacts to starting, stopping or completing a command.

::VLR-DeepClone-Reactor Notifies a deep clone event.

::VLR-DWG-Reactor Reacts to saving, closing, etc. the current drawing.

::VLR-DXF-Reactor Reacts to reading/ writing a DXF file.

::VLR-Insert-Reactor Reacts to insertion of a block.

::VLR-Lisp-Reactor Reacts to starting/ending the evaluation of a LISP expression.

::VLR-Miscellaneous-Reactor Other Editor events.

::VLR-Mouse-Reactor Reacts to mouse click/double-click.

::VLR-SysVar-Reactor Reacts to modifying a system variable.

::VLR-Toolbar-Reactor Reacts to changing images in a toolbar.

::VLR-Undo-Reactor Reacts to Undo/Redo.

::VLR-Wblock-Reactor Reacts to exporting a block as a new file.

::VLR-Window-Reactor Reacts to resizing an AutoCAD window.

::VLR-XREF-Reactor Reacts to inserting/manipulating an XRef.

LINKER Reactor

::VLR-Linker-Reactor Reacts to loading/unloading an ObjectARX app.

OBJECT Reactor

::VLR-Object-Reactor Reacts to modifying/ copying/ deleting an object

Table 21.2. Database reactor events.


:VLR-AcDb-Reactor
::VLR-objectAppended Object created for the first time.
::VLR-objectUnAppended Object removed by UNDO.

::VLR-objectReAppended Object restored by REDO

::VLR-objectOpenedForModify The modification of an object begins.

::VLR-objectModified The modification of an object is concluded.

::VLR-objectErased Object removed by ERASE

::VLR-objectUnErased Object retrieved by undeleting (OOPS).

Document events.
When he status of a document changes the Document reactor is triggered. The events to which this
reactor responds are shown in Table 21.3.

Table 21.3. Document reactor.


:VLR-DocManager-Reactor
A new document has been created (a new one created or an existing one opened).
::VLR-documentCreated
Used for updating per-document structures.

::VLR-documentToBeDestroyed The destruction of a document begins.

A command will initiate or complete modifying elements in the document, changing its
::VLR-documentLockModeWillChange
lock status.

::VLR- A reactor vetoed itself from a :VLRdocumentLockModeChanged


documentLockModeChangeVetoed callback.

::VLR-documentLockModeChanged The document's lock mode of the document is being set or released.

The document has become the current document. It does not imply that it has been
::VLR-documentBecameCurrent
activated.

::VLR-documentToBeActivated The document is about to be activated.

Another document (it can even be a different application) has become the active
::VLR-documentToBeDeactivated
document.

Editor events.
Events associated to the :vlr-Editor-Reactor are included in the other Editor reactors, so we
will not review it as a separate entity. Its use is not recommended, as it could disappear in future
releases. The Editor events are shown in Table 21.4.

Table 21.4. Editor events.


:VLR-Command-Reactor

::VLR-unknownCommand An unknown command has been issued.

::VLR-commandWillStart A command has been invoked.

::VLR-commandEnded The current command is finished.

::VLR-commandCancelled A WBLOCK command has been canceled.

::VLR-commandFailed The command has failed to complete.

:VLR-DeepClone-Reactor

::VLR-beginDeepClone A deep clone operation begins.

A deep clone operation has two stages. First, each object and any other object it owns
::VLR-beginDeepCloneXlation are cloned. Second, any object ID references are translated to their cloned IDs. This
callback occurs between these two stages.

::VLR-abortDeepClone A deep clone operation is being aborted.

::VLR-endDeepClone A deep clone operation is being finished.


:VLR-DWG-Reactor

::VLR-beginDwgOpen A DWG file is about to being opened.

::VLR-endDwgOpen The opening of the DWG file has concluded.

::VLR-dwgFileOpened The drawing file has been loaded in the AutoCAD window.

::VLR-databaseConstructed The construction of the drawing database has concluded.

::VLR-databaseToBeDestroyed The contents of the drawing database are about to be deleted from memory.

::VLR-beginSave The drawing file is about to be saved.

::VLR-saveComplete The current drawing has been saved to disk.

::VLR-beginClose The drawing database is about to be closed.

:VLR-DXF-Reactor

::VLR-beginDxfIn The contents of a DXF file are about to be appended to the drawing database.

::VLR-abortDxfIn The import of a DXF file has been aborted.

::VLR-dxfInComplete A DXF file has been successfully imported.

::VLR-beginDxfOut The drawing database is about to be exported as a DXF file.

::VLR-abortDxfOut Export to a DXF file has been aborted.

The document has become the current document. It does not imply that it has been
::VLR-documentBecameCurrent
activated.

:VLR-dxfOutComplete Exporting to a DXF file has concluded successfully.


:VLR-Insert-Reactor

:VLR-beginInsert A block is about to be inserted.

A 3D transformation matrix (MINSERT) is about to be inserted into the drawing


:VLR-beginInsertM
database.

A single or multiple insertion has concluded but ID translation or entity transformation


:VLR-otherInsert
has no occurred yet.

:VLR-abortInsert Insertion has been aborted. The database may be in an unstable state

An insert operation has concluded. However, the inserted objects cannot be used until
:VLR-endInsert
the :vlr-commandEnded notification is received.

:VLR-Lisp-Reactor

:VLR-lispWillStart A LISP form is about to be evaluated.

:VLR-lispEnded The evaluation of a LISP form has concluded.

:VLR-lispCancelled The evaluation of a LISP form has been canceled.

:VLR-Miscellaneous-Reactor

:VLR-pickfirstModified The current document’s pickfirst selection set has been modified.

:VLR-layoutSwitched A different Layout has been set current.

:VLR-Mouse-Reactor
:VLR-beginDoubleClick The user has double-clicked

:VLR-beginRightClick The user has right-clicked.

:VLR-SysVar-Reactor

:VLR-sysVarWillChange A system variable is about to be changed.

:VLR-sysVarChanged The value of a system variable has been changed.

:VLR-Toolbar-Reactor

::VLR-toolbarBitmapSizeWillChange The size of the toolbar icons is about to change.

::VLR-toolbarBitmapSizeChanged The size of the toolbar icons has changed.

:VLR-Undo-Reactor

:VLR-undoSubcommandAuto UNDO has been executed with the AUTO option.

:VLR-undoSubcommandControl UNDO has been executed with the CONTROL option.

:VLR-undoSubcommandBegin The UNDO command is being executed with the BEGIN or GROUP options.

:VLR-undoSubcommandEnd The UNDO command is being executed with the END option.

:VLR-undoSubcommandMark The UNDO command is being executed with the MARK option.

:VLR-undoSubcommandBack The UNDO command is being executed with the BACK option.

:VLR-undoSubcommandNumber The UNDO command is being executed with the default NUMBER option.

:VLR-Wblock-Reactor

:VLR-wblockNotice A WBLOCK operation is about to start.

:VLR-beginWblockPt The command affects a group of objects.

:VLR-beginWblockId The command is performed on a block.

:VLR-beginWblock The command affects the entire drawing.

The command affects the database (objects have been copied but their identifiers but
:VLR-otherWblock
have not yet been resolved).

:VLR-abortWblock A WBLOCK command has been canceled.

:VLR-endWblock The WBLOCK operation has concluded successfully.

:VLR-beginWblockObjects WBLOCK has just initialized the object ID translation map.

:VLR-Window-Reactor

:VLR-docFrameMovedOrResized An MDI child frame window (a document window) has been moved or resized.

:VLR-mainFrameMovedOrResized The AutoCAD application window has been moved or resized .

:VLR-XREF-Reactor

:VLR-beginAttach An XRef is about to be attached.

:VLR-otherAttach The XREF’s objects have been added but their markers have not been resolved yet.

:VLR-abortAttach An XREF attach operation has been aborted.

:VLR-endAttach An XREF is about has been successfully attached.


:VLR-redirected An object ID in the XREF drawing is being modified to point to the associated object in
the referenced drawing.

The ID of the object is being added to the symbols table of the drawing to which the
::VLR-comandeered
XREF is being attached .

::VLR-beginRestore An existing XREF will be resolved, typically when loading a drawing that includes it.

::VLR-abortRestore The reload of an XREF has been aborted.

::VLR-endRestore An existing XREF has been successfully resolved.

::VLR-xrefSubcommandBindItem The XREF command's BIND option is invoked.

::VLR-xrefSubcommandAttachItem The XREF command's ATTACH option is invoked.

::VLR-xrefSubcommandOverlayItem The XREF command's OVERLAY option is invoked.

::VLR-xrefSubcommandDetachItem The XREF command's DETACH option is invoked.

::VLR-xrefSubcommandPathItem The XREF command's PATH option is invoked.

::VLR-xrefSubcommandReloadItem The XREF command's RELOAD option is invoked.

Linker events.
These are the means provided by AutoCAD to notify when an ObjectARX application is loaded or
unloaded. ObjectARX is AutoCAD programming environment for C++.

Table 21.5. Linker reactor events.


:VLR-Linker-Reactor

::VLR-rxAppLoaded A new ObjectARX program has been loaded. The program's initialization has ended.

::VLR-rxAppUnLoaded An ObjectARX program has been unloaded. The program has released the allocated memory.

Object events.
Object reactors inform the application when a particular object, which has previously been linked to
the reactor, has undergone a transformation, modifying, copying, or erasing it.

Table 21.6. Object reactor events.


:VLR-Object-Reactor

::VLR-cancelled The modification of the object has been canceled.

::VLR-copied The object is copied.

::VLR-erased The object's erase-flag has been set.

::VLR-unerased The object's erase-flag has been reset.

::VLR-goodbye The object is about to be permanently removed from memory.


::VLR-openedForModify The object has been open for editing.

::VLR-modified The object is about to be modified.

::VLR-subObjModified An object's sub-entity (polyline or mesh vertex, blockReference attribute) has been modified.

::VLR-modifyUndone The modification of the object has been undone.

:VLR-Object-Reactor

::VLR-modifiedXData The object's XDATA has been modified.

::VLR-unappended The object is removed from the drawing's database.

::VLR-reappended The object is re-attached to the drawing's database.

::VLR-objectClosed The object's modification has been concluded.

21.3 Actions.
Each reactor responds only to those events for which it was designed. The actions implemented in
response to events that trigger the reactors are defined in the callback functions.

A callback function is a user function like any other, created by evaluating a defun expression. In
them, only Visual LISP ActiveX extensions should be used in dealing with the drawing and its
environment.

Except for those corresponding to those for object reactors which receive three, all other callback
functions receive two arguments:

A VLR-object contains a reference to the reactor that invoked the function.


A list of parameters provided by the system.

In the case of object reactors, a first argument is added, the VLA-object representing the reactor’s
owner.

The name of the callback function is included as the second term in a dotted pair to create the reactor
object. The first term of this dotted pair will be the keyword which identifies the event to which the
callback function should react.

Implementing a reactor.
When developing an application which uses reactors one must have a clear idea about the behavior
we expect and the best way to accomplish it. A database or editor reactor would be necessary when
trying to keep track of the drawing globally. If we wish to address the behavior of specific objects, an
object reactor that will fire only when the event we are listening for takes place in relation to that
object would be the best choice.
The callback function.

Once what to do in response to which event has been decided, we must define the callback function.
The callback function is no different from a normal function that could be invoked from a program. It
is only necessary to follow some basic rules:

Don't make your action depend on the sequence of reactor notifications. The only certain thing in
an event sequence is that a Begin event will occur before the corresponding End event.
Don't rely on the sequence of actions occurring between notifications. They may not reflect the
true sequence taking place within the drawing database.
A dialog box should never be opened. But warnings such as those generated by the alert
function are admitted.
Never use the command/vl-cmdf interface within a callback function. In fact, we should
rather say that functions designed for user interaction should never be used. This rule would also
exclude get... functions, entsel, etc..
All operations on drawing entities should only be made using ActiveX methods, this also
excluding the entmod function.
An object should never be modified by a callback function triggered by itself. Only retrieving
information from it is admitted.
No callback function should trigger the same event to which it responds, as this would make the
system enter an infinite loop.
Different callback functions should not be associated with the same event. Whether the reactor is
already in use can be checked before associating a new callback function.
Check if a reactor exists before setting it. Doing so could activate several similar responses to
the same event.

Callback functions should be designed to accept two arguments (except for object reactors). The first
argument will contain a reference to the REACTOR object and the second a list of parameters (which
varies according to the event) that is provided by the system. The callback functions for object
reactors will receive as their first argument a reference to the object that triggers the action. The other
two arguments are the same as those required for other reactors.

Response functions used for debugging.

There are two predefined callback functions that can be used for debugging purposes. The vl-
beep-reaction function causes a beep (or the system’s default sound). The vlr-trace-
reaction function prints the keyword that identifies the event and the arguments received in the
Trace window. Both functions can be included in callbacks to find out which events are raised in a
particular operation and when one of them is taking place .

Creating the reactor.

Once we have programmed our callback function it must be associated to the event that shall trigger
it. There is a specific VLISP function for creating each type of reactor. This is the reason for such a
large number of reactor functions.

Our callback function can be tested before it is associated to a reactor. This can be done by
supplying the required number of arguments, which may well be nil. In the case of the object
reactor callback function shown below, it was tested during its development by supplying an
object selected on screen instead of the one that the reactor would supply when the event was
raised. This ensured that the basic operation was correct, which doesn't mean certain
adjustments were not necessary when associated with the reactor, but these were mostly
related to the event previously chosen than with the function's operation.

The first argument, which may be nil, is a LISP object (e.g., a string) that can serve to identify the
reactor using the function vlr-data. The second is a list of dotted pairs in each of which the first
term is a keyword that identifies the event and the second, the name of the callback function.

The Object reactor.


To create the object reactor it is necessary to include a first argument representing its owner list, i.e.,
those to which the reactor is associated. These objects are the only ones able to raise the event that
will trigger the callback action. The syntax for the function that creates the object reactor is:
(vlr-object-reactor (owner…) data ((event . callback)…))

The other arguments are the same as for other reactors.

Reactors in an MDI environment.


A reactor can be modified so that its callback functions can be run even if the document containing the
reactor (i.e., its associated namespace) is not the current one. The vlr-set-notification
function can be used for this. This function takes as arguments the reactor and a symbol that identifies
the range in which it will be triggered.
(vlr-set-notification reactor-obj 'range)

The reactor-obj argument is a VLR-object and range can be 'active-document-


only to be applied only if the reactor is associated with the active document, or 'all-
documents so the callback will be triggered even if the reactor is not associated with the active
document. Obviously this makes no sense for an object reactor that resides on a single document, but
could be used for reactors of other types. Moreover, if a response function operating in the all-
documents range tried to modify a drawing object the system may become unstable. Their use
should be limited to reading/writing AutoLISP variables. This way of using reactors may have an
informative purpose, for example, keeping track of the time each draftsman spends in each project.
The vl-notification function can be used to find out the range defined for a given reactor. It
receives a VLR-object and returns the symbol that identifies its range.

Other operations on REACTORS.


Finding out the reactors defined in a drawing.

To find out what reactors are defined in the current drawing the vlr-reactors function can be
used. Without arguments, this function returns a list with all the existing reactors.
_$ (vlr-reactors)
((:VLR-Object-Reactor #<VLR-Object-Reactor>))
_$

The expression above tells us that there is one reactor object in the current active drawing. The first
term is the keyword identifying the type and it is followed by the pointers to each reactor of that type.
If a reactor type is specified as an optional argument the list will only contain reactors of that type.
_$ (vlr-reactors :vlr-editor-reactor)
nil
_$

The expression above checks if there are any Editor reactors in the drawing. Other functions provide
more information on the existing reactors.

The expression (vlr-type reactor-obj) Returns a keyword identifying the reactor’s type.

The AutoLISP application-specific data associated with a reactor are returned by:
(vlr-data reactor-obj)

These data can be changed with:


(vlr-data-set reactor-obj data)

A reactor’s owners can be can be found in the list returned by:


(vlr-owners reactor-obj)

And the following expression adds a VLA-object as new owner:


(vlr-owner-add reactor-obj owner)

An owner will be removed by:


(vlr-owner-remove reactor-obj owner)

To determine a reactor’s callback function and the event that triggers it vlr-reactions may be used.
_$ (vlr-reactors)
((:VLR-Object-Reactor #<VLR-Object-Reactor>))
_$ (setq reactor-obj (cadar (vlr-reactors)))
#<VLR-Object-Reactor>
_$ (vlr-reactions reactor-obj)
((:VLR-objectClosed . SCALE-TEXT))
_$

The expressions above show how to retrieve a reference to a reactor in order to investigate the
callback function (in this case SCALE-TEXT) and the event that triggers it, :VLR-
objectClosed. The callback function can be changed evaluating:
(vlr-reaction-set reactor-obj event function)

Inspecting Reactors.

If we select the name of the variable to which a reactor object is assigned and the Inspect button
(CTRL+SHIFT+I) is clicked, the Inspect dialog box will be displayed (see Figure 21.1) showing
the reactor’s data, including its owners, events and callback functions, if it is active, its associated
data, its range and the document it is defined in.

Figure 21.1. Inspecting a reactor.

Deleting and restoring Reactors.

A reactor defined in a drawing can be deleted with (vlr-remove reactor-obj).


_$ (vlr-remove reactor-obj)
#<VLR-Object-Reactor>
_$ (vlr-reactors)
nil
_$
But if a reference to it is kept it can be restored with:
(vlr-add reactor-obj)

All the drawing’s reactors deleted with a call to:


(vlr-remove-all [reactor-type])

The optional argument reactor-type allows deleting only those of the specified type.

Persistence.
A reactor usually lasts no more than the work session in which it was created. They are not persistent.
For a reactor to be available the next time the drawing, is opened, the vlr-pers function should be
used. This function receives a reference to the reactor to be preserved as its argument. But making a
reactor persistent does not imply that its callback function will be available the next time the drawing
is opened. For this to be so, care must be taken to include in AutoCAD’s startup files (for example, in
acaddoc.lsp) the required code. An alternative is defining the reactor and callback functions in a
separate-namespace VLX. In that case the associated callback functions will be loaded automatically.
In any case the program’s file must be in the AutoCAD support files search paths.

To check whether a reactor is persistent the vlr-pers-p predicate is used. A list of the persistent
reactors in the drawing them can be obtained with vlr-pers-list. To remove the persistent
character from a reactor vlr-pers-release can be used.

21.4 Tutorial: An application using Reactors.


To demonstrate the use of reactors we will make use of an application which uses object and editor
reactors. We will study how to make these reactors persistent and ensure that the necessary callback
functions are loaded using the acaddoc.lsp file.

The purpose of this application will be to enable a PaperSpace Viewport to recalculate the
ModelSpace text dimensions so that they maintain the same size even when the scale of that view
relative to paper space changes.

This capability has been present for a long time for non annotative dimension texts by
activating the Scale dimensions to layout option in the corresponding dimension style (or
setting the DIMSCALE system variable to 0). This automatic text scaling characteristic is not
available for normal texts, that to attain a somewhat similar functionality should be set to
annotative, meaning that the text will display only at its predefined scale level.
In our case, what we pretend is changing the existing ModelSpace text’s height each time the
viewport’s scale changes, so their size in the viewport remains constant. This, as shown in Figure
21.2, without affecting any text in PaperSpace or texts in frozen or locked Layers.

In order to do this we must have a predefined text size. This value may be stored in various ways: as
XDATA in the text entity, using the text style’s specified height, etc. Since this is just a reactors
example we don’t want to complicate with topics already studied in other parts of this book, we will
simply use in this program the value assigned to the TEXTSIZE system variable. Basing our
calculations on a single value has the disadvantage that all texts will have the same size, but we
consider it sufficient for our purpose.

Figure 21.2. Effect of the reactor when changing the Viewport's scale.

Listening only to the viewport’s events would have the disadvantage that changing its scale factor by
zooming in ModelSpace does not trigger events that indicate that its value has changed. For this
reason we must also implement besides a viewport object reactor, a command reactor that checks if
the viewport has been closed (which is done by the _PSPACE command) in order to check the scale
factor and trigger the text height modifying callback function. In any case at least two editor reactors
should be implemented to avoid that the object reactor triggers the callback function when any
command is active.

Application components.
A vital consideration for this application is the project’s structure, which must be distributed in two
different files. The code will be organized in this way with the intention that callback functions can be
loaded independently from the command used to link the reactors to the viewport. A folder structure
like the one shown in Figure 21.3 will be used for this project.

Figure 21.3. Folder structure for the Reactors project.

Inside the TextScale folder we create two files: scale-text.lsp and reactor-functions.lsp. The
scale-text.lsp file will include:

the new command C:AUTO-SCALE (Listing 21.6),


the vport-reactor (Listing 21.4) and command-reactor (Listing 21.5) functions
designed to build the reactors,
the owner? (Listing 21.7) auxiliary function that acts as a predicate to check if the selected
object is already assigned to a reactor.

The reactor-functions.lsp file will include all the code necessary for the reactors to operate in a
drawing with reactors linked to a viewport and made persistent in a previous work session. This file
will include:

the vport-callback (Listing 21.2) and command-end-callback (Listing 21.3)


callback functions.
the process-texts function (Listing 21.1) which implements ModelSpace text height
changes.
the can-use? function (Listing 10.40) which acts as a predicate to check that the Layer where
the text resides is not locked or frozen.
A project named TextScale will be created including in it these two files. In the project build
options Standard as Compilation Mode and One module for each file as Merge files mode
should be selected. And as FAS and TMP directories, the folders created with those names1.

Functions in the REACTOR-FUNCTIONS.LSP FILE.


In the first place we will discuss the callback actions, as this way we will be able to discern which
reactors should be created to reach our objectives. To change text sizes we must implement actions
responding to events with different origins: changes in the viewport’s properties and the conclusion
of the _PSPACE command.

Viewport events.

When changing some viewport property a sequence of object events that include the following will be
raised:

:vlr-openedForModify, the object's modification process has begun.


:vlr-modified, when the object has been modified.
:vlr-objectClosed, indicating that its modification has concluded.

Trying to read any property without having closed the object will produce an error. For this reason,
the event that must trigger the reaction we want is :vlr-objectClosed, indicating that the
modification process has concluded.

Command events.

In case the scale factor is modified by zooming in ModelSpace through the open viewport, a
command sequence will take place that includes:

_MSPACE to open the window and allow access to ModelSpace.


ZOOM, either explicitly or by rolling the mouse scroll wheel.
_PSPACE to close the viewport and return to PaperSpace.

It is on concluding the _PSPACE command that we will be able to examine the viewport’s scale in
order to modify the text size. In that case we must monitor the :vlrcommandEnded event to
implement a callback function in this case customized to run only if the concluded command is
_PSPACE. To prevent the object reactor’s callback function being triggered for any reason during a
command’s execution we will check the value of the CMDACTIVE system variable so the viewport
object reactor’s callback action will not be executed if its value is not zero.

Processing TEXT.

Before getting into specific issues relating to the operation of the reactors, we will examine the
procedure used for updating text heights. This is one of the functions to be included in the reactor-
functions.lsp file.

T he process-texts function receives as its notif-obj argument the reactor’s associated


Viewport object. The function is designed to do the following:

Select the TEXT objects in ModelSpace.


Retrieve the Viewport object's CustomScale property that will be used in calculating the
scale factor for the text heights.
Calculate the new text height from the CustomScale and TEXTSIZE system variable values.
Traverse the selection set changing one by one each text's height, provided they are not in Layers
that are locked, frozen or turned off. This condition is checked by the can-use? predicate that
was defined in Chapter 10.

As for drawing entities modification, we have seen far more complex examples in previous chapters.
We will see that the difficulty in this example lies mainly in the reactors implementation .

Selecting texts.

The selection of texts will be done using the ssget function. A peculiarity of this selection is that
we will do it while a PaperSpace Layout is active, but the texts selected must be precisely those that
are not in PaperSpace, but in ModelSpace. There would be two filters that can be used to achieve
this. One would be using the value associated to DXF group code 67, which if greater than 0
indicates that the entity is in PaperSpace. But this group code is optional according to AutoCAD’s
documentation (although in our experience it usually appears in the entity list). Moreover, since
AutoCAD 2000 there can be several PaperSpace Layouts each containing different objects. So a safer
filter would use the Layout name, associated with group code 410 which, according to the
documentation, is never omitted. Although PaperSpace Layouts can be renamed, the ModelSpace
Layout is always called "Model".

Scale factor and new text size.

The scale factor is set to (/ 1 CustomScale), CustomScale being the Viewport’s property
that contains its current scale. The text height value is calculated by multiplying the value of the
TEXTSIZE variable by this scale factor. If a different text size is desired, TEXTSIZE can be entered
in the command line to specify the new value.

Transforming the text.

Once this value is calculated, the selection set is traversed using the index i, which is initially set to
zero and is incremented by 1 for each iteration. Using the ename returned by ssname a reference to
the VLA-object is obtained. It is necessary to check whether this object can be modified, using the
vlax-write-enabled-p predicate. If the text object is in a locked2, frozen or turned Off Layer
it must not be modified, so the can-use? function (Listing 10.40) will be used to check this
condition. It acts as a predicate, returning T if the Layer object obtained by vla-get-Layer is not
locked, frozen or off. Neither will it be modified if it already has the desired size.

In case the program determines that the entity can and should be transformed, it will be done by
setting its Height property to the value assigned to the text-height variable.
(defun process-texts (obj-notif / sel-text
new-scale text-height i
txt-str)
(setq sel-text
(ssget
"X"
'((0 . "TEXT")(410 . "Model")))
new-scale
(vla-get-CustomScale obj-notif)
text-height
(* (getvar "textsize")
(/ 1 new-scale))
i 0)
(while (setq txt-str (ssname sel-text i))
(setq txt-str
(vlax-ename->vla-object txt-str))
(if
(and (vlax-write-enabled-p txt-str)
(can-use? (vla-get-Layer txt-str))
(/= (vla-get-height txt-str)
text-height))
(vla-put-Height txt-str text-height))
(setq i (1+ i))))

Listing 21.1. Function that processes the texts.

T he process-texts function is called from the vport-callback function (Listing 21.2)


triggered by the viewport’s :VLR-objectClosed event and from the command-end-
callback function (Listing 21.4) triggered by the _PSPACE command’s :VLR-
commandEnded event.

VPORT-CALLBACK function.

By the term modification we are referring to any property change. This fact, as we shall see,
introduces numerous difficulties in operating with reactors. As many commands can modify in an
unforeseen way the VIEWPORT object the rule would be, so to speak, to stay still while any
command is active. This is accomplished by checking the CMDACTIVE system variable’s value that
must be 0 if no command is currently active. This is the first thing checked by the vport-
callback function (Listing 21.2) within an and expression.
(defun vport-callback
(obj-notif obj-react lis-param)
(if (and (zerop (getvar "CMDACTIVE"))
(vlax-read-enabled-p obj-notif)
(= (getvar "ctab")
(car (vlr-data obj-react))))
(process-texts obj-notif)))

Listing 21.2. Callback function triggered by changes in the viewport.

It is wise to anticipate other possible sources of error. For this reason the predicate vlax-read-
enabled-p is used to check that the notifying object’s properties can be read. This checked, then it
is verified that the current layout’s name (as read in the CTAB system variable) matches the custom
data saved in the reactor. Only if all these conditions are met the text modification process will be
launched.

COMMAND-END-CALLBACK function.

T h e command-end-callback function (Listing 21.3) is triggered by the :VLR-


commandEnded event. This callback function has two purposes:

Checking that the event has been raised by concluding the PSPACE command
Checking that the selected viewport is not read-only.

If these requirements are met, the process-texts function is called.

The Viewport is obtained from the custom data associated with the reactor object (first argument
obj-react passed to the vport-callback function) retrieved using the vlr-data function.
These data form a list in which the second term is the viewport’s ename:
(vlax-ename->vla-object
(handent (cadr (vlr-data obj-react))))

The command name is obtained as the first term in the parameter list received by the function as the
param-lst argument.
(defun command-end-callback
(obj-react lis-param / vport-obj)
(setq
vport-obj (vlax-ename->vla-object
(handent (cadr (vlr-data obj-react)))))
(if (and (= (car lis-param) "PSPACE")
(vlax-read-enabled-p vport-obj))
(process-texts vport-obj)))

Listing 21.3. Callback function triggered on concluding a command.

The functions process-texts, can-use?, vport-callback and command-end-


callback will be included in the reactor-functions.lsp file.

Creating the REACTORS.


The functions for creating the reactors will be included in the scale-text.lsp file. According to what
was explained above it will be necessary to define reactors for the two events we have decided to
monitor.

One of these events corresponds to the object reactor: (:VLR-Object-Reactor)and the other to
the command reactor (:VLR-Command-Reactor), so we have to create reactors for those two
events.

The OBJECT reactor.

The vport-reactor function (Listing 21.4) creates the object reactor. It receives as argument the
Viewport VLA-object. It evaluates the VLR-Object-Reactor function that in addition to
defining the Viewport as the reactor’s owner, sets a custom data list that includes the name of the
current Layout and the viewport’s handle. Finally it establishes the link between the event and its
callback function. It can be seen that in the dotted pair list '((event. reactor)…) :VLR-
objectClosed is associated with vport-callback.
(defun vport-reactor (vport)
(vlr-pers
(vlr-object-reactor
(list vport)
(list (getvar "ctab")
(vla-get-Handle vport))
'((:VLR-objectClosed . vport-callback)))))

Listing 21.4. Creating the Object reactor.

The Command reactor.

To monitor the commands status the command-reactor function (Listing 21.5) creates a :VLR-
Command-Reactor that listens to the :VLR-commandEnded event, associating it with the
command-end-callback function.
(defun command-reactor (vport)
(vlr-pers (vlr-command-reactor
(list (getvar "ctab")
(vla-get-handle vport))
'((:vlr-commandEnded
.
command-end-callback)))))

Listing 21.5. Creating the Command reactor.

Both the creation of the object reactor and the command reactor object are included in calls to the
vlr-pers function, which has the effect of making them persistent, i.e., storing in the drawing these
reactors’ associations. This does not guarantee that the callback functions on which they depend will
be available the next time the drawing is opened. the way to achieve this will be explained later.
User interface for creating the reactors.
C:AUTO-SCALE function.

To create the reactors a user interface that prompts the user in order to avoid mistakes has been
designed. It is implemented as an AutoCAD command in the C:AUTO-SCALE function (Listing
21.6).

The first thing checked is whether a PaperSpace Layout is current. In case it is not a message warning
that this command only works only in PaperSpace is displayed.

If run from PaperSpace a loop will start prompting for an object selection, filtering it so that only a
VIEWPORT will be admitted. Assigning several reactors to the same object can be a source of
problems, so to avoid it we call the owner? function (Listing 21.7) that checks whether the object is
already associated with a reactor. A new reactor is associated only if the object owns no other
reactor. If all these tests are passed, the functions that enable the reactors, vport-reactor
(Listing 21.4) and command-reactor (Listing 21.5), will be called. In case they must be disabled
we can evaluate the expression (vlr-remove-all).
(defun C:AUTO-SCALE (/ viewport vport-obj)
(cond
((/= (getvar "ctab") "Model")
(prompt
"\nSelect viewport for text auto-scaling: ")
(while (not
(setq viewport
(ssget "_:S"
'((0 . "VIEWPORT"))))))
(setq
vport-obj (vlax-ename->vla-object
(ssname viewport 0)))
(if (not (owner? vport-obj))
(progn (vport-reactor vport-obj)
(command-reactor vport-obj))
(alert
"This viewport already owns reactors.")))
(t
(alert
"Command only valid \nin PaperSpace."))))

Listing 21.6. C:AUTO-SCALE. User interface for creating the reactors.

Function that checks if an object owns reactors.

To verify whether an object owns reactors, the owner? function (Listing 21.7) must go from the
drawing’s environment to the object. We must do so because there is no function that given the object
will return the reactors it owns. But we can get the objects from the reactors. So we will start by
creating a list of the existing reactor objects. The vlr-reactors function returns that list and as
we are only interested in object reactors, we include the optional argument which indicates the
reactor type we want:
(vlr-reactors :VLR-Object-Reactor).

As this function returns a list of lists, each one with the keyword identifying the type followed by
pointers to each reactor of that type, and we are only interested in the reactor objects, we map cadr
on the list to get rid of the unwanted terms. To find the a reactor’s owners we can use the vlr-
owners function. Hence, the lambda expression we map on the list of pointers obtained, which
will return a list containing T for each reactor if the object being tested is found among its owners or
nil if it is not. Applying or to this new list we obtain the final result, T if the object has a reactor
and nil if it does not.
(defun owner? (obj / reactors)
(if (setq reactors
(mapcar 'cadr
(vlr-reactors
:VLR-Object-Reactor)))
(apply
'or
(mapcar
'(lambda (x)
(if (member obj (vlr-owners x))
t))
reactors))))

Listing 21.7. Function that determines if an object owns reactors.

21.5 Enabling persistent reactors functionality.


For persistent reactors to operate in future work sessions we have to enable a mechanism that loads
its callback functions automatically. That is why I have recommended grouping this project’s
functions in two different files. Those needed for the operation of the persistent reactors are included
in the reactor-functions.lsp file. The functions included in scale-text.lsp will only be required for
associating reactors to new Viewports. These files must be saved in the special folder Visual Lisp
Projects, dedicated to the applications we have developed and which we have included the AutoCAD
support files search paths using the Files tab in the Options dialog box.

Once our files are in that folder we will create, if we had not done it before, a file named
acaddoc.lsp in the same folder. That file will contain instructions for loading those files that contain
functions that must be available for all drawings that are opened. Although we can include the code of
functions in this file, this is not advisable. It is far better to include calls to only two functions: load
and autoload.

The load function.


The load function receives as argument the name of the file containing the code we want to be loaded
for each drawing opened or created as a new document. The file’s extension is not needed, as when
searching for a file with that name the extension vlx will be tried first continuing, if not successful,
with the fas extension and finally the lsp extension.

The name can include the path, using as directory delimiters the slash (/) or two backslashes (\\).
The paths can be relative: the expression (load "./test/test1") loads the test1.lsp file
from a test subfolder in any of the directories included in the support files search paths.

The load function accepts an optional second argument that will be evaluated in case the load fails:
(load "./Test/testXXX" "ERROR: Could not find testXXX"))

which in case the file is not found will print in the command line the message
ERROR: Could not find testXXX.

The following expression in acaddoc.lsp will load the reactor-functions file:


(load "reactor-functions" "REACTOR-FUNCTIONS not found")

The autoload function.


T he autoload function associates the name of a function defined as an AutoCAD command
(prefixed with C:) to an AutoLISP code file in which it is defined, loading it the first time the user
enters the command. Until that occurs, these functions will not occupy memory space. For this reason,
this is the function that will be used for loading the scale-text.lsp file that contains a command that
will only be used when associating reactors to a new viewport.

Autoload takes two arguments, the first is the file name in the same conditions as in load and the
second is a list of the commands defined in that file that should be considered for automatic loading.
In our case, as it is a single command, the expression would be:
(autoload "scale-text" '("AUTO-SCALE"))

The ACADDOC.LSP file.


So to the two files that we have already included in our personal applications folder we must finally
add a file called acaddoc.lsp that contains the calls to load and autoload. For practical reasons a call
to vl-load-com is also included so the Visual LISP ActiveX functions will always be available.
(vl-load-com)
(load "reactor-functions"
"REACTOR-FUNCTIONS not found")
(autoload "scale-text" '("AUTO-SCALE"))
Listing 21.8. Code to include in ACADDOC.LSP.

21.6 Summary.
The same effect demonstrated in this program instead of being automated through reactors, could
simply rely on the selection of a menu option. In fact, AutoCAD with the introduction of annotative
objects approaches this problem in a different way. Instead of changing the text height, it simply
associates the texts with different scale ranges, out of which the text will be invisible. It’s a different
approach to the subject, but whose implementation also depends on reactors.

On the other hand, as stated in the introduction to this chapter, with each new release more and more
commands are based on reactors functionality with no need for programming. Until recently,
establishing geometric relationships between objects so that changing the orientation of one would
automatically modify the orientation of the other was among the most widespread demonstrations of
reactors programming. This can be done today applying geometric constraints, without a single line of
code.

Finally, we will summarize the concepts expressed regarding reactors. The steps for implementing a
reactor would be:

1. Assess if the desired behavior merits working with reactors. Many times an action can be
automated to a great degree without leaving to the system the decision of what to do and when.
2. Once convinced of the need for reactors, we must decide the action and the event that should
trigger it.
3. Program the action using ActiveX procedures.
4. When programming the action, anticipate all the possible situations that may induce errors.
5. Create the reactor linking the event to the planned callback action.
6. Arm yourself with a great deal of patience while testing your program. We will find that an
event never comes alone. Any operation raises a series of events that may occur in an
unexpected order. It is likely that we'll find other events that should also be listened to, besides
the one that triggers the action we are interested in. In the case we have studied, its operation
would be unfortunate without taking into account the two command events.

As can be seen, working with reactors can be extremely complex. We would recommend their use
only to experienced developers. If you’re now just in your first steps, don’t worry about using
reactors. But it’s our intention to make a book worth keeping. If a topic like this now overwhelms
you, don’t worry. Keep on programming and take a look at it a year from now.

Functions to examine the sequence of events.


To develop this program and its fine-tuning we had to code a series of reactors for information
purposes that can be used to verify the sequence of events that could be used in triggering the different
actions. As we consider them very instructive we list them here. All these reactors trigger the
reaction callback function (see Listing 21.9) which prints to the text window information on the
monitored events.
(defun reaction (reactor params)
(princ (vlr-data reactor))
(princ " | ")
(princ (vlr-current-reaction-name))
(princ " | ")
(princ params)
(princ "\n"))

Listing 21.9. Callback function that reports on events.

This function can be applied to all except the object reactor, the only difference being that it must
receive an additional argument.
(defun reaction-obj (obj reactor params)
(princ (vlr-data reactor))
(princ " | ")
(princ (vlr-current-reaction-name))
(princ " | ")
(princ params)
(princ "\n"))

Listing 21.10. Object reactor callback function.

Database informative reactor.

To trace the sequence of database events the reactor shown in Listing 21.11 is used.
(vlr-acdb-reactor
"DataBase"
'((:vlr-objectAppended . reaction)
(:vlr-objectUnAppended . reaction)
(:vlr-objectReAppended . reaction)
(:vlr-objectOpenedForModify . reaction)
(:vlr-objectModified . reaction)
(:vlr-objectErased . reaction)
(:vlr-objectUnErased . reaction)))

Listing 21.11. Creation of the Database reactor.

Editor informative reactor.

To trace the sequence of Editor events the reactor shown in Listing 21.12 is used.
(vlr-editor-reactor
"Editor"
'((:vlr-beginClose . reaction)
(:vlr-beginDxfIn . reaction)
(:vlr-abortDxfIn . reaction)
(:vlr-dxfInComplete . reaction)
(:vlr-beginDxfOut . reaction)
(:vlr-abortDxfOut . reaction)
(:vlr-dxfOutComplete . reaction)
(:vlr-databaseToBeDestroyed . reaction)
(:vlr-unknownCommand . reaction)
(:vlr-commandWillStart . reaction)
(:vlr-commandCancelled . reaction)
(:vlr-commandEnded . reaction)
(:vlr-commandFailed . reaction)
(:vlr-lispWillStart . reaction)
(:vlr-lispEnded . reaction)
(:vlr-lispCancelled . reaction)
(:vlr-beginDwgOpen . reaction)
(:vlr-endDwgOpen . reaction)
(:vlr-dwgFileOpened . reaction)
(:vlr-beginSave . reaction)
(:vlr-sysVarWillChange . reaction)
(:vlr-sysVarChanged . reaction)))

Listing 21.12. Creation of the Editor reactor.

Object informative reactor.

For the Database and Editor reactors, evaluating the above expressions is all that is needed to enable
the reactors. As in the case of the object reactor it is necessary to designate the object to be
monitored, the function in Listing 21.13 is used to prompt for an interactive selection of the object.
(defun C:VLR-OBJECT ()
(setq obj
(vlax-ename->vla-object (car (entsel))))
(vlr-object-reactor
(list obj)
"Objeto"
'((:vlr-cancelled . reaction-obj)
(:vlr-copied . reaction-obj)
(:vlr-erased . reaction-obj)
(:vlr-unerased . reaction-obj)
(:vlr-goodbye . reaction-obj)
(:vlr-openedForModify . reaction-obj)
(:vlr-modified . reaction-obj)
(:vlr-subObjModified . reaction-obj)
(:vlr-modifyUndone . reaction-obj)
(:vlr-modifiedXData . reaction-obj)
(:vlr-unappended . reaction-obj)
(:vlr-reappended . reaction-obj)
(:vlr-objectClosed . reaction-obj))))

Listing 21.13. Function to link an informative object reactor.

Exercises.
Exercise 1.
In our sample application the possibility that there could be Layers frozen only for the current
viewport was not taken into account. It wouldn’t be reasonable to include the texts in these Layers
among those which are modified. To check whether a Layer is frozen in the current viewport it is
necessary to read its DXF group code 331 that contain the enames of Layers frozen for that
viewport. This is an additional condition that can be added to the can-use? predicate.

Exercise 2.

Study the events raised by different operations performed in the drawing and their sequence, creating
the informative reactors shown in the previous section. In relation to the sample program, one of the
greatest difficulties we met in its development was the incredible series of events that occur when
switching between Layouts. This would be one of the cases we recommend examining using the
informative reactors.

1 These options correspond to the way we will handle the loading of the persistent reactor callback functions. In Chapter 26
the way of compiling an application as a VLX will be studied.

2 Being in a locked layer will be detected by vlax-write-enabled-p which will return nil in this case, but not the other conditions
(being frozen or off).
Chapter 22
DCL: The Graphical User Interface
Since Release 12 AutoCAD introduced the possibility of displaying dialog boxes from AutoLISP
programs. This way AutoLISP caught up with how current applications interact with their users. The
dialog boxes creation is a field where Visual LISP innovations have not been felt. The Dialog Control
Language or DCL in Visual LISP is still the same Release 12 AutoLISP DCL from 20 years ago
However, Visual LISP’s capacity for acting as an ActiveX client and server makes it possible to link
a Visual LISP application with dialog boxes programmed in other languages and compiled as DLL
files. An alternative would be using the OpenDCL application, Especially developed for its use with
AutoLISP, whose use will be explained in this book’s last chapter.

22.1 The DCL language.


Visual LISP does not offer, like other "visual" environments, a graphic interface that allows drawing
on the screen the DCL dialog boxes. The dialogs are created by a proprietary programming
language’s expressions that are included in a file with the DCL extension. These expressions may be
checked in the Visual LISP Editor. For this, select the Interface tools option in the Editor’s Tools
menu. Two possibilities are offered here, Preview DCL in selection and Preview DCL in Editor.
The first one is enabled when part of the text is selected in the Editor window.

In addition to displaying the resulting dialog, this tool will report any errors found in the DCL code. If
the error is too severe, for example missing closing braces, the dialog’s name will not even appear in
the initial window1.

Components of a Dialog Box.


Dialog boxes are constructed from a series of predefined components (also known as tiles) in what is
known in AutoCAD as the PDB (Programmable Dialog Box) feature. Predefined components can be
used as such or as the basis for programming more complex components. Their descriptions appear
as comments in the BASE.DCL file2, which can be found in the application’s User Support folder.
To find the path to this folder, click Options in the graphic window’s contextual menu. On the
Options dialog’s Files tab, the path to the User Support folder is the first one displayed under
Support File Search Path. The DCL components can be classified into a number of categories
according to their purpose:

Active components.
Containers.
Decorative and informative components.
Text components.
Dialog box exit buttons and error message texts.
Restricted Tiles.

A brief description of them and their use follows.

Predefined active components.

When the user selects an active component, for example a button, an event is raised that is notified by
the dialog box to the application that controls it. An active component can have an associated action,
with effects that may be visible to the user or have only an internal effect in the application. The
notified events are accompanied by a code specifying the reason that gave rise to them. The meaning
of this code depends on the component’s type. The active and selectable components are:

Buttons: button, image_button.


List boxes: list_box, popup_list.
Edit boxes: edit_box.
Checkboxes: radio_button, toggle.
Slider tile: Slider.

Containers.

Containers are used for grouping tiles into composite rows or columns (collectively known as
clusters). For layout purposes, a cluster is treated as a single tile. The rows or columns can be
enclosed in a box and have a label if desired.

The user cannot select a cluster, only the tiles (selectable and active) within the cluster. Clusters
cannot have actions assigned to them, with the exception of radio rows and columns. The following
components are used as containers:

The dialog: dialog.


Columns: column, radio_column.
Boxed columns: boxed_column, boxed_radio_column.
Rows: row, radio_row.
Boxed rows: boxed_row, boxed_radio_row.

Decorative and informative components.

These components do not raise events nor can be selected, its function is to display information or
visually complement the dialog box.

Images: image.
Texts: text.
Spacers: spacer, spacer0, spacer1.
Text components.

To compose messages with a certain complexity in the dialog box, where fixed text elements can be
combined with others that can assume values set by the program, independent text components can be
combined with concatenation components in continuous lines composed to form paragraphs.

Independent strings: text_part.


Grouping of texts: concatenation, paragraph.

Dialog retirement buttons and error components.

They include the standard button subassemblies used for exiting dialog boxes. Their use ensures a
consistent appearance between your dialogs and AutoCAD’s. The text in these buttons can be
customized using the retirement_button prototype.

Error messages: errtile.


Dialog exit: ok_only, ok_cancel, ok_cancel_help, ok_cancel_help_info,
ok_cancel_help_errtile.

Restricted Tiles.

The cluster or tile components must not be used in DCL files. Neither can the basic retirement button
types (cancel_button, help_button, info_button and ok_button) be used unless the
standard retirement button subassemblies are redefined.

22.2 Programming a dialog in the Visual LISP


Editor.
The Visual LISP Editor is programmed so that when it detects a DCL file it behaves as in LISP,
coloring syntax and adding or removing comment markers automatically. It does not indent texts, so
we’ll have to do it manually using the Tab key. This book’s extension does not allow us to delve too
far into DCL programming. The best DCL documentation appeared in print as Chapter 9 in the
Release 12 AutoCAD Customization Manual.

This Chapter will demonstrate how to work with DCL and AutoLISP building a dialog box,step by
step, in the Visual LISP IDE.

The active components we will use are:

edit_box: Displays a field in which a single line of text can be entered and edited. It may
include a text label on its left.
slider: provides a means to change a numeric value by dragging a cursor along a path.
radio_button: Always forms part of a cluster (radio column or row). Within a cluster only
one of the radio buttons can be checked. When this tile is used, checking one in the cluster
unchecks all the others. For those cases where multiple selections are allowed the toggle tile
must be used.
button (retirement buttons): will be used to trigger the dialog's action or to cancel it. The
buttons used in this tutorial are predefined in the ok_cancel subassembly.

For making the dialog box’s information more comprehensible a series of decorative and informative
components are included:

image: A tile used for displaying a vector images either drawn at run time or saved as .SLD
files.
errtile: Displays a text string for user information.

These tiles will be used to select the options or parameters required for creating a 3D model. These
will be structured in a series of clusters of the following types:

row: Positions the tiles horizontally as a row.


column: Places the tiles vertically, one above the other.
boxed_column: A column that has a border drawn around it. It can have a label next to the
upper left corner.
boxed_radio_row: A row of radio buttons with a border and title.

The dialog box we will design is aimed at entering the data required for a parametric 3D model. For
this we have to analyze the information required by the command we will use.

22.3 Tutorial: Dialog box for generating Parametric


models.
We want to generate 3D revolution models whose shape depends on several parameters to be defined
from the dialog box. The profile’s form shown in Figure 22.1 depends on four parameters:

FilletRadius: The fillet radii for two of the rectangle's corners


DimX and DimY: the rectangle's horizontal and vertical dimensions,
CenterRadius: the distance from the rectangle to the axis of rotation.

It is our purpose to create a command that displays a dialog box in which the user can enter these
data. It will also include the profile’s image as an aid in identifying these data. Once the data are
supplied, the command will create the model.
Figure 22.1. Parametric profile.

The dialog box will also allow selecting from among four predefined forms. This selection will be
done through a row of radio buttons in which in addition to the Normal option, in which all
parameters > 0, the following options will be offered:

Sphere: where CenterRadius = 0, FilletRadius = DimX


and DimY = 2 x FilletRadius.
Bar: where both DimX and DimY > 0 and both CenterRadius
and FilletRadius = 0.
Tube: where DimX, DimY and CenterRadius > 0 and FilletRadius = 0.

The dialog will check the input data to prevent geometric inconsistencies such as FilletRadius
being greater than DimX or DimY being less than 2 x FilletRadius.

Other possible choices that can be made in the dialog refer to creating a solid or an associative
procedural surface model and specifying the profile’s rotation angle.

Creating the DCL file.


The first thing to do is to open a new Editor window and save it as DCL source file. In the Save As
dialog box’s drop-down list, we can select the DCL source files option. Selecting it, the DCL
extension will be automatically assigned by the system. Saving the file as DCL source file will
enable this language’s syntax coloring , which differs from AutoLISP’s.

Composition of a DCL dialog box.

We can conceive a DCL dialog box as a large box in which other smaller boxes are placed, which in
turn may contain other boxes and so on. These "boxes" fix the place where the dialog components
will appear.

The big box.

All these boxes and their contents fit in a big box that is the dialog itself. This box is automatically
stretched to make room for whatever we put inside. This box is also a component, the dialog
component. This component is identified by the keyword dialog and must be preceded by a name
that will identify it within the application. A colon and the keyword dialog that identifies the
component follow the name. Continuing with an opening brace "{" which marks the beginning of this
container and which must correspond to a closing brace, just like parentheses in LISP.

This box cannot be empty. You must have at least a button to accept or cancel the dialog. This is
because although a dialog box lacking both components may be displayed, there would be no way to
close it. The Visual LISP IDE warns of this error and does not display the dialog.

Figure 22.2. Minimal dialog box components.

There are predefined components that are used in these cases. In the code shown in Figure 22.2
includes the predefined subassembly ok_cancel that displays the default OK and Cancel buttons.
Note that all the expressions included between the opening and closing braces end in a semicolon.
This does not apply to comments, marked by two bars. As we can see in Figure 22.3 the Dialog Box’s
size is just the necessary so that the two buttons in the ok_cancel subassembly fit in. This is one of
DCL’s characteristics. There is no need to define the dialog box’s dimensions. The container will
stretch automatically as we add new components, or tiles as they are known in DCL programming.

Figure 22.3. Preview of the dialog generated from the code in Figure 22.2.

Tiles and attributes.

The label and key attributes define a tile’s characteristics. In this case the dialog’s title is defined
by the label attribute and key identifies it within the program. All DCL tiles support a number of
attributes that can vary from one another.

Some attributes such as height and width, are common to all of them. Specifying the attributes is
optional. Many of them have default values that are used if not specified. Other attributes may only be
used with certain tiles, for example, an image’s background color. Trying to use these attributes in
tiles that do not support them could cause an error in AutoCAD, however they are usually are
ignored. The key attribute should always be specified so that the program can act on the tile it
identifies.

Data types for attributes.

Attributes can use the following data types as their value:

Integers: Numeric values (both for integers and for real) representing distances such as a tile's
width or height are expressed in character-width or character-height units3.
Real Numbers: A real number less than zero must necessarily include the zero to the left of the
decimal point. For example 0.5 and not .5.
Strings: A string must always be delimited by double quotes. Attribute values are case sensitive.
If quotes must be included within a string, they must always be preceded by the backslash
control character (\").
Reserved words: A reserved word is an identifier made up of alphanumeric characters,
beginning with a letter.. As examples we have true and false. Reserved words are also case
sensitive. True is not the same as true.

Attribute names are also case sensitive. The name Label name cannot be used in assigning a tile’s
label. Applications that manage them always receive attribute values as strings. If the values are
numeric it will be necessary to convert the strings to the appropriate numeric type before using them
in calculations.

Columns and rows.

The dialog’s tiles may be structured in columns or rows. The tiles included in columns are placed one
above the other and those in the rows, one beside the other. A row may contain columns, columns may
contain rows, or whatever row and column combination we can need. Column is the dialog’s default
tile arrangement.

The dialog we will use has the distribution shown in Figure 22.4. Inside the dialog container we
will place a row subassembly followed by a boxed_radio_row subassembly.

The row will hold two column subassemblies which will contain the following tiles:

Left column:

A radio_row with two radio_button tiles.


A boxed_column with four edit_box tiles. and an errtile. The errtile component
is a text usually placed at the bottom of AutoCAD dialogs to display error warning messages. Its
key is "error".
An ok_cancel subassembly.

Right column:

An image tile where to insert sketches representing the different options.


A boxed_row subassembly containing:
An edit_box for the angle of rotation's value.
A slider tile for selecting the angle of rotation by dragging.

A boxed_radio_row with four radio_button tiles.

To emphasize the dialog’s structure Boxed Columns and Rows are used. Containers having the
boxed prefix display a border that delimits dialog areas including related controls and can have a
text assigned to their label attribute identifying their contents.
Figure 22.4. Distribution of the dialog box components.

Source code for the Dialog Box.

The structure shown in Figure 22.4 will be attained with the code in Listing 22.1, which defines the
tile subassemblies and their arrangement. Notice the code’s formatting, indenting the tiles in relation
to their containing subassemblies and aligning the closing brace vertically with the tile’s initial
position. To indent the lines it will be necessary to use the TAB key, since the VLISP Editor can’t
format DCL code automatically. Following this indentation style, with each component indented in
relation to its container and all its attributes as a column under it, aids in understanding the code.

Something the Editor can do is to check matching opening and closing braces. To highlight everything
between opening and closing braces we can double-click next to one of them. Strings delimited by
double quotes may also be checked this way.

The attributes defined for all of this dialog’s tiles are key, label and value. The image tile will
also have its size specified. The dialog’s total width and height will result from the combination of
the image size with the size of the predefined tiles, specially the errtile and ok_cancel
widths. If the edit boxes are too small for our needs, especially if a long label is necessary, they
could also be assigned a width attribute. This attribute defines its minimum width so it may be
increased to match other dialog components. There is also a fixed_width attribute, but its use is
not advised except in very special cases. The value attribute sets a default value for the edit boxes
that can be changed by the user. If it is not set here, it may be assigned by the program when
initializing the dialog.
parametric : dialog { // Begin dialog
label = "Parametric model";
key = "title";
: row { // Upper row
: column { // Begin left column
: radio_row {
: radio_button { // Solid model
label = "Solid";
key = "sol";
value = "1";
}
: radio_button { // Surface model
label = "Surface";
key = "sur";
value = "0";
}
}
: boxed_column { // Parameter edition
label = "Parameters";
: edit_box {
edit_width = 15;
label = "DimX";
key = "dx";
value = "100.00";
}
: edit_box {
edit_width = 15;
label = "DimY";
key = "dy";
value = "100.00";
}
: edit_box {
edit_width = 15;
label = "FilletRadius";
key = "ra";
value = "25.00";
}
: edit_box {
edit_width = 15;
label = "CenterRadius";
key = "rc";
value = "50.00";
}
errtile; // Warning messages
}
ok_cancel; // Ok and Cancel buttons
}// End left column
: column { // Begin right column
: image { // Image
key = "img";
width = 35;
aspect_ratio = 1;
color = graphics_background ;
}
: boxed_row {
label = "Revolution angle: ";
width = 35;
:edit_box { // Angle value
key = "inf" ;
value = "360";
edit_width = 3;
}
: slider { // Slider bar
key = "ang";
width = 27;
max_value = 360;
min_value = 1;
value = 360;
small_increment = 1;
big_increment = 10;
}
}
} // End right column
} // End upper row
: boxed_radio_row { // Begin boxed radio row
label = "Predefined forms";
: radio_button {
label = "Normal";
key = "nor";
value = "1";
}
: radio_button {
label = "Sphere";
key = "sph";
value = "0";
}
: radio_button {
label = "Bar";
key = "bar";
value = "0";
}
: radio_button {
label = "Tube";
key = "tub";
value = "0";
}
} // End boxed radio row
} // End dialog

Listing 22.1. Code for the PARAMETRIC dialog.

Checking the dialog box's layout.

To verify the design we can use the tool available in the VLISP IDE.Figure 22.5 shows the dialog’s
preview. The image cannot yet be seen, as it must be loaded at run-time. The values shown in the edit
boxes are the defaults set using the value attribute.
Figure 22.5. Dialog box preview.

About the image.

The space reserved for the image is the same color as AutoCAD’s graphic screen as the image tile’s
color attribute has been assigned the reserved word graphics_background. The size for the
image tile can be defined in two ways: by assigning values to its width and height attributes, or
by assigning a value to width and to aspect_ratio. In this case we have assigned 1 as ratio to
obtain the same width and height (i.e., a square).

The image displayed in the dialog must have been saved as a slide (SLD) file. To save a drawing as
a slide the AutoCAD MSLIDE command is used. The to create an image of the desired proportions
follow these steps:

1. Make the drawing in ModelSpace.


2. Then go to a PaperSpace Layout and create a viewport in the desired proportions. In our case
we used a square viewport.
3. Enable ModelSpace within the viewport using the _MSPACE command and position the
drawing the way it should be displayed. Best results are obtained if this viewport fills a screen
set at the highest resolution possible.
4. With the ModelSpace active in the viewport, use the _MSLIDE command to save the slide. The
SLD file should be saved to a folder included in the AutoCAD search paths.

22.4 Controlling the dialog.


Once we have designed the dialog box, the AutoLISP code that will make it work must be prepared.
The program that controls the dialog box is composed by three distinct parts. As we shall see, some
reusable library functions can be defined. The dialog’s operation will follow these five steps:

1. Load the dialog in memory. This step includes checking that the system locates the DCL file and
that it contains the code for the dialog we want to show. In case an error is found it should be
notified to the user, exiting the program.
2. Set the initial values for the different tiles in case they have not been preset using the value
attributes in the DCL file.
3. Link the tiles to their callback actions. Each active tile raises an event when the user interacts
with it. This linkage, which is set by evaluating an action_tile expression, will define the
function that is triggered following the event raised by each tile.
4. Display the dialog. Even if loaded into memory, the dialog will not be displayed until the
start_dialog expression is evaluated . This function remains active for as long as the
dialog is visible. It ends only when selecting a component that has done_dialog as its
callback action. When concluded, start_dialog will return the numeric value that was
passed to done_dialog which is used to identify the tile that caused the action. This numeric
value can be assigned to a local variable that can be used as an argument when invoking the
function that processes the data collected from the dialog, in our case, creating the 3D model.
5. Close the dialog. The actions linked to certain tiles may call the done_dialog function. This
function is usually called by the OK and Cancel buttons. It indicates that the dialog must be
closed and the numeric argument it receives will determine the dialog's effect. Typically OK
brings forth the dialog's expected consequences and Cancel closes it without any consequence.

Loading the dialog: the DISPLAY-DIALOG function.


To load the dialog a standard function, display-dialog (Listing 22.2), will be used. This
function receives as arguments the dialog and the DCL code file names. Specifying the dialog’s name
is necessary because in a single DCL file several different dialogs can be included.
(defun display-dialog (dialogname dclfile /)
(if (not *Position*)
(setq *Position* ‘(-1 -1)))
(if
(= -1 (setq dcl_id (load_dialog dclfile)))
(alert (strcat "File not found:\n" dclfile))
(if (not (new_dialog
dialogname
dcl_id
""
*Position*))
(alert (strcat "Dialog not found:\n "
dialogname))
t)))

Listing 22.2. Function that starts a dialog.


The operations executed within this function are:

1. Establishing, if it did not already exist, the global variable *Position* in which the dialog's
position when it was last used will be saved. The initial value assigned to this variable is the
list '(-1 -1) which indicates the center of the screen.
2. Loading the DCL file: the load_dialog function is evaluated with the file name as argument
and the value returned is assigned to the local variable dcl_id. This value is checked, as if it
were -1 it would mean that the dialog could not be loaded, and in that case a warning message
should be displayed and the program ended.
3. If the DCL file was loaded successfully the new_dialog function is evaluated to display the
dialog. The arguments new_dialog receives are:
a. The dialog's name,
b. The DCL file identifier returned by load_dialog,
c. An AutoLISP expression that will be used as the dialog's default action. If we don't wish to
assign any, it can be replaced with an empty string (""),
d. The dialog's position, specified in the *Position* global variable.
4. The new_dialog function returns nil if the dialog could not be displayed. This will be
checked to display an error message.
5. If no errors occurred, the display-dialog function returns T, value that can be used as the
condition for continuing with the program.

All these operations should be carried out for any dialog. That is why we group them as a separate
function that may be included in any program using DCL dialogs.

Tile values and actions.


The other steps needed to run a dialog are specific for each one so specific functions should be
defined. However the structure for these functions is much the same in all cases. It will Include the
processes described above.

The image.

First of all we will set the image’s content. As this always follows the same steps, we will define
another library function that we will name display-image (Listing 22.3). It receives the
arguments key, for the tile’s key, and image which is the SLD file name without its extension4.

The image’s loading process begins with a call to start_image using the attribute key as
argument. Anticipating that, as in the case of this program, the image will change, an initial call is
made to fill_image which will fill the image tile with the AutoCAD drawing area’s background
color using -2 as its last argument.
Then slide_image is evaluated, receiving as its two initial arguments are the X and Y offsets
from the tile’s upper left corner and the next two are the image’s width and height. So that this
function can be used in all cases, rather using the values set in the DCL file, we will use the
dimx_tile and dimy_tile functions to retrieve these values. And as the final argument, the
SLD file name. To complete the image’s creation process, end_image is evaluated.
(defun display-image (key image)
(start_image key)
(fill_image
0
0
(dimx_tile "img ")
(dimy_tile "img ")
-2)
(slide_image
0
0
(dimx_tile key)
(dimy_tile key)
image)
(end_image))

Listing 22.3. Function that loads a SLD image in a dialog.

22.5 Event callback functions.


In addition to actions assigned to the OK and Cancel buttons, we will assign actions aimed at
preventing data inconsistencies to edit boxes and other components. If any is found, a warning
message is displayed in the errtile component below the edit boxes and the OK button is disabled
until all the values are correct.

Functions not allowed while a dialog is active.


DCL dialog boxes are Modal. This means that, while they are displayed, user action is limited to
them. With an active dialog (during a call to start_dialog) is not possible to invoke the
AutoLISP functions detailed in Table 22.1.

Table 22.1. Functions not allowed while dialog boxes are displayed.
AutoCAD queries and commands.
command vl-cmdf osnap

User input functions


getint getreal getstring getpoint getcorner

getdist getangle getorient getkword

Display control functions


prompt menucmd redraw graphscr textscr

textpage print princ prin1

Low-level graphics functions


grclear grdraw grread grtext grvecs

Selection set functions


ssget Non-interactive ssget options are allowed.

Entity management functions


entmod entmake entdel entsel nentsel

entupd

These are functions affecting the display or that require user’s data input. Should user’s data entry or
object selection be necessary it would be necessary to hide the dialog box temporarily with a call to
done_dialog, restarting the dialog once the desired action has been performed. If any of these
restricted functions is called while a dialog box is active, it is canceled and the message "AutoCAD
rejected function" is displayed. To check if a dialog is active the CMDACTIVE system variable can
be examined, where a bit value of 8 indicates an active dialog.

Callback function specific variables.

The callback functions can access certain variables that identify the tile that triggered the action and
give information about its state when the event was raised. The names of these read-only variables
are reserved words, and their value is only accessible from the callback functions. They are
described in Table 22.2.

Table 22.2. Variables used in callback functions.


Variable: Description:
$key Contains the key attribute of the component that triggered the action. This variable applies to all actions.

The string currently assigned to the value attribute. It applies to all actions. For a list box, if nothing is selected nil
$value
is returned.

$data Any application-specific data assigned after new_dialog by the client_data_tile


function.

A code that specifies why the event was triggered. It is used with edit_box, list_box,
$reason
image_button and slider tiles. It indicates the reason that causes the action.

Reasons may be crucial in choosing the action callback functions should perform. The callback
reason codes are described in Table 22.3.

Table 22.3. Callback reason codes.


Code: Description:
1 The user has selected the tile. This code applies to all of the action tiles.

The user has exited an edit box but a final selection has not been made. This event usually causes the verification
2
of the input values coherence.

3 In sliders, the user has changed its value by dragging the indicator but has not made a final selection.

In list boxes or image buttons This callback reason always follows a code 1. It usually means “commit to the
4
previous selection”. In list boxes it means a double-click.

Definition of the callback functions.


The callback functions used in this dialog box are three, defined for the three types of active tiles that
will be used:

Edit boxes: used to define the values of the parameters that define the profile's dimensions. Their
associated callback function is param-edit, which receives as arguments the key ($key), the
value ($value) and the reason ($reason).
Radio buttons: those used for selecting predefined profile shapes have form-ops as their
associated callback function. This function receives the key ($key) and the value ($value) as
arguments.
Slider and associated edit box: both are associated with the sel-rotation function which
receives the key ($key), the value ($value) and the reason ($reason).

Edit boxes.

We will use the edit boxes management as an example of how to run a dialog box when a rather
complex interaction exists between different values and possible inconsistencies must constantly be
verified. Other tiles will interact with edit boxes, but with a lower degree of complexity.

PARAM-EDIT Function.

The param-edit function (Listing 22.5) is associated as the callback action to every edit box in
the boxed column labeled "Parameters". It receives as its first argument the triggering tile’s key
($key), its current value ($value) and the triggering action’s reason code ($reason). It has two
purposes:

Checking that the value entered is numeric and non-negative, and displaying the number in the
edit box with two decimal places. It does this by invoking the auxiliary function param-
format (Listing 22.4).
Checking that parameter values are consistent with the selected shape option. To do this one of
two functions is used, test-normal (Listing 22.8) or test-other (Listing 22.6),
according to the "Normal" radio button or any other being selected.

Establishing the correct numeric format.


The DCL dialog definition’s default numeric values include two decimal places. But as the content of
these boxes is actually text, if a value without decimals or with any number of them is entered, that
would be the value assigned to the tile. To attain a uniform appearance, always with two decimal
places, the param-format function (Listing 22.4) retrieves the edit box’s value, converts it to a
real number using atof and converts it back to a string using rtos, this time specifying the desired
number of decimal places. This function also prevents the introduction of negative values, applying to
the number returned by atof the abs function which returns its absolute value. Using atof
conversion controls another possible error: that letters were entered in the edit box instead of
numbers. Instead of producing an error, atof would return 0.0.
_$ (atof "abcd")
0.0
_$

Care must be exercised with the DIMZIN system variable settings which would suppress trailing
zeros in decimal dimensions. To avoid this DIMZIN must be set to 15.
(defun param-format (key value /)
(set_tile
key
(rtos (abs (atof value)) 2 2)))

Listing 22.4. Function that checks and formats edit box values.

Checking the consistency of parameter values.

Because of the profile’s geometry, DimX values smaller than FilletRadius or DimY values less
than twice FilletRadius cannot be accepted. We should also watch for zero parameter values in
certain options that don’t admit this.

To simplify the code for such verifications two test functions have been defined, one named test-
normal used with the Normal option that is the most complex one, and for the rest of predefined
shapes another function named test-other.

These functions will be called from the param-edit function according to the selected form type,
which is determined by reading the Predefined forms radio button values. The code is executed
whenever the $reason argument has the values 1 or 2 (see Table 22.3). The param-edit
function takes as arguments the key ($key) of the edit box that raised the event, its current value
($value) and the event’s reason ($reason).
(defun param-edit (key value reason / form)
(param-format key value)
(if (or (= reason 1) (= reason 2))
(cond ((= (get_tile "nor") "1") ;Normal
(test-normal key))
((= (get_tile "nor") "0") ;Other
(setq form (form-sel))
(test-other form)))))
Listing 22.5. Parameter edit boxes callback function.

Functions to check the parameter values coherence.

The parameter values verification for the Sphere, Bar or Tube predefined form options are limited
to checking that some parameter values are not zero. These verifications are grouped in a single
function, test-other (Listing 22.6) that will enable or disable the OK button printing informative
messages in the errtile component. The OK button is disabled by the expression (mode_tile
"accept" 1). To enable the button, mode_tile should be re-evaluated with the argument 0
instead of 1.

T he Normal case bears a greater complexity, so to simplify the code three distinct verification
functions are defined (see Listing 22.7):

test-1: Verifies that FilletRadius is not equal to or less than zero.


test-2: Verifies that DimY is not less than twice FilletRadius.
test-3: Verifies that DimX is not less than FilletRadius.

If the test fails each of these functions will assign a string stating the problem to the variable msg and
will set nil as the value of one of the variables ok1, ok2 or ok3.

These three functions are invoked from the test-normal function (Listing 22.8). This function
converts to real numbers the values of DimX, DimY, FilletRadius and CenterRadius,
assigning them to the variables dim-x, dim-y, rad-f and rad-c.
(defun test-other
(form / rad-f dim-x dim-y rad-c)
(cond
((= form "sph")
(setq rad-f (atof (get_tile "ra")))
(if (<= rad-f 0)
(setq ok nil)
(progn
(setq ok t)
(set_tile
"dx" (rtos rad-f 2 2))
(set_tile
"dy" (rtos (* rad-f 2) 2 2)))))
((= form "bar")
(setq dim-x (atof (get_tile "dx"))
dim-y (atof (get_tile "dy")))
(if (or (<= dim-x 0) (<= dim-y 0))
(setq ok nil)
(setq ok t)))
((= form "tub")
(setq dim-x (atof (get_tile "dx"))
dim-y (atof (get_tile "dy"))
rad-c (atof (get_tile "rc")))
(if (or (<= dim-x 0)
(<= dim-y 0)
(<= rad-c 0))
(setq ok nil)
(setq ok t))))
(if ok
(progn (mode_tile "accept" 0)
(set_tile "error" ""))
(progn (mode_tile "accept" 1)
(set_tile
"error"
"Parameters must be > 0"))))

Listing 22.6. Function test-other.

(defun test-1 (rad-f /)


(if (<= rad-f 0)
(progn
(setq msg
"FilletRadius must be > 0")
(setq ok1 nil))
(setq ok1 t)))
(defun test-2 (rad-f dim-y /)
(if (> (* rad-f 2) dim-y)
(progn
(setq msg
(strcat
"DimY must be > "
(rtos (* rad-f 2) 2 2)))
(setq ok2 nil))
(setq ok2 t)))
(defun test-3 (rad-f dim-x)
(if (> rad-f dim-x)
(progn (setq
msg
(strcat
"DimX must be > "
(rtos rad-f 2 2)))
(setq ok3 nil))
(setq ok3 t)))

Listing 22.7. Verification functions test-1, test-2 and test-3.

(defun test-normal
(key / dim-x dim-y rad-f
msg ok1 ok2 ok3)
(setq dim-x (atof (get_tile "dx"))
dim-y (atof (get_tile "dy"))
rad-f (atof (get_tile "ra")))
(test-1 rad-f)
(test-2 rad-f dim-y)
(test-3 rad-f dim-x)
(cond ((= key "ra")
(test-1 rad-f))
((= key "dx")
(test-3 rad-f dim-x))
((= key "dy")
(test-2 rad-f dim-y)))
(if (and ok1 ok2 ok3)
(progn (mode_tile "accept" 0)
(set_tile "error" ""))
(progn (mode_tile "accept" 1)
(set_tile "error" msg))))

Listing 22.8. Function test-normal.

Once the values of these variables are set, the three test functions are applied in succession, assigning
values of T or nil to ok1, ok2 and ok3, that will allow checking if any inconsistency between the
different parameters exists. But the error message that is assigned to the msg variable is the one
generated by test-3. In order that the error message actually matches the tile that is checked, the
test function that examines that specific parameter should be run the last. This is achieved by a cond
expression that according to the key received will run again one of the four test functions so that the
appropriate error message, if any, is the one found for that specific parameter.

Radio buttons.
The dialog box includes two radio button rows. The top left one is used to define whether the model
created will be a Solid or a Surface. These buttons don’t have a callback function. Their value is
checked when clicking OK to define the model’s object type.

The radio buttons at the bottom of the dialog are assigned the form-ops (Listing 22.9) callback
function. This function takes as arguments the radio_button tile’s key ($key) and its value
($value), and its action will be enabling or disabling some of the edit boxes and changing certain
values according to the chosen form. For example, FilletRadius must always be zero and cannot
be modified for the Bar and Tube options.

Changing the Predefined forms option will also have the effect of changing the image displayed in
the image tile. This will be done by calling the same display-image function (Listing 22.3)
used when initializing the dialog.

In addition to making these changes in the dialog box, the consistency of the current parameters must
be checked again, using the test functions defined for the edit boxes. To determine the changes to be
made, key and value are used as conditions in a cond expression .
(defun form-ops
(key value / dim-x dim-y rad-f rad-c)
(cond
((and (= key "nor") (= value "1")) ;NORMAL
(setq dim-x (atof (get_tile "dx"))
dim-y (atof (get_tile "dy"))
rad-f (atof (get_tile "ra")))
(mode_tile "dx" 0)
(mode_tile "dy" 0)
(mode_tile "ra" 0)
(mode_tile "rc" 0)
(if (= rad-f 0)
(set_tile "ra" (rtos (/ dim-x 2) 2 2)))
(if (< dim-y (* rad-f 2))
(set_tile "dy" (rtos (* rad-f 2) 2 2)))
(display-image "img" "./img/nor")
(test-normal "dx"))
((and (= key "sph") (= value "1")) ;SPHERE
(setq dim-x (atof (get_tile "dx"))
rad-f (atof (get_tile "ra")))
(if (= rad-f 0)
(progn (set_tile "ra" (get_tile "dx"))
(setq rad-f dim-x))
(set_tile "dx" (get_tile "ra")))
(set_tile "dy" (rtos (* rad-f 2) 2 2))
(set_tile "rc" "0.00")
(mode_tile "dx" 1)
(mode_tile "dy" 1)
(mode_tile "ra" 0)
(mode_tile "rc" 1)
(display-image "img" "./img/sph")
(test-other key))
((and (= key "bar") (= value "1")) ;BAR
(set_tile "ra" "0.00")
(set_tile "rc" "0.00")
(mode_tile "dx" 0)
(mode_tile "dy" 0)
(mode_tile "ra" 1)
(mode_tile "rc" 1)
(display-image "img" "./img/bar")
(test-other key))
((and (= key "tub") (= value "1")) ;TUBE
(setq rad-c (atof (get_tile "rc")))
(set_tile "ra" "0.00")
(if (= rad-c 0)
(set_tile "rc" (get_tile "dx")))
(mode_tile "dx" 0)
(mode_tile "dy" 0)
(mode_tile "ra" 1)
(mode_tile "rc" 0)
(display-image "img" "./img/tub")
(test-other key))))

Listing 22.9. Callback function to the Predefined Forms radio buttons.

Slider and associated edit box.


The slider tile will be used for selecting graphically the profile’s angle of rotation. In the dialog
box definition code the increments at which the slider’s value will change are set as the
small_increment (as 1) and big_increment (as 10) attributes. The minimum
(min_value = 1) and maximum (max_value = 360) values are also set here. The minimum
value is set to 1 since an angular value of zero is interpreted by the _REVOLVE command as 360 º.

To inform the user about the slider’s selected angular value it is usually accompanied by a
synchronized edit box where the value is updated whenever the slider is moved. This edit box must
also allow modifications so that when a numeric value is entered the slider will be updated
accordingly. Both the slider and the associated edit box trigger the same callback function, sel-
rotation (Listing 22.11). This function receives as arguments the triggering tile’s key ($key), its
value ($value) and its reason ($reason). If the action is triggered by the slider, the effect is
writing in the edit box the slider’s current value. If the action is triggered by the edit_box, the
effect is to update the slider’s position to the new value. A common mistake would be to enter a non-
numeric value. This is verified should the action be triggered by the edit box applying the numberp
predicate to the value returned by (read value).
_$ (type (read "abcd"))
SYM
_$ (numberp (read "abcd"))
nil
_$ (type (read "1234"))
INT
_$ (numberp (read "1234"))
T
_$

If value contains a text that does not represent a number the value returned by read would be a
symbol and not a number, so numberp will return nil. In that case, instead of changing the cursor,
the slider’s current value obtained by (get_tile "ang") is rewritten in the edit box.
(defun form-sel (/)
(cond ((= (get_tile "nor") "1") "nor")
((= (get_tile "sph") "1") "sph")
((= (get_tile "bar") "1") "bar")
((= (get_tile "tub") "1") "tub")))

Listing 22.10. Detecting the selected predefined form radio_button.

To verify that the parameters do not conflict, the selected Predefined forms radio_button tile
must be known. Since this operation must be done in more than one occasion, we include an auxiliary
function named form-sel (Listing 22.10) which returns the key for the selected radio_button.
(defun sel-rotation
(key value reason / form)
(cond
((= key "ang")
(if (or (= reason 3)
(= reason 2)
(= reason 1))
(set_tile "inf" value)))
((= key "inf")
(if (or (= reason 2) (= reason 1))
(if (numberp (read value))
(set_tile "ang" value)
(set_tile "inf"
(get_tile "ang"))))))
(setq form (form-sel))
(if (= form "nor")
(test-normal "ra")
(test-other form)))

Listing 22.11. Slider callback function.

22.6 Assignment of the callback functions.


Having defined the callback functions it will be necessary to link them to the different dialog box
tiles. An action is assigned to a tile by the action_tile function. This function receives two
strings as arguments:

The tile's key.


A string with the expression that will be evaluated when the tile is selected.

The following expression assigns a callback action to the edit box whose key is "dx":
(action_tile "dx" "(param-edit $key $value $reason)")

All of the callback action assignments are grouped into the assign-actions function (Listing
22.12). This function includes the assignment of the callback actions described below to the
ok_cancel predefined buttons.

OK button callback action.


The OK button reads the values currently assigned to all of the edit boxes, transforms them into real
numbers using atof and assigns them to variables. It also determines through the auxiliary function
model-type (Listing 22.13) if a 3DSolid or a Procedural Surface is the kind of object that will
be created.

These are the variables that would be used as arguments in the modeling function executed when the
dialog is unloaded. The done_dialog function is then evaluated with the numeric argument 1 and
its returned value is assigned to the global variable *Position*.

CANCEL button's callback action.


The Cancel button simply evaluates the argument done_dialog with the argument 0 and assigns to
*Position* its returned value. The value returned by done_dialog indicates the position of the
dialog box in case the user has moved it.
(defun assign-actions (/)
(action_tile
"nor" "(form-ops $key $value)")
(action_tile
"sph" "(form-ops $key $value)")
(action_tile
"bar" "(form-ops $key $value)")
(action_tile
"tub" "(form-ops $key $value)")
(action_tile
"dx"
"(param-edit $key $value $reason)")
(action_tile
"dy"
"(param-edit $key $value $reason)")
(action_tile
"ra"
"(param-edit $key $value $reason)")
(action_tile
"rc"
"(param-edit $key $value $reason)")
(action_tile
"ang"
"(sel-rotation $key $value $reason)")
(action_tile
"inf"
"(sel-rotation $key $value $reason)")
(action_tile
"accept"
"(setq dim-x (atof (get_tile \"dx\"))
dim-y (atof (get_tile \"dy\"))
rad-f (atof (get_tile \"ra\"))
rad-c (atof (get_tile \"rc\"))
ang-r (atof (get_tile \"ang\"))
obj-type (model-type)
*Position* (done_dialog 1)))")
(action_tile
"cancel"
"(setq *Position* (done_dialog 0))"))

Listing 22.12. Assigning callback actions to tiles.

(defun model-type (/)


(if (= (get_tile "sol") "1")
"_SO"
"_SU"))

Listing 22.13. Function that determines the model type.

22.7 Activating the Dialog Box.


Having defined the callback functions for the dialog box tiles we will discuss the function controlling
the dialog box. The param-dialog function (Listing 22.14) calls the following auxiliary functions:

display-dialog (Listing 22.2), which looks for the "parametric" dialog in the
"./dcl/parametric.dcl" file. This is the condition to continue running the rest of the
function. If it is not found a message box is displayed warning the required code is not available.
display-image (Listing 22.3) that searches for the image it receives as argument and draws
it on the image tile.
assign-actions (Listing 22.12) that assigns dialog tiles their callback actions.
start_dialog that displays the dialog box ready for user input. The value returned by
start_dialog is assigned to the action variable. If this value is 1 the function that creates
the 3D model is invoked.
unload_dialog as the last expression, whose effect is unloading the DCL file from memory.
(defun param-dialog (/ action)
(setvar "DIMZIN" 1)
(if (display-dialog
"parametric"
"./dcl/parametric.dcl")
(progn (display-image
"img" "./img/nor")
(assign-actions)
(setq action (start_dialog))
(if (= action 1)
(param-dwg
obj-type dim-x dim-y
rad-f rad-c ang-r))
(unload_dialog dcl_id))))

Listing 22.14. Function that activates the dialog box.

Closing the dialog and creating the model.


When selecting either the OK or the Cancel button, done_dialog is evaluated. This concludes
start_dialog that returns the numeric argument passed to done_dialog that in this case will
be 0 or 1, but that more complex dialogs may have buttons returning other values.

To determine the action to take the value returned by start_dialog, assigned to the variable
action, is examined. This test is performed within an if, although if there were more values, it
could be done using a cond expression. In case action is 1, the paramdwg function is called,
using the variables assigned by the OK button’s callback.

22.8 Generating the Model.


The model’s generation is done using the _REVOLVE command both for the Solid and for the
associative Procedural Surface models. The only difference lies in the mode selected in the dialog’s
upper left corner radio buttons. A closed 2D polyline that will be used as profile will be created
according to the specified parameters. This profile is created on the XY plane and the revolution will
be about the axis Y.

T he param-dwg function (Listing 22.17) begins by calculating the 2D point values of for the
polyline’s vertices. Once the points are calculated the rev-profile function (Listing 22.16) is
used to draw the profile. For the arc segments the auxiliary function bulge calculates the bulge
(curvature) magnitude that must be associated with that segment’s group code 42.

Figure 22.6. Dialog box during the program’s execution.

(defun bulge (ang /)


(/ (sin (/ ang 4))
(cos (/ ang 4))))

Listing 22.15. Function for calculating the bulge magnitude.

The bulge function receives as argument the arc’s subtended angle in radians. For profile shape
options other than the normal one there will be superposed vertices, but the LWPOLYLINE entity will
admit this. When the profile is created, its ename is assigned to the profile variable.
(defun rev-profile
(max-x med-x max-y min-y rad-c / pts)
(setq
pts (list
(list rad-c min-y)
(list med-x min-y)
(list max-x (+ min-y rad-f))
(list max-x (- max-y rad-f))
(list med-x max-y)
(list rad-c max-y)))
(if
(entmake
(list ‘(0 . "LWPOLYLINE")
‘(100 . "AcDbEntity")
‘(100 . "AcDbPolyline")
(cons 90 (length pts))
‘(70 . 1)
(cons 10 (nth 0 pts))
(cons 10 (nth 1 pts))
(cons 42 (bulge (/ pi 2)))
(cons 10 (nth 2 pts))
(cons 10 (nth 3 pts))
(cons 42 (bulge (/ pi 2)))
(cons 10 (nth 4 pts))
(cons 10 (nth 5 pts))
‘(210 0.0 0.0 1.0)))
(setq profile (entlast))))
(setq profile (entlast))))

Listing 22.16. Function that draws the profile as a LWPOLYLINE.

Once the profile is created, if the mode is SUrface:

SURFACEMODELINGMODE will be assigned the value 0 so that a Procedural Surface will


be created,
SURFACEASSOCIATIVITY will be set to 1 so as to enable associativity for the surface that
will be created,
the _AUTOCONSTRAIN command will be executed using as its argument the profile's ename
so that the geometric constraints derived from its geometry will be automatically applied.

The model is created by calling the _REVOLVE command to sweep the profile around the Y
coordinate axis. After creating the 3D object a southwest isometric view will be set and the visual
style we’ve explained in previous chapters will be set as the current visual style.
(defun param-dwg (mode dim-x dim-y rad-f rad-c
ang-r / max-y min-y max-x med-x
pts profile)
(setq max-y (/ dim-y 2)
min-y (- max-y)
max-x (+ rad-c dim-x)
med-x (- max-x rad-f))
(rev-profile max-x med-x max-y min-y rad-c)
;; Profiles
(if profile
(progn
(if (= mode "_SU") ; Model
(progn
(setvar
"SURFACEMODELINGMODE" 0)
(setvar
"SURFACEASSOCIATIVITY" 1)
(vl-cmdf
"_AutoConstrain"
profile "")))
(vl-cmdf
"_REVOLVE" "_MOde" mode
profile "" "_Y" ang-r)
(ax-SWt))))

Listing 22.17. Function that creates the 3D model.


Implementation as an AutoCAD command.
As usual, we will create a main function that can be invoked as a new AutoCAD command. The
C:DCL-PARAM function supposes that the document object is assigned to the global variable
*aevl:drawing* and since we will make use of an AutoCAD command the cmd-in and cmd-
out functions will be called in order to disable and enable those system variables that may interfere.
An important aspect relates to the detection of the current UCS, to restore the WCS if it was not
current. This is important since we will employ the Y axis as axis of revolution.
(defun C:DCL-PARAM (/ *error*)
(defun *error* ()
(cmd-out)
(vla-EndUndoMark *aevl:drawing*))
(vla-startundomark *aevl:drawing*)
(cmd-in)
(if (= (getvar "WORLDUCS") 0)
(vl-cmdf "_UCS" "_W"))
(param-dialog)
(cmd-out)
(vla-EndUndoMark *aevl:drawing*))

Listing 22.18. Main function C:DCL-PARAM.


Figure 22.7. Solids and Associative Surfaces created with the program.

22.9 Summary.
We have seen how to implement a DCL dialog box for a 3D modeling application using the functions
available in AutoLISP’s Programmable Dialog Box (PDB) language.

Although we have not explored more than a few tiles, how to design these dialogs and make them
operational through LISP programming has been exposed in its fundamental aspects.

We must also note that these dialog boxes can be packed with the Visual LISP code that make them
work as Visual LISP compiled executable (VLX) files. This capability simplifies the distribution of
applications because it is not necessary to install the DCL file independently.

More elaborated dialogs may be created by other means like the OpenDCL application to which we
devote this book’s final chapter. In both cases, as they are created by procedures specifically
designed for AutoLISP/Visual LISP, we are able to create standalone applications that include in a
single file both the application’s code and the user interface’s definition, thing that is not possible
using dialogs created by other means.

Exercises.
Exercise 1.

Compile this application as a Visual LISP Executable, including the DCL dialog definition file.
However, the SLD files will have to be installed in the system where the application is to be run. As
a way to facilitate this we recommend using the SLIDELIB program.

Exercise 2.

Surely the reader will have a program where a drawing is made using a series of dimensions, angles,
etc. Using this tutorial’s dialog box and its callback functions as a model, he can give these old
programs a renewed aspect.

1 But this will not work in recent OS like Windows Vista or 7. A message like ; warning: cannot create tmp file for DCL:
"C:/Program Files/Autodesk/AutoCAD 2014/$vld$.dcl" is displayed in the Visual LISP Console. A workaround in case these
options must be used is to include the current user in the Administrators group with all privileges and start AutoCAD as
administrator.

2 This file, as the others included in the application’s folders should not be modified, for this could compromise the system.

3 Character width usually means the width of the letter M. The capital X is usually used for measuring the height. The
characters used in a DCL dialog box are drawn using a proportional typeface so the width is different for each character. For
this reason, the actual length of a text as it appears in the dialog box is usually less than the width we can expect from the value
assigned to the width attribute. In some cases whitespace can be added to compensate for this especially in edit boxes where the
width of the text label affects the space available for the box itself.

4 AutoCAD images are contained in a SLB (for Slide Library) file. These files can be created with the utility SLIDELIB.EXE that
is supplied with AutoCAD and can be found in the program folder.

5 In fact, for units expressed in the metric system any value less than 4 turns off zero suppression as DIMZIN less than 4 only
applies to feet and inches. A value of 1 includes zero values also in feet and inches values.
Chapter 23
Associating information to Graphic
Objects
Although this book focuses on the management of graphical entities, we will refer briefly to an aspect
of unquestionable relevance as is the association of alphanumeric information to drawing objects.
Computer aided design applications have evolved into sophisticated systems providing intelligent
solutions in a number of highly specialized fields. A special mention is deserved by those related to
the automated generation of thematic maps, facilities management and georeferenced databases,
usually identified by the well-known acronyms AM (Automated Mapping) FM (Facilities
Management) and GIS (Geographic Information Systems). The key to all these developments is the
possibility of linking additional information fields to the data that defines the entity in a strictly
graphical sense.1

AutoCAD is a good example of the developments that have been made in this field. A timeline for the
introduction of features aimed at accomplishing these objectives is shown in Table 23.1.

Table 23.1. AutoCAD non-graphical data linking timeline.


Release/Date Methods for linking non-graphical information
Release 1.0 DXF (Drawing eXchange Format) files: Exports the geometric information in ASCII format so it can be read and
1982 processed by other applications.

Release 2.0
User defined Layer names. Attributes that associate alphanumeric information to blocks.
October 1984

Release 2.18 First version of the AutoLISP programming language. Access to the entity database was not added to AutoLISP
January 1986 until Release 2.6 in June of 19861. It had been partially included in Releases 2.1 and 2.16.

Release 10
Optional permanent hexadecimal identifiers (HANDLES) for drawing entities.
October 1988

Release 11 Extended Entity Data (XDATA) may be appended to drawing entities by processing the DXF files and by
October 1990 AutoLISP programs.

The AutoCAD SQL Extension (ASE)/Autodesk SQL Interface (ASI) enables linking AutoCAD with an SQL
Release 12
database using AutoLISP. The Autodesk Data Extension (ADE) technology is introduced as an option for
June 1992
managing structured data.

Release 13 Entity handles are always enabled. DICTIONARY and XRECORD objects can be used as standard data
November 1994 containers. ADE 2.0 for R13c4 introduces the concept of Object Data Tables.

AutoCAD MAP is released as a vertical application addressed to the emerging GIS market. It is based on the
AutoCAD MAP
AutoCAD Data Extension (ADE) which is marketed since Release 12 and that from this date on is no longer
August 1996
available in AutoCAD.

Release 14 T h e DICTIONARY and XRECORD objects can now be managed as universal data containers for user
February 1997 applications development. First Visual LISP release is sold separately.

Visual LISP ActiveX extensions introduce LDATA dictionaries as a means of storing LISP objects in the drawing.
Release 2000 Visual LISP's ability to act as an ActiveX client allows operating on spreadsheets and databases. The ASE/ASI
March 1999 interface is for this reason obsolete. The recommended practice now is to establish data links directly through the
Microsoft ADO libraries. The final version of AutoCAD MAP's ActiveX object hierarchy is released.

The new TABLE command introduces anAcadTable object used for presenting data structured in rows and
columns. A AcadTable object can be linked to data in a Microsoft Excel spreadsheet. Tables support formulas
Release 2005 that do calculations using the values in other table cells.
March 2004
Since this release text objects can include fields whose contents can be formulas or drawing object properties.
They are automatically updated when data is modified. Fields can be included in table cells and block attributes.

The extraction of block attribute information is simplified with the enhanced EATTEXT command. This command
Release 2006
displays the Attribute Extraction dialog box. Run as -EATTEXT from the command line or a script it can export
March 2005
the data specified in an attribute extraction template file (BLK).

Release 2008 The EATTEXT command is replaced by the DATAEXTRACTION command, much more powerful, capable of
March 2007 extracting information both from attributes and drawing properties combining them into tables or external files.

Chronologically ordered, the features AutoCAD has been adding for associating alphanumeric
information to graphic objects are:

Block Attributes
Extended Entity Data (XDATA)
XRECORD objects and Dictionaries
LDATA Dictionaries.

23.1 Blocks with attributes.


Blocks assemble a variable number of graphic entities in a single container object. This block
definition (BLOCK) container object can be inserted in the drawing generating block reference
(INSERT) objects. The block reference concept corresponds to a user-defined symbol linked to the
geometry stored in a specific section of the drawing’s database (DWG file’s BLOCK table). The
model can be constructed from instances (transformed copies) of that geometry. A symbol’s instance
contains the same graphic entities in the original block definition, although subjected to displacement,
rotation and scaling.

Incorporating TEXT entities to the block definition does not involve any difficulty according to this
idea of a block. However, the presence of unchangeable alphanumeric data would be of scarce utility.
Including in a block definition alphanumeric information that can vary for each occurrence is an
anomaly that presents obvious difficulties. It would no longer be a copy transformed in a strictly
geometrical sense. To solve this a special graphic entity is used, the ATTDEF or attribute definition
object which acts as a placeholder predefining the visual characteristics the text will adopt.
Figure 23.1. Block with invisible attributes and its insertion.

Its content is defined only when each specific instance is generated, i.e., when the Block (see Figure
23.1) is inserted and a new entity, the Attribute Reference (ATTRIB) object, always associated to the
Block Reference (INSERT) object is created. The Attribute Reference is associated with the Block
Reference by following a sequence in the entities database, the INSERT entity acting as the header
for a succession of ATTRIB entities which comes to an end with a special sequence-end (SEQEND)
entity. This relationship was shown in Chapter 10 when programming for the creation of blocks with
attributes.

Both the Attribute Definition object as the Attribute Reference object are only instances of subclasses
(AcDbAttributeDefinition and AcDbAttribute) derived from the text objects
AcDbText subclass. What we’re doing is including texts which are modified to add an identifying
tag, a prompt and a default value, related to the block header (INSERT) by their sequential
position in the database. This expedient, although simple, is highly effective, but with the limitations
we’ll discuss later.

Attribute extraction using AutoCAD commands.


AutoCAD 2008 incorporates a new Data Extraction wizard which is displayed by the
DATAEXTRACTION command. This wizard assists in extracting information from objects, blocks,
attributes and drawing properties included in the current drawing or in a set of drawings. This
command can be used to insert the collected data as a AcadTable object in the drawing itself or to
export it to an Excel spreadsheet (XLS), an Access database table (MDB) or to the traditional text
interchange formats (CSV or TXT).

This makes the old ATTEXT command obsolete. The required external template file is an added
difficulty for this command . To automate the data extraction process, instead of scripting the
DATAEXTRACTION command line option we can design Visual LISP user functions.
Variable attributes extraction using ent... functions.
Listing 23.1 shows a function of this kind. It receives an entity name, from which its definition list is
retrieved. The rest of the function is conditioned to the selected entity being an inserted block with a
group code 66 value greater than 0. Group code 66 indicates that this block insertion is followed by
attributes. If both conditions are fulfilled, a while loop is used to retrieve the entities following the
INSERT (which will be variable attributes) and read the values associated with their group codes 1
and 2 that correspond to the attribute text’s value and its ID. A new feature of AutoCAD in regard to
attributes is the possibility to include multiline texts. In this case, in addition to group codes 1 and 2
we must take into account group code 3.

In the case of multiline texts, the ATTRIB entity will include more than one instance of group code 1,
the first one probably empty, and as many group codes 3 as were necessary, given the 250 characters
limit for the text in each sublist. To foresee this possibility a loop is implemented to traverse the
attribute’s entity list collecting in a list assigned to the txt variable the values associated with each
group code 1 or 3 found. This list is converted to a single string applying strcat to the reversed
list. In this string a series of "\\P" format codes used as paragraph ends may appear. To clean up
this text the replace function (Listing 5.12) proposed in Chapter 5 will be used. Using the attribute
tag associated with group code 2 and the string composed from group codes 1 and 3 a dotted pair is
created and added to a list. The loop ends when a SEQEND entity is found. The function returns the
reversed list so the values are ordered as they were found.
(defun ent-read-attributes
(ename / ent lst txt)
(setq ent (entget ename))
(if
(and (equal (cdr (assoc 0 ent)) "INSERT")
(> (cdr (assoc 66 ent)) 0))
(progn
(setq ent (entget (entnext ename)))
(while
(not (= (cdr (assoc 0 ent)) "SEQEND"))
(foreach datum ent
(if (or (= (car datum) 1)
(= (car datum) 3))
(setq
txt (cons (cdr datum) txt))))
(setq
lst (cons
(cons
(cdr (assoc 2 ent))
(replace
""
"\\P"
(apply ‘strcat
(reverse txt))))
lst)
ent (entget
(entnext
(cdr (assoc -1 ent))))))))
(reverse lst))

Listing 23.1. Function that reads a block insert's variable attributes.

We must remember that the attributes that can be recovered this way are only the variable attributes
that are those included in the sequence between the INSERT and the SEQEND entities. Constant
attributes, if any, should be read in the block’s definition. Although constant attributes tend to be less
used, it is something that should be considered. This is especially inconvenient because no
information in the INSERT entity can be used to detect if the block definition includes constant
attributes.

Attributes extraction using ActiveX extensions.


This difficulty regarding constant attributes has an immediate solution using ActiveX methods and
properties. To discover the available ActiveX functions related to attributes we can use the Apropos
tool entering the term attribute in the Apropos options edit box

The Apropos results include 9 constant names prefixed with ac and 17 function names with names
that include the vla- prefix.

Of these functions only 8 can be applied to blocks with attributes (see Table 23.2). The others apply
to Table (AcadTable) objects. To determine if the block contains attributes, the vla-get-
HasAttributes predicate, which reads the INSERT’s (BlockRef VLA-object)
HasAttributes property. Unlike group code 66, HasAttributes returns true (:vlax-
true) if constant or variable attributes or a mixture of both are present.
Figure 23.2. Available attribute symbols.

T h e vla-GetAttributes function is used to retrieve the variable attributes and vla-


GetConstantAttributes for the constant ones. The same procedure is followed in both cases:

vla-GetAttributes returns a Variant whose value is read with vlax-variant-


value.
This variant contains a safearray, which can be converted to a list with vlax-safearray-
>list.

This list contains the attributes as VLA-objects. The properties we are interested in can be
queried for each attribute. If as in the previous example we are interested in its tag and value, we
must query the TagString and TextString properties. The quickest way for checking the
available properties is vlax-dump-object (Figure 23.3). The same procedure applies to
constant attributes that will be retrieved using the vla-GetConstantAttributes
function.

Table 23.2. Methods and properties for working with attributes.


Function: Description:

Method that creates an attribute definition with the specified position and properties. The space
argument can be the ModelSpace, the PaperSpace or a Block.
(vla-AddAttribute space height mode message ins-point value)
Mode values may be:
acAttributeModeInvisible Value not displayed.
AddAttribute
acAttributeModeConstant Has a fixed value.
acAttributeModeVerify Prompts for verifying the attribute value when inserting the block.
acAttributeModeLockPosition Locks position.
acAttributeModeMultipleLine Allows multiline text as value.
acAttributeModePreset Sets the attribute to its default value when the block is inserted.

Method that returns an array of AttributeReference objects associated with a block


GetAttributes insertion.
(vla-GetAttributes insertObj)

Method that returns an array of constant AttributeReference objects associated with a


vla-
block insertion.
GetConstantAttributes
(vla-GetConstantAttributes insertObj)

Boolean property. Determines if a block insertion has attributes.


HasAttributes
(vla-get-HasAttributes insertObj)

Boolean property. Specifies if an attribute will support multiline text.


MTextAttribute
(vla-put-MTextAttribute attribObj :vlax-true)

Gets or sets the string value for an attribute.


MTextAttributeContent
(vla-put-MTextAttributeContent attribObj "pqrst")

Updates an attribute after it has been modified.


UpdateMTextAttribute
(vla-UpdateMTextAttribute attribObj)
Figure 23.3. Properties and Methods of a variable attribute.

AX-EXTRACT-ATTRIB.

Since the procedure is identical for all attributes, constant or not, we can design a single attribute
extraction function (Listing 23.2), which receives the block insert as a VLA-object and a boolean
argument (T or nil) to indicate whether the attributes to extract are constant or not.

The program must check if the value returned by the vla-GetConstantAttributes and vla-
GetAttributes functions is not empty. This should be done before trying to evaluate vlax-
safearray->list.

As in the previous case the character strings are processed so that multiline paragraph ending control
characters are removed. In this case we can check whether it is multiline text by applying the
condition (= (vla-get-MTextAttribute attrib) :vlax-true).
(defun ax-extract-attrib
(block constant / attributes lst)
(setq attributes
(vlax-variant-value
(if constant
(vla-getconstantattributes
block)
(vla-getattributes block))))
(if
(>= (vlax-safearray-get-u-bound attributes 1)
0)
(foreach attrib
(vlax-safearray->list attributes)
(setq lst
(cons
(cons
(vlax-get-property
attrib
"TagString")
(if
(=
(vla-get-MTextAttribute
attrib)
:vlax-true)
(replace
""
"\\P"
(vlax-get-property
attrib
"TextString"))
(vlax-get-property
attrib
"TextString")))
lst))))
lst)

Listing 23.2. Standard function to extract attribute values.

AX-READ-ATTRIBUTES function.

The above function is called twice from ax-read-attributes (Listing 23.3), once with the
argument constant as T and the other as nil and the lists returned by both function calls are
joined using append. Before doing that, it has been verified that the object is a block and that it has
attributes. This verification is done checking if applying vla-get-HasAttributes to the
object whose ename has been received raises an error. There is no need to use the vl-catch-
all-error-p predicate, as checking whether the value assigned to result equals :vlax-
true will be enough.
(defun ax-read-attributes
(ename / block result lst)
(setq block (vlax-ename->vla-object ename))
(setq result (vl-catch-all-apply
‘vla-get-HasAttributes
(list block)))
(if (eq result :vlax-true)
(append
(reverse (ax-extract-attrib block t))
(reverse (ax-extract-attrib block nil)))))

Listing 23.3. Processing a block to extract a list with its attributes.

Block attribute drawbacks.


The use of attributes to associate alphanumeric data to blocks has a major limitation: the Block
References are punctual entities. The only geometric information associated with them is constrained
to the transformations applied to the original block definition, i.e., an insertion point, the X, Y and Z
scale factors and its rotation angle. A geometry with a linear or surface nature would not be
adequately represented by a block, even if their on-screen display might suggest otherwise.

23.2 Extended Entity Data (XDATA).


That difficulty is evident, for example, in the case of electrical power or transportation networks. In
this cases we need to associate each transmission line or roadway with codes that describe their
nature, capacity, etc. as well as those that identify them in the operating company’s control and
maintenance systems. A drawing standard where each Layer name is associated with this information
is possible, but the way to assign identifying codes to each individual object within a specific Layer
remains to be solved. An application aimed at solving this problem should also be compatible with to
as many AutoCAD releases as possible. This is what Extended Entity Data provide since their
introduction in Release 11.

Structure of Extended Entity Data (XDATA).


Each AutoCAD entity supports up to 16 kilobytes of extended data information. Another advantage of
XDATA over block attributes is the variety of data types supported. Table 23.3 presents an overview
of these data types, with the group codes used to identify them within the entity’s data records.
In this case the data container is no longer a new object that is located within a sequence in the
drawing’s database, but these data are integrated within the each graphic (and also symbol tables and
other non-graphic) entity’s data record as additional information fields. As these data are intended to
be managed by user applications, they are grouped according to the application originating them.
These are applications that must be previously registered in the drawing’s database using the
regapp function. Once registered, the application name is included in the drawing’s APPID symbol
table.

The beginning of the XDATA fields in the entity’s data record is signaled by a group code -3 entry.
The data for each registered application is included under a 1001 code associated to the
application’s name. The data are grouped according to their type, in fields identified by group codes
ranging from 1000 to 1071. The extended data (as well as other entity definition data fields) can be
retrieved using the entget function, supplying a list containing the application’s name as an
optional argument.

Table 23.3. Data Types supported as XDATA.


Group Code: Data Type: Remarks:
1000 String Up to 255 bytes long
1001 Application name Up to 31 bytes long
1003 Layer Name
1005 Database handle
1010 Point or vector value 3 real numbers
1040 Real Number Floating point number
1070 Integer. 16-bit integer (signed or unsigned)
1071 Long Integer 32-bit signed (long) integer (used by ARX applications)
1002 Control string "{" Or "}" enables the grouping of data as lists.

1004 Chunks of Binary Data Up to 127 bytes (used by ARX applications)


1011 WCS position Transformed along with the entity to which the extended data belongs

Scaled, rotated or reflected along with the parent entity, but not stretched or
1012 WCS displacement
moved.
1013 WCS direction Unit vector that is rotated or reflected, but not scaled, stretched or moved.
1041 Distance Scaled along with the parent entity
1042 Scale Factor Transformed along with the entity to which the extended data belongs

Choosing the application's name.

It is advisable to choose an application name which is not likely to match one used by other
developers. A way for attaining this would be to use the company and/or product name linked to a
randomly chosen number (e.g., date and time) or any other hard to duplicate combination. The number
identifying the product version may be part of this name or can be included in an additional numeric
field, for example, (1040 2.1). Autodesk supports a developer registration service to prevent
namespace conflicts between different applications. Each developer registers with Autodesk one or
more "registered developer symbols" (RDS) to use as part of any name that will appear in a global
namespace.

Observations on the XDATA method.


Although a standard way for appending application specific data to any graphic entity is solved this
way, it leaves much to be done by the application’s developer, which must program the way to
manage this information. Proof of this is the small number of specific XDATA functions offered by
both the AutoLISP and ActiveX programming environments. They are limited in AutoLISP to those
necessary to register the application (regapp) and checking the size in bytes of the linked
information (xdroom and xdsize) and the ActiveX method functions vla-SetXdata and vla-
GetXdata. An application’s data returned by entget has an additional difficulty derived from the
appearance of repeated DXF group codes, which requires using functions such as those studied in
Chapter 10 (Listing 10.17) for their retrieval. Moreover, to find out the meaning of each of the stored
values requires setting rules to discriminate between different data assigned to the same group code.

Ways to identify the data.

Trusting the order in which the values were entered may not be reliable enough. The use of opening
and closing braces (group code 1002) can be used to create lists that include descriptive strings for
the rest of the data in each list. The AutoCAD Data Extension (ADE) 1.0 application prefixes the
string containing the data with "XX =" where XX represents a particular identifier for that data
within the application. Data thus coded can be retrieved using the Autodesk MAP API by the
ade_expreval function or an AutoLISP function could be defined to do it.

XDATA assignment example using the ADE 1.0 format.

The code in Listing 23.4 demonstrates how to add data (always as strings) to any entity using the
ADE 1.0 format described above.

The ename argument is an object’s entity name, appname a string with the application name, lis-
id a list of identifiers such as '("DAT1 =" "DAT2 =" ... "DATn ="), lis-val a list of
values '("value-1" "value-2" ... "value-n") matching those identifiers, also as
strings. First, the function must ensure that the application is registered in the APPID table. If it is not
registered the expression (tblsearch "appid" appname) will return nil, otherwise it will
return T. If it is not already registered, regapp is used to register it.
(defun ent-xdata (ename appname lis-id lis-val /
data lst obj)
(if (not (tblsearch "appid" appname))
(regapp appname))
(setq data
(list
-3
(cons
appname
(foreach term (mapcar ‘strcat
lis-id
lis-val)
(setq lst
(append
lst
(list
(cons 1000 term))))))))
(setq obj (append (entget ename) (list data)))
(entmod obj))

Listing 23.4. XDATA assignment.

Reading XDATA.

To read these data we will define a function that receives four arguments: ename, for the entity
name, appname for the application’s name, id for the data identifier (not including the "=") and
as-string, a boolean argument that can be T or nil, indicating if the data is to be preserved as a
string or if it should be converted to the data type that matches its printed representation by applying
the read function.
(defun ent-read-xdata
(ename appname id as-string / val)
(setq
id (strcat id "=*")
val (cdar
(vl-remove-if-not
‘(lambda (x) (wcmatch (cdr x) id))
(cdadr
(assoc
-3
(entget ename
(list appname)))))))
(if val
(progn
(setq val (vl-string-left-trim id val))
(if as-string
val
(read val)))))

Listing 23.5. Reading XDATA.

The following expression assigns extended data to any object selected on the screen:
(ent-xdata (car (entsel)) "APP-TEST"
'("TEXT=" "NUM=" "POINT=") '("TEST" "104.5" "(15.0 22.0 0.0)"))

To retrieve a data item, for example, POINT we can use:


_$ (ent-read-xdata (car (entsel)) "APP-TEST" "POINT" nil)
(15.0 22.0 0.0)
_$

In this case we have chosen to return the data as a list and not as a string. Finally we must note that
any new data will overwrite the old one, so the programmer must implement the necessary control
measures to prevent that. The proposed function will not affect XDATA that belongs to an application
other than the one it receives as argument. This is because the inner workings of AutoCAD are
designed so as to ensure the integrity of data belonging to other applications.

23.3 XRECORD objects and Dictionaries.


The XRECORD object provides an alternative way of permanently storing data in the drawing. Just as
XDATA were introduced in Release 11, XRECORD objects appeared in Release 13c4. If a drawing
w i th XRECORD entities is opened with an earlier release, they disappear. They represent an
improvement over XDATA in that storage space is not limited. Although it is possible to link a
dictionary to a graphic entity, they are typically used to save information so it is not visible and thus
cannot be affected by the user deleting blocks with attributes or graphic entities with XDATA.

XRECORD objects are used to store and manage arbitrary data and consist of DXF group codes
corresponding to normal object groups (i.e., group codes that are not XDATA) that range from 1 to
369. They are conceptually similar to XDATA, but are not limited by size and order.

AutoCAD uses dictionaries to store a variety of application related data. These dictionaries have
names in which the prefix ACAD appears. These dictionaries cannot be renamed. Among the
information stored in AutoCAD dictionaries are multiline styles, groups, Layouts, plot styles, etc.

The master dictionary.


All of the drawing dictionaries are grouped in a master dictionary, the Named Object Dictionary.
This dictionary is another drawing object and can be accessed by namedobjdict function. This
function returns an ename, from which its definition list can be retrieved using entget. The
expression:
(entget (namedobjdict))

can return in a new drawing a list like the following:


((-1 . <Entity name: 40077c60>)
(0 . "DICTIONARY")
(330 . <Entity name: 0>)
(5 . "C")
(100 . "AcDbDictionary")
(280 . 0)
(281 . 1)
(3 . "ACAD_GROUP")
(350 . <Entity name: 40077c68>)
(3 . "ACAD_LAYOUT")
(350 . <Entity name: 40077cd0>)
(3 . "ACAD_MLINESTYLE")
(350 . <Entity name: 40077cb8>)
(3 . "ACAD_PLOTSETTINGS")
(350 . <Entity name: 40077cc8>)
(3 . "ACAD_PLOTSTYLENAME")
(350 . <Entity name: 40077c70>))

The dotted pairs identified by group code 3 contain the names of the drawing’s dictionaries. In
vertical applications built on AutoCAD other specific dictionaries maintained by the application will
be found.

It may be useful to define a function (see Listing 23.6) that will return the list of names for the
dictionaries in a drawing in order to check whether a certain one already exists.
(defun dict-list ()
(mapcar ‘cdr
(vl-remove-if-not
‘(lambda (x) (= (car x) 3))
(entget (namedobjdict)))))

Listing 23.6. Obtaining a list of all the dictionaries.

Searching a dictionary.

The same way we can check for an entry in a symbol table with tblsearch, dictsearch checks
for a dictionary in the drawing’s master dictionary:
(dictsearch ename symbol [setnext])

The function takes three arguments: ename, representing the dictionary to be searched, symbol as a
string with the name of the object to look for and the optional argument setnext which, if not nil
adjusts the dictnext entry counter so that the object returned by the following call to the dictnext
function (similar in this to entnext and tblnext) is the one following the one returned by this
dictsearch call.

The expression (dictsearch (namedobjdict) "ACAD_LAYOUT") will return the entity


list of the dictionary containing the current drawing’s Layouts. Within the dictionary found by
dictsearch a certain item can in turn be found.

The following expressions search for the "Layout1" object in the ACAD_LAYOUT dictionary.
(setq lyt (dictsearch (namedobjdict) "ACAD_LAYOUT"))
(dictsearch (cdr (assoc -1 lyt)) "Layout1")

Sequential search.
The dictnext function is used to retrieve dictionaries sequentially.
(dictnext ename [rewind])

The ename argument is the entity name of the dictionary where to search and the optional argument
rewind if not nil will make the function return the first object in the dictionary. Each time the
function is called without the rewind argument the next object in the dictionary is returned. For the
ACAD_LAYOUT dictionary, the following expression will return the name of the ModelSpace Layout.
_$ (cdr (assoc 1 (dictnext (cdr (assoc -1 lyt)) T)))
"Model"
_$

The entity type in this case is LAYOUT, as can be seen in the value associated with group code 0.
_$ (cdr (assoc 0 (dictnext (cdr (assoc -1 lyt)) T)))
"LAYOUT"
_$

Custom Dictionaries.
An application can also use dictionaries to store its own information. This information is permanently
recorded in the drawing, so that is not lost at the end of a work session as would happen to any data
assigned to global variables.

The XRECORD object.

There is a special data container for these custom dictionaries. This container is the XRECORD
object. XRECORD objects can perform functions similar to XDATA but without their size limitations.

The XRECORD objects are created from lists of dotted pairs associated to DXF group codes 1
through 369. To create these XRECORD objects we use a variant of entmake: the entmakex
function that creates an object without an owner.

Ownerless objects: ENTMAKEX.

The entmakex function is able to create both graphic and non-graphic objects . It receives an
association list with DXF format identical to the one supported by entmake. But instead of
returning the entity list, it returns the newly created object’s entity name or nil if unable to create it.
This is an object without an owner. The entity name returned must be stored in a variable so an owner
can be set after its creation. This would be the way for creating custom dictionaries:

1. Create the new custom dictionary using entmakex (without an owner).


2. Include the new dictionary in the drawing's main dictionary (its owner) using dictadd.
3. Create the new ownerless XRECORD object also using entmakex.
4. Assign this XRECORD object to the custom dictionary.

Changes in this sequence do not influence the results. The XRECORD objects could be created before
the dictionary, provided their references are preserved assigning them to variables.

Although the Visual LISP documentation warns that ownerless objects created with
entmakex are not saved unless they are assigned an owner (indicated by the DXF group
code 330), graphic objects created by entmakex are automatically assigned to the current
space, in this being just the same as entmake. In fact, if inspected immediately after they are
created group code 330 will contain the current space's ename.

Creating a new dictionary.

A new dictionary must be created by entmakex before it can be added to the main dictionary. The
entity list needed is very simple. Only group codes 0 and 100 must be included. Once the dictionary
is created it must be added to the main dictionary using the dictadd function which assigns it a
name.

The dictadd function’s syntax is: (dictadd dict key newobj) where:

dict is the ename for the dictionary to which the object will be added.
key is a unique name (as string) that does not already exist in the dictionary. It will be used to
identify the added object.
newobj is the new Dictionary or XRECORD object.
_$ (setq new-dict
(entmakex '((0 . "DICTIONARY")(100 . "AcDbDictionary"))))
<Entity name: 4007c880>
_$ (dictadd (namedobjdict) "COUNTIES" new-dict)
<Entity name: 4007c880>

And using the function dict-list (Listing 23.6) defined above we can see that indeed, this
dictionary has been included in the main dictionary.
_$ (member "COUNTIES" (dict-list))
("COUNTIES")
_$

Creating the XRECORD entities.

T he XRECORD entities will also be created using entmakex. Suppose we want to create an
XRECORD with two fields, each one containing a string. We must remember that the numerical values
used for DXF group codes that identify the data are selected by the programmer, provided they are
within the 1 to 369 range. The code should be chosen according to the type of data you want to save.
The correspondence between data types and group codes are shown in Table 23.4.
_$ (setq d1 '((0 . "XRECORD") (100 . "AcDbXrecord") (1 . "Marin")))
((0 . "XRECORD") (100 . "AcDbXrecord") (1 . "Marin"))
_$ (setq xrec (entmakex d1))
<Entity name: 4007c888>

As for the other entities, the information regarding the meaning of DXF codes for XRECORD
and DICTIONARY entities and can be found in the online documentation's DXF Reference.

Table 23.4. DXF group codes and data types.


Codes: Data Type:
0-9 String (maximum of 2049 bytes in most cases)
10-39 Double-precision 3D point
40-59 Double precision floating point value (real number)

60-79 16-bit integer value


90-99 32-bit integer value
100-102 String (up to 255 characters, less for Unicode strings)
105 String representing a hexadecimal handle value
110-139 Double precision floating point values
140-149 Double precision scalar floating-point value
170-179 16-bit integer value
210-239 Double precision floating point values
270-279 16-bit integer value
280-289 8-bit integer value
290-299 Boolean flag value

300-309 Arbitrary text string


310-319 String representing hex value of a binary chunk
320-329 String representing a hexadecimal handle value
330-339 String representing hexadecimal Soft Pointer IDs
340-349 String representing hexadecimal Hard Pointer IDs
350-359 String representing hexadecimal Soft Ownership IDs
360-369 String representing hexadecimal Hard Ownership IDs

Adding XRECORD entities to a DICTIONARY.

The above expressions have created a new XRECORD entity. Supposing we must save names as text
strings associated with an existing map’s graphic entities. In the previous example we can assume that
the value associated with group code 1 is a toponym we want to associate with the graphic entity. For
each entity-name relationship a new XRECORD would be added to the dictionary where the entity’s
handle would be used as key.
_$ (setq id (cdr (assoc 5 (entget (car (entsel))))))
"AF"
_$ (dictadd new-dict id xrec)
<Entity name: 4007c888>
_$

Retrieving data from the DICTIONARY.

This would allow creating an application from which obtaining the graphic entity’s identifier and
knowing the dictionary’s name, we could retrieve the string containing the associated toponym.
_$ (setq toponyms (cdr (assoc -1 (dictsearch (namedobjdict) "COUNTIES"))))
<Entity name: 4007c880>
_$ (cdr (assoc 1 (dictsearch toponyms "AF")))
"Marin"
_$

23.4 Sample Program: Data in Dictionaries.


Using these functions we can program a small application that keeps in a dictionary those place
names we want to associate a map’s graphical entities.

Associating the data.

This application will include two commands. One of them, C:TOPONYMS will associate data to
graphic entities. And the other one, C:IDENTIFY will query these data by selecting the graphic
entities. We will describe now the functions that make up the first part, the ones used for associating
data.

Data Entry.

Initially the data-input function (Listing 23.7) enters a loop in which the user is prompted for
selecting a graphic entity and once it has been selected, to enter a name for it. That entity’s handle and
the name supplied by the user will be used in forming a dotted pair that would be added to a list. The
loop will end if ENTER is pressed in response to either prompts.

We can then implement a function capable of creating a new XRECORD for each dotted pair, adding it
to the custom dictionary. The toponym and the handle which acts a the key for retrieving the
information will be saved in the XRECORD.
(defun data-input (/ ent name lst)
(while
(and (setq ent
(car (entsel
"\nSelect entity:")))
(setq name
(getstring t "\nSpecify name:"))
(not (= name "")))
(setq lst
(cons
(cons (cdr (assoc 5 (entget ent)))
name)
lst)))
lst)

Listing 23.7. Data entry function.

Creating the DICTIONARY.

But before that we must ensure that the dictionary exists, otherwise it must be created.. This is done
by the make-dict function (Listing 23.8), after checking that it doesn’t exist. To do this it calls the
dict-list function defined in Listing 23.6. The dictionary is then created using entmakex and
added to the drawing’s main dictionary using as key the name argument, which must be a string. If it
existed, its entity name is retrieved. In both cases the function will return the DICTIONARY’s
ename.
(defun make-dict (name)
(if (not (member name (dict-list)))
(dictadd
(namedobjdict)
name
(entmakex
‘((0 . "DICTIONARY")
(100 . "AcDbDictionary"))))
(cdr
(assoc -1
(dictsearch
(namedobjdict) name)))))

Listing 23.8. Function that creates a dictionary, or retrieves its ENAME in case it already existed.

Inserting new records.

Once we have the data list and the dictionary’s ename, we can implement the data insertion
procedure. Processing (Listing 23.9) Is done within a foreach cycle that traverses the data list. It
verifies that there is no record associated with the same handle. This would be the case in which a
toponyms is to be changed. To do this the existing XRECORD must be removed using the
dictremove function. The XRECORD removed from the dictionary still exists, but it is now
ownerless. So once removed from the dictionary, the XRECORD entity is erased using entdel.

Once this phase is concluded, the new XRECORD entity is created and if this step is successful it is
added to the dictionary using dictadd. If this operation does not succeed an error message is
printed in the command line.
(defun new-records (dict-ent data-list / xrec)
(foreach datum data-list
(if (dictsearch dict-ent (car datum))
(entdel (dictremove dict-ent (car datum))))
(if (setq xrec
(entmakex
(list ‘(0 . "XRECORD")
‘(100 . "AcDbXrecord")
(cons 1 (cdr datum)))))
(dictadd dict-ent (car datum) xrec)
(prompt "\Error adding record"))))

Listing 23.9. Adding new entries to the dictionary.

Integration of these functions as an AutoCAD command.

To invoke all these functions a new command is defined as the C:TOPONYMS function (Listing
23.10). This function checks in the clauses of a cond conditional:

If a TOPONYMS dictionary exists. In that case its ename is retrieved.


If it doesn’t exist, a new one is created by calling make-dict.
If none of the previous two clauses succeeds, an error message is printed in the command line.

If the dictionary’s ename has been retrieved within the cond expression, an additional condition is
checked: that the data-input function doesn’t return nil. If these conditions are met, new-
records is called to link the information to the selected objects. Otherwise an alert box is
displayed notifying an application error.
(defun C:TOPONYMS (/ data dict)
(cond
((setq dict (dictsearch
(namedobjdict)
"TOPONYMS"))
(setq dict (cdr (assoc -1 dict))))
((setq dict (make-dict "TOPONYMS")))
(t
(prompt
"\nError creating the dictionary")))
(if (and dict (setq data (data-input)))
(new-records dict data)
(alert "Application error"))
(princ))

Listing 23.10. Main function C:TOPONYMS.

Querying the data.

This application would not be complete without the ability to query the linked data. This is done by a
function that, despite being very simple, demonstrates the process for reading data linked to the
graphic entities selected on screen. In this example only one data item has been associated, but it
could be easily modified to include more information.
(defun C:IDENTIFY (/ dict ent place)
(if (and (setq dict (dictsearch
(namedobjdict)
"TOPONYMS"))
(setq dict (cdr (assoc -1 dict))))
(while (setq ent
(car
(entsel
"\nEntity to identify: ")))
(if (setq place
(dictsearch
dict
(cdr (assoc 5 (entget ent)))))
(alert
(strcat
"The entity represents\n"
(cdr (assoc 1 place))))
(alert
"The entity does not have a name.")))))

Listing 23.11. Function that queries the linked data.

The C:IDENTIFY function (Listing 23.11) begins by checking that the application dictionary exists
and that its ename can be recovered. Having made these verifications, the user is repeatedly
prompted in a while loop for the selection of an entity. Once the selection has been made its handle
is searched for in the dictionary. If found, the value associated with the XRECORD’s group code 1 is
read and the linked toponym is displayed in a message box. If none appears, a message box is
displayed to inform that the designated entity has no associated name.

ActiveX Functions for XDATA, XRECORD and Dictionaries.


XDATA, XRECORD and Dictionaries emerged in the pre-ActiveX AutoCAD era. Although there are
equivalent ActiveX methods and properties, processing from Visual LISP functions has no
disadvantages. Quite the contrary, the ease with which LISP creates and processes data lists
represents an advantage over ActiveX methods that require packing data as safearrays.

But there is a simpler set of ActiveX dictionary functions. These dictionaries belong to a special type
known as LDATA. We already used this format in the Chapter 1 Tutorial.

LDATA functions can save a lot of programming effort. In the following section we will summarize
the use of these functions when dealing to storing non-graphical information in the drawing, with or
without links to graphic entities.

23.5 LDATA Dictionaries.


We can distinguish between two different types of dictionary objects: the LDATA dictionary aimed at
storing information in LISP format and dictionary objects composed by XRECORD entities in which
information is managed using a DXF style coding. As we have seen, the XRECORD object
dictionaries require considerable programming effort. But for LDATA dictionaries, Visual LISP
provides a set of functions that ease their use.

Caution should be exercised when using this kind of object because the LDATA information
for Release 2000 and above is not compatible with AutoCAD Release 14. If the drawing is
saved in R14 drawing format information may be lost or corrupted. Although it's not likely
today, a drawing to be used with such an old version should use XRECORD dictionaries. But
everything is relative, since we know that XRECORD would be lost when using the drawing in
a release prior 3c4, and XDATA would only survive the round trip to Release 11, but not
beyond it.

There is no specific function for the creation of LDATA dictionaries. Adding data will automatically
create them. The dictionary can exist by itself in the drawing with a name that identifies it, but it can
also be attached to any drawing object.

Adding data.
For adding data to a LDATA dictionary the vlax-ldata-put function is used.
(vlax-ldata-put dict key data [private])

The dict argument may be a VLA-object, An AutoCAD drawing entity, or a string with the name
of a global dictionary. If an object or entity, the information will remain linked with it. Otherwise it
will remain as a global object in the document. The dictionary created in the Volume 1, Chapter 1
tutorial was of this latter type. The argument key is a text string that identifies the associated data.
This associated data can be any valid LISP expression. Thevlax-ldata-put function can
receive an optional argument [private] that in case of having any non-nil value indicates that this
information, if created by a separate namespace VLX application will not be accessible from any
other application.

LIST->LDATA function.

The following function (Listing 23.12) creates a dictionary from an association list using a foreach
loop that repeatedly calls vlax-ldata-put:
(defun list->ldata (dict lst)
(foreach sublist lst
(if (listp sublist)
(vlax-ldata-put
dict
(vl-princ-to-string (car lst))
(cdr sublist)))))
Listing 23.12. LIST->LDATA function.

To test it let’s draw some entity, such as a circle. Its entity definition list can be stored in a dictionary
with the following expression:
_$ (list->ldata "DAT-ENT" (entget (entlast)))
(0.0 0.0 1.0)
_$

Retrieving LDATA information.


To retrieve the information stored in a dictionary we use the vlax-ldata-get function that uses
the following syntax:
(vlax-ldata-get dict key [default-data] [private])

The arguments dict and key assume the same values as in the previous function. The optional
argument default-data would be the LISP expression returned in case key is not found in the
dictionary.
_$ (vlax-ldata-get "DAT-ENT" "0" "Data does not exist")
"CIRCLE"
_$ (vlax-ldata-get "DAT-ENT" "1" "Data does not exist")
"Data does not exist"
_$

If a value other than nil is specified in the optional argument private and the vlax-ldata-
get is called from a separate namespace VLX application, the dictionary’s private information is
returned. As the argument is identified by its position, to specify the argument private, the
argument default-data must be included before even if its value is nil.

A separate namespace VLX application can store both private and non-private information using the
same dictionary and key. Information marked as private can only be read by the application that
created it, but any other application can access non-private information.

Data as Association List.


The vlax-ldata-list function can be used if extracting all the data contained in the LDATA
dictionary is required. Its syntax is:
(vlax-ldata-list dict [private])

Arguments dict and private are the same described above. The information is returned as an
association list in which sub-lists are dotted pairs. Mapping print on this list would print all the
values in a column. Enclosing the mapcar expression in a progn block ending with (princ) will
provide a cleaner output on screen, since otherwise the list as it is returned by mapcar would print
again as a row.

In case vlax-ldata-list is invoked from a separate namespace VLX application and a


non-nil value is specified for the argument private only the private information stored by that
VLX will be returned.
_$ (progn (mapcar 'print (vlax-ldata-list "DAT-ENT"))(princ))

("8" . "0")
("67" . 0)
("5" . "31")
("410" . "Model")
("40" . 25.0)
("330" . <Entity name: 40086cf8>)
("210" 0.0 0.0 1.0)
("100" . "AcDbCircle")
("10" 50.0 100.0 0.0)
("0" . "CIRCLE")
("-1" . <Entity name: 40086d88>)

Removing LDATA
To remove information from a dictionary the vlax-ldata-delete function is used.
(vlax-ldata-delete dict key [private])

The function returns T if successful, and nil otherwise (for example, if the data does not exist).
_$ (vlax-ldata-delete "DAT-ENT" "5")
T
_$ (vlax-ldata-delete "DAT-ENT" "6")
nil
_$

We must remember that these functions are destructive. Which means that they permanently modify the
dictionary object they receive as argument.

Confirming data persistence


Certain types of information may be preserved between work sessions, but there is data that would
not stay valid once the drawing is closed. An example of this type of data are the LISP user functions
that must be loaded again for a new session. This verification is done by the vlax-ldata-test
that returns nil if the information will not persist between sessions. The persistence of the function
in Listing 23.13 can be checked this way:
_$ (vlax-ldata-test list->ldata)
nil
_$

Including this predicate in that same list->ldata function would avoid including information that
could not be restored over the session boundary.
(defun list->ldata (dict lst)
(foreach sublist lst
(if (and (listp sublist)
(vlax-ldata-test (cdr sublist)))
(vlax-ldata-put
dict
(vl-princ-to-string (car sublist))
(cdr sublist)))))

Listing 23.13. Function LIST->LDATA including VLAX-LDATA-TEST.

Some of the LDATA information we have included in the dictionary is automatically updated between
sessions. This is the case of entity names that may change between sessions. Let’s draw some more
objects and evaluate once more:
(list->ldata "DAT-ENT" (entget (entlast)))
Now we check the value stored in the "-1" key:
_$ (vlax-ldata-get "DAT-ENT" "-1")
<Entity name: 40089e50>
_$

Because this value depends at least in part, on the entities sequential order in the drawing’s database,
we can delete some entities, taking care not to delete the last object drawn. If once we have created
the DAT-ENT dictionary we save the drawing, close and re-open it, we will find that the data
associated with the "-1" key has changed:
_$ (vlax-ldata-get "DAT-ENT" "-1")
<Entity name: 40087e10>
_$

To verify that this is actually the same object we can use the entdel function to erase it:
_$ (entdel (vlax-ldata-get "DAT-ENT" "-1"))
<Entity name: 40087e10>
_$

We can now see that this object has disappeared from AutoCAD’s graphic screen. If we evaluate
again the same expression above, the object will be recovered. This is a very interesting feature we
can use to establish permanent links that are updated automatically between objects. The function in
Listing 23.14 associates an object to another by attaching LDATA to the one it receives as the ent-
main argument:
(defun associate (ent-main ent-assoc key)
(vlax-ldata-put ent-main key ent-assoc))

Listing 23.14. Function to associate entities using LDATA.


To test this function you can use the following expression, which uses entsel to select objects on
screen:
_$ (associate (car (entsel "\nMain entity: "))
(car (entsel "\n Associate entity: ")) "ASOC")
<Entity name: 40087ec0>
_$

This data is retained even if both objects are exported from the AutoCAD drawing using the
_WBLOCK command and are inserted into a different drawing. This functionality has been available
in AutoLISP since Release 11 by using the XDATA format, but as we have seen, the use of XDATA
required, just as with XRECORD dictionaries, programming by user all of the assignment, reading and
modification functions.

23.6 Access to external databases.


When storing non-graphic information in the drawing itself we should take into account issues such as
the increase of the drawing’s size it implies and most of all the difficulties for maintaining the data
integrity, which in any case must be ensured by the programmer whose application will make use of
such data.

Large volumes of information are more conveniently stored it in external databases. This is a topic to
which a whole book can be devoted2. But this chapter must not be concluded without having at least
indicated the way to follow for those interested in the subject.

Access to external databases is done in AutoCAD 2000 and subsequent releases through the ActiveX
Data Objects (ADO) provided by Microsoft. AutoCAD’s Connectivity Automation Objects (CAO)
are used to establish links between AutoCAD objects and external databases. In both cases these are
ActiveX component libraries that must be imported before its methods and properties are available
as Visual LISP functions.

Importing the ActiveX component libraries.


T he vlax-import-type-library function must be used to import an ActiveX component
library. The syntax for this function is:
(vlax-import-type-library
:tlb-filename filename
[:methods-prefix mprefix
:properties-prefix pprefix
:constants-prefix cprefix])

The names preceded by a colon are not arguments. They are keywords indicating the role for the
arguments that follow. They are protected system symbols, as we can verify from their color-coding
when typed in the editor.

The :tlb-filename keyword indicates that the argument that follows is the name of the file that
contains the library. It will usually have a TLB or OLB extension, although sometimes it may be a
DLL or EXE. The other keywords indicate the prefixes to be assigned to constants, properties and
methods. Although this is not essential, it is a quite beneficial practice as it allows us to recognize the
library from which the function proceeds and if it is a constant, a method or property. In our own
practice we use the prefixes adoC- for constants, adoP- for properties and adoM- for methods in
the ADO library. When importing the CAO library we use caoC-, caoP- and caoM- respectively.

Searching for the libraries.

You must include the search path in which to look for those files, but these can change depending on
the system. For their use in real applications some way to establish the correct path is needed. In
versions 2010 through 2013 the CAO and ADO libraries are located in common program files
subdirectories whose path can be retrieved from the COMMONPROGRAMFILES environment
variable. Its value is returned by getenv:
_$ (getenv "COMMONPROGRAMFILES")
"C:\\Program Files\\Common Files"
_$

The locate-ADO-CAO function (Listing 23.15) uses this expression in trying to locate the ADO
(msado15.dll) and the CAO libraries. Of the latter there are a localized versions that can be
imported if working in a localized version. Otherwise the English version (cao16enu.tlb) will be
used. Localized versions can be identified, as explained in Volume 2, Chapter 10 from the
application’s LocaleID property. LocaleID values are shown in Table 10.8.
(defun locate-ADO-CAO (/ dir CAOdir ADOdir)
(setq dir (getenv "COMMONPROGRAMFILES")
ADOdir (findfile
(strcat
dir
"\\system\\ado\\msado15.dll"))
lcid (vla-get-LocaleID
(vlax-get-acad-object))
CAOdir
(findfile
(strcat dir
"\\AUTODESK SHARED\\"
(cond ((= lcid 1028)
"cao16cht.tlb")
((= lcid 1029)
"cao16csy.tlb")
((= lcid 1031)
"cao16deu.tlb")
((= lcid 1034)
"cao16esp.tlb")
((= lcid 1036)
"cao16fra.tlb")
((= lcid 1038)
"cao16hun.tlb")
((= lcid 1040)
"cao16ita.tlb")
((= lcid 1041)
"cao16jpn.tlb")
((= lcid 1042)
"cao16kor.tlb")
((= lcid 1046)
"cao16ptb.tlb")
((= lcid 1049)
"cao16rus.tlb")
((= lcid 2052)
"cao16chs.tlb")
(t "cao16enu.tlb")))))
(list ADOdir CAOdir))

Listing 23.15. Function that searches the libraries paths.

Although in the current Visual LISP version libraries are imported at run time, rather than at
compile time, this may change in future releases. Visual LISP documentation advises that
vlax-import-type-library should be called directly in the code from a top-level
position at the beginning of the source code file, and not inside other AutoLISP expression.
The functions shown below were developed for the current conditions and may not be valid in
case the current way of importing component libraries changes.

Importing the library.

To load the component libraries we can use the standard function import-ADO-CAO (Listing
23.16). This function uses the locate-ADO-CAO function described above.

It returns T if imported successfully and nil if not, value which can be used to interrupt program
execution and warn the user if any of the libraries is not installed.
_$ (import-ADO-CAO)
T
_$

A first clause has been added to check if the library has been already loaded, which operates by
obtaining the list of symbols returned by atoms-family and using member to check if a symbol
matching a pattern composed by the prefix followed by a wildcard is found in the list. Once the
libraries are imported we can use the Apropos tool to inspect the new functions, searching for them
by prefix. Figure 23.4 shows the results of this search.
(defun import-ADO-CAO (/ libs)
(vl-load-com)
(setq libs (locate-ADO-CAO))
(if (car libs)
(cond
((vl-member-if
‘(lambda (x) (wcmatch x "ADOM-*"))
(atoms-family 1))
t)
(t
(vlax-import-type-library
:tlb-filename
(car libs)
:methods-prefix
"adoM-"
:properties-prefix
"adoP-"
:constants-prefix
"adoC-")))
(prompt "\nERROR: ADO not found"))
(if (last libs)
(cond
((vl-member-if
‘(lambda (x) (wcmatch x "CAOM-*"))
(atoms-family 1))
t)
(t
(vlax-import-type-library
:tlb-filename
(last libs)
:methods-prefix
"caoM-"
:properties-prefix
"caoP-"
:constants-prefix
"caoC-")))
(prompt "\nERROR: CAO not found.")))

Listing 23.16. Function that imports component libraries.


Figure 23.4. Apropos results for the imported component libraries.

Moving forward...
We announced that our purpose is not to describe the procedures for external database access and
linking. Once the necessary components have been imported the programmer will be working in an
area that is out of the scope of this book. As a programming language, Visual LISP provides many
resources, but to understand the behavior of these new functions we must refer to these libraries
specific documentation. For ADO it may be found in Microsoft’s ado210.chm Help file. The
information on CAO components can be found in the online Help, under the Connectivity
Automation (CAO) heading. For more information about ADO, you can search the Microsoft
Website.

23.7 Summary.
This chapter has outlined the different ways in which Visual LISP ban be used to assign, retain and
retrieve alphanumeric information within the drawing.

The different ways to do this reflect the application’s development from its origins to the present day.
We have seen that the information can be stored in three ways:

As part of the entity, assigning it to block attributes or as extended entity data (XDATA).
In autonomous containers within the drawing, which can be linked or not to entities: XRECORD
objects, Dictionaries and LDATA Dictionaries.
In external databases, linked or not to the drawing’s entities.

Some applications built on AutoCAD incorporate their own variants for data management built on the
features offered by these basic procedures. As an example we can point to the Object Data used by
Autodesk MAP, implemented with a combination of Dictionaries and XDATA.

Although we have not delved into the topic of external databases, one of the applications included in
this book shows how to extract the data in block attributes and export it to an Excel spreadsheet.

Exercises.
Exercise 1.

Modify the C:TOPONYMS application so that instead of asking the user to associate names to
graphical entities it read these names from a external text file and prompts the user only for selecting
the entity to which each name belongs.

1 John Walker, A brief History of IGES. (http://www.fourmilab.ch/autofile/www/section2_79_1.html)

2 Those interested in this subject can read Scott McFarlane's AutoCAD Database Connectivity, Autodesk Press/Thomson
Learning, ISBN 0-7668-1640-0.
Chapter 24
Tables
Since AutoCAD 2005 a new kind of object, the AcadTable can be used to automate the creation of
tables in the drawing. This is a compound object containing data structured in a variable number of
rows and columns. The AutoCAD 2012/2013 user interface is highly effective for managing tables
using the commands described in Table 24.1.

Table 24.1. Commands for creating tables.


Command: Description:
Creates an empty table object for which style, number and height of rows and number and width of columns
_TABLE
can be specified.

_TABLEDIT Edits the text in a table cell.

_TABLESTYLE Creates, modifies, or specifies table styles.

To create tables from Visual LISP we have the methods and properties exposed by the AcadTable
object. These reproduce the effects of these commands from a Visual LISP application. The
AcadTable class is the one which more specific methods (181) and properties (28) exposes, so
addressing this topic in an exhaustive way in a book like this is not possible. However, for the usual
applications just a very small subset of these methods and properties is necessary. To them we
dedicate this chapter.

24.1 Fundamental Methods for working with


TABLES.

Creating a Table.
A table is created by the AddTable method. This method receives as parameters the space in which
to create the table, its insertion point, the number of rows and of columns, the row height and the
column width. After adding the table to the drawing, content may be added, and the number of
columns, the number of rows and other parameters can be changed. The syntax for the AddTable
method is:
(vla-AddTable
space insertion-point num-rows
num-cols row-height column-width)

Table 24.2. Table creation parameters.


Parameter: Description:
The space in which to create the table. Graphic objects are usually created in ModelSpace. But annotation
objects are frequently placed in PaperSpace Layouts. To obtain the adequate space parameter in such cases
space
the current-space function (Listing 10.31) will return the current Layout's Block object to which the
Table can be added.

insertion-point Coordinates for the Table's upper left corner as the safearray type returned by vlax-3d-point.

num-rows An integer specifying the number of rows in the Table.

num-cols An integer specifying the number of columns in the Table.


row-height A real number that specifies the Table's row height in drawing units.
column-width A real number that specifies the Table's column width in drawing units.

Creating the Table.

The ins-table function (Listing 24.1) creates an empty Table after prompting the user for the
necessary information. The space in which to create the Table (Model or any of the Layouts) is
obtained using the current-space function defined in Listing 10.31.
(defun ins-table
(/ pt row-height col-width nrows ncols)
(setq pt (getpoint "\nUpper left corner: ")
row-height (getdist pt "\nRow height: ")
col-width (getdist pt "\nColumn width: ")
nrows (getint "\nNumber of rows: ")
ncols (getint "\nNumber of columns: "))
(vla-AddTable
(current-space *aevl:drawing*)
(vlax-3d-point pt)
nrows
ncols
row-height
col-width))

Listing 24.1. Function that inserts a table in the drawing.

The format the Table adopts depends on the current TableStyle. This style is specified in the
CTABLESTYLE system variable. The default style is "Standard". This style includes a cell style
named "title" that merges the row’s cells and centers the text. This style is used in an additional
title row that is inserted as the new Table’s first row. Because of this, the Table created using the
"Standard" style will contain one more row than the number vla-AddTable receives as the
num-rows argument.

The RegenerateTableSuppressed property.

All the methods that modify a Table object operate as follows:

Opening the Table in write mode.


Modifying the Table according to the parameters received.
Closing the Table and automatically regenerating it.
When dealing with large tables, this regeneration consumes a considerable amount of time and
memory as the Table is entirely rebuilt. If several modifications are to be made in the same Table,
performance can improve by enabling the RegenerateTableSuppressed property by setting
its value to :vlax-true before the modifications begin, turning it off once they are completed. To
check or modify this property’s value we can use the following expressions, which initially create a
Table using the ins-table function, checking then the value of
RegenerateTableSuppressed and then modifying it.
_$ (setq ntab (ins-table))
#<VLA-OBJECT IAcadTable2 000000002e3f0808>
_$ (vla-get-RegenerateTableSuppressed ntab)
:vlax-false
_$ (vla-put-RegenerateTableSuppressed ntab :vlax-true)
nil
_$ (vla-get-RegenerateTableSuppressed ntab)
:vlax-true
_$ (vla-put-RegenerateTableSuppressed ntab :vlax-false)
nil
_$

Setting and reading a Table cell's text.


SetText method.

The SetText method adds text to a Table cell. This method receives as parameters the Table as a
VLA-object, the row index, the column index and the text string. Its syntax is:
(vla-SetText table-obj row-index column-index text)

Table 24.3. Parameters for inserting text in a Table.


Parameter: Description:
table-obj The Table as a VLA-object.
row-index Zero-based index for the row where the text will be inserted.
column-index Zero-based index for the column where the text will be inserted.
text String with the text to be inserted into the cell specified by the row and column indices.

GetText method.

The GetText method retrieves text from any of the Table’s cells. This method returns a string with
the text assigned to the cell specified by its row and column indices. Its syntax is:
(vla-GetText table-obj row-index column-index)

The parameters are the same as those described in Table 24.3.


Set and read the text's formatting.
SetTextHeight/SetTextHeight2 methods.

The SetTextHeight method can be used to set a row’s text height. This method receives as
parameters the Table object to modify, the row type and the text height. Its syntax is:
(vla-SetTextHeight table-obj row-type text-height)

Table 24.4. Parameters for managing a row's text height.


Parameter: Description:
table-obj The Table as a VLA-object.
The row's type (AcRowType enum constant):

acDataRow: data row.


row-type acHeaderRow: header row.
acTitleRow: title row.
acUnknownRow: undetermined row type.

text-height Zero-based index for the column where the text will be inserted.

The SetTextHeight method changes the text height in all rows of a given type. If the text in an
isolated cell must be modified it will be necessary to use the SetTextHeight2 method, which
receives as parameters the Table’s VLA-object, the row and column indices (row-index and
column-index), a content index (icont) usually 0, which provides for multiple contents in the
cell, and the new text height. Its syntax is:
(vla-SetTextHeight2
table-obj row-index column-index icont text-height)

Using this method it would be possible to change the text height of a single cell and in a loop, of an
entire row or column. The set-row-text-height function (Listing 24.2) shows the use of this
method to change a whole row’s text height.
(defun set-row-text-height
(table-obj i-row text-height /)
(setq i 0)
(repeat (vla-get-Columns table-obj)
(vla-setTextHeight2
table-obj i-row i 0 text-height)
(setq i (1+ i))))

Listing 24.2. Function that changes a row's text height.

In the set-column-text-height function (Listing 24.3) a slight change to the previous


function’s code can do the same for a column.
(defun set-column-text-height
(table-obj i-col text-height /)
(setq i 0)
(repeat (vla-get-Rows table-obj)
(vla-setTextHeight2
table-obj i i-col 0 text-height)
(setq i (1+ i))))

Listing 24.3. Function that changes a column's text height.

GetTextHeight/GetTextHeight2 Methods.

The GetTextHeight method retrieves the text height for a type of row. The parameters it receives
are the Table as a VLA-object and the AcRowType enum constant for the row type. Its syntax is:
(vla-GetTextHeight table-obj row-type)

These parameters are described in Table 24.4.

The GetTextHeight2 method retrieves the text height for an individual cell. The parameters are
the same as described for the previously explained methods. Its syntax is:
(vla-GetTextHeight2 table-obj row-index column-index icont)

SetCellAlignment method.

The SetCellAlignment method sets the text alignment for a cell specified by its row and column
indices. Its syntax is:
(vla-SetCellAlignment
table-obj row-index column-index cell-alignment)

The last parameter, cell-alignment is one of the AcCellAlignment enum constants


described in Table 24.5. These constants correspond to some of those that set text justification shown
in Figure 10.6.

Table 24.5. AcCellAlignment constants.


Constant: Value: Description:
acTopLeft 1 Top Left. Equivalent to AcAlignmentTopLeft.

acTopCenter 2 Top Center. Equivalent to acAlignmentTopCenter.

acTopRight 3 Top Right. Equivalent to acAlignmentTopRight.

acMiddleLeft 4 Middle Left. Equivalent to acAlignmentMiddleLeft.

acMiddleCenter 5 Middle Center. Equivalent to acAlignmentMiddleCenter.

acMiddleRight 6 Middle Right. Equivalent to acAlignmentMiddleRight.

acBottomLeft 7 Bottom Left. Equivalent to acAlignmentBottomLeft.

acBottomCenter 8 Bottom Center. Equivalent to acAlignmentBottomCenter.


acBottomRight 9 Bottom Right. Equivalent to acAlignmentBottomRight.

GetCellAlignment method.

The GetCellAlignment method retrieves the enum constant that defines the text alignment in a
cell specified by its row and column indices. Its syntax is:
(vla-GetCellAlignment table-obj row-index column-index)

Inserting Rows or Columns in an existing Table.


ActiveX exposes four methods for inserting rows and columns in an existing Table. Two are applied
to rows and two are applied to columns. They differ in the manner used to specify the row height or
the column width.

Inserting Rows.
For inserting rows into an existing Table we can use the InsertRows method or the
InsertRowsAndInherit method.

InsertRows method.

The InsertRows method receives as parameters the Table as a VLA-object, the zero-based
index (row-index) for the first row of the group to be inserted, the row’s height (row-height)
and the number of rows (num-rows) to be inserted. Its syntax is:
(vla-InsertRows table-obj row-index row-height num-rows)

InsertRowsAndInherit method.

Instead of the row height, the InsertRowsAndInherit method receives the index
(inheritFrom-index) for the row whose characteristics the new rows will adopt. The other
parameters are similar to those for the previous method. Its syntax is:
(vla-InsertRowsAndInherit
table-obj row-index inheritFrom-index num-rows)

Inserting Columns.
For inserting columns into an existing Table we can use the InsertColumns method or the
InsertColumnsAndInherit method.

InsertColumns method.
The InsertColumns method receives as parameters the Table as a VLA-object, the zero-
based index (column-index) for the first column of the group to be inserted, the column’s width
(column-width) and the number of columns (num-cols) to be inserted. Its syntax is:
(vla-InsertColumns
table-obj column-index column-width num-cols)

InsertColumnsAndInherit method.

Instead of the column width, the InsertColumnsAndInherit method receives the index
(inheritFrom-index) for the column whose characteristics the new columns will adopt. The
other parameters are similar to those for the previous method. Its syntax is:
(vla-InsertColumnsAndInherit
table-obj column-index inheritFrom-index num-cols)

24.2 Sample Program: Block attributes Table.


Using what we have studied in the previous chapter regarding block attributes we can propose a
sample program that draws a Table with the values of the attributes in selected block’s inserts.

Selecting the attributes and extracting the associated data.


The first step will be the selection of the block whose attributes we want to include in the Table. The
main function C:ATTRIB-TABLE will enter a while loop that will end when the user selects a
block insert that has attributes. To do so two conditions are checked:

That an INSERT entity has been selected.


That this INSERT has attributes.

Both are checked within an and expression that includes a ssget single object selection filtered for
the INSERT entity type and verifying that this object’s HasAttributes property is :vlax-
true. If both conditions are not met the message "Select a block with attributes" is
printed in the command line.
(defun sel-block
(name / ss count ename data data-list)
(setq count 0)
(if (setq ss (ssget "X"
(list (cons 0 "INSERT")
(cons 2 name))))
(while (setq ename (ssname ss count))
(setq obj (vlax-ename->vla-object ename))
(setq data
(list
(cons "LAYER"
(vla-get-layer obj))
(cons
"COORDS"
(vlax-safearray->list
(vlax-variant-value
(vla-get-insertionpoint
obj))))))
(setq data-list
(cons
(append
data
(ax-read-attributes
ename))
data-list))
(setq count (1+ count))))
data-list)

Listing 24.4. Selection of the block to process and extraction of its attributes as a list.

Once the user has selected a block insert with attributes its name is retrieved and used as argument
for the sel-block function. This function is not limited to extracting the attribute values. It also
retrieves the name of the Layer in which it has been inserted and its insertion point coordinates, data
which is added to the values obtained from the attributes. For extracting attributes the ax-read-
attributes function defined in the previous Chapter (Listing 23.3) is used. This function calls in
turn the auxiliary function ax-extract-attrib (Listing 23.2). The list returned for each entity is
accumulated in the data-list variable which is returned by sel-block.

This is a list with three nesting levels. The top-level list, delimited by the outermost parentheses
represents the entire Table. This list must be contain other nested lists (second level lists) in varying
numbers, each of which represents a row. Each of these sublists in turn contain other lists in which
the head (its first term, as returned by the car function) represents the meaning of the data in the list’s
tail (the rest of the list as returned by the cdr function). Each of these third level lists represents a
cell. The second level lists (rows) are structured as association lists similar to those used to
represent entity properties in AutoLISP, i.e., those returned by the entget function. The steps
followed are:

1. The variable count is initialized as 0.


2. All the insertions for the block name received are selected. The existence of this selection set
will be a requisite for continuing the program’s execution.
3. A while loop starts whose condition is to obtain a new entity name (ename) from the
selection set, which is recovered by ssname using the index value in count. For each ename
obtained the following processes are performed:
4. The VLA-object is obtained from the ename.
5. From the VLA-object the Layer name and the insertion point coordinates are obtained. With
these data, an association list assigned to the variable data is built. The list will have the format:
(("LAYER" . layer-name) ("COORDS" coordX coordY coordZ))
Note that the sublist headed by "COORDS" is not a dotted pair.
6. The list resulting from appending data to the value returned by ax-read-attributes is
assigned to the data-list variable. The sel-block function returns the list assigned to
data-list.

After selecting the INSERT and obtaining the data from its attributes, the user is prompted for the
Table’s insertion point (upper left corner) and for the opposite corner, which will define the area
occupied by the Table. From these two points the program calculates the row height, the column
widths and the text height. The first two are used in creating the Table and the text height is defined
once it has been created.

To avoid regenerations, RegenerateTableSuppressed will be enabled while the Table is


being processed. Only when the Table is completed will it be disabled. Without disabling
RegenerateTableSuppressed the texts cannot be seen.

We are assuming that the default TableStyle "Standard" is the current one. This style includes a
first row in which all cells are combined into a single one. That first row that is intended for the
Table’s title is of the AcTitleRow type. The second row, Intended for the column headers, is of the
AcHeaderRow type and the rest, which will be used for data are all of the AcDataRow type. For
the title row cell’s text the block’s name, assigned to the variable name, is used. Once the title text
has been inserted, the column headers will be inserted in the Table’s second row. The text to be used
will be the first term of each sublist in the first data list. A format property we will modify is the text
alignment, but as this is a cell property it will be done when inserting each cell’s text.

Text will be inserted by vla-SetText. The title row has only one cell with index 0 for both the
row and the column. The row index for header cells is 1, with their column indices varying from zero
to (1- data-length).
(vla-SetText table-obj 0 0 name) ; Title
(setq i 0)
(foreach datum (car data-list)
(vla-SetText table-obj 1 i (car datum)) ; Header
(setq i (1+ i)))

Upon completion of the title and headers rows the list is processed in two nested repeat loops,
where for each row the text in the cdr of each sublist is inserted in a cell. Note that as the inclusion
of texts in the data rows must begin with the third row, 2 is always added to the index used to specify
the row.
(setq i 0)
(repeat (1- nrows) ; Data rows
(setq j 0)
(repeat ncols
(vla-SetCellAlignment
table-obj
(+ i 2)
j
acMiddleCenter)
(setq txt (vl-princ-to-string
(cdr (nth j (nth i data-list)))))
(vla-SetText table-obj (+ i 2) j txt)
(setq j (1+ j)))
(setq i (1+ i)))

Column widths.

As the contents of the different cells cannot be predicted we must have a way to adjust column widths
to the data that must be inserted in the Table. Column widths will be calculated from the information
contained in data-list by the col-w function.

This function receives as arguments that list and the number of columns (ncols). This function will
traverse each row sublist obtaining the maximum number of characters for each cell, considering both
the data and the header text. This is done by two nested mapcar expressions that will return in a list
assigned to lst-w, for each row, a list with the number of characters in each cell.

This list will then be processed in a repeat loop which will obtain the maximum number of
characters found in each column. these are included in a list assigned to the variable ls using cons.
This list’s order will be reversed and returned by col-w. These values will be used in the main
function C:ATTRIB-TABLE to set the width of each column. This will be done by adding them to
obtain the maximum possible number of characters in a row. Dividing the Table width specified by
the user by this number we will get a width-per-character value which will be multiplied by the
maximum number of characters in each column to obtain the width that will be assigned to each
column in a foreach loop:
(setq char-per-cols (col-w data-list ncols)
width-per-char (/ table-width
(apply ‘+ char-per-cols))
i 0)
(foreach w char-per-cols
(vla-SetColumnWidth
table-obj
i
(* width-per-char w))
(setq i (1+ i)))

(defun col-w (data-list ncols / lst-w ls)


(setq lst-w (mapcar
‘(lambda (row)
(mapcar
‘(lambda (cel)
(apply
‘max
(list
(strlen
(vl-princ-to-string
(car cel)))
(strlen
(vl-princ-to-string
(cdr cel))))))
row))
data-list)
i 0)
(repeat ncols
(setq ls
(cons
(apply
‘max
(mapcar ‘(lambda (x) (nth i x))
lst-w))
ls))
(setq i (1+ i)))
(reverse ls))

Listing 24.5. Function that calculates the approximate relative widths for columns.

Main function C:ATTRIB-TABLE.


All these functions are invoked from the main function C:ATTRIB-TABLE (Listing 24.5). This
function takes into account the active Layout when running the program. Should it be a PaperSpace
Layout the TILEMODE variable is set to 1 so ModelSpace is made current before selecting the
Block INSERT. The selection will be interactive using ssget in the "_:S" modality limiting
selection to a single object. This selection is filtered to ensure that an INSERT entity is selected.
Once the selection has been made, the program will check if it has attributes. If the selected object is
not an insert or has no attributes, the user is informed and prompted for a new selection. If the
designation is successful, the original Layout, if not ModelSpace, is restored to request the data for
the Table’s position and size.

The Table created this way will use many properties define by the TableStyle. Once it is created it
can be easily adjusted by changing Style parameters. It can be also adjusted in size using the grips
which are displayed when the Table is selected.
(defun C:ATTRIB-TABLE (/ *error* curr-layout
block name char-per-cols
width-per-char data-list
nrows ncols pt-ins
pt-corner row-height
col-width table-obj i j
txt)
(vla-StartUndoMark *aevl:drawing*)
(defun *error* (msg)
(vla-EndUndoMark *aevl:drawing*)
(vl-cmdf "_U")
(prompt msg))
(if (/= (setq curr-layout (getvar "CTAB"))
"Model")
(setvar "TILEMODE" 1))
(while
(not
(and (setq
block (ssget "_:S"
‘((0 . "INSERT"))))
(equal (vla-get-HasAttributes
(vlax-ename->vla-object
(ssname block 0)))
:vlax-true)))
(prompt
"\nSelect a block with attributes: "))
(setq name (cdr (assoc 2 (entget (ssname block 0))))
data-list (sel-block name)
nrows (length data-list)
ncols (length (car data-list)))
(setvar "CTAB" curr-layout)
(initget 1)
(setq pt-ins
(getpoint
"\nSpecify table insertion point: "))
(initget (+ 1 32))
(setq pt-corner (getcorner pt-ins
"\nSpecify table size: ")
row-height (/ (abs (- (nth 1 pt-ins)
(nth 1 pt-corner)))
(1+ nrows))
table-width (abs (- (nth 0 pt-corner)
(nth 0 pt-ins)))
table-obj ; Table creation
(vla-AddTable
(current-space *aevl:drawing*)
(vlax-3d-point pt-ins)
(1+ nrows)
ncols
row-height
(/ table-width ncols)))
(vla-put-RegenerateTableSuppressed
table-obj
:vlax-true)
;;Column widths are adjusted to cell text content
(setq char-per-cols (col-w data-list ncols)
width-per-char (/ table-width
(apply ‘+ char-per-cols))
i 0)
(foreach w char-per-cols
(vla-SetColumnWidth
table-obj
i
(* width-per-char w))
(setq i (1+ i)))
(vla-SetText table-obj 0 0 name) ; Title
(setq i 0)
(foreach datum (car data-list)
(vla-SetText table-obj 1 i (car datum))
; Header
(setq i (1+ i)))
(setq i 0)
(repeat (1- nrows) ; Data rows
(setq j 0)
(repeat ncols
(vla-SetCellAlignment
table-obj
(+ i 2)
j
acMiddleCenter)
(setq txt
(vl-princ-to-string
(cdr (nth j (nth i data-list)))))
(vla-SetText table-obj (+ i 2) j txt)
(setq j (1+ j)))
(setq i (1+ i)))
(vla-put-RegenerateTableSuppressed
table-obj
:vlax-false)
(vla-EndUndoMark *aevl:drawing*))

Listing 24.6. Main function C:ATTRIB-TABLE.

Figure 24.1. Drawing that includes a Table generated by the C:ATTRIB-TABLE program.

24.3 Summary.
We have seen how to program a Table with drawing data, in this case block attributes, but easily
extensible to any type of data as those described in Chapter 23. We have only explained in this
chapter the Table’s essential features, which in most cases may be enough. To create tables more
elaborate in their format the AcadTable class exposed methods and properties may be used, but in
many cases it will be easier to use one or more custom Table Styles.
Chapter 25
Visual LISP as ActiveX client
Visual LISP can act as an ActiveX client and server. This chapter demonstrates an alternative to the
sample program in Chapter 24. If in that chapter we showed how to create a Table with drawing data,
in that case the Block INSERT attributes. we will devote this chapter to a variant that exports those
data to an Excel spreadsheet. As we saw in Chapter 22 how to design a DCL dialog box, we will use
this exercise to demonstrate the use of another tile, the list_box. The user interface of AutoCAD
since release 2008 provides commands specially designed to establish these links without
programming. The _DATAEXTRACTION command can be used for extracting drawing information
to an Excel spreadsheet, an Access database table or to CSV and TXT exchange files. This command
displays a dialog box to select the information and set its destination. It is our goal to explain in this
chapter the basic principles for doing this as a part of a Visual LISP application.

25.1 Tutorial: From AutoCAD to Excel.


There are two ways to access the methods and properties of an application that can act as an ActiveX
server. We can import its type library using vlax-import-type-library, or we can use the
generic functions vlax-invoke-method, vlax-get-property and vlax-put-
property to access to its methods and properties in a very simple and straightforward way. This
second procedure is enough for programs that don’t require full control of the other application. This
way we are able to achieve our goals without loading in memory all the objects in the server
application. It’s the one we’ll use in the program proposed here.

We will implement an utility function, called list->excel that will receive as argument an
association list (see Figure 25.1) Identical to the one used for creating the Table in Chapter 24 and
use the information it contains to fill an Excel spreadsheet. Any list that respects the structure
described above can serve as an argument for the list->excel function.

As columns headers the association list keys will be used. Clearly, a correct result is obtained only if
all these second-level lists have the same structure, i.e., the same keys in the same order.
Figure 25.1. Data list structure.

Creating and releasing the Excel object.


The first step is to connect to the Excel application, add the Worksheet and get its Cells collection, in
which the data will be written.

CONNECT-EXCEL function.

The connect-excel function (Listing 25.1) has been defined to establish this connection.
(defun connect-excel (/)
(setq excel-app (vlax-get-or-create-object
"Excel.Application")
wbook-coll (vlax-get-property
excel-app
"Workbooks")
wbname (strcat (vl-filename-base
(getvar "dwgname"))
".xls"))
(setq wbook (vl-catch-all-apply
‘vlax-get-property
(list wbook-coll
"Item"
wbname)))
(cond
((vl-catch-all-error-p wbook)
(setq wbook (vlax-invoke-method
wbook-coll
"Add"))
(vlax-invoke-method
wbook
"SaveAs"
(strcat (getvar "dwgprefix") wbname)
; Filename
56 ; FileFormat
"" ; Password
"" ; Write Reservation Pwd
:vlax-false ; ReadOnlyRecommended
:vlax-false ; CreateBackup
1))) ; XlSaveAsAccessMode
(setq sheet-coll
(vlax-get-property
wbook
"Sheets"))
(vla-put-visible excel-app :vlax-true))

Listing 25.1. CONNECT-EXCEL function.

The function vlax-get-or-create-object creates the initial link returning an instance of the
Excel Application object, a reference to which is assigned to the LISP variable excel-app.

If Excel is not running, it will be launched. The Application is the root for the Excel hierarchy of
objects. From there on this hierarchy is traversed down to the collection (Range) of cells. All these
VLA-objects are assigned to variables. Information about Excel’s object hierarchy can be found in
that program’s Help files, but in recent versions an understandable description of the object model is
missing. As always we can read these objects’ properties and methods using vlax-dump-
object. Information about Excel ActiveX Object properties and methods can also be found via the
Object Explorer in the Excel Visual Basic Editor. The steps to reach the Cells collection are:

1. From the Application object we obtain its "Workbooks" property. This property returns the
Workbooks collection that includes all the open Excel Workbooks in the current instance of
the Application. A reference to this object is assigned to the wbook-collection variable.
A Workbook is the Excel Document. In our program we want to create an Excel XLS
Document named as the current drawing.
2. If a Workbook by that name is open in the current Excel session it will be retrieved using the
Workbooks collection's Item property. In case it does not exist an error will be thrown and
this will be used as the condition for adding a new Workbook to the collection and saving it to
the drawing's folder. Although the Workbook object has a Name property, it is read-only. To
name a Workbook we must use its SaveAs method. This method's arguments are shown in
Table 25.1. The Workbook will be assigned to the wbook variable.
3. From this Workbook we'll obtain through its "Sheets" property the collection of all the
Sheets in the Workbook, assigning it to the sheet-collection variable.

Table 25.1. SaveAs method arguments.


Argument Values Description
The name of the file to be saved. If a full path is not included Excel saves it
Filename String
in the current folder.

FileFormat XlFileFormat enum Specifies the file format to save. See Table 25.2.

Password String Case-sensitive string (15 characters or less).


WriteResPwd String Write-reservation password.

:vlax-true or
ReadOnlyRec If True displays a message recommending that it be opened as read-only
:vlax-false

:vlax-true or
CreateBackup True to create a backup file.
:vlax-false

XlSaveAsAccessMode
AccessMode Workbook access mode. See Table 25.3.
enum

Table 25.2. Workbook SaveAs Constant values for Excel 2007-2010.


Extension Constant Name Value File Type
xlsb xlExcel12 50 Excel 2007-2010 Binary.
xlsx xlOpenXMLWorkbook 51 Open XML 2007-2010
xlsm xlOpenXMLWorkbookMacroEnabled 52 Open XML 2007-2010 w/macros.
xls xlExcel8 56 Excel 97-2003 format.
csv xlCSV 6 Comma-Separated Values.
-
txt xlCurrentPlatformText Text.
4158

prn xlTextPrinter 36 Printer Text File.

Table 25.3. XlSaveAsAccessMode Enumeration Constants.


Constant Name Value Description
xlNoChange 1 Default (does not change the access mode).
xlShared 2 Share list.
xlExclusive 3 Exclusive mode.

DISCONNECT-EXCEL function.

As important as establishing a link with Excel is to terminate it correctly. The disconnect-


excel function (Listing 25.2) applies the vlax-release-object function to the Excel
Application object we are using. While a VLA-object points to an object, AutoCAD will retain the
memory needed to contain it. The function vlax-release-object indicates that this object is
not necessary and therefore the memory it occupied can be retrieved. When an object is released it is
no longer accessible through the VLA pointer. A call to the gc (garbage collection) function will
force memory recovery thus effectively removing the link. This is one of the few cases where
explicitly invoking this memory recovery mechanism is recommended.
(defun disconnect-excel ()
(vlax-release-object excel-app)
(gc))

Listing 25.2. DISCONNECT-EXCEL function.

Error handling function.

Due to the importance of this process, a call to the vlax-release-object function is also
included in the app-err error handling procedure (Listing 25.3), in order to avoid that in case of an
error this object would not be released. Before calling this function the error handler checks that the
object exists and that it has not been released. If vlax-release-object were not employed, the
only way to release Excel would be to quit AutoCAD’s session. Keeping the link has consequences
not only for AutoCAD. Any operation in Excel without releasing this link may in some cases make the
system unstable. The app-err function replaces the default error handler represented by the symbol
*error*.
(defun app-err (msg)
(if
(and excel-app
(not (vlax-object-released-p
excel-app)))
(vlax-release-object excel-app))
(prompt msg))

Listing 25.3. APP-ERR function.

Prior to Release 2000, redefining the standard *error* function required saving the original
function assigning it to a variable in order to restore it on ending the program. With Visual LISP
setting a custom error function becomes simpler, because we can just declare *error* as a local
symbol in the function parameters list where a custom error routine is used. Thus, the scope of this
function is limited to where it is declared as local. Once it concludes, *error* will regain its
default value or any other that had been assigned globally. For this reason *error* appears as a
local variables in the list->excel function.

25.2 Writing in the Worksheet.


LIST->EXCEL function.

The connect-excel and disconnect-excel functions are called from the list->excel
function (Listing 25.4). This function also replaces the standard error routine by the app-err
routine and calls the vl-load-com function that will load the necessary vlax functions. It
receives as arguments the name of the block and the list of data with which the spreadsheet will be
filled.

Writing to the Worksheet itself is included in the process-table function which receives as
input a list with the structure explained above. The sequence of operations performed by list-
>excel is:

1. The app-err function is set as the current error routine.


2. The ActiveX extensions are loaded with vl-load-com.
3. A link with the Excel application is established calling connect-excel.
4. The program tries to obtain an existing Worksheet with the block's name. If this Worksheet
exists, its information will be overwritten. If it does not exist an error will be thrown and this
will be the condition that will determine that a new Worksheet is added, naming it as the
selected block. This name is assigned using vlax-put-property. This name will appear in
the tab on the Worksheet's bottom. The Worksheet is assigned to the wsheet variable.
5. The Worksheet's "Cells" property is used to retrieve its Range object that represents all the
worksheet's cells (not only those currently used). The Item property is the Range object's
default property, allowing us to specify the row and column index for each particular cell. The
Range object is assigned to the excel-cells variable.
6. The function process-table is called.
7. After the writing of the worksheet, the link with Excel is removed.
(defun list->excel (name lst / *error*
excel-cells wsheet
sheet-coll wbook wbname
wbook-coll excel-app)
(setq *error* app-err)
(vl-load-com)
(connect-excel)
(setq wsheet
(vl-catch-all-apply
‘vlax-get-property
(list sheet-coll "Item" name)))
(cond
((vl-catch-all-error-p wsheet)
(setq wsheet (vlax-invoke-method
sheet-coll
"Add"))
(vlax-put-property wsheet "Name" name)))
(setq excel-cells
(vlax-get-property
wsheet
"Cells"))
(process-table lst)
(disconnect-excel))

Listing 25.4. LIST->EXCEL function.

PROCESS-TABLE function.
Writing data to the Excel spreadsheet is performed by the process-table function (Listing 25.5).
In this function, as in the following routines, an extensive use of LISP’s list processing functions
foreach, mapcar and apply is made.

T he process-table function in turn calls two auxiliary functions defined for this program:
data->cell and process-row.

The argument received is an association list with the characteristics described above represented by
the lst argument. The tasks accomplished by process-table can be described as follows:

1. Initializing the value of the numrow variable to 1.


2. The first sublist of the association list that represents the table is extracted, traversing it in a
foreach loop. During this loop the first term (car) of each sublist is written to the
worksheet's first row using the auxiliary function data->cell. This row is the column
headers row.
3. Once this foreach loop finishes, a while loop begins whose condition is that there is a first
term in the list. If this requirement is met, that first term, which is an association list with the
data we want to insert in the next row, is assigned to the row variable and in the loop:
a. The value of numrow is incremented by 1.
b. The rest of the list is assigned to the lst variable, removing its first term.
c. The process-row function is Invoked passing to it the association list assigned to the
row variable and the row number (numrow) to which the data must be written.
(defun process-table (lst / numrow numcol)
(setq numrow 1
numcol 0)
(foreach field (car lst)
(data->cell
numrow
(setq numcol (1+ numcol))
(car field)))
(while (setq row (car lst))
(setq numrow (1+ numrow)
lst (cdr lst))
(process-row row numrow)))

Listing 25.5. PROCESS-TABLE function.

PROCESS-ROW auxiliary function.

This function (Listing 25.6) is in charge of filling each row. It receives from process-table the
list row and the integer numrow. The process-row function performs the following operations:

1. It assigns 0 to the variable numcol.


2. The row list is traversed in a foreach loop. For each term (a dotted pair) in the list, which is
assigned to the field variable, the data->cell function is called with the column number
(assigned to the variable numcol) increased by 1 and the dotted pair's second term as
arguments. Note that to write the header row the same procedure is used, but passing to data-
>cell the dotted pair's first term.
(defun process-row (row numrow / numcol)
(setq numcol 0)
(foreach field row
(data->cell
numrow
(setq numcol (1+ numcol))
(cdr field))))

Listing 25.6. PROCESS-ROW function.

DATA->CELL auxiliary function.

Data is written to each cell by the data->cell function (Listing 25.7). This function is conceived
as a utility function, which can be used in any program that must carry out this operation. It takes as
arguments the row and column indices (as integers) that determine the cell in which to write, and the
value to be written.

This value can be any of the Visual LISP supported types. The cell’s value is a property of the Excel
Range object which was obtained in the connect-excel function and assigned to the variable
excel-cells. The Visual LISP functionvlax-put-property can then be applied. Note that
the value’s string nature is ensured by the vl-princ-to-string function.
(defun data->cell (row col value)
(vlax-put-property
excel-cells
"Item"
row
col
(vl-princ-to-string value)))

Listing 25.7. DATA->CELL auxiliary function.

25.3 The Dialog box.


For the application we are developing the best way to interface with the user is through a dialog box.
The one proposed here (see Figure 25.2) Is very simple, yet fulfills the intended purpose perfectly.
DCL programming basics were explained in Chapter 22. Only this dialog's composition will be
briefly explained here.

The dialog’s main component is a list_box tile that displays the block names that exist in the
drawing. Whether the block has attributes is indicated by adding the text "ATTRIB" to the
corresponding row. The arrangement of these texts as a column to the right of the list box is achieved
by inserting a tab character "\t" between the block name and the text "ATTRIB ", and assigning
the to the list_box tile’s tabs attribute a value of 33, being 40 its total width. This list box allows
only the selection of a single block name, which is determined by setting its multiple_select
attribute to false.

Figure 25.2. ttribute extraction dialog box.

The other dialog components are the OK and Cancel buttons that are part of ok_cancel predefined
subassembly and a line of text for messages, also predefined as the errtile component. The
complete code file for the attributes.dcl file is shown in Listing 25.8. This file must be located in a
folder included in the AutoCAD support files search paths. As for earlier examples in this book we
can save it to a folder named DCL within the Visual Lisp Projects folder that we added to the
search paths using the Options dialog box.
attributes : dialog {
label = "Attribute extraction"; // Dialog title
key = "title";
: list_box { // Start list
key = "block_list"; // Component ID
label = "Block to process:";
allow_accept = true; // Double-click
multiple_select = false; // Single select
width = 40;
height = 12;
tabs = "32"; //Tab
} // End list
ok_cancel; //Accept & Cancel
errtile; //Messages
}
Listing 25.8. Code for the dialog box.

Managing the Dialog Box.


The Dialog Box’s operation is enabled by the main function C:EXCEL-ATTRIBUTES (Listing
25.9). All the operations required to activate the dialog box are invoked from this function.

Main function C:EXCEL-ATTRIBUTES.


(defun C:EXCEL-ATTRIBUTES (/ block-list)
(if (display-dialog
"attributes"
"./dcl/attributes.dcl")
(progn
(if
(setq block-list (read-blocks))
(fill-list "block_list"
block-list)
(set_tile
"error"
"No blocks in the active drawing"))
(action_tile
"block_list"
"(check-attributes $value)")
(action_tile
"accept"
"(extract (get_tile \"block_list\"))")
(action_tile
"cancel"
"(done_dialog 0)")
(start_dialog)
(unload_dialog dcl_id)
(princ))))

Listing 25.9. Main function C:EXCEL-ATTRIBUTES.

Once the dialog box is displayed, the user will decide the action to execute: extracting the attributes
of one of the blocks or exiting without performing any operation. The procedure can be summarized
as follows:

1. The dialog box specified by its dialog and file names is loaded. Although these names are the
same in our example it does not have to be that way. In one DCL file several different dialogs
can be defined. This process, with the required error handling is done by the display-
dialog function (See Listing 22.2) that returns T if the dialog has been successfully loaded and
nil otherwise.
2. If successfully loaded, two initialization processes will take place:
a. Assigning values to the dialog's tiles. A tile's value can be predefined in the DCL file code,
employing its value attribute. But in our case the list should display the blocks in the current
drawing. These values must be assigned at run-time. This is done in two steps:
i. The list of blocks is retrieved by the read-blocks function and assigned to the
block-list variable.
ii. The fill-list function is called with the list_box tile's key and the list of
blocks as arguments.
b. After filling the list, the callback functions must be assigned. Callback functions will be
called when the tile's mouse-click event is raised. The list_box, the OK button and the
Cancel button will have callback functions. Callback actions are defined by AutoLISP
action_tile function. The assigned callback actions are:
i. list_box: Calls the check-attributes function using the value of the selected
line represented by the $value variable. (this variable is created automatically by
the system). If the selected line represents a block without attributes, a message
indicating this is printed in the errtile component.
ii. OK Button: Calls the extract function which receives the list_box selected
line's position as a zero-based index that is returned by the get_tile function. Note
that in this case we cannot use $value because that variable contains the value of the
selected tile, in this case the OK button. To get the value of another tile we use
get_tile with the key attribute as argument.
iii. Cancel button: Its only action is to close the dialog by calling the done_dialog
function.
3. Displaying the dialog: The start_dialog function displays the dialog. This function remains
active until done_dialog is called to close the dialog. Once the dialog has been closed,
unload_dialog unloads it, freeing the memory it occupied.

Auxiliary functions.
The auxiliary functions called from C:EXCEL-ATTRIBUTES are described below.

DISPLAY-DIALOG function.

This function, which was defined in Chapter 22 (Listing 22.2) will verify that the DCL file is
available and that the dialog definition can be loaded, alerting of any errors found. If one of these
messages appears, we must make sure the DCL file has been placed in the appropriate folder and that
it contains no syntax errors. This is a function that can be used for any dialog.

Filling the List Box: FILL-LIST function.

If all goes well, the list_box shall be filled by the fill-list function (Listing 25.10) with the
names of the blocks found in the drawing. These names are obtained by the read-blocks function,
which is somewhat complex and will be explained later. For the moment being we can say that the
names are stored in an association list composed of dotted pairs assigned to the block-list
variable. The procedure for filling a DCL list_box is the usual one:
1. A call to the start_list function with the tile's key as argument: (start_list
"block_list").
2. A call to the mapcar function applying 'add_list to each list member. We will use another
call to mapcar nested in the previous one, to create a string that will convert the dotted pair
("BLOCK" . "ATTRIB") into the string "BLOCK\tATTRIB".
(mapcar '(lambda (term)
(strcat (car term) "\t" (cdr term)))
text-list)
3. If block-list returns an empty list, rather than filling the list_box an error message will
be written in the errtile component:
(set_tile "error" "No blocks in the active drawing")
4. The list writing process finishes with a call to end_list.
(defun fill-list (list-tile block-list)
(start_list list-tile)
(mapcar 'add_list
(mapcar '(lambda (term)
(strcat (car term)
"\t"
(cdr term)))
block-list))
(end_list))

Listing 25.10. FILL-LIST function.

List box callback action: CHECK-ATTRIBUTES function.

The callback function check-attributes has been assigned to the list_box. This action will
be triggered when the user selects a row. We must also note that besides this action that is triggered
by a single mouse click, the double-click event will also trigger the OK button's callback action as
defined in the DCL code with the expression allow_accept = true;.

The $value argument passed to the check-attributes function (Listing 25.11) contains the
zero-based index of the row on which the mouse is clicked. The list box’s management requires
keeping the original block-list list in memory. To identify the block name on the selected row
check-attributes applies the nth function to retrieve the term of the list that occupies the
position specified by the index in $value:

(nth (atoi value) block-list)

The returned value is a String so it must be converted to an Integer using the atoi function.
(defun check-attributes (value)
(if
(not
(equal
(cdr (nth (atoi value) block-list))
"ATTRIB"))
(set_tile
"error"
"The selected block has no attributes")
(set_tile "error" "")))

Listing 25.11. CHECK-ATTRIBUTES function.

The dotted pair’s second term is then checked to verify if it is the "ATTRIB" string. If not, a
message is displayed indicating in errtile that "The selected block has no
attributes".

OK button's callback action: EXTRACT function.

Clicking on the OK button activates the attribute extraction process. The action for this button is
defined in the extract function which receives the selected row's index, this time obtained by
get_tile:
(action_tile "accept"
"(extract (get_tile \"block_list\"))")

This function (Listing 25.12) verifies that the selected block has attributes and in that case calls the
list->excel function to export them. It proceeds as described below:

1. Check that block-list exists. If so the value for the selected row in block-list is
assigned to the variable selection. The value we obtain from the list_box is only the
index that indicates the selected row. That row's text is not returned. To retrieve the text we have
to keep the list that was used to fill the list_box (the argument passed to fill-list).
Having the index and the original list, the selected row's text is retrieved using the nth function.
As the value returned by a DCL dialog box is always a String, it must be converted to an Integer
using atoi before passing it to nth.
2. Verify that the dotted pair's second term is the string "ATTRIB". If this verification succeeds,
the list->excel function is called with the value returned by the sel-block function as
argument. In case the second term is not the "ATTRIB" string, the function does nothing.
(defun extract (value / selection name)
(if block-list
(setq selection (nth (atoi value)
block-list)))
(if (= (cdr selection) "ATTRIB")
(progn
(set_tile
"error"
"Processing, please wait...")
(setq name (car selection))
(list->excel name (sel-block name))
(done_dialog))))

Listing 25.12. EXTRACT function.


SEL-BLOCK function.

This function that was proposed in Chapter 22 (Listing 24.4) receives as argument the block name
assigned to the variable name by the extract function. A selection set is created based on name
including all of that block’s insertions in the current drawing. Then this selection set will be
processed to build an association list with the structure required by list->excel. This list shall
include, in addition to the attributes with their tags, the name of the Layer in which the block is
inserted and its insertion point coordinates.

Creating the Block names list.


As we have seen, this dialog's functionality revolves around the list of block names assigned to the
block-list variable. To obtain this information we use the ActiveX object methods and
properties. This is done in the read-blocks function. We must highlight the difference between
the procedure followed in creating the block names list and the selection done to retrieve their
insertions' attribute values. The names of the existing blocks are read directly from the BLOCKS
collection that includes all the existing block definitions. This process is faster, as there can be a huge
amount of Inserts for a very small number of different blocks.

READ-BLOCKS function.

The process followed (Listing 25.13) is:

1. The Blocks collection can be retrieved from the Document's Blocks property using the
vla-get-Blocks function. The Blocks collection is traversed using the vlax-for
function in a way very much alike to traversing a list with foreach.
2. From each of the objects in the collection which we retrieve sequentially, we obtain its
"Name" property: (vla-get-Name obj).
3. But we are not interested in all the objects recovered from the Blocks collection. We only
want the names of user-defined blocks. There are a number of possible Block objects we must
exclude: the so-called anonymous blocks generated and managed by AutoCAD that are
distinguished by having an asterisk "*" as their name's first character (that will be detected
using as wcmatch pattern the escaped sequence `** in which the initial reverse quote makes
the first asterisk to be read literally while the second asterisk acts as a wildcard which will
match any character sequence) , the user-defined blocks dependent on Xrefs identified by
including the ASCII vertical bar (pipe) character "|" (ASCII 124 ) and the Xrefs themselves
(identified by their IsXRef property). Hence the conditional included in the code used to build
the block list. For the first two cases the wcmatch function is used with the string "`**,*|*"
as pattern.
4. But only those blocks actually inserted should be considered. This is checked by selecting them
with the expression:
(ssget "X" (list '(0 . "INSERT") (cons 2 name)))
5. If there are Inserts for this block we must check whether the it includes constant or variable
Attribute definitions. This is the has-attributes? function's purpose. Once all this has
been verified, we add the bock`s name to a list that will be used to populate the dialog's list box.
For those blocks with attributes the text "ATTRIB" will be added to the right of the list.
(defun read-blocks (/ name blk-list)
(vlax-for obj
(vla-get-blocks *aevl:drawing*)
(setq name (vla-get-name obj))
(if
(and (not (wcmatch name "`**,*|*"))
(equal (vla-get-IsXref obj)
:vlax-false))
(if
(ssget "X"
(list (cons 0 "INSERT")
(cons 2 name)))
(setq blk-list
(cons
(cons
name
(if
(has-attributes?
obj)
"ATTRIB"
""))
blk-list)))))
(setq blk-list
(vl-sort
blk-list
'(lambda (n1 n2)
(< (car n1) (car n2)))))
blk-list)

Listing 25.13. READ-BLOCKS function.

HAS-ATTRIBUTES? function.

This will require examining, one by one, the block definitions found. A Block Definition is also a
Collection. In this case composed by entities among which Attribute Definitions may be found.
The has-attributes? function (Listing 25.14) traverses each Block Definition using vlax-
for assigning T (true) to result in case an attribute definition (an object whose "ObjectName"
property is "AcDbAttributeDefinition") is found.
(defun has-attributes?
(blkdef-obj / result)
(vlax-for obj blkdef-obj
(if (equal (vla-get-ObjectName obj)
"AcDbAttributeDefinition")
(setq result t)))
result)

Listing 25.14. HAS-ATTRIBUTES? function.


25.4 Project Structure.
We will use the C:EXCEL-ATTRIBUTES program in the next chapter to compile a Visual LISP
Executable (VLX) application. To demonstrate how to generate a compiled application from a Visual
LISP project we shall distribute the functions described in this tutorial among three different LSP
source code files in addition to the DCL dialog definition file.

Table 25.1 includes all the functions used in this application. We must remember that some of these
functions have been explained in previous chapters so their code is not in this chapter’s listings. We
have distributed the functions according to their purposes in three different source code files. This
distribution is justified by the possibility to reuse code in other applications pursuing similar goals.

These files are:

list-excel.lsp: Functions related to exporting to the spreadsheet.


read-attributes.lsp: Functions related to the extraction of attribute values.
interface.lsp: Functions related to the dialog box management.
attributes.dcl: DCL source code for the dialog box.

Table 25.4. Functions used in the application.


LIST-EXCEL.LSP file functions
Function: Listings: Function: Listings:
connect-excel Listing 25.1 process-table Listing 25.5
disconnect-excel Listing 25.2 process-row Listing 25.6
app-err Listing 25.3 data->cell Listing 25.7
list->excel Listing 25.4

READ-ATTRIBUTES.LSP file functions


Function: Listing: Function: Listing:
ax-extract-attrib Listing 23.2 ax-read-attributes Listing 23.3
replace Listing 5.12

INTERFACE.LSP file functions.


Function: Listing: Function: Listing:
C:EXCEL-ATTRIBUTES Listing 25.9 extract Listing 25.12
display-dialog Listing 22.2 sel-block Listing 24.4
fill-list Listing 25.10 read-blocks Listing 25.13
check-attributes Listing 25.11 has-attributes? Listing 25.14

ATTRIBUTES.DCL
Function: Listing:
DCL code Listing 25.8

Figure 25.3. Spreadsheet showing the data exported from a drawing.

25.5 Summary.
We believe that, once explained, it may surprise how simple it is to set up a connection between
Visual LISP and an application like Excel in order to export drawing data directly to the spreadsheet.
In fact, most of the code in this application has nothing to do with this. It deals with data extraction
and GUI management. I’ve waited to have an application like this, of a certain complexity to address
a pending topic: compilation. To it we will devote the next chapter.

Exercises.
Exercise 1.

Other alphanumeric information that, as studied in the previous chapter can be stored in XDATA,
XRECORD or LDATA format can also be exported to spreadsheets. And regarding geometric
information, it would be possible to do it with almost all the graphic entity’s properties. Precisely the
list->excel function requires an association list such as the one returned by entget. As an
exercise we could define a function able to export these data types. For example, the coordinates for
the vertices of polylines.
Chapter 26
VLX: The Visual LISP Executable
In the previous chapter we completed the code for a program that exports attribute values of the
selected block to an EXCEL spreadsheet. This program includes three LISP source files and one with
the DCL code for a dialog box. Managing a project of some complexity like this one can profit from
the tools provided by the Visual LISP Integrated Development Environment (IDE).

The source code files can be structured as a project. This allows us to open any file by double-
clicking in the project window. It also enables searching for expressions in all of the project files,
and loading all the source code files at the same time. But the greatest advantage in applications like
this one is the possibility packing it for its distribution as a single compiled file, including the dialog
interface.

26.1 Managing an Application.


Usually a real application will be even more complex than the one we have developed. It can include
thousands of code lines distributed among a large number of LSP files. This source code can be
compiled to increase efficiency, a process in which other intermediate and executable files are
generated. Keeping track of all these files can become very cumbersome, for example to determine
when modified files should be recompiled or the whole application rebuilt. The parameters that
specify how to compile and build the application according to its characteristics may also be set. If
other resource files (dialog definitions or text files) are to be included, it is advisable to package
them as a single VLX file.

The VLISP-COMPILE function.


The function call the Visual LISP compiler is vlisp-compile. This function is sufficient if compiling a
single file:
(vlisp-compile 'mode "source-filename" "output-filename")

It can be run either from the VLISP console or from AutoCAD’s command line (provided the Visual
LISP IDE has been previously loaded). The arguments it requires are:

'Mode (observe the single quote) that represents the desired compilation type:
'st standard mode.
'lsm optimize and link indirectly.
'lsa optimize and link directly
"source-filename" the name of the LISP source code as a string, if the extension is not
specified, LSP is assumed.
"output-filename" the name for the compiled output file. This argument is optional, if not
specified the output file name will be the same as the source code file with the extension FAS,
and will be located in the same folder.

Optimization Modes.
The results of the compilation process are at least two. First, it obviates the translation to the LISP
virtual machine language that takes place every time the AutoLISP source code is loaded. The FAS
file resulting from the compilation contains instructions directly understandable by the system, with
no need to further code interpretation. Besides saving time (hence the FAS extension, derived from
FASt loading), the code obtained is understood by the machine but not by snoopers. This would be
achieved with the standard mode.

The other optimization modes make programs even more efficient, marking a difference as they grow
in size and complexity.

They allow:

Linking function calls so direct references to the compiled function are created, instead of
invoking the symbol that represents the function, which improves performance and avoids that a
redefinition of the symbol may affect the program's behavior.
Discarding the function names to make the compiled code more secure, while reducing the
program size and loading time.
Discarding local variables names directly linking their references, gaining even more speed and
achieving a further reduction in size.

Not all can be applied in every case. Some degree of optimization can cause errors.

26.2 The VLISP Project Manager.


The compilation mode when using vlisp-compile will almost always be standard ('st) and limited
to a single file. Although several AutoLISP files may be compiled into a single VLX file using the
Make Application wizard, for a complex application that requires managing code distributed in
several different files it will be useful to set up a Visual LISP project.

The project maintains a list of source files and a set of detailed compilation rules, allowing to:

Check which of the application's LSP code files have been modified so they are automatically
recompiled.
Open the listed files with a mouse click.
Search text strings in all the files that make up the project.
Link directly to the appropriate parts of the code even if they belong to different files.

All the project management functions are integrated in the Project Window that displays the list of
files, a button bar at the top (see Figure 26.1) and a contextual menu which is reached by pressing the
right mouse button (see Figure 26.2).

Figure 26.1. Project Window.


Figure 26.2. Project contextual menu.

Configuring the Project.


Our project will be configured with the files created for the tutorial in Chapter 25. Before proceeding
to configure the project we will create a folder structure where the various files used in the process
will be located. The files interface.lsp, read-attributes.lsp, list-excel.lsp will be saved to the
ATTRIBUTES folder. The DCL dialog definition will be saved to a folder named DCL.

The other files generated by the IDE and the compiler are described in Table 26.1. To hold these files
three additional subfolders will be created within the ATTRIBUTES folder, with the names FAS,
TMP and VLX. This process is the same followed in the Tutorial from Volume1, Chapter 2. But the
compilation options will now be examined in more depth.

Figure 26.3. Project's Folder Structure.

Table 26.1 Files involved in the compilation process.


Folder: Ext: File Type: Purpose:

ATTRIBUTES .LSP AutoLISP source code Application's source code

.DCL Dialog Box definition Controls the application's GUI.

Location and names of the LSP files and compilation parameters and
.PRJ Project definition
rules

.GLD Global declarations Detailed settings for a more precise compilation control

ATTRIBUTES/TMP .OB Object Code AutoLISP compiled code used in building the FAS files

.PDB Project Database Symbols information used by the compiler

AutoLISP compiled
ATTRIBUTES/FAS .FAS Compiled programs that can be executed or packaged into VLX modules
code

ATTRIBUTES/VLX .VLX VLISP Application Stand-alone AutoCAD applications

Defines the files and options used to build the VLX application using the
.PRV Application's Make file
Application Wizard

To build our project we have the New Project... option from the Project menu. When selected, a
dialog box is displayed in order to select the project folder and its name. We will select the
ATTRIBUTES folder in the structure explained above.
Figure 26.4a. Project Properties: Files Tab.

Figure 26.4b. Project Properties: Build Options Tab.


This done, the Project Properties dialog box is displayed. This dialog has two tabs, Project Files
and Build Options. To select the LSP files, if they were not visible, we can select the ellipsis button
[...] to locate the ATTRIBUTES folder or otherwise we can write the path in the Look in edit box.
When the correct folder is selected the existing LSP file names appear in the list box on the left side.
They can be selected by clicking on them (multiple files can be selected without having to press the
CTRL key) and clicking on the [>] button passes them to the list box on the right (See Figure 26.4,
Left). If it were necessary to include files from different folders, we can repeat this process until the
list on the right displays all the application's files. The list order defines the order in which files are
loaded. In our case the last file should be INTERFACE.LSP, so we select it and click on the Bottom
button to put it into this position. To see the paths and file sizes we can highlight them and select Log
filenames and size from the right-click contextual menu.

Generation Options.
We will strive for the highest optimization possible. The options we will use involve a greater risk,
but they can provide the highest efficiency in our application. The options we will choose are shown
in Figure 26.4 (Right). They are:

Merging files in a single module, i.e., one FAS file. This will speed loading and avoid having to
manage three different files.
Choosing Internal link mode, so the compiler tries to resolve all explicit function calls by
referencing the function's definition in memory, dropping then the function name altogether so
that the function becomes invisible to users.
Selecting Localize variables, so the compiler will also remove all local symbol names directly
linking their references if possible, rather than a symbol representing the variable's memory
address.
To prevent as far as possible errors due to this high degree of optimization, we also select Safe
optimize that instructs the compiler to reject certain optimizations that may be problematic.
In Message Mode we select Full reports to ensure we have all the information about the
compilation process including errors, warnings and compiler statistics.
In this tab we must also indicate the folders where FAS and temporary files will be stored. For
now we won't use the Edit Global Declarations… button.

By selecting the OK button we will have completed the project definition. Before compiling the
project we must remember that Visual LISP ActiveX extension functions (vlax-... etc.) will not
be available without evaluating (vl-load-com).

Once these functions are loaded, we can compile the project, selecting the Build Project Fas button
on the Project window’s toolbar. Examining the messages in the Build Output window can be very
instructive. We should consider especially the lines highlighted in blue which show the Safe
Optimize warnings (see Figure 26.5).
Figure 26.5. Messages in the Build Output window.

Double-clicking on any highlighted line and will open the source code file containing the related code
snippet. These warnings make up most of the Build Output window’s content. Detailed compilation
instructions can be set by creating a GLD (Global Declarations) file. These compiler directives
functionality is not documented and unless there is an error that can be traced to a specific aspect,
their use is not recommended. To modify the compiler options the Edit Global Declarations...
button can be used to create a GLD file for the project.

The FAS module must be loaded to test it. If we have organized the project as indicated, with the
Visual Lisp Projects folder in the AutoCAD search paths, we can load the FAS module evaluating the
expression:
(load "./ATTRIBUTES/FAS/EXCEL-ATTRIBUTES.fas")

We can also use the Load Project Fas button on the project window's toolbar.

Although compilation as a FAS file can provide effectiveness and security to our program, we still
have to rely on an external DCL dialog file that must be installed in a location where AutoCAD can
find it. This is something that can be solved by packaging the application as a Visual LISP
Executable, i.e., as a VLX file.

26.3 Namespaces.
Up to Release 14 AutoCAD worked as what is known as Single Document Interface (SDI). To open a
new drawing the current one should be closed. The concept of namespaces was introduced when
AutoCAD 2000 enabled the possibility of working as a Multiple Document Interface (MDI). Its
purpose is to prevent interference between programs running on different drawing windows. A
namespace is a LISP environment that contains a set of symbols (variables, functions, etc.). Each
drawing open in AutoCAD has its own namespace. LISP objects defined in a document's namespace
are isolated from those defined in other drawing. Sharing information between different namespaces
is possible but it must be done explicitly.

Applications with a separate namespace.


A VLX application can have, within the drawing, its own namespace. A separate namespace VLX
application operates in its own namespace and not in the document's. Creating this kind of application
is an option defined in the Application Wizard's Application Settings tab. The variables and
functions defined in a separate namespace VLX are accessible only to the application itself and not to
the drawing in which it is loaded. This application can export to the document's namespace the names
of those functions that must be available from the drawing's context.

Making the functions accessible from the drawing.

To make a function defined in a separate namespace VLX application accessible from the drawing's
environment, it must be exported explicitly by calling the vl-doc-export function. This function
takes a single argument which is the symbol that identifies the function's name. The vl-doc-
export expression must be invoked before the defun expression where the function is defined. An
exported function can be imported by another separate namespace VLX application using the vl-
doc-import function.

To determine which functions have been exported from a separate namespace application vl-
list-exported-functions can be used. It requires as argument a string with the name of the
application whose exported functions must be checked.

Access to drawing variables from a separate namespace VLX.

An application compiled as a separate namespace VLX can access variables defined in the drawing's
namespace using the functions vl-doc-ref and vl-doc-set functions. The vl-doc-ref
function copies the value of a variable defined in the drawing's namespace. It requires a single
argument which is the symbol that identifies the variable to be copied. Assuming the existence of a
variable named point-list, its value is copied evaluating the expression (vl-doc-ref
'point-list). When called from the document's namespace it produces the same results as
eval.

The vl-doc-set function assigns a value to a variable in the drawing’s namespace. It requires two
arguments: the symbol that identifies the variable and the value to assign.

The following expression assigns a list of three real numbers to the variable pt:
(vl-doc-set 'pt (list 100.0 250.0 10.0))

When evaluated from the document's namespace it produces the same effect as setq.
If the value of a variable defined in the VLX application’s namespace must be copied to the
namespaces of all open drawings the vl-propagate function can be used. The following
expression sets the value of the variable website in the namespaces all open drawings:
(setq website "http://www.togores.net/")
(vl-propagate 'website)

The effect of vl-propagate is not limited to open drawings. The variable will be automatically
copied to the namespace of all drawings to be opened during the current AutoCAD session.

Sharing data between namespaces.

Visual LISP provides a namespace that is not linked to any drawing or any VLX application. This
namespace is known as the blackboard namespace. Its purpose is to establish and reference variables
that can be used by any drawing or VLX application. The vl-bb-set function is used to set a
variable and vl-bb-ref to retrieve the assigned value. As in previous cases, these functions
require the variable name as a symbol preceded by quote. Variables with the same name and
different values can coexist in the drawing and blackboard namespaces.
_$ (setq test "This is in the drawing's namespace")
"This is in the drawing's namespace"
_$ (vl-bb-set 'test "This is in the blackboard namespace")
"This is in the blackboard namespace"
_$ test
"This is in the drawing's namespace"
_$ (vl-bb-ref 'test)
"This is in the blackboard namespace"
_$

Error handling.

Applications that have their own namespace can define *error* functions using vl-exit-
with-error a nd vl-exit-with-value to transfer control to the drawing's *error*
function. With vl-exit-with-error a string informing about the error's nature can be passed to
the document's *error* function. When it is executed the stack is unwound, and control returns to a
command prompt. The vl-exit-with-value function can be used to return a numeric value to
the program that called the VLX.

Trying to load an already loaded separate namespace VLX application will raise an error. Whether a
separate namespace VLX application is loaded can be verified using the vl-vlx-loaded-p
predicate. These applications can be loaded with the same load function used for .LSP and .FAS
files.

The vl-list-loaded-vlx function returns a list with the names the separate namespace VLX
files associated with the current document. This function returns only the file name, without extension
or path. The vl-unload-vlx function unloads a separate namespace VLX application. It receives
as argument the application’s name as a string, also without extension or path. If the application is not
loaded this function raises an error.

26.4 Creating the Application Module.


When creating the VLX application besides adding the DCL code file, we can choose to create an
application that runs in the drawing's namespace or in its own separate namespace. If we choose to
create an application that operates within the drawing's namespace its compilation as VLX will cause
no problems. But as if we were working with LSP files, the risk of interfering with functions defined
by other applications exists. Evaluating function symbols in the Visual LISP Console once the
Project's FAS has been loaded, we can see that although most of these symbols return nil,
demonstrating that they have been dropped by the compiled application, some will still return USUBR
objects. This means they would not be totally secure in a shared memory environment.
_$ check-attributes
#<SUBR @0000000035e5b1d8 CHECK-ATTRIBUTES>
_$ extract
#<SUBR @0000000035e5b160 EXTRACT>
_$

An application that operates in its own separate namespace avoids this risk.

Aspects to take into account when using a Separate


Namespace.
But if the project is compiled as it is, we will get an error so serious that will leave no other option
than using the Windows Task Manager to close AutoCAD. I guess you have already guessed what this
error's source is. For all of this book's programs we are assuming that acaddoc.lsp has loaded a file
that creates two global protected variables for the Application and Document objects. But if
the application works in its own namespace, these global variables are not accessible. There would
be two different solutions:

Importing those variables from the drawing's namespace using vl-doc-ref.


Creating those variables within the application's namespace for its exclusive use.

The second of these variants is safer allowing the application to be used in any environment. For this
we will create a new LSP file with the code shown in Figure 26.6, the same included in the excel-
globals.lsp file, but in this case there is no need to protect them with a pragma expression.
Figure 26.6. EXCEL-GLOBALS.LSP file code.

This file is not going to be added to the project, as it is only necessary if we create a separate
namespace VLX application.

Create Application Wizard.


T he VLX module is created using a Wizard displayed by the File>Make Application>New
Application Wizard... menu option, following the steps described below:

Check the Expert radio button in the Wizard Mode


Step dialog box. The Simple mode does not allow adding
1 more than LSP files, which we have already solved
by compiling to a single FAS module.

In the Application Directory dialog box select the


ATTRIBUTES/VLX folder (for PRV and VLX files)
Step
as the Application Location and as Application
2
Name the same name used for the AutoCAD
command, EXCEL-ATTRIBUTES.
In the Application Options dialog box we can
choose whether to compile the application as a
Separate Namespace VLX. We will select the
Step
Separate Namespace option for our application.
3
Checking both Separate Namespace and ActiveX
Support loading the VLX file will automatically load
the Visual LISP ActiveX extensions.

Files that will integrate our application.


In the LISP Files to Include dialog box use the
pull-down button to select Visual LISP Project
Step
Files. Then click on Add... to locate the project we
4
created earlier in the ATTRIBUTES folder. After
this we must remember to add the EXCEL-
GLOBALS.LSP file.

Additional resource files.


The Resource Files to Include dialog box that is
displayed by the Wizard in Expert mode can be
Step used to add additional files to the application. These
5 can be AutoLISP source files, compiled LISP files,
Visual LISP project files, DCL files, DVB files and
text files. In our case, it is will be the DCL code file,
located in the ATTRIBUTES/DCL folder.
Also working in Expert mode the Application
Compilation Options dialog box will be displayed.
Step
6 In this dialog box we will select the Optimize and
Link radio button, bearing in mind that we have
already established in the Project detailed
compilation parameters.

Review Selections/Build Application is the final


dialog box displayed. By selecting the Finish button
Step the specified parameters are saved to a Make file
7 (PRV). This file is located in the application folder.
The VLX application will be created immediately if
the Build Application checkbox is marked.

The compiled VLX file includes the DCL dialog definition, simplifying the application's distribution.
The application can be loaded with
(load "./ATTRIBUTES/VLX/EXCEL-ATTRIBUTES")

provided that the application folder is the one that has been indicated in this tutorial.

Importing ObjectARX/ADSRX functions into a separate


namespace VLX
In an application such as the one we have compiled, problems when operating in its own namespace
are not likely to appear. But in case that an application should use commands or functions from
ObjectARX applications it will be necessary to import them explicitly through a vl-arx-import
expression. An ObjectARX function is of the EXRXSUBR data type. If we need to use a function like
startapp within a separate namespace VLX application its type could be previously checked:
_$ (type startapp)
EXRXSUBR
_$
When it is necessary to import functions because of this we will notice it immediately. When trying to
run the program we will receive the usual message telling us that the function is undefined.

26.5 Summary.
This chapter forms a unit with the previous one, showing the steps to build a compiled application,
from our source code. This application although simple, meets all the requirements expected of a
professional job. At this point the reader will have accumulated sufficient knowledge to venture into
the development of their own ideas.

Exercises.
The topics studied in this chapter shall be from now on the finishing stage in or applications
development. Whether as a FAS file, or in cases that merit it as a VLX application, we can resolve
from now on that not a single line of our work is distributed as source code.

This does not mean we should not share our knowledge. What keeps the AutoCAD users community
alive is precisely such a generosity and willingness to help others who undertake this rewarding task
of programming. A good example is the forums on the Internet like those in
autodesk.autocad.customization and theswamp.org.

But now the programs we generate, whether to distribute it commercially, for use within our company
or simply to make everyday life easier to us, will have that touch of professionalism for which we
can feel proud.
Chapter 27
OpenDCL
The DCL language has extremely interesting features. The procedure used to control dialog structure
using the column and row containers is comparable to the containers and Layout Managers in Java,
which undoubtedly is far better than the way Visual BASIC forms are programmed. For many
applications, its performance is more than enough. This can be verified just by checking the many
DCL files in a typical AutoCAD installation. ButDCL is limited to modal dialog boxes and can only
use a few of the wide range of widgets available in the Windows graphic user interface.

Having a truly “visual” environment for their design that allows on-screen placement is also missed.
This second aspect, given the extreme simplicity of the DCL language, is not so problematic.

This chapter will be devoted to examining OpenDCL, an alternative that even if it does not meet all
the requirements we would wish to find in a tool like this, is currently the most practical solution for
the Visual LISP programmer wishing to enrich the graphical user interface beyond that which DCL
offers.

OpenDCL is justified in those cases in which:

A Non-Modal Dialog is required, i.e., a dialog that allows working in AutoCAD despite the
dialog box being open.
The use of controls (widgets) not available in DCL.

This chapter will discuss the operation of OpenDCL developing an application with these
characteristics.

27.1 The OpenDCL project.


OpenDCL not part of AutoCAD. It is based onObjectDCL, a commercial application by 3rd day
Software that was released as open source under the GNU General Public License in 2006 by owner
Chad Wanless. OpenDCL has been enriched with the contribution of a large group of developers led
by Owen Wengerd and David Robison in an open source project hosted on SourceForge, from where
the application’s latest version can be downloaded.1.

The downloaded package includes an installer that creates the application’s folders and installs the
necessary files. The OpenDCL runtime module is automatically registered for all versions of
AutoCAD installed in the host computer.

Once installed, the OpenDCL development environment may be started as a stand-alone program by
double-clicking the icon created on the desktop or the Start menu.

Figure 27.1. OpenDCL application window.

Events.
In DCL programming the action triggered by an event is defined by the action_tile function or
by the tile's action attribute, being limited to one event per tile. For example, a button tile raises
the mouse-click event and an edit_box only raises an event when switching to another tile.

However OpenDCL can monitor different events for the same control. The term control will be used
from now on to designate the dialog’s widgets. A PictureBox control can notify a Visual LISP
function such events such as mouse single or double clicks, mouse movements including pointer
coordinates and its entering or leaving the control’s area, key pressing and others.

Definition of some terms.


For a better understanding of the following discussion we should define the meaning we attach to
certain terms.

Object: An object is a programming data structure that has properties (data) defining its
characteristics and methods (procedures) that specify the way they interact. An object recognizes
certain events it can respond to. All the OpenDCL controls and forms are objects.
Methods: A method is a function that can be used to retrieve or change data.
Properties: they supply the way to define the object's characteristics, such as size, position, font
or color used. Properties are also used in retrieving this data.
Events: user actions and program or operating system notifications. Examples include pressing a
key, a mouse click, etc.
Forms: A Form is a template used at runtime to create a dialog box instance superposed to the
AutoCAD graphic screen or docked to one of its sides. Forms are created and edited
interactively in the OpenDCL Studio, dragging controls onto a form and setting their design
time properties. OpenDCL can create applications using six different Form types.
Controls: reusable objects that provide parts of a program's GUI. An OpenDCL control is
usually analogous to a standard Windows control, such as a Label, a TextBox or a
TextButton. But some of its controls are more complex, made up by of several synchronized
Windows. In OpenDCL the forms are also controls.
Image List: An internal collection of images stored independently of their location. An Image
List is linked to the control using it. The TreeControl uses an Image List to specify the
image displayed with the tree's elements.
Picture Folder: A global ImageList, its images can be used for various controls that are able to
display them. Picture Folder images are numbered starting with 100 while the ImageList
numbering begins with 1.

27.2 The OpenDCL development environment.


On starting, OpenDCL Studio displays a window similar to those used in the development of Visual
Basic programs. This environment has the following elements:

Menu Bar.
The menu bar offers access to all the Editor's commands. In addition to the usual File, Edit, View,
Window and Help menus, it includes two which are specific to this program, the Projects and Tools
menus.
Toolbar.
The OpenDCL Toolbar includes the usual Windows program commands as well as a button for the
Picture Folder and a pull-down button used to add new forms.

Picture Folder.
To add images to GraphicButton or PictureBox controls, these images must be previously
loaded in the Picture Folder. This is where the images shown by these two controls are stored.
Images in ICO, BMP, JPG and WMF format can be added to the Picture Folder. The Project menu
option or the Toolbar button may be used to open the Picture Folder. Images that are assigned
directly to a specific control's Picture property in the Properties panel are also added to the
Picture Folder.

Figure 27.2. Picture Folder window.

In developing the application that is discussed further on a series of numbers are referred to, being
those that were assigned to the images when adding them to the Picture Folder. This numbering
depends on the order in which images are selected. To add an image, the Add button in the Picture
Folder window must be used. Doing so displays a file selection dialog box. Using it we can browse
to the folder where the image is stored in order to select it. The other buttons are used to replace an
image (Change), Delete it, accept the changes (OK) or Cancel the operation.

Toolbox.
Controls from which dialog can be composed (See Table.27.1) are included in the ToolBox. They
are described below, specifying its DCL equivalent in case there is one. When the control selection
(arrow) button (See Figure 27.3) is selected one or more of controls can be picked for editing.
Multiple selections are useful when the same property value must be assigned to several controls.

Figure 27.3. Toolbox showing the available controls.

27.3 Form Types.


A new form is added to a project with the Project Panel contextual menu or selecting the ToolBar’s
Add Form drop-down button. Double-clicking on a form in the Project Panel opens it for editing in
the workspace. When working with multiple forms, they can be tiled or cascaded using the Window
menu.
While DCL can only create Modal dialogs, OpenDCL allows other modalities characteristic of the
Windows windowing system. These modalities are described below.

Modal Dialog.
A Modal dialog has the focus of the application while it is open and does not allow any action
outside itself. For this reason Modal dialogs are easier to manage since AutoCAD’s state does not
change during the dialog’s lifetime. A modal dialog box in AutoCAD usually will not make any
changes until it is closed, typically using a button labeled OK. Modal dialogs return a status code that
is used by the application to decide which actions to take. OpenDCL can create the usual type of
Windows dialog boxes working in a modal fashion.

Modeless dialogs.
Modeless dialogs allow the user to operate outside their boundaries. Modeless dialogs present
additional difficulties that arise from the fact that our work is done in a multiple document
environment. For this reason, changes in the AutoCAD application’s state such as opening or closing
documents must be taken into account. In this case the fact that each drawing has its own AutoLISP
namespace must be considered. This means that when a new document becomes current the events’
callback code will no longer be available. OpenDCL can create five different types of non-modal
dialogs.

Modeless dialog box.

It is a standard Windows dialog box that can remain open while the user continues working in
AutoCAD. It is identical in appearance to the modal dialog box.

Control Bar.

A Control Bar form is a modeless dialog that can be docked to one side of the AutoCAD window.

Options Tab.

The Options Tab form is displayed as an additional tab in the AutoCAD Options dialog.

File Dialog.

This form allows creating custom file search dialog boxes based on the standard Windows file
dialog. It includes a FileExplorer control along with other custom controls chosen by the
programmer.

Palette.
The Palette form displays a modeless dialog box can remain floating or be docked to the AutoCAD
window. Pallets have a vertical title bar, and include the ability to collapse or expand automatically
in response to the cursor’s movement.

Table 27.1. OpenDCL controls.


OpenDCL Control: DCL equivalent:

Label text
Displays a text that cannot be edited and only raises click and double-click events.

TextButton button
Button that triggers an action. It can only display text caption.

GraphicButton image_button
Button that triggers an action, but that can display an image (ICO, BM P or JPG). It may have two different border styles,
raised or flat, like in recent Windows applications. The DCL image_button can only display vector images (SLD).

Frame boxed_row boxed_column


Draws a captioned frame around a logical grouping of other controls.

TextBox edit_box
Similar to the M FC Edit box control, used both for displaying and entering text. It incorporates many data input control
filters for strings, multiline text, uppercase, lowercase or hidden (as for passwords) characters, AutoCAD symbols, integers,
linear and angular drawing units format.

CheckBox toggle
A small square box that is marked when selected.

OptionButton radio_button
A small circular box marked with a dot when selected. Usually forms part of a group, in which only one of the buttons may be
checked.

ComboBox popup_list

Displays a text box with a button that can be used to display a list of values as in the ListBox. The main difference with its
DCL equivalent is that there are a number of specialized ComboBox styles equivalent to the AutoCAD controls used for
colors, dimension arrows, Lineweights, and plot style names. Three additional styles are available: Combo (with editable text
boxes), Simple (in which the list appears under an editable text box) and DropDown (where the text is read-only).

ListBox list_box
Displays a list of text items which the user can select.

ScrollBar No DCL equivalent.


Shows a standard horizontal or vertical Windows scroll bar. This movement can be used to set a value and/or to control the
information displayed in another control.

Slider No DCL equivalent.


Used to specify an integer value constrained to a preset range by dragging a cursor.

PictureBox image
Used for displaying a variety of images. A large number of methods are available for drawing on the control at runtime. The
DCL equivalent only supports vector graphics (SLD).

Tab Strip No DCL equivalent.


Allows choosing from multiple overlaid tab pages. Each tab page contains its own set of controls.

Calendar No DCL equivalent.


Shows the months and days in a calendar format, where a date or a range of dates can be selected

Tree No DCL equivalent.


Displays data items structured as a hierarchical tree structure. Items can include an image as well as a text label. The use of
this control is demonstrated in this chapter's tutorial.

Rectangle boxed_row boxed_column


Rectangle used to visually organize the controls.

ProgressBar No DCL equivalent.


Displays a bar that grows as a process runs.

SpinButton No DCL equivalent.


Specifies an integer value constrained to a preset range by incrementing or decrementing the value by one. Can be linked
with a TextBox in which the integer filter is set, changing its value as the user clicks on the arrows.

Hyperlink No DCL equivalent.


Similar to a Label, but acting as a hyperlink to open the configured URL or filename when clicked.
image
SlideView
image_button
Displays images from SLD or SLB files.

Html No DCL equivalent.


Web browser that displays HTML web pages.

AngleSlider No DCL equivalent.


Circular slider which shows the selected angle in its center.

BlockView No DCL equivalent.


Displays any 2D or 3D block in the current drawing. Allows zooming, panning, and orbiting.

DwgPreview No DCL equivalent.


Displays a DWG file's saved bitmap preview.

ListView No DCL equivalent.


Presents a list of items either as labeled and grouped icons, or as rows of tabulated data including column headers. Unlike
the grid control, the list view does not allow editing individual cells.

BlockList No DCL equivalent.


The BlockList control displays a list of all blocks defined in the current drawing file or in an external .DWG file.

radio_column
OptionList
boxed_radio_column
The OptionList control displays a list of radio buttons. Unlike the Option button control, this list can be easily modified at
runtime.
Insert ActiveXControl No DCL equivalent.
ActiveX controls are Windows controls that expose their functionality through a COM interface. OpenDCL ActiveX Control
acts as a container for the actual control used. ActiveX controls must be registered (usually when the application is installed)
in the system which will be used. In addition to the OpenDCL control's specific properties, most ActiveX controls expose their
own properties, methods, and events.

DWGList No DCL equivalent.


The DWGList control displays a list of all drawing files in the selected folder.

Grid No DCL equivalent.


The Grid has the appearance of a spreadsheet, displaying tabulated data in rows and columns. These cells can be edited by
clicking on them.

Splitter No DCL equivalent.


The Splitter control represents a division that allows the user to resize associated controls. When the Splitter is dragged to a
new position associated controls also move.

Animation No DCL equivalent.


The Animation control plays movies from AVI files.

ImageCombo No DCL equivalent.


ImageCombo control is a drop-down list of image and/or text items that may be selected by the user. It may include an
editable text box. Its Style property specifies the drop-down list type.

Hatch No DCL equivalent.


The Hatch control displays an AutoCAD hatch pattern at the specified scale.

27.4 Control Property Wizard.


The Control Property Wizard dialog provides an interface in which the values of a set of control
properties can be set. This wizard is displayed by double-clicking on the control’s name in the
Tab/Z-Order Panel, by selecting Properties from the control’s context menu on the form or by
selecting the control’s (Wizard) field in the Properties panel. Each control has a different set of
wizards according to its characteristics. For setting some properties such as tooltips, fonts and
alignment, the Wizard is the primary interface. For other controls such as the Grid, the TabStrip or
the ImageCombo the Wizard allows access to hidden properties that cannot be managed through the
Properties Pane.

Geometry: Assigns resizing rules for each control. The way controls will move when the form is
resized is defined by these rules.
Background and Foreground: Specifies a control's background or foreground colors.
Font: Selects a control's text font and size.
Button Styles: Specifies the GraphicButton control's presentation style.
ComboBox Styles: Some styles define the ComboBox control's behavior and others define the
information listed (AutoCAD colors, Lineweights, etc.).
TextBox Filter Styles: Assigns a predefined filter to the text box input.
Image List: Used for selecting the images displayed in the Tree control. Unlike the
PictureBox and GraphicButton controls, the Tree control requires that all the images
used are loaded on initializing the program. This is accomplished through the use of the
ImageList object. The wizard displays all the loaded images and allows adding or removing
items. All images added to the ImageList must be the same size.
Tabs/Pages: Defines the number of tabs displayed in a TabStrip control and their captions.
ToolTips: Sets the text, the formal characteristics and the style for a control's tooltips.

Figure 27.4. Tooltip Wizard.

Properties and Events Pane.

T he Properties/Events Pane is a fundamental part of OpenDCL Studio. It includes two tabs,


Properties and Events. The Properties tab is used to set a control’s appearance and performance.
The Events tab allows selecting those events to which the program will react. The box on the pane’s
bottom shows a template for the Visual LISP callback function that will be triggered by the event.
This template should be copied to the clipboard by selecting the Copy to Clipboard button so it can
be pasted it in the AutoLISP source code file. The properties and events are listed in alphabetical
order.

Establishing how to react to different events is the fundamental task of the AutoLISP code in an
OpenDCL application. The OpenDCL Studio aids in implementing event callback functions. The
events to which a control will listen are set in the Events tab by marking the checkbox beside the
event’s name. The name of the event’s callback function is shown in a text box and under it a function
template ready to be copied to the clipboard and pasted into the AutoLISP file. This Clipboard Copy
button can be used for this. The template function includes a call to the dcl_MessageBox function
that displays a message warning that the function does not yet contain the necessary code. The
function template can be written directly to the AutoLISP file via the Add to File button that is
displayed if Write Events to Lisp File is selected in the Tools menu.

Figure 27.5. Events tab.

Control Browser.

Another way to generate an event callback function’s template is the Control Browser, which also
provides context-sensitive reference for all the properties, methods and events. The Control
Browser is displayed by double-clicking a control in the forms workspace. When a property, method
or event is selected in the Browser the documentation on its use and syntax is shown. The Copy-To-
Clipboard button copies the function template to the clipboard.
Figure 27.6. Control Browser.

Project Window.

The Project window is a fundamental part of the development environment. In it all the project’s
forms are represented as a tree structure. This tree includes placeholders for the source code file’s
name and for a username and password in case the form’s code should be password-protected.

27.5 Tutorial: An application developed with


OpenDCL.
To show how to create and use an OpenDCL dialog linked to a Visual LISP application, we will
discuss the application we have named XREF Explorer. This application displays the Layers in a
hierarchical tree view related to the drawing they belong to, whether it is the main drawing or an
attached external reference. All the drawings, both the active one and its attached external references
appear as branches dependent on a root node identified as All. Next to the each drawing’s name an
icon is displayed to distinguish between the current drawing and the attached external references.
This icon changes color to indicate whether the corresponding node is selected or not. From the
drawing nodes new branches originate representing their Layers. These branches are identified as
Layers by a different icon, that also changes color to indicate it the Layer it represents is selected.

The application can be used to perform some Layer operations such as turning them on and off,
isolating selected Layers by turning off the others and changing the Layer colors. More operations
were not included to keep the code as simple as possible, but by creating more buttons and their
callback actions the functionality of AutoCAD’s native Layer command can be reproduced.
For this project a non-modal Palette form will be used. This form can be docked to one side of the
AutoCAD window and can also be shown or hidden automatically according to the cursor location.
Being a non-modal form is will be necessary:

To load the AutoLISP code for each open drawing, which could be done with an expression
included in the acaddoc.lsp file.
To foresee possible environment changes such as opening new files or changing the focus
between different open drawings in order to update the Drawings and Layers Tree view.
To consider the possibility that all drawings are closed, entering a no-document state. In this
case, the form should be closed.

In addition to this, resizing will be enabled showing how to keep a consistent arrangement of the
controls in the resized form.

Creating the OpenDCL Project.


The first thing to do will be to create a new OpenDCL project, which we call XLayer. The best
option is to save it to a folder with that same name which we include in our Visual Lisp Projects
folder or another one in AutoCAD’s support files search paths. An OpenDCL application consists of
AutoLISP code and OpenDCL project data. The OpenDCL project is created and edited in
OpenDCL Studio and may be stored in an ODCL file or embedded into the AutoLISP source code
file. This second option will be discussed later on.
Figure 27.7. XREF Explorer palette in use.

OpenDCL follows the event-driven programming paradigm, so what happens in the user interface
triggers the action specified by that particular event's callback function. A typical OpenDCL
application includes in an associated LSP file:

The code required to load and display a form.


The event handlers, i.e., functions that react to events such as selecting a button or list item.

To simplify the project's structure a LSP file should be created for each form, with the code
organized in such a way that the main function is placed first, followed by the auxiliary functions and
finally the event callback functions.

To create the new project, select New from the File menu. An Untitled project will be created, which
we will save as XLayer.odcl to the folder with that same name.

At the same time, using the Visual LISP Editor we will create a new file which will be saved with the
same name to the same folder. This nomenclature is not essential, but it can avoid confusion.

Adding a non-modal form.


A Non-Modal form can remain open throughout the AutoCAD session, which may mean that in the
course of time, different drawings may be opened and closed. As each drawing has its own
namespace, even if the form remains open, the event handlers would no longer be available in a new
drawing. Somehow it should be ensured that the AutoLISP code is again loaded for each document.
This is the same situation we found when studying reactors in Chapter 21. We have already indicated
how this difficulty can be solved resorting to the acaddoc.lsp file.

Another problem relates to the necessity of updating the form’s content according to the new
environment found when the active drawing changes. Some information must be updated only when
initializing the form but not when opening a new drawing. This would present no difficulties as this
task can be assigned to the Initialize event’s callback function. In other cases, as in the sample
application we will develop, the current drawing’s information must be reloaded each time we
switch between open drawings. Or at any given time all the drawings may have been closed while
AutoCAD is still running. This would require defining the callback actions for theDocActivated
and EnteringNoDocState events.

Modeless Dialogs are normally activated in the AutoCAD application context and not in the document
context. In that case the AutoLISP code running in the application context would have difficulties
when invoking document context dependent functions. Demonstrating a solution for this is one of this
chapter’s goals.

We will begin our project by adding a Palette type modeless form (Project>Add Palette). If we
select the new form we will see in the Properties tab (Properties/Events Pane) that its (Name)
property reads Form1. Using a more explicit nomenclature is advisable in order to avoid confusion,
especially in large applications that include several different forms. Controls are usually named using
a three-letter prefix that identifies the type of control. In this case we shall use the name frmPal.

Below (Name) we find the (VarName) property. This row is now empty. When a form or control
instance is created, OpenDCL sets the value of an AutoLISP variable to a handle used to reference it
in the project’s functions. If the (VarName) property field is not empty, that name is used for the
variable. Otherwise a variable name is generated by OpenDCL using the pattern:
<project-name>_<form-name>_<control-name>

This aims to ensure a unique name for the variable, given that will be global in the drawing's context.
Using names generated by OpenDCL leaving (VarName) empty is the safest practice. This will also
avoid errors when a control is renamed. Let's now review the form's properties.

The user should be able to resize it. For this reason we mark the checkbox next to the Allow
Resizing property. The caption then changes to True.
We are not interested in changing the Background Color property, which by default appears as
-24 (transparent). This property supports AutoCAD color index and RGB colors, or Windows
default color constants like this one.
Dockable Sides specifies to which side of the AutoCAD window the palette may be docked.
Due to this form's nature in which the vertical dimension will probably be the largest one, we
choose the left and right sides (0 - Left & Right).
Event Invoke is left as 0 - Synchronous. A Synchronous function call forces the dialog to wait
until the event handler returns, while in Asynchronous mode the dialog box code continues
running and the event handler is not executed until AutoCAD is ready to process commands. Any
events that have return values are always called synchronously (otherwise the return value
would be lost). When event handlers need to use the AutoLISP command function the Event
Invoke property must be set to 1.
A number of properties (Height, MaxHeight, MaxWidth, MinHeight and MinWidth) refer to the
form's dimensions. As we have decided that it can be resized, we are only concerned with two
of these properties, MinWidth and MinHeight. They will prevent that some components will not
fit in the form. Both will be set to 170. All these dimensions should be interpreted as pixels.
Finally we will set the Title Bar Text property to "XRef Explorer" so it will be displayed
as the form's title.
Figure 27.8a. Form's Properties.
Figure 27.8b. Form's Events.

All the form properties have now been set. Now we must define in the Events tab those to which the
form must react. Three events will be selected here: Initialize, DocActivated and
EnteringNoDocState.

When each of these events is checked the template for the event handler function is shown under the
AutoLISP Function to Call On Event label. This template can be copied by clicking on the
Clipboard Copy button and pasted in the XLayer.lsp source code file.

Loading the Project and Displaying the Form.


Although we only have an empty form, things must be carefully checked as we move on. So with no
further delay, we'll load the project and display the dialog. Any application that uses OpenDCL
dialogs should begin by loading the OpenDCL runtime system. If the OpenDCL installation was
successful, it will be demand loaded by calling:
(command "_OPENDCL").

After running this command, we can use the Apropos tool to search for functions including
the dcl_ prefix. This way we can verify the number of new OpenDCL functions loaded in the
Visual LISP environment. These functions enable access to the methods and properties of
OpenDCL controls. These functions are defined as protected system symbols, which means
that they will also be colored blue when typed in the editor window, just like AutoLISP/Visual
LISP primitives.

The call to the OPENDCL command should be followed by a call to dcl_loadproject to load
the project and to dcl_form_show that displays the form. These functions will be grouped in the
C:XLAYER function (Listing 27.1) that will act as a new AutoCAD command.
(defun C:XLAYER (/)
(command "_OPENDCL")
(dcl_loadproject "./XLayer/XLayer.odcl" t)
(dcl_form_show XLayer_frmPal))

Listing 27.1. Function that implements the new XLAYER command.

Before the form is displayed the message included in the default Initialize event's callback
function will be shown. This is the event that must be used to set the form's content before it is
displayed. Once we click on this message's OK button the Palette form will be displayed, anchored to
the left side of AutoCAD's window. Dragging it we can verify the minimum resizing limits. If other
drawing is opened the DocActivated event's message is displayed. And on closing all the
drawings the EnteringNoDocState event's message will appear. Surely we'll be surprised at
how scarce the code is. This is a characteristic of event driven programming. Most of the code will
appear in the event callback functions.

27.6 Adding the necessary controls.


Now we'll add the controls that will complete the Palette's design. We will add a Tree control and
fi ve GraphicButton controls. These buttons will be assigned images and tooltips for their
identification. The Tree control will also include images to distinguish node types and their status.

The Tree Control.


In the Toolbox we select the Tree control, dragging on the form to place it. It will be drawn so it
fills the palette’s width leaving a space at the bottom where the buttons will be placed. Its default
(Name) property is TreeControl1, which we will change to trcLayers.

We must establish the properties that will ensure that the control’s relation to the Form is maintained
when it is resized in run time. The Control Property Wizard assists in setting the correct values.

Geometry.

This wizard is displayed by clicking in the Properties Pane the field to the right of the (Wizard)
property or by the control's contextual (right-click) menu. The control's position is defined in the
Geometry tab. This tab displays four drop-down buttons with the options shown in Table 27.2:

Table 27.2. Control Alignment Options.


Offset From Left Edge

Left Side Alignment Offset From Right Edge

Offset From Center of Dialog

Offset From Top Edge


Top Side Alignment
Offset From Bottom Edge

Offset From Left Edge


Right Side Alignment
Offset From Right Edge

Offset From Top Edge


Top Side Alignment
Offset From Bottom Edge

For those with experience in resizing forms in languages such as Visual Basic the way controls are
aligned in OpenDCL is innovative. To set a Visual Basic control's position we only have the
properties defining its size, Height and Width, and defining its position, Left and Top. When a
form`s size is changed the Resize event is triggered and according to the new Height and Width
values, the new Left, Top, Height and Width values for all its controls must be recalculated.

Table 27.3. Properties that set the dimensional relationships.


Property: Description:

Bottom From Bottom Distance in pixels from the control's bottom edge of the lower edge of its parent window.

Left From Right Distance in pixels from the control's left edge to the right edge of its parent window.

Right From Right Distance in pixels from the control's right edge to the right edge of its parent window.

Top From Bottom Distance in pixels from the control's top edge to the bottom edge of its parent window.

OpenDCL avoids the need for programming all these calculations adding to controls the properties
described in Table 27.3.
When resizing the form only those logical properties described in Table 27.4 that are enabled (i.e.,
whose value is 1) are taken into account.

Table 27.4. Logical properties that define resizing.


Property: Description:

Use Bottom From The specified distance between the control's bottom edge and the parent window's bottom edge is
Bottom maintained.

Use Left From Right The specified distance between the control's left edge and the parent window's right edge is maintained.

The specified distance between the control's right edge and the parent window's right edge is
Use Right From Right
maintained.

The specified distance between the control's top edge and the parent window's bottom edge is
Use Top From Bottom
maintained.

The wizard provides a graphical interface for setting the logical properties described in Table 27.4.
The relationships between the selected control (shown in the wizard as an OK button) and its
container (represented by a form titled Example) are shown as dimension lines. For our
trcLayers control we will set the following options:

For Left Side Alignment we select [Offset from Left Edge]. The effect of this choice is that
when resizing, the Left property is always 0 pixels, as established at design time. The Use Left
From Right property is turned off, adopting 0 as its value.
For Top Side Alignment we select [Offset from Top Edge]. The effect of this choice is that
when resizing, the Top property will maintain the value of 0 pixels set in design time. The Use
Top From Bottom property is disabled.
For Right Side Alignment we select [Offset From Right Edge]. With this setting, the value of
the Right From Right variable, that when designing the form was set to 0 pixels, will be kept
when the form is resized. The Use Right From Right property is enabled, adopting 1 as its
value.
For Bottom Side Alignment we select [Offset From Bottom Edge]. This way, when resizing
the form, the distance between the control's bottom edge and the parent window's bottom edge
will be kept constant with the value defined for Bottom From Bottom, in this case 40 pixels.
The Use Bottom From Bottom property is enabled taking the value 1. Having established the
geometrical properties it would be advisable to save the project and run the main function
C:XLAYER to verify that the Tree control is resized correctly when resizing the form.
Figure 27.9. Geometry tab for the trcLayers control Properties Wizard.

Image List.

The other Control Property Wizard tabs are used to set the font type and size (Font tab), set the
images to be used (Image List tab) and define the control's help and warning messages (ToolTips
tab). As regards the font type and size no changes will be made. Where we will have some work to
do is in adding images to the Image List and setting up a Tooltip.

To identify the Tree control’s nodes we will use 14 x 14 pixel images. We can create them in an
image editing program or extract them from AutoCAD or other programs .exe or .dll resource files
using some of the programs available online. Or they could start as screen captures to be later
processed in any image editor. Numbers that will be used to identify them are assigned according to
the order in which the images are added to the list. Figure 27.10 shows the images used for this
tutorial, with the numbers assigned by the Wizard.

Tool Tips.

The Wizard's ToolTips tab can be used to define messages that are displayed when the pointer hovers
over the control. In this case we want to show a small box with the "Select layer" text. We want the
text in bold font-weight, so we type it in the Title edit box. The simple box style we want for the
tooltip is achieved by deselecting the Balloon style tooltip checkbox. To see how the message will
be displayed we can click the Preview button.
Figure 27.10. Images used in the Tree control.

Figure 27.11. ToolTips tab for the Tree control.

Tree Control's Events.


The trcLayers control must listen to the SelChanged event. This event is raised when a new
Tree node is selected. When this event is selected in the Properties/Events Pane, its callback
function template is shown (Figure 27.12). As we can see in the template, this function takes two
arguments, Label and Key.

Figure 27.12. Template for the Tree control's SelChanged event.

Label is the text caption for the selected item. Key is the item's identifier. The Key argument is a
string if it was specified when creating the control, otherwise it is a unique identifier (handle) that
is automatically assigned. We will click on the Clipboard Copy button and paste the template in the
project's AutoLISP code file. For the time being we are finished with the Tree control, so we'll go
on to create the buttons that will launch the application's commands.

Picture Folder.
From the two types of buttons available in OpenDCL we choose the GraphicButton, to which we
can assign icons and tooltips indicating their purpose. The Palette will include five graphic buttons
that will be used for:

Turning Layers off and on.


Change the Layer's color.
Isolating the selected Layers, turning the rest off.
Undoing changes.
Updating the Tree control in case attached external references change.

Since we will use images, we must use an object similar to the Tree control's Image List. That
object is used for that control exclusively. OpenDCL can create a Picture Folder object that can be
used by other of the form's controls.

The Project>View/Edit Picture Folder menu option displays the Picture Folder dialog box. It can
also be displayed by clicking on the Toolbar button with the camera icon. The images used in graphic
buttons are stored in the Picture Folder. Those we will be using are displayed in Figure 27.13. The
images are duplicated with slight hue and lightness differences that will highlight the button when the
pointer hovers over it. These differences will also reflect the selected Layers state. For example, the
Layers On/Off button will display a different image depending on this condition in the selected Layers
group.
Figure 27.13. Graphic Button Images.

The order in which they are added is important, since their PictureID numbers that will identify them
will depend on this order.

Graphic Buttons.
The way in which the Graphic Buttons are created is the same for all five. We will start with the
rightmost button, the one that will be used to update the Tree should any external reference be
attached or detached. To create this button a Graphic Button control is selected in the Toolbox
(Figure 27.14).

Figure 27.14. GraphicButton control selection.

Once the control is selected, it is placed by clicking in the free space under the Tree control and
dragging to define a square with the desired size. This done, we change its (Name) property to
btn_UPDATE in the Properties Pane.
Geometry.

We must check that its Height and Width properties are both 32 pixels. As we want this button to
be located in the form's lower right corner and vertically centered in the free 40 pixels margin we
have at the bottom of the form, we will set the following properties:

Bottom From Bottom: 4


Left From Right: 32
Right From Right: 0
Top From Bottom: 36

Now we will resort to the Control Property Wizard to set the way this button will behave when the
form is resized. We want that the distance from the button to the form's right and bottom edges is kept
constant. Figure 27.15 shows the settings used to achieve this.

We will see in the Properties Pane that the properties Use Bottom From Bottom, Use
Left From Right, Use Right From Right and Use Top From Bottom have all been
set (value = 1).

Figure 27.15. Adjusting the geometry for the Graphic Button

Button Styles.

The Button Styles tab is where we define the properties related to the button's appearance. This tab
allows choosing between buttons with a raised border, a flat appearance, an XP visual style or no
border at all. As an alternative a series of predefined buttons like those used in AutoCAD can be
selected. We can also choose here the image to be displayed on the button among those we have
added to the Picture Folder. The image to be used when the mouse hovers over the button cannot be
defined in this wizard. It must be defined in the Properties Pane as the Mouse-Over Picture
property value.

Figure 27.16. Button Styles tab.

Figure 27.17. ToolTips tab.

ToolTips.

The button can display a message when the cursor hovers over it. We already saw this possibility
when defining the Tree control's properties. We will now explore other style options for these
messages. Selecting the Balloon style tooltip checkbox will display a cartoon-style "balloon" with a
stem pointing to the button. An image may be added; in this case it will be the same one that identifies
the button. The ToolTip's text can be multiline if it is entered in the Main Text edit box. It is also
possible to apply format to the text (bold, italics, underlines and color).

Graphic button events.

The Graphic Buttons must listen to the Clicked event. This is the event generated by pressing the
left mouse button when the pointer is over the object. By selecting it in the Properties/Events Pane's
Events tab a template for the callback function is displayed. This template must be copied to the
AutoLISP code file.

Figure 27.18. Template for the btnUPDATE button's Clicked event.

Creating the other Graphic buttons.

The other buttons are created in the same way. When creating each, its Clicked event’s callback
function template must be copied to the AutoLISP code file. The properties that must be set for these
buttons are specified in the Table 27.5.

Table 27.5. Properties of Graphic buttons.


btnUPDATE btnUNDO btnISOLATE btnCOLOR btnACT
Picture 106 105 104 103 100
MouseOver Picture 113 112 111 110 107
ToolTip Update Layers Undo Changes Isolate Layers ByLayer color Layer On/Off
Bottom From 4 4 4 4 4
Bottom
Left From Right 32 64 96 128 160
Right From Right 0 32 64 96 128
Top From Bottom 36 36 36 36 36

Callback Functions.
Global variables.

In the event callback functions we frequently reference the application and the document objects. We
are counting on the file acaddoc.lsp to define the global symbols *aevl:acad* for the
application object and *aevl:drawing* for the document object. Since this is a modeless form,
these variables must be available in each drawing's context. As the condition of protected system
symbols has been assigned to these variables, they should not be redefined them in the program's
code.

Initialize Event.

The function template generated by OpenDCL Studio (see Listing 27.2) only includes an expression
that displays a message warning that the code for our function is still pending.
(defun c:XLayer_frmPal_OnInitialize (/)
(dcl_MessageBox
"To Do: code must be added to event handler\r\nc:XLayer_frmPal_OnInitialize"
"To do"))

Listing 27.2. Template function created by OpenDCL Studio.

Populating the tree.


T he Initialize event is triggered when the form starts loading. It is here where all the
expressions used to fill the drawing and Layers Tree structure will be evaluated. Achieving this will
require:

1. Retrieving a list of attached drawings, if any.


2. Retrieving a list of the existing Layers.
3. Creating from the lists of drawing and Layer names a hierarchical structure represented by a list
of sublists. This list will contain a sublist for each drawing, headed by the drawing's name and
followed by the names of that drawing's Layers.
4. Using this data structure the Tree control is filled with the corresponding nodes and branches,
assigning them the images that represent the selected and unselected states.
5. After filling the Tree control the character "*" that acts as a wildcard is assigned to the
global variable *XLayer*. This will indicate an initial selection of all the drawing's Layers.
6. This value is passed as an argument to the draw-buttons function, which sets the run-time
image displayed in the Layers On/Off Graphic button.

Retrieving the list of drawings attached as XRefs.

To populate the Tree we begin by obtaining a list of all the names of the drawings attached as
external references (dwg-list function, Listing 27.3). These drawings appear as objects in the
drawing's BLOCK collection that is traversed using the vlax-for function. To differentiate
between normal blocks and external references, the block's IsXref property is checked. The dwg-
list function returns a list of all the external references. This list is sorted alphabetically and the
current drawing's name (retrieved from the DWGNAME system variable) is added as its first term.
(defun dwg-list (/ blocks-coll xref-lst)
(setq blocks-coll (vla-get-Blocks *aevl:drawing*))
(vla-get-Blocks
*aevl:drawing*))
(vlax-for block blocks-coll
(if (equal (vla-get-IsXref block)
:vlax-true)
(setq xref-lst
(cons
(vla-get-name block)
xref-lst))))
(setq xref-lst (acad_strlsort xref-lst)
xref-lst (cons (getvar "DWGNAME")
xref-lst)))

Listing 27.3. Function that retrieves the list of attached drawings.

Retrieving the Layers list.

To obtain the drawing Layer list the Layers collection is traversed to extract the Name property for
each of the objects in the collection. The process is similar to the one used to iterate through the
blocks collection, sorting the list alphabetically. When it comes to XRef-dependent Layers, their
names are composed by the drawing's name and the Layer's name, separated by the pipe "|"
character. The Layer name extraction is performed by the layer-list function (Listing 27.4).
(defun layer-list (/ layer-coll lay-lst)
(setq layer-coll
(vla-get-Layers *aevl:drawing*))
(vlax-for lyr layer-coll
(setq
lay-lst (cons (vla-get-Name lyr)
lay-lst)))
(acad_strlsort lay-lst))

Listing 27.4. Function that retrieves the list of Layers.

Associating the Drawing and Layer names.

To create the drawings/Layers association list we use the dwg-layers function. This function takes
as arguments the drawings list returned by dwg-list and the list of Layers returned by layer-
list. It operates as follows:

1. For each drawing in dwg-list:


a. For each Layer in layer-list:
i. If the Layer name includes the pipe character "|", it is an External Reference:
ii. If it belongs to the drawing being processed, it is added to tmp0.
iii. If the pipe character "|" is not present it does not belong to an external reference so it
is added to the list tmp1.
b. When the end of the Layers list is reached, the list tmp0 if not nil, is added to the list
res0. If the list tmp1 is not nil, it is added to the list res1. Before adding the lists, the
name of the drawing they belong to is included as the list's first term.
2. Before moving on to next drawing the variables tmp0 and tmp1 are reset to nil.

Finally we will have two lists, res1 and res0, the first including the drawing's Layers and the
second one with the Layers that depend on external references. These two lists are joined using the
append function. This way we ensure that the first item in the tree corresponds to the current
drawing, followed by the XRefs.
(defun dwg-layers (drawings layers / current
pos tmp0 tmp1 res0 res1)
(setq current (getvar “dwgname”))
(foreach drawing drawings
(foreach lyr layers
(cond
((setq pos
(vl-string-search “|” lyr))
(if
(wcmatch lyr
(strcat drawing “|*”))
(setq tmp0
(cons
(substr lyr
(+ pos 2))
tmp0))))
(t
(if (= drawing current)
(setq tmp1 (cons lyr tmp1))))))
(cond (tmp1
(setq res1
(cons
(cons current
(acad_strlsort tmp1))
res1)))
(tmp0
(setq res0
(cons
(cons drawing
(acad_strlsort tmp0))
res0))))
(setq tmp0 nil
tmp1 nil))
(append res1 (reverse res0)))

Listing 27.5. Function that creates the drawings and Layers association list.

Creating the Tree Control's nodes.

The list returned by dwg-layers is passed to the make-nodes function (Listing 27.6). This is the
function that populates the Tree control. To populate it make-nodes must use LISP functions that
are specific to the OpenDCL API. This is an example of how Visual LISP functions can be enriched
by those added by other applications.
(defun make-nodes (tree / res_root res_xref
id-img sel-img res_layer)
(setq
res_root (dcl_tree_addparent
XLayer_frmPal_trcLayers
“All”))
(dcl_tree_setitemimages
XLayer_frmPal_trcLayers
res_root
0
1)
(foreach refx tree
(setq res_xref
(dcl_tree_addchild
XLayer_frmPal_trcLayers
res_root
(car refx)))
(if (= (getvar “dwgname”) (car refx))
(setq id-img 2
sel-img 3)
(setq id-img 4
sel-img 5))
(dcl_tree_setitemimages
XLayer_frmPal_trcLayers
res_xref
id-img
sel-img)
(foreach lyr (cdr refx)
(setq res_layer
(dcl_tree_addchild
XLayer_frmPal_trcLayers
res_xref
lyr))
(dcl_Tree_SetItemImages
XLayer_frmPal_trcLayers
res_layer
6
7)
(dcl_tree_expanditem
XLayer_frmPal_trcLayers
res_root
1)
(dcl_tree_selectitem
XLayer_frmPal_trcLayers
res_root))))

Listing 27.6. Function that creates the Tree's nodes.

The process is as follows:

1. The Tree's root node is created by the dcl_Tree_AddParent function. This function takes
as its first argument the name identifying the control that OpenDCL created automatically. In this
case this identifier is XLayer_frmPal_trcLayers. The other argument is that node's label
as it will appear in the Tree control, in this case the word "All". When the root node is
created, the function returns its handle which is assigned to the res_root variable.
2. After creating the root node we assign the images by which it will be identified. They will be
two, because we want one to represent its unselected state and the other one for the selected
state. This is done using the dcl_Tree_SetItemImages function. This function receives as
arguments the Tree control's handle, the value returned by the function that creates the node and
the ImageList index numbers.
3. Now we proceed to create the child nodes. This is done calling repeatedly the
dcl_Tree_AddChild function, which receives in addition to the arguments that
dcl_Tree_AddParent received, the root node's handle which was assigned to res_root.
The nodes for drawings are created in an initial foreach loop that traverses the list creating a
root node's child for each sublist in the list it receives as the tree argument. Within this
foreach a second foreach loop is nested that traverses the cdr of the list assigned to the
variable xref. This second foreach is the one which creates the Layer nodes by passing to
dcl_Tree_AddChild the handle returned when the drawing node was created assigned to
the variable res_xref. The creation of both the drawing and Layer nodes will be followed by
the assignment of the images for the node's two states, selected and unselected.
4. Once this double foreach concludes there only remains to define how the Tree will be
displayed when starting the application. We have opted for expanding the root node to display
the drawing nodes. This is set using dcl_Tree_ExpandItem. Finally we will preselect the
root node using the dcl_Tree_SelectItem function. This will be noticed by its icon color
and by its highlighted label.

Setting the Graphic Buttons icons.

The draw-buttons function (Listing 27.7) affects the Graphic Button used for turning the
selected Layers On and Off. This button changes its image depending on the Layers in the selected set
being all on, all off or some on and some off. To do this, three functions will be used, with the
intention of simplifying the program to make it more understandable.

The process followed in draw-buttons is as follows:

1. The list of Layers returned by layer-list is traversed, checking for each Layer name, if it
matches the pattern received. In the case of the Initialize event's callback function, all the
Layers will match the wildcard pattern "*" received, but draw-buttons will be called
every time a new selection is made, so the pattern used will vary.
2. In case the Layer name matches the pattern, it checks the value associated with its entity list's
group code 62. If its value is negative, it means that the Layer is Off. In that case nil is added
to the active-lst list and if positive T is added.

Once the foreach concludes, active-lst is passed as argument to the expression:


(btn-act (check-state active-lst)). This expression relies on two new nested
auxiliary functions: check-state and btn-act.
(defun draw-buttons
(sel-layer / active active-lst)
(foreach lyr (layer-list)
(if (wcmatch lyr sel-layer)
(progn (setq active
(cdr
(assoc
62
(tblsearch “LAYER”
lyr))))
(if (minusp active)
(setq active-lst
(cons nil
active-lst))
(setq active-lst
(cons t
active-lst))))))
(btn-act (check-state active-lst)))

Listing 27.7. Auxiliary function DRAW-BUTTONS.

CHECK-STATE function.

T he check-state function (Listing 27.8) examines the list received from draw-buttons
analyzing it to check:

1. If all the list elements are T, in which case it returns 1.


2. If any item in the list is T, in which case it returns 0.
3. If none of the above is true, it returns -1.
(defun check-state (lst / result)
(cond ((apply 'and lst) (setq result 1))
((apply 'or lst) (setq result 0))
(t (setq result -1)))
result)

Listing 27.8. CHECK-STATE function.

BTN-ACT function.

This function assigns an image to the On/Off button (Listing 27.9). According to the value it receives
as the status argument, it will call the dcl_Control_SetPicture function assigning to the
button identified as XLayer_frmPal_btnACT the Picture Folder images 100, 101 or 102 to
i ts Picture property and the dcl_Control_SetMouseOverPicture function to assign
images 107, 108 and 109 to its MouseOverPicture property.
(defun btn-act (state)
(cond
((= state 1)
(dcl_Control_SetPicture
XLayer_frmPal_btnACT
100)
(dcl_Control_SetMouseOverPicture
XLayer_frmPal_btnACT
107))
((= state 0)
(dcl_Control_SetPicture
XLayer_frmPal_btnACT
101)
(dcl_Control_SetMouseOverPicture
XLayer_frmPal_btnACT
108))
(t
(dcl_Control_SetPicture
XLayer_frmPal_btnACT
102)
(dcl_Control_SetMouseOverPicture
XLayer_frmPal_btnACT
109))))

Listing 27.9. Function to change the btnACT button's image.

Saving Layer States.


It is desirable to have the possibility of undoing the changes in order to recover previous Layer
settings. Using the _UNDO command is not desirable as it would affect no only those changes to
layer properties set by our form, but would indiscriminately undo anything done in the drawing. A
way to limit undoing to those changes made with our application is to establish a new Layer State
with each change. These Layer States can have names that distinguish them from any other user-
defined Layer State. The save-state function (Listing 27.10) saves the current Layer state with
the name "#_XLayer" where # is a number that will be incremented for each new change done
with the Palette. A list of the existing Layer state names is retrieved by the layerstate-
getnames function. From this list those names that do not match the "*_XLayer" pattern are
removed by vl-remove-if-not and the final list is sorted with vl-sort using the greater than
(>) comparison operator. The resulting list will have the higher numbered named as its first term. In
case no matching name is found nil will be returned.

From the list’s first term the numerical value is obtained using atoi and assigned to the variable
num. In case the list is empty 1 is assigned to num. This is the number that will be used for the new
Layer State that will be created by the expression:
(layerstate-save
(strcat (itoa num) "_XLayer") (+ 1 32) nil)

The expression (+ 1 32) is a bitcoded mask that defines the properties to be restored: 1 for saving
the Layers’ On or Off value and 32 for saving the Layers’ color setting. The last argument nil
indicates that VPLAYER settings are not to be saved.
(defun save-state (/ num)
(if (setq num
(car
(vl-sort
(vl-remove-if-not
‘(lambda (l)
(wcmatch l
“*_XLayer”))
(layerstate-getnames))
‘>)))
(setq num (1+ (atoi num)))
(setq num 1))
(layerstate-save
(strcat (itoa num) “_XLayer”)
(+ 1 32)
nil))

Listing 27.10. Function that saves the Layer State.

Restoring Layer States.

Undoing Layer settings changes will be the purpose of the btnUNDO button. This will be done by
calling the prev-state function (Listing 27.11). This function retrieves the sorted Layer State
names created by the application as was explained for the save-state function and assigns it to
the variable states. As the current Layer State will be the highest numbered one, its name will be
retrieved from the first term of the states list and assigned to the variable current. The second
term of the list will be assigned to the variable previous. If there is a previous Layer State it
will be restored with a call to layerstate-restore and the current Layer State will be deleted
with a call to layerstate-delete. The Layer State when the Form is initialized will be saved
in order to have an initial state to which return.

As some of the layer modifications will require a REGEN in order to be displayed, this function
calls vla-Regen. If the current Layout is ModelSpace it is enough with a simple REGEN done by
supplying the acActiveViewport argument. In case it is one of the Paperspace Layouts a
REGENALL will be forced supplying the acAllViewports argument.
(defun prev-state
(/ states current previous)
(setq states
(vl-sort
(vl-remove-if-not
‘(lambda (l)
(wcmatch l “*_XLayer”))
(layerstate-getnames))
‘>))
(if states
(progn
(setq current (car states)
previous (cadr states))
(if previous
(progn
(layerstate-restore previous)
(layerstate-delete current)
(if
(= (getvar “CTAB”) “Model”)
(vla-Regen *aevl:drawing*
acActiveViewport)
(vla-Regen *aevl:drawing*
acAllViewports)))
(prompt
“\nNo previous state saved.”)))))

Listing 27.11. Function that restores a previous Layer State.

Initializing Layer States.

In order to the Layer States restoring functionality to operate the XLayer Layer States must be
initialized on starting the application. This is done by the init-states function (Listing 27.12)
that will be called from the Form's Initialize event's callback function. The init-states
function looks for Layer States with names matching "*_XLayer" and if any is found it is deleted.
Then the current state is saved with the name "0_XLayer".
(defun init-states (/)
(if (setq states
(vl-remove-if-not
‘(lambda (l)
(wcmatch l “*_XLayer”))
(layerstate-getnames)))
(mapcar ‘layerstate-delete states))
(layerstate-save
“0_XLayer”
(+ 1 32)
nil))

Listing 27.12. Layer states initializing function.

Incorporating these functions to the Initialize event.

The Tree is populated during the form's Initialize event, but it must also be done on changing
the active document, within the DocActivated event's callback function. If the attached external
references are changed it will also be necessary to update its contents. This could be managed using a
reactor, but to simplify this example we will just have a button to do this. For this reason, it is more
practical to group this sequence of functions in a single one that that can be invoked whenever
updating the tree's content is necessary. This will be done in the start-view function (Listing
27.13).
(defun start-view (/ tree)
(setq tree (dwg-layers
(dwg-list)
(layer-list)))
(make-nodes tree)
(setq *XLayer* “*”)
(draw-buttons *XLayer*))

Listing 27.13. Function that populates the tree view.


Now we can simply replace, in the form’s Initialize event callback function template, the code
generated by the Editor for a call to the start-view function. We will also include the code to
save the current Layer State with the name "0_XLayer". If one with this name already exists, it
will be deleted and a new one saved.
(defun c:XLayer_frmPal_OnInitialize ()
(init-states)
(start-view))

Listing 27.14. Final code for the form's Initialize event.

SelChanged event.

This Palette's most important event is the one raised when the user selects a different Tree control's
node. This event's callback function must prepare, according to the information received from the
Tree control, a Layer name search pattern that identifies all the Layers affected by this selection.

This event’s callback function (Listing 27.15) will receive two arguments, the selected node’s
Label and the Handle that identifies the node. To find out which kind of node it is: Layer,
Drawing or All, we will retrieve the selected node’s ascendants. The procedure is:

1. The text of the selected node's Label is included in the list s-list.
2. A while loop is entered using as test condition that the previously selected node's parent is
returned. The loop will end on arriving to the root node.
3. The selected node's Handle (Key argument) is used to retrieve its parent using
dcl_Tree_GetParentItem. The Handle returned for this parent node is assigned to the
variable Key.
4. The node's Label text is extracted using dcl_Tree_GetItemLabel and added to the list s-
list.
5. Upon the loop's completion, which can include at most three iterations in the event that a Layer
node had been selected, a cond expression is evaluated using the length of the list s-list
as condition.
a. If s-list has only one term, it means that the root node has been selected. The pattern
assigned to *XLayer* will then be the wildcard "*".
b. If s-list has two terms and the second term is equal to the drawing's name, the pattern
will then be "~*|*", which selects all the Layers that do not include the pipe "|"
character in its name. The character "~" acts as the logical NOT.
c. In case it is not the name of the current drawing, then the pattern is composed by
concatenating the name of the drawing with the string "|*" to obtain a pattern of the kind
"drawing-name|*".
d. If s-list has three terms and the second term is the current drawing's name, the third term
(which is the Layer's name) shall be assigned *XLayer*.
e. If none of the above is true the default clause is evaluated, concatenating the second term in
the list, the character "|" and the list's third term, creating a pattern with the structure
"drawing-name|layer-name".
6. Like other functions, it concludes with a call to draw-buttons that updates the On/Off
button's image.
(defun c:XLayer_frmPal_trcLayers_OnSelChanged
(Label Key / s-list)
(setq s-list (cons Label s-list))
(while (setq Key (dcl_Tree_GetParentItem
XLayer_frmPal_trcLayers
Key))
(setq s-list
(cons
(dcl_Tree_GetItemLabel
XLayer_frmPal_trcLayers
Key)
s-list)))
(cond ((= (length s-list) 1)
(setq *XLayer* “*”))
((and (= (length s-list) 2)
(= (nth 1 s-list)
(getvar “DWGNAME”)))
(setq *XLayer* “~*|*”))
((= (length s-list) 2)
(setq
*XLayer* (strcat (nth 1 s-list)
“|*”)))
((and (= (length s-list) 3)
(= (nth 1 s-list)
(getvar “DWGNAME”)))
(setq *XLayer* (nth 2 s-list)))
(t
(setq
*XLayer* (strcat (nth 1 s-list)
“|”
(nth 2 s-list)))))
(draw-buttons *XLayer*))

Listing 27.15. Tree control SelChanged event's callback function.

That completes the implementation of the Tree control, including the reaction to the SelChanged
event the application listens to. Although the buttons are not yet operational, we can now verify the
Palette's operation displaying the populated Tree view.

Other Form events.


Before considering the events raised by the Graphic Buttons, we will define the callback functions for
the events raised by the Form itself. These are the events that indicate that context has changed to
another document (DocActivated) and indicating that no drawings are open
(EnteringNoDocState).
DocActivated event callback function.

When a new document is made current the situation is similar to that when the form is initialized. The
only difference here is that the tree is already populated. For this reason before calling the start-
view function, the dcl_Tree_Clear function must be called to delete the existing tree before
creating the one that reflects the new context (Listing 27.16).
(defun c:XLayer_frmPal_OnDocActivated (/)
(dcl_Tree_Clear XLayer_frmPal_trcLayers)
(start-view))

Listing 27.16. DocActivated event's callback function.

EnteringNoDocState event's callback function.

In the case that no open documents remain in AutoCAD, the simplest solution is to close the form.
This is achieved with the form's CloseAll method. This method takes a numeric argument
indicating the type of forms close. The values for this argument are specified in Table 27.6.

Table 27.6. Dialog type argument values.


Value: Description:
0 Closes all types of dialog boxes.
1 Closes all modal dialogs.
2 Closes all modeless dialogs.
4 Close all the Control Bar type dialogs .
8 Closes all the Options tab type dialogs.
16 Closes all the TabStrip control pages.
32 Closes all the File Explorer type dialogs.

64 Closes all the Palette type dialogs.

The code for the EnteringNoDocState event's callback function is shown in Listing 27.17.
(defun c:XLayer_frmPal_OnEnteringNoDocState (/)
(dcl_Form_CloseAll 64))

Listing 27.17. Code for the EnteringNoDocState event.

Button Actions.
All buttons raise the Clicked event. All their Clicked event handler functions follow the same
general procedure: they traverse the Layers executing a certain action on those matching the pattern
string returned by the Tree control's SelChanged event handler.
btn_ACT (Layer On/Off) Button.

The c:XLayer_frmPal_btnACT_OnClicked function (Listing 27.18) is the Clicked event


handler for the btn_ACT button. This function's template as the other button event handler templates
were generated in the Events Pane by checking the Clicked event and copied to the VLISP Editor
window containing our source code file.
(defun c:XLayer_frmPal_btnACT_OnClicked ()
(if (= (dcl_Control_GetPicture
XLayer_frmPal_btnACT)
100)
(progn
(activate-layer
*XLayer*
(layer-list)
:vlax-false)
(dcl_Control_SetPicture
XLayer_frmPal_btnACT
102)
(dcl_Control_SetMouseOverPicture
XLayer_frmPal_btnACT
109))
(progn
(activate-layer
*XLayer*
(layer-list)
:vlax-true)
(dcl_Control_SetPicture
XLayer_frmPal_btnACT
100)
(dcl_Control_SetMouseOverPicture
XLayer_frmPal_btnACT
107)))
(vla-update *aevl:acad*)
(save-state))

Listing 27.18. Event handler for the btnACT button's Clicked event.

This is an event handler that not only performs an action on the drawing's objects, in this case its
Layers, turning them On or Off, but that also acts on the control itself, changing the image it displays.
At the same time, the information about which is the button's current image is one of the criteria the
function uses to decide what action to take. The button's Clicked event handler's action can be
described as follows:

1. An UNDO start mark is set.


2. The currently displayed image's identifier is retrieved. If the displayed image is the one that
indicates a selected set of Layers all of which are On (in our case the Picture Folder image
100), the activate-layer function is called with the keyword :vlax-false as its last
argument. This will change the LayerOn property for all the Layers with names matching the
pattern assigned to *XLayer* to false, turning them off. To reflect this new state of the selected
Layers, the button's image is replaced by the one identified by the number 102, meant to indicate
that all selected Layers are Off.
3. If the button's image does not indicate that all the selected Layers are On, the Layers matching the
pattern are turned on. The same activate-layer function, is used but this time with the
keyword :vlax-true. The new state of the selected Layers is indicated on the button by
assigning it the image 100, meaning that all the selected Layers are on.
4. For these modifications to be displayed the expression (vla-update *aevl:acad*) that
updates the AutoCAD viewport must be evaluated.
5. An UNDO End mark is set.
6. Being a modeless dialog, these modifications are effective immediately, without closing the
dialog. This type of forms usually will not display an OK button.

ACTIVATE-LAYER auxiliary function.

The activate-layer function (Listing 27.19) receives three arguments: pattern, which is the
string returned from the Tree control's SelChanged event handler; lst which is the Layers list
returned by layer-list and state which is the keyword that indicates if the Layers must be
turned On (:vlax-true) or Off (:vlax-false).

Its action is limited to traversing with foreach the Layer names list to set their LayerOn property
a s :vlax-true or :vlax-false depending on their names matching or not the pattern. The
Layer object is retrieved using the Layers collection Item method.
(defun activate-layer (pattern lst state / layer-coll)
(setq layer-coll (vla-get-Layers *aevl:drawing*))
(foreach lyr lst
(if (wcmatch lyr pattern)
(vla-put-LayerOn (vla-item layer-coll lyr) state))))

Listing 27.19. Function that sets Layers On or Off.

BtnCOLOR (Layer Color) Button.

This button is intended to change the color of the selected Layers.

T he c:XLayer_frmPal_btnCOLOR_OnClicked function (Listing 27.20) is its Clicked


event handler. To choose the desired color, it uses the acad_colordlg function which displays
the standard AutoCAD color dialog. This function works as follows:

1. The color selection dialog is displayed. The value selected in the dialog is assigned to the s-
col variable.
2. The drawing's Layers collection is traversed searching for those whose name matches the pattern
contained in the global variable *XLayer*. To do this the Layers collection retrieved with
vla-get-Layers is traversed using vlax-for.
3. If the Layer's name (contained in the VLA-object's Name property) matches the pattern, its
Color property is modified to the selected color number using vla-put-color.
4. Once the loop concludes, the AutoCAD viewports are regenerated with a call to vla-Regen.
5. A call to save-state saves the new Layer state.
(defun c:XLayer_frmPal_btnCOLOR_OnClicked ()
(if (setq s-col (acad_colordlg 256 t))
(progn
(setq layer-coll
(vla-get-layers
*aevl:drawing*))
(vlax-for lyr layer-coll
(if
(wcmatch (vla-get-name lyr)
*XLayer*)
(progn
(vla-put-color lyr s-col))))
(if (= (getvar “CTAB”) “Model”)
(vla-Regen *aevl:drawing*
acActiveViewport)
(vla-Regen *aevl:drawing*
acAllViewports))
(save-state))))

Listing 27.20. Event handler function for the btnCOLOR button's Clicked event.

btnISOLATE (Isolate Layers) Button.

The effects of selecting this button are partly similar to those of the _LAYISO command. All Layers
whose names do not match the pattern assigned to *Layers* will be turned off, leaving only the
selected ones visible. T he c:XLayer_frmPal_btnISOLATE_OnClicked function (Listing
27.21) is its Clicked event handler. This function works as follows:

1. The Layers collection is traversed.


2. If the Layer name matches the pattern, its LayerOn property is set to :vlax-true.
3. If it does not match, LayerOn is set as :vlax-false.
4. The Tree control's images are updated.
5. The AutoCAD window is updated to reflect the changes.
6. A call to save-state saves the new Layer state.
(defun c:XLayer_frmPal_btnISOLATE_OnClicked
(/)
(setq layer-coll
(vla-get-layers
*aevl:drawing*))
(foreach lyr (layer-list)
(if (wcmatch lyr *XLayer*)
(vla-put-layeron
(vla-item layer-coll lyr)
:vlax-true)
(vla-put-layeron
(vla-item layer-coll lyr)
:vlax-false)))
(draw-buttons *XLayer*)
(vla-update *aevl:acad*)
(save-state))

Listing 27.21. Event handler for the btnISOLATE button's Clicked event.

btnUNDO (Undo Changes) Button.

For each button action a new Layer state has been saved by the event handler functions described
above. This allows us to include a button that can be used to undo those modifications. If several
changes have been made, they will be undone one by one each time the Undo Changes button is
clicked. This is done by recovering the previous Layer state each time the button is clicked calling the
prev-state function defined in Listing 27.11.

Once a previous state has been recovered, the buttons are updated with a call to draw-buttons
using "*" as pattern.

The event handler is defined as the c:XLayer_frmPal_btnUNDO_OnClicked function


(Listing 27.22).
(defun c:XLayer_frmPal_btnUNDO_OnClicked (/)
(prev-state)
(draw-buttons "*"))

Listing 27.22. Event handler for the btnUNDO button's Clicked event.

btnUpdate (Update Layers) Button.

Being a modeless form, Layers and external references may change while the palette is displayed. In
many cases the list will be updated when operating on the palette. A reactor could also be
implemented to detect these changes. But as that is not this chapter's subject we will simply solve this
by adding a fifth button which can be used to update the Tree control. The event handler function of
this button's Clicked event merely reproduces the behavior that would take place when changing
the active document: deleting the existing tree and repopulating it to reflect the current situation
(Listing 27.23).
(defun c:XLayer_frmPal_btnUPDATE_OnClicked (/)
(dcl_Tree_Clear XLayer_Trv_Capas)
(start-view))

Listing 27.23. Event handler for the btnUPDATE button's Clicked event.

Packaging the form's code.


OpenDCL provides a handy way for packing the form in the application's AutoLISP source code file.
The procedure is very simple. It only requires saving the project file not as an ODCL file, but as LSP
file. The LSP file must have the same name as the OpenDCL project. The file created this way will
contain a list of strings where the entire OpenDCL project is encrypted. This code can be copied to
the AutoLISP code file. A few adjustments must be made to the application's code. The exported
string list must be passed as argument to the dcl_Project_Import function. I usually paste this
at the end of the file and create a function named import-project (Figure 27.19). Once this is
done, we must modify the main function, since there will be no need for importing the OpenDCL
project. Instead we will include a call to the import-project function (See Figure 27.20).

Figure 27.19. Project's encrypted code included in the import-project function.

Figure 27.20. Main function adapted to import the encrypted project.

27.7 Distributing the Application.


We have seen how we can include the user interface created with OpenDCL in our LSP source code
file. But for this application to work on a computer that does not have OpenDCL installed we must
include with our application its runtime module. Ensuring proper installation of the necessary files
automatically requires the creation of an installer, for which there are widely used applications, but
this subject is beyond this book's scope. An excellent free application that can be used to create our
application's installer, including the runtime module, is Jordan Russell's InnoSetup. The OpenDCL
website includes a step by step tutorial written by Lance Gordon that shows how to create an
application installer using InnoSetup. In the case of an application using modeless dialogs like the
one created in this chapter we must take into account the need to include in an acaddoc.lsp file the
expression needed to load the application for each drawing.

27.8 Summary.
A Graphic User Interface allows a programming style based on reacting to events raised within a
dialog box. Two chapters have been devoted to this subject. One of them explores the Programmable
Dialog Box (PDB) language introduced with AutoCAD Release 12. The one we are concluding
examines the advanced features offered by OpenDCL, an open source tool, which may well be the
prototype for what would be a welcome extension to Visual LISP.

Exercises:
Exercise 1.

The application developed in this chapter could be expanded to not only turn Layers On and Off, but
also to Lock/Unlock or Thaw/Freeze Layers.

Including these new features in the XREF Explorer would demonstrate the mastery attained by he
who has concluded studying this book.

Exercise 2.

Compile your application as Visual LISP Executable. Compiling it as a separate namespaceVLX


would require importing the ARX runtime module, making it more complex, so we do not recommend
this option until a more extensive experience has been attained.

1 The URL for OpenDCL is http://www.sourceforge.net/projects/opendcl. This chapter has been prepared using the
ENU.7.0.0.4 version. The OpenDCL help is available online at http://www.opendcl.com/HelpFiles/index.php.
Index of Function Listings.
Chapter 21
Listing 21.1. Function that processes the texts.
Listing 21.2. Callback function triggered by changes in the viewport.
Listing 21.3. Callback function triggered on concluding a command.
Listing 21.4. Creating the Object reactor.
Listing 21.5. Creating the Command reactor.
Listing 21.6. C:AUTO-SCALE. User interface for creating the reactors.
Listing 21.7. Function that determines if an object owns reactors.
Listing 21.8. Code to include in ACADDOC.LSP.
Listing 21.9. Callback function that reports on events.
Listing 21.10. Object reactor callback function.
Listing 21.11. Creation of the Database reactor.
Listing 21.12. Creation of the Editor reactor.
Listing 21.13. Function to link an informative object reactor.

Chapter 22
Listing 22.1. Code for the PARAMETRIC dialog.
Listing 22.2. Function that starts a dialog.
Listing 22.3. Function that loads a SLD image in a dialog.
Listing 22.4. Function that checks and formats edit box values.
Listing 22.5. Parameter edit boxes callback function.
Listing 22.6. Function test-other.
Listing 22.7. Verification functions test-1, test-2 and test-3.
Listing 22.8. Function test-normal.
Listing 22.9. Callback function to the Predefined Forms radio buttons.
Listing 22.10. Detecting the selected predefined form radio_button.
Listing 22.11. Slider callback function.
Listing 22.12. Assigning callback actions to tiles.
Listing 22.13. Function that determines the model type.
Listing 22.14. Function that activates the dialog box.
Listing 22.15. Function for calculating the bulge magnitude.
Listing 22.16. Function that draws the profile as a LWPOLYLINE.
Listing 22.17. Function that creates the 3D model.
Listing 22.18. Main function C:DCL-PARAM.

Chapter 23
Listing 23.1. Function that reads a block insert's variable attributes.
Listing 23.2. Standard function to extract attribute values.
Listing 23.3. Processing a block to extract a list with its attributes.
Listing 23.4. XDATA assignment.
Listing 23.5. Reading XDATA.
Listing 23.6. Obtaining a list of all the dictionaries.
Listing 23.7. Data entry function.
Listing 23.8. Function that creates a dictionary, or retrieves its ENAME in case it already existed.
Listing 23.9. Adding new entries to the dictionary.
Listing 23.10. Main function C:TOPONYMS.
Listing 23.11. Function that queries the linked data.
Listing 23.12. LIST->LDATA function.
Listing 23.13. Function LIST->LDATA including VLAX-LDATA-TEST.
Listing 23.14. Function to associate entities using LDATA.
Listing 23.15. Function that searches the libraries paths.

Chapter 24
Listing 24.1. Function that inserts a table in the drawing.
Listing 24.2. Function that changes a row's text height.
Listing 24.3. Function that changes a column's text height.
Listing 24.4. Selection of the block to process and extraction of its attributes as a list.
Listing 24.5. Function that calculates the approximate relative widths for columns.
Listing 24.6. Main function C:ATTRIB-TABLE.

Chapter 25
Listing 25.1. CONNECT-EXCEL function.
Listing 25.2. DISCONNECT-EXCEL function.
Listing 25.3. APP-ERR function.
Listing 25.4. LIST->EXCEL function.
Listing 25.5. PROCESS-TABLE function.
Listing 25.6. PROCESS-ROW function.
Listing 25.7. DATA->CELL auxiliary function.
Listing 25.8. Code for the dialog box.
Listing 25.9. Main function C:EXCEL-ATTRIBUTES.
Listing 25.10. FILL-LIST function.
Listing 25.11. CHECK-ATTRIBUTES function.
Listing 25.12. EXTRACT function.
Listing 25.13. READ-BLOCKS function.
Listing 25.14. HAS-ATTRIBUTES? function.

Chapter 27
Listing 27.1. Function that implements the new XLAYER command.
Listing 27.2. Template function created by OpenDCL Studio.
Listing 27.3. Function that retrieves the list of attached drawings.
Listing 27.4. Function that retrieves the list of Layers.
Listing 27.5. Function that creates the drawings and Layers association list.
Listing 27.6. Function that creates the Tree's nodes.
Listing 27.7. Auxiliary function DRAW-BUTTONS.
Listing 27.8. CHECK-STATE function.
Listing 27.9. Function to change the btnACT button's image.
Listing 27.10. Function that saves the Layer State.
Listing 27.11. Function that restores a previous Layer State.
Listing 27.12. Layer states initializing function.
Listing 27.13. Function that populates the tree view.
Listing 27.14. Final code for the form's Initialize event.
Listing 27.15. Tree control SelChanged event's callback function.
Listing 27.16. DocActivated event's callback function.
Listing 27.17. Code for the EnteringNoDocState event.
Listing 27.18. Event handler for the btnACT button's Clicked event.
Listing 27.19. Function that sets Layers On or Off.
Listing 27.20. Event handler function for the btnCOLOR button's Clicked event.
Listing 27.21. Event handler for the btnISOLATE button's Clicked event.
Listing 27.22. Event handler for the btnUNDO button's Clicked event.
Listing 27.23. Event handler for the btnUPDATE button's Clicked event.
Functions library.
These functions from previous Volumes may be called in programs defined in the present text.
(defun replace (new old string / pos len)
(setq pos 0
len (strlen new))
(while (setq pos (vl-string-search old string pos))
(setq string (vl-string-subst new old string pos)
pos (+ pos len)))
string)

Listing 5.12. Replacement of characters in a string.

(defun id-bits (value /)


(vl-remove 0
(mapcar '(lambda (i) (logand i value))
'(1 2 4 8 16 32 64 128 256 512 1024 2048
4096 8192 16384 32768 65536))))

Listing 7.5. Function to detect the enabled bits.

(defun bits-on? (bits value)


(= bits (logand bits value)))

(defun enable-bits (bits value) (logior bits value))

(defun disable-bits (bits value)


(logand (~ bits) value))

Listing 7.6. Functions to check, enable or disable bits.

(defun switch-sysvars (varsis bits)


(setvar varsis (boole 6 (getvar varsis) bits)))

Listing 7.7. Variables switch.

(defun cmd-in (/ 3dosm)


(if (and (setq 3dosm (getvar "3DOSMODE"))
(not (bits-on? (lsh 1 0) 3dosm)))
(switch-sysvars "3DOSMODE" (lsh 1 0)))
(if (not (bits-on? (lsh 1 14) (getvar "OSMODE")))
(switch-sysvars "OSMODE" (lsh 1 14)))
(if (bits-on? (lsh 1 0) (getvar "CMDECHO"))
(switch-sysvars "CMDECHO" (lsh 1 0))))

Listing 7.8. Setting system variables before entering the command.

(defun cmd-out (/ 3dosm)


(if (and (setq 3dosm (getvar "3DOSMODE"))
(bits-on? (lsh 1 0) 3dosm))
(switch-sysvars "3DOSMODE" (lsh 1 0)))
(if (bits-on? (lsh 1 14) (getvar "OSMODE"))
(switch-sysvars "OSMODE" (lsh 1 14)))
(if (not (bits-on? (lsh 1 0) (getvar "CMDECHO")))
(switch-sysvars "CMDECHO" (lsh 1 0))))

Listing 7.9. Restoring the original values on exiting the command.

(defun current-space (drawing /)


(vla-get-block
(vla-get-ActiveLayout drawing)))

Listing 10.31. Function that retrieves the current space.

(defun 3d->2d (pt) (list (car pt) (cadr pt)))

Listing 10.32. 3d->2d auxiliary function.

(defun can-use? (lyr)


(zerop
(logand
(cdr (assoc 70 (tblsearch "LAYER" lyr)))
(+ 1 4))))

Listing 10.40. Checking if a layer is not off, frozen or locked.

(defun ax-list->variant (lst)


(vlax-make-variant
(vlax-safearray-fill
(vlax-make-safearray
vlax-vbObject
(cons 0 (1- (length lst))))
lst)))

Listing 11.14. Conversion of the list into an array.

(defun ax-no-group (obj-list group-obj / tmp)


(vlax-for obj group-obj
(setq tmp (cons (vla-get-handle obj) tmp)))
(foreach obj obj-list
(if (member (vla-get-handle obj) tmp)
(setq obj-list (vl-remove obj obj-list))))
obj-list)

Listing 11.15. Function to detect and remove objects that belong to a Group.

(defun ax-add-group
(name obj-list / groups-coll group)
(setq groups-coll (vla-get-Groups *aevl:drawing*)
group (vl-catch-all-apply
'vla-Item
(list groups-coll name)))
(cond
((vl-catch-all-error-p group)
(setq group (vla-Add groups-coll name))
(vla-AppendItems
group
(ax-list->variant obj-list))
group)
(t
(if (setq objects
(ax-no-group obj-list group))
(vla-AppendItems
group
(ax-list->variant objects)))
group)))

Listing 11.16. Function for adding objects to a Group.

(pragma
'((unprotect-assign *aevl:acad* *aevl:drawing*)))
(setq *aevl:acad* (vlax-get-acad-object)
*aevl:drawing* (vla-get-ActiveDocument
*aevl:acad*))
(pragma
'((protect-assign *aevl:acad* *aevl:drawing*)))

Setting the references for the Application and Document objects.

(vl-load-com)
(load "aevl-globals"
"AEVL-GLOBALS not found")
(load "./Utility/vlisp-library"
"VLISP-LIBRARY not found")
(autoload "./Numera/FAS/numera.fas"
'("NUMERA" "NUM-OPTIONS"))

Code for the ACADDOC.LSP file.

;;; Vector A to B
;;; Arguments: A, B, lists of three real numbers.
(defun vec (A B) (mapcar '- B A))

;;; Vector addition


;;; Arguments: v1, v2, lists of three real numbers.
(defun v+v (v1 v2) (mapcar '+ v1 v2))

;;; Scalar product (dot product)


;;; Arguments: v1, v2, lists of three real numbers.
(defun x-scalar (v1 v2) (apply '+ (mapcar '* v1 v2)))

;;; Vector length (module)


;;; Argument: v, a list of three real numbers.
(defun m-vec (v) (sqrt (apply '+ (mapcar '* v v))))

;;; Unit vector


;;; Argument: v, a list of three real numbers.
(defun v-unit (v / m)
(cond ((zerop (setq m (m-vec v))) nil)
(t (mapcar '(lambda (n) (/ n m)) v))))

;;; Cross product (vector product)


;;; Arguments: v1, v2, lists of three real numbers.
(defun vec-prod (v1 v2)
(list (- (* (cadr v1) (caddr v2)) (* (cadr v2) (caddr v1)))
(- (* (car v2) (caddr v1)) (* (car v1) (caddr v2)))
(- (* (car v1) (cadr v2)) (* (car v2) (cadr v1)))))

Listing 13.4. Vector operations.

(defun ax-translation (obj vector)


(vla-TransformBy
obj
(vlax-tmatrix
(list (list 1.0 0.0 0.0 (nth 0 vector))
(list 0.0 1.0 0.0 (nth 1 vector))
(list 0.0 0.0 1.0 (nth 2 vector))
(list 0.0 0.0 0.0 1.0)))))

Listing 13.5. Translation function.

;;; Degrees to Radians


(defun dtr (g) (/ (* g pi) 180.0))
;;; Radians to Degrees
(defun rtd (r) (* (/ r pi) 180.0))

Listing 13.6. Radian-Degree conversions.

(defun ax-rot-x (obj a)


(setq a (dtr a))
(vla-TransformBy
obj
(vlax-tmatrix
(list (list 1.0 0.0 0.0 0.0)
(list 0.0 (cos a) (sin a) 0.0)
(list 0.0 (- (sin a)) (cos a) 0.0)
(list 0.0 0.0 0.0 1.0)))))

Listing 13.7. Rotation about X.

(defun ax-rot-y (obj a)


(setq a (dtr a))
(vla-TransformBy
obj
(vlax-tmatrix
(list (list (cos a) 0.0 (sin a) 0.0)
(list 0.0 1.0 0.0 0.0)
(list (- (sin a)) 0.0 (cos a) 0.0)
(list 0.0 0.0 0.0 1.0)))))
Listing 13.8. Rotation about Y.

(defun ax-rot-z (obj a)


(setq a (dtr a))
(vla-TransformBy
obj
(vlax-tmatrix
(list (list (cos a) (- (sin a)) 0.0 0.0)
(list (sin a) (cos a) 0.0 0.0)
(list 0.0 0.0 1.0 0.0)
(list 0.0 0.0 0.0 1.0)))))

Listing 13.9. Rotación en torno a Z.

(defun ax-scale (obj vector / res)


(setq
res (vl-catch-all-apply
'vla-TransformBy
(list obj
(vlax-tmatrix
(list (list (nth 0 vector) 0.0 0.0 0.0)
(list 0.0 (nth 1 vector) 0.0 0.0)
(list 0.0 0.0 (nth 2 vector) 0.0)
(list 0.0 0.0 0.0 1.0))))))
(if (vl-catch-all-error-p res)
(prompt "This object can not be transformed!")))

Listing 13.10. XYZ Scaling function.

(defun ax-shear-x (obj factor / res)


(setq res (vl-catch-all-apply
'vla-TransformBy
(list obj
(vlax-tmatrix
(list (list 1.0 factor 0.0 0.0)
(list 0.0 1.0 0.0 0.0)
(list 0.0 0.0 1.0 0.0)
(list 0.0 0.0 0.0 1.0))))))
(if (vl-catch-all-error-p res)
(prompt "This object can not be transformed!")))

Listing 13.11. Shear along X.

(defun ax-shear-y (obj factor / res)


(setq res (vl-catch-all-apply
'vla-TransformBy
(list obj
(vlax-tmatrix
(list (list 1.0 0.0 0.0 0.0)
(list factor 1.0 0.0 0.0)
(list 0.0 0.0 1.0 0.0)
(list 0.0 0.0 0.0 1.0))))))
(if (vl-catch-all-error-p res)
(prompt "This object can not be transformed!")))
Listing 13.12. Shear along Y.

(defun ax-shear-z (obj factor / res)


(setq res (vl-catch-all-apply
'vla-TransformBy
(list obj
(vlax-tmatrix
(list (list 1.0 0.0 0.0 0.0)
(list 0.0 1.0 0.0 0.0)
(list factor factor 1.0 0.0)
(list 0.0 0.0 0.0 1.0))))))
(if (vl-catch-all-error-p res)
(prompt "This object can not be transformed!")))

Listing 13.13. Shear along Z.

(defun ax-ucs (name origin dirx diry / tmp)


(setq tmp
(vla-Add (vla-get-UserCoordinateSystems *aevl:drawing*)
(vlax-3d-point '(0 0 0))
(vlax-3d-point dirx)
(vlax-3d-point diry)
name))
(vla-put-Origin tmp (vlax-3d-point origin))
tmp)

Listing 13.15. Function that adds a new UCS to the current document.

(defun ax-ucs-matrix (/ name ucs-num new-ucs)


(setq name (getvar "UCSNAME"))
(cond
((or (equal name "")
(and (vl-string-search "*" name 0)
(vl-string-search "*" name (1- (strlen name)))))
(setq ucs-num (vla-get-Count
(vla-get-UserCoordinateSystems *aevl:drawing*))
name (strcat "SCP_" (itoa ucs-num)))
(setq new-ucs (ax-ucs name
(getvar "UCSORG")
(getvar "UCSXDIR")
(getvar "UCSYDIR")))
(vla-put-ActiveUCS *aevl:drawing* new-ucs)
(list name (vla-GetUCSMatrix new-ucs)))
(t
(list (vla-get-name (vla-get-ActiveUCS *aevl:drawing*))
(vla-GetUCSMatrix (vla-get-ActiveUCS *aevl:drawing*))))))

Listing 13.16. Function that returns the current UCS transformation matrix.

(defun ax-view (direction zoom / vport)


(setq vport (vla-get-ActiveViewport *aevl:drawing*))
(vla-put-Direction vport (vlax-3d-point direction))
(vla-put-ActiveViewport *aevl:drawing* vport)
(vlax-release-object vport)
(if zoom
(vla-ZoomExtents *aevl:acad*))
(princ))

Listing 13.17. Function that sets the view direction and visual style.

(defun var-vis ()
(if (= (getvar "BLOCKEDITOR") 0)
(progn (setvar "VSMONOCOLOR" " RGB:211,76,3")
(setvar "VSFACECOLORMODE" 1)
(setvar "VSEDGES" 1)
(setvar "VSEDGECOLOR" "RGB:255,212,82")
(setvar "VSISOONTOP" 0)
(setvar "VSFACESTYLE" 2)
(setvar "VSOBSCUREDEDGES" 0)
(setvar "VSOCCLUDEDEDGES" 0)
(setvar "PERSPECTIVE" 1))))

Listing 13.18. Function that sets a custom visual style.

(defun ax-top () (ax-view '(0 0 1) t) (var-vis))

(defun ax-right () (ax-view '(1 0 0) t) (var-vis))

(defun ax-front () (ax-view '(0 -1 0) t) (var-vis))

(defun ax-NEt () (ax-view '(1 1 1) t) (var-vis))

(defun ax-NWt () (ax-view '(-1 1 1) t) (var-vis))

(defun ax-SWt () (ax-view '(-1 -1 1) t) (var-vis))

(defun ax-SEt () (ax-view '(1 -1 1) t) (var-vis))

(defun ax-bottom () (ax-view '(0 0 -1) t) (var-vis))

(defun ax-left () (ax-view '(-1 0 0) t) (var-vis))

(defun ax-back () (ax-view '(0 1 0) t) (var-vis))

(defun ax-NEb () (ax-view '(1 1 -1) t) (var-vis))

(defun ax-NWb () (ax-view '(-1 1 -1) t) (var-vis))

(defun ax-SWb () (ax-view '(-1 -1 -1) t) (var-vis))

(defun ax-SEb () (ax-view '(1 -1 -1) t) (var-vis))

Listing 13.19. Functions that set the 3D isometric view.

(defun ax-normal-ucs (origin vec-z / ang vec-x vec-y)


(setq ang (- (angle '(0 0 0) vec-z) (/ pi 2))
vec-X (vec '(0 0 0) (polar '(0 0 0) ang 1.0))
vec-Y (vec-prod vec-z vec-x))
(vla-put-ActiveUCS
*aevl:drawing*
(ax-ucs "TMP" origin vec-x vec-y)))

Listing 15.12. UCS from the Z axis direction vector using ActiveX.

(defun ax-UCSMatrix (origin vec-z / ang vec-x vec-y)


(setq ang (- (angle '(0 0 0) vec-z) (/ pi 2))
vec-X (vec '(0 0 0) (polar '(0 0 0) ang 1.0))
vec-Y (vec-prod vec-z vec-x))
(vla-GetUCSMatrix (ax-ucs "TMP" origin vec-x vec-y)))

Listing 15.13. Function that returns a UCS transformation matrix.

(defun SCP->U (/ tmp)


(setq tmp (ax-ucs "Univ"
'(0.0 0.0 0.0)
'(1.0 0.0 0.0)
'(0.0 1.0 0.0)))
(vla-put-ActiveUCS *aevl:drawing* tmp))

Listing 19.23. Function that sets the WCS as current..


Contents of other Volumes
Volume 1.

P ART 1. INTRODUCTION.
Chapter 1. AutoLISP/Visual LISP.
1.1 Visual LISP.
1.2 New LISP functions.
2.3. Summary.

Chapter 2. A Visual LISP Project, Step by Step.


2.1. Work Space and Project Structure.
2.2. A custom dictionary.
2.3. The calculus Function.
2.4. The drawing function.
2.5. The user interface.
2.6. Placing the labels.
2.7. Updating the dictionary.
2.8. On error...
2.9. Compiling the program.
2.10. Demand loading the program.
2.11. Summary.

Chapter 3. The Visual LISP IDE.


3.1. The Visual LISP IDE user interface.
3.2. Interactivity: The Visual LISP console.
3.3. The Programming Editor.
3.4. Interaction between the Editor and the Console.
3.5. Summary.

Chapter 4. Evaluating Expressions.


4.1. Data.
4.2. Expressions.
4.3. Symbols and assignment.
4.4. Lists.
4.5. Variables and data types.
4.6. Manipulating the elements of a list.
4.7. Lambda.
4.8. Summary.

Chapter 5. User-defined Functions.


5.1. Defun.
5.2. Loading and executing user functions.
5.3. Global and local variables.
5.4. Predicates and Conditionals.
5.5. Recursion.
5.6. Iteration.
5.7. Summary.

Chapter 6. ActiveX Data and Structures.


6.1. Safearrays.
6.2. Variants.
6.3. VLA-Objects.
6.4. Collections.
6.5. Working with methods and properties.
6.6. Collections processing.
6.7. Managing exceptions.
6.8. Summary.

Chapter 7. Data Entry.


7.1. Integrated error control.
7.2. Default values.
7.3. Prompting for data with options.
7.4. Input control through INITGET.
7.5. Data coded as binary values.
7.6. File search dialog box.
7.7. Summary.

Chapter 8. File Operations.


8.1. Opening files.
8.2. File reading.
8.3. Writing files.
8.4. Files and Folders.
8.5. Summary.

Chapter 9. Debugging Visual LISP Code.


9.1. Finding the error 's origin.
9.2. The debugging session.
9.3. Data inspection tools.
9.4. Error tracing.
9.5. Summary.

Volume 2

P ART 3. CONTROLLING AUTOCAD FROM AUTOLISP /VISUAL LISP .


Chapter 10. Drawing with Visual LISP.
10.1. Three ways to draw.
10.2. The COMMAND/VL-CMDF interface.
10.3. Creating entities with ENTMAKE.
10.4. Creating complex entities with ENTMAKE.
10.5. Sample Program: Defining a Block with ENTMAKE.
10.6. Using AutoLISP/Visual LISP in the Block Editor.
10.7. The ActiveX interface.
10.8. Complex objects with ActiveX methods.
10.9. Non-graphic objects.
10.10. Non-graphic objects from ActiveX extensions.
10.11. VLA-Objects and the use of available memory.
10.12. Summary.

Chapter 11. Selecting Entities.


11.1. Selection sets.
11.2 Creating selection sets.
11.3 Preselected sets.
11.4 Modifying selection sets.
11.5 ActiveX selection sets.
11.6 Groups.
11.7 Summary.

Chapter 12. Modifying entities.


12.1 Modifying properties using COMMAND/VL-CMDF.
12.2 Sample Program: Editing Geometry.
12.3 The ENTMOD function.
12.4 Differences between 2D and 3D entities.
12.5 Modifying entities using the ActiveX extensions.
12.6 Creating a Hyperlink.
12.7 Lineweight assignment.
12.8 Setting the TrueColor property.
12.9 Sample Program: Color scales.
12.10 Object Properties and Methods.
12.11 AutoLISP non-ActiveX property modification functions.
12.12 Summary.

Volume 3.

P ART 4. 3D P ROGRAMMING.


Chapter 13. 3D Objects.
13.1. Programming options from Visual LISP.
13.2. How does AutoCAD work in 3D?.
13.3. Transformation matrices.
13.4. Sample Program: Scailin transformations specifying the base point.
13.5. Transformation between Coordinate Systems.
13.6. Viewpoint and Visual Style.
13.7. Summary.

Chapter 14. NURBS curves: The Spline entity.


14.1. Creating SPLINE entities.
14.2. SPLINE Methods and Properties.
14.3. Creating ca Helix shaped SPLINE by Control Vertices.
14.4. Sample Program: Creatins a HELIX.
14.5. Summary.

Chapter 15. VLAX-CURVE... measuring curves and something else.


15.1. Visual LISP 's VLAX-CURVE Extensions.
15.2. Common arguments.
15.3. Determining a curve 's length.
15.4. Distance between points along a curve.
15.5. Measuring Areas.
15.6. Calculating the first and second derivatives.
15.7. Sample Program: Drawing tangents to a curve.
15.8. Sample Program: UCS perpendicular to a curve at a selected point.
15.9. Determining points on a curve.
15.10. Sample Program: Breaking a curve into equal segments.
15.11. Finding intersections.
15.12. Summary.

Chapter 16. Legacy Polygon and Ployface Meshes.


16.1. Mesh building procedures.
16.2. PolygonMesh.
16.3. Smoothing the PolygonMesh.
16.4. Sample Program: Creating a PolygonMesh.
16.5. PolyfaceMesh.
16.6. Sample Program: Creating a PolyfaceMesh.
16.7. Modifying Polygon and Polyface Meshes.
16.8. Summary.

Chapter 17. Solid Modeling.


17.1. 3DSolid Primitives.
17.2. Creating a Primitive using ActiveX.
17.3. Creating 3DSolids from 2D or 3D objects.
17.4. Creating Regions.
17.5. Sample Program: Complex Regions.
17.6. Properties and Methods of the 3DSolid object.
17.7. Sample Program: Extruded Solid.
17.8. Sample Program: Solid by Sweeping along a path.
17.9. Sample Program: Sweeping along a Helix.
17.10. AddRevolvedSolid: Solids of Revolution.
17.11. Sample Program: Creating a Solid of Revolution.
17.12. Physical and Geometric Properties.
17.13. Summary.

Chapter 18. Editing 3D Solids.


18.1. Slicing Solids.
18.2. Sample Program: Polyhedra obtained by slicing 3DSolids.
18.3. Sectioning 3DSolids.
18.4. Sample Program: Sections of a Sphere.
18.5. Boolean operations on 3DSolids.
18.6. Sample Program: UNION and SUBTRACTION operations.
18.7. Sample Program: Part created by INTERSECTION.
18.8. CheckInterference: Interference operations.
18.9. Sample programs: 3DSolid TRIM and SPLIT commands.
18.10. Section objects.
18.11. Sample program C:SOL-SECT.
18.12. Summary.

Chapter 19. Subdivision Surfaces.


19.1. Programming MESH objects with Visual LISP.
19.2.Creating MESH entities with ENTMAKE.
19.3.Sample Program: Polyhedral MESH.
19.4. Sample Program: MESH approximating mathematical functions.
19.5. Creating meshes using command/vl-cmdf.
19.6. Modifying Subdivision Surfaces.
19.7. Sample Program: Modifying MESH objects.
19.8. Generalizing MESH transformations.
19.9. Sample Program: Shape up a MESH object.
19.10. Meshes created from 2D entities.
19.11. Summary.

Chapter 20. Procedural and NURBS Surfaces.


20.1. Creating surfaces.
20.2.Properties exposed by Surfaces.
20.3.Sample Program: NURBS surfaces.
20.4. Creating a Procedural surface.
20.5. Sample Program: Associative Surface with Parametric Profiles.
20.6. Modifying the cross-section’s constraint parameters.
20.7. Creating a dynamic block from the associative surface.
20.8. Summary.

You might also like