The+Language+and+Its+Developmen+ +Togores%2C+Reinaldo+N
May 2, 2017 | Author: Feng Zhou | Category: N/A
Short Description
Download The+Language+and+Its+Developmen+ +Togores%2C+Reinaldo+N...
Description
AutoCAD expert’s Visual LISP Volume 1
The Language and its Development Environment
Reinaldo N. Togores
AutoCAD expert’s Visual LISP Volume 1 The Language and its Development Environment
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 are 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.
To Teté, with love.
Contents Preface. Acknowledgements. About the Author.
PART 1. INTRODUCTION. Chapter 1. AutoLISP/Visual LISP. 1.1. Visual LISP. 1.2. New LISP functions. 1.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.
PART 2. THE LANGUAGE AND ITS DEVELOPMENT ENVIRONMENT. 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.
Index of Function Listings. Appendix 1. Contents of other Volumes.
Preface This book is the product of eighteen years teaching Computer Aided Design to Civil and Industrial Engineering students at the University of Cantabria. During this time I have published two books about Visual LISP programming for AutoCAD. Back in 2003 the book Programación en AutoCAD con Visual LISP1 which I wrote in collaboration with Professor César Otero was the first one dealing with this subject in Spanish. For ten years it has been the main resource in Spanish for Visual LISP programming. I still receive messages asking me where to buy it. It is impossible, since it is now out of print. A couple of years ago I undertook the task of preparing an updated version. The new functionalities added since 2003 to AutoCAD required a thorough revision and rewriting of the text. The new book Experto AutoCAD con Visual LISP is updated to Release 2012. But being written in Spanish limits this kind of book’s readers. My English speaking friends have encouraged me to prepare an English version which may reach a wider audience. During this time AutoCAD 2013 has been released, so the English edition has been updated to cover the few changes introduced from 2012 to 2013. Back in 2003 when the first book was published, AutoLISP was not "in fashion". Not even with Visual LISP’s new contributions. Fashion followers then bet on the novelty represented by VBA. But fashion is not always rationally justified. In that book we aimed to demonstrate that the availability of other Windows dialog box modalities was not reason enough to forgo what had been our way of customizing AutoCAD for more than 15 years. That’s why we ended our book with a chapter devoted to Chad Wanless’s ObjectDCL, a plug-in which allowed the use of this kind of Graphic User Interface with AutoLISP. VBA, like all fashions, passed away. It’s over and those who opted for it are now hastily rewriting their applications2. But AutoLISP/Visual LISP is still here. And ObjectDCL, now OpenDCL, has become an open source project which we can use free of charge. And once more it deserved the new book’s last chapter. I wish this book will guide he who uses AutoCAD in becoming a real expert. That kind of AutoCAD expert that is acquainted with, understands and can manipulate the program’s inner workings to achieve the desired output in a fast and efficient way. He who is not satisfied with what comes out of the box, but demands more. Among the most significant new contributions of recent versions we have 3D
modeling, including surfaces associativity. To them, and other advanced techniques, including parameterization, reactors, user graphic interfaces and compiling applications, more than half of this new book is devoted. The contents have been tested for compatibility with AutoCAD 2014. Source code has been tested with the Windows 32 and 64 bit versions. Although much of the above also applies to previous versions there is no excuse not to study it using the latest release. Autodesk offers student versions of most of its software completely free of charge. To obtain them, simply register in the Autodesk Education Community website (students.autodesk.com) or in the Autodesk Students Facebook page (https://www.facebook.com/Autodeskedcommunity). The source code for all the examples included in the book can be freely downloaded from my Blog at http://lispexpert.blogspot.com/ where readers can post questions and comments about the book’s contents. You can also visit my website: http://www.togores.net/ where I have published other materials related to computer-aided design developed during the last twenty years both in spanish and english. I hope that you will not only learn from this book, but that you will enjoy doing it.
Acknowledgements. First of all, I wish to recognize the support received from the Developers Assistance Services of the Autodesk Developer Network with their answers to my many questions when preparing the chapters devoted to the new Mesh and Surface entities. Without this information it would have been difficult to attain the goals I had set myself. To this I have to add the contribution of the participants to the many AutoLISP forums in the Internet, in particular Autodesk’s Visual LISP, AutoLISP and General Customization group and the forum at www.theswamp.org. A special mention is deserved by Owen Wengerd and the group of enthusiasts that keep alive the OpenDCL project. Many of the ideas developed in this book have been tested in exercises proposed during my 18 years as a teacher in the Geographic Engineering and Graphic Expression Techniques Department at the University of Cantabria (Spain). I want to recognize the contribution of my colleagues and in particular of Professor César Otero, director of my doctoral thesis on Computational Geometry Methods Applied to the Design of Space Structures. It was during the work in this thesis that I could investigate in depth some of the procedures for transformations in 3D space proposed in this book. From the University’s Applied Mathematics and Computational Sciences Department I wish to express
my debt with Professors Andrés Iglesias and Jaime Puig-Pey for their research on NURBS curves and surfaces from which I have profited for the preparation of these themes. No doubt many are the names missing here, but I must not leave out that of Nikolas Bokisch, former Autodesk EMEA Education Program Manager, for his interest in our work and the help that he has always provided.
About the Author. Reinaldo N. Togores has been using AutoCAD for about 25 years in his work as an Architect and Industrial Designer. For the past eighteen years he has taught AutoLISP programming to Civil and Industrial Engineering Students at the University of Cantabria, in Northern Spain. As a researcher on Computer Aided Design topics he has worked with several research groups at the University of Cantabria and has been a member of the Autodesk Developers Network since the late ‘90s. He has authored three books about Visual LISP programming. 1 Togores, R. and Otero, C.; Programación en AutoCAD con Visual LISP. McGraw-Hill Interamericana de España, S.A.U. ISBN: 84-481-3694-2. Madrid, 2003. 2 On August 6, 2012 Autodesk announced that due to Microsoft’s recent renewed investment in VBA, it has become possible for Autodesk to upgrade the VBA engine (in those products that support VBA) from version 6.3 to 7.1. It will be available as a separate download for AutoCAD® and AutoCAD verticals, and as part of the standard installation for Autodesk® Inventor® as of the next release. However, while this means VBA code will not become immediately obsolete, Autodesk does not recommend using VBA for professional application development.
Part 1 Introduction The spirit of Lisp hacking can be expressed in two sentences. Programming should be fun. Programs should be beautiful. Paul Graham, in his preface to ANSI Common Lisp.
Chapter 1 AutoLISP/Visual LISP LISP syntax is none other than that of mathematical functions, already familiar from our secondary education. We all know how to solve an expression such as (8 x ((17 + 3) / 4)). First we would add 17 + 3, obtaining a number which we would then divide by 4, to finally multiply this new result by 8. That is, we solve the innermost parentheses passing the resulting value to the operations represented in the containing parentheses The figures and the sign of the mathematical operations to be performed are grouped within parentheses. A data sequence between parentheses is what in LISP we call a list. The solution of the previous expression consisted, as we have seen, in the successive processing of each one of these lists. Hence the name of LISP, derived from LISt Processing. In LISP notation the operator always appears at the beginning of the list. would be the equivalent LISP expression. This typed directly into the AutoCAD command line (see Figure 1.1) would return 40. (* 8 (/ (+ 3 17) 4))
Figure 1.1. Evaluation of a LISP expression.
We’re already using LISP. As simple as that. AutoCAD Has interpreted the function received and has returned the result of its evaluation, which is printed on the screen. Thus we have accessed LISP’s fundamental mechanism. Expressions like the one just written are the basic units of the language, which operates through repeated reading, evaluating and printing a result, which in the terminology of the language is known as the read-eval-print loop. An additional advantage, at least from the standpoint of the group of users to which this book is addressed: other programming languages demand a series of arrangements related to memory management. We would have to foresee the types of data to employ and define in advance the suitable containers for these data types (declaration of variables, etc.). In LISP memory is managed automatically, and only some minimum cautions should be taken by the programmer regarding possible interferences between the names of variables.
Thus, the success of AutoLISP as a means for extending the capabilities of AutoCAD was overwhelming. Perhaps today we are not aware that some of the tools that we consider essential were not available in the original program, being developed as AutoLISP macros and programs, primarily for the personal use of their creators. If we review Schaefer and Brittain’s AutoCAD Productivity Book published in 1986 1, we will find the COPYPARA menu macro whose function was to perform a parallel copy of the designated line using AutoLISP expressions, the same that is now advantageously performed by the OFFSET command. Or the LTC (Line To Circle) program with which you could draw lines tangent to two circles, unnecessary today thanks to the introduction of the differed mode of the TANgent object snap. The book’s title was very precise regarding what AutoLISP offered to the user: a way to increase his productivity automating any repetitive task.
1.1. Visual LISP. AutoLISP had barely changed since its introduction in 1985-86. Its limitations were obvious. Among the most important, processing speed, especially in regard to complex numerical calculations and a safer way to protect the source code. A group of programmers working from Moscow in the Basis Software2 company sought a solution for these limitations. The initial proposal of this group was the ACOMP compiler that was included in the international releases of AutoCAD 10, 11 and 12. The fact that it was not distributed with the release distributed in the United States, limited its use. BASIS Software introduced Vital LISP in the spring of 1995 as a standalone product for AutoCAD releases 12 and 13. In addition to the compiler, this application supplied a complete Development Environment. The success of Vital LISP led AutoCAD to buy it in 1997, renaming it as Visual LISP 3. Without the possibility of adding it to Release 14, already in the market, it was offered as a complementary program at a price substantially lower than that of BASIS Software. Not being a part of AutoCAD R14’s core, it required us to include a RTS (Runtime System) to be loaded along with our programs. This need was covered in a very practical way by allowing all the application’s files, both compiled LISP files (FAS) and those that must remain as plain text (DCL files, for example) along with the RTS to be packaged as an executable ARX (AutoCAD Runtime eXtension) format file similar to those developed with C++ using the ObjectARX API.
Since AutoCAD 2000, Visual LISP is part of AutoCAD’s standard releases. Thus, it is no longer necessary to include an RTS with our application, which leads to a considerable program size reduction. According to our experience, an application whose sources (LSP and DCL files) took about 102 Kb is, once compiled as ARX (Release 14) a 602 Kb file, while compiled as Visual LISP Executable file (VLX) for AutoCAD 2000 or subsequent releases occupies only 46 Kb
What does Visual LISP offer? The two most pressing demands of the expert user, speed and safety are largely taken care of. Besides programming will be easier with these new tools. But there’s still a question to be answered: Is AutoLISP/Visual LISP the programming language we need? Back in 1986 there was no choice. But today you can create applications that manage AutoCAD with almost any modern programming language. To a programmer who wishes to engage in building applications that extend the features of AutoCAD we would certainly recommend the use of languages such as C#, Visual Basic or VC++ on the .NET platform. This is not the user profile to which Visual LISP is oriented. Visual LISP is the ideal language for anyone who makes a living day by day using AutoCAD as a tool. For those looking to increase their productivity by automating everyday tasks. For those who want something more than "what comes out of the box". In short, to become a real expert, what we usually deem as an AutoCAD "Poweruser". Visual LISP provides them, with respect to classical AutoLISP, a number of advantages.
1.2. New LISP functions. First, Visual LISP significantly increases the number of available functions, including many which have been for quite a long time part of the Common LISP standard4. These functions are distinguished from the primitive AutoLISP functions by the VL- prefix totaling about 77, including several functions for reading and writing in the Windows registry or to manage storage in files and folders, some that can be used for a more effective treatment of run-time errors and others which deal with LISP data treatment (symbols, strings, lists, etc.) and type conversion.
Visual LISP ActiveX Extensions.
ActiveX extensions supplied with Visual LISP include functions for the measurement of curves, data conversion, and objects, properties, collections and dictionaries manipulation. These features provide a number of advantages, among which are greater execution speed and wider functionality. They also allow us to use data types that are not available in AutoLISP, such as arrays. These functions are distinguished, like the other new features built into Visual LISP, by their prefixes. We have them in two types: Functions that directly correspond to the native AutoCAD ActiveX methods. VLAX- Functions specialized functions that are unique to Visual LISP. VLA-
This ActiveX interface allows Visual LISP to act as a client and a server in the Windows environment, allowing AutoCAD to control or be controlled from other applications. This way the ability to interact with other applications is enabled, something almost impossible for the old AutoLISP. VLA- and VLAXfunctions are not available in AutoCAD for Mac.
Reactors Other new features group, identified by the VLR- prefix, aim at implementing reactors. Reactors make it possible for a specific AutoCAD application to receive notifications when certain actions occur, making it possible to schedule a response to that event. There are four types of reactors that correspond to the general categories of events to which an application can respond: Editor reactors, which notify each time an AutoCAD command is called. Database Reactors, linked to specific entities or objects in a drawing's database. Object reactors that notify the program if certain actions are performed on an AutoCAD entity. Link Reactors, which notify the application when loading or unloading an ARX application. This is a complex matter which we will address in Part 5, Chapter 21. Callback routines invoked from the reactors must necessarily use the new Visual LISP ActiveX functions instead of the AutoLISP command function.
The Development Environment. Finally we have the programming tools included in the Visual LISP Integrated Development Environment (IDE). Among the components of the IDE we find:
A syntax checker that recognizes erroneous expressions and inappropriate arguments within calls to primitive AutoLISP functions. A file compiler that increases the speed of execution and provides a safe and efficient platform for application delivery. The compiler is able to process files both isolated or grouped as projects A source code debugger designed specifically for AutoLISP, which allows stepping through the code in the Editor window while showing the results of its execution in the AutoCAD viewport. A text editor which enables color coding for AutoLISP and DCL sources, while providing assistance in syntax checking. An AutoLISP formatter which restructures the program code to improve readability. Inspection and Watch windows which provide access to the values assumed by variables and expressions allowing their review and modification. They can be used to examine AutoLISP data and AutoCAD entities. A context-sensitive help providing information about AutoLISP functions and an efficient Apropos feature for searching symbol names. A project management system that facilitates the maintenance of applications that span multiple archives. The possibility of packing compiled AutoLISP files in a single module, together with the corresponding DCL dialog definitions. An intelligent LISP console for immediate code evaluation that provides a series of other features useful in the process of program development.
1.3. Summary. Today the applications that manage AutoCAD drawings and can be developed in such different programming languages as Visual BASIC, Delphi, C++, C # Java and even other LISP implementations. For a programmer with experience in any of these environments to learn Visual LISP would be useful but not essential. But in the same manner, an expert AutoCAD user, not a professional programmer, but with knowledge of AutoLISP/Visual LISP need not miss, in order to streamline his daily work, the features offered by those languages. Hopefully this will be demonstrated in the following chapters. AutoLISP / Visual LISP is also the programming interface for a large number of alternative CAD programs, most of them based on the IntelliCAD 5 technology. 1 Schaefer, A.T.; Brittain, J. The AutoCAD Productivity Book, Ventana Press, Piedmont, California, 1986. ISBN: 0-940087-00-6 2 Information from http://wiki.alu.org/Success%20Stories and
http://en.wikipedia.org/wiki/AutoLISP. Basis Software later moved to Pennsylvania. 3 Visual LISP was, before Autodesk bought it, the name of an AutoLISP code editor developed by Western Sierra Software, available as freeware on the Internet. 4 Defined in editions 1 and 2 of Guy Steele’s book Common LISP, The Language, and more recently as an ANSI standard. 5 For a long list of CAD programs developed from IntelliCAD see: https://members.intellicad.org/members/index.php.
Chapter 2 A Visual LISP Project, Step by Step As an introduction to both the programming language and its Development Environment we shall follow the process that leads from the initial statement of the problem to solve to its completion in the form of a new AutoCAD application. This is the spirit that this book will maintain hereafter: addressing such problems as any user can find in his daily work. We hope that this book in addition to being a learning tool will provide a library of programs to which the reader may resort, adapting them to his needs. For readers with previous experience in AutoLISP programming we hope to give in this chapter a glimpse of the power available through the new tools. And from here on the more impatient readers will surely start to use immediately and with benefit tools like the Programming Editor. The rest of the book will be a reference and indispensable guide in their daily work. Those without such experience will surely find more questions than answers. These answers will be found in the following chapters. To become a true expert always requires effort and dedication. We hope this tutorial will set the mood for this endeavor.
A real case. An AutoCAD expert always looks for ways to make more efficient his work and the work of those who depend on him. In this first approach we will address how to develop a very simple tool that can represent great savings in time and effort. Some time ago, a friend from Colombia posed the following problem: I must identify the buildings on a site with a number which increases by 10 each time. I would like that if I placed a number x in the first house of a block, I could just click on this first number and by clicking on the second house I would have placed the previous number increased by 10, and so on. Right now what I have to do is to copy the same text on all houses and then change them one by one, procedure which I find to be very slow. This is a typical example of how a few lines of code will increase our productivity. Despite its simplicity, we will establish in the development of this
program a series of work habits essential in more complex applications.
2.1. Work Space and Project Structure. The first thing we will do is to establish a folder and file structure that allows us to organize our work. To locate the components of our programs we will create a folder named Visual LISP Projects. After creating this folder it should be included in the AutoCAD support files search paths. This is done from the Files tab in the Options dialog box1. Within it we will create the folders for our various projects. The one for this particular project shall be called NUMERA. In turn, the folder NUMERA will contain two subfolders, which we will name FAS and TEMP. The NUMERA folder is intended for the source code files and the FAS and TEMP folders will contain files generated during the project's compilation. Within the Visual LISP Projects folder we will also create a folder named Utilities, designed to contain source code files for those functions intended to be used in more than one project. Figure 2.1 shows the tree view of the proposed structure.
Figure 2.1. Folder structure.
As the book progresses we will incorporate new projects, each in its own folder, always with the same layout: a folder for the project and within that, the FAS and TEMP folders. Having included the Visual LISP Projects folder in the support files search paths allows us to load a program from a relative path excluding its name, with an expression such as: (load ".//")
The file name need not include the file extension. The system will search for files with the extensions .vlx (Visual LISP compiled application) .fas (Compiled AutoLISP file) or .lsp (AutoLISP source file) in that order and load
the first one it finds. In our projects folder we will also create a file called acaddoc.lsp, which is a special file which, if found, AutoCAD will automatically load for each new drawing opened. At this chapter’s end we will indicate what to do with it. Starting with AutoCAD 2014, custom applications like those that we will develop in this book must work under secure mode. Secure mode is enabled when the new system variable SECURELOAD is set to 1 or 2. When operating under secure mode loading of applications is restricted to files contained in trusted locations. These trusted locations are specified by the TRUSTEDPATHS system variable. So besides including the Visual LISP Projects folder in the Support Files Search Paths list, it must be also included in the Trusted Locations list of the Files tab in the Options dialog box. This must also be done for the Numera and the Utility folders and any other folders that will contain executable code.
Figure 2.2. Setting the Support and Trusted locations folders.
These folders, once we have concluded development should be set to a ReadOnly staus. In case we add a folder which is not Read-Only to the Trusted Locations list, a Security Concern warning box will be shown.
Figure 2.3. Security concern alert box.
Project structure. Once organized our workspace, we shall analyze the components of our project. In general, any program that we develop: Will almost always have as its goal the creation, modification or checking of drawing elements, that is to say they will have almost always a graphic output. The characteristics of what we will draw will be determined through a series of calculations. To perform these calculations, we must allow the user to select parameters such as size, distance, etc. he considers appropriate. That is, data entry functions, calculation functions and graphical output functions (i.e., drawing) from the calculations made. Separate source files will correspond to these three parts of our project. This way of organizing our work can be managed by creating Visual LISP projects. Source Code Files. We shall proceed to create the new source code files and set up the project including them. To do so we will use the Visual LISP Integrated Development Environment (IDE). To access it will be necessary to type VLIDE or VLISP in the AutoCAD command line. We can also access the Visual LISP IDE from the Manage ribbon tab (Figure 2.4.) clicking on the Visual LISP Editor button in the Applications group.
Figure 2.4. Access to the Visual LISP Editor.
Once in the IDE select the New File button to create the source code file, to which we then give a name, clicking on the Save File button. Using the Save As dialog box search for the previously created \Visual LISP Projects\NUMERA folder naming this new file as numera-calculus.lsp. Check before you save it that the drop-down list box at the bottom of the dialog reads Lisp source files. If not, select this option. The file is saved with a LSP extension. The same shall apply to create another file that is saved with the name of numera-input.lsp. We will create a third file, but in this case the target folder will be Utility. This last file will be named draw-text.lsp. This file will contain the code needed to create text entities. It is obvious that a function used to create text entities can be necessary in many different programs so we should design it as a generic function to be used in more than one project. To create a new source code file we can use the key combination CTRL+N and to save it CTRL+S. The New File... and Save as... options are also available on the Files menu. In more complex applications it may be necessary to use a larger number of files to organize your work. You do not need to create them as we have now, from the start. We can do it as the situation demands, grouping interrelated functions. Create the project. The management of the files in which we subdivide the project would be uncomfortable if we did not count with the Visual LISP project. To create the project, select the option New Project... from the Project menu. In the New Project dialog box we will select the NUMERA folder, and type numera as the file name. The .PRJ extension will be automatically assigned to this file, indicating that this is the file that stores the project properties. On saving the
Project Properties dialog box shown in Figure 2.5.a opens. This dialog opens showing the Project Files section that will allow us to select the LISP source code files we want to include. Having saved the project in the NUMERA folder, the files numera-input.lsp and numera-calculus.lsp will appear in the list to the left of the dialog. Using the button with the > symbol we will add the desired files to the project. These files disappear from the left window appearing on the one to the right. To include the file draw-text.lsp we have to search for the Utility folder, using the ellipsis button on the right of the Search edit box. After selecting the folder we shall include the draw-text.lsp file in the project. The Project properties dialog has another section (Figure 2.5.b), Build Options, Which is accessed by clicking on the appropriate tab at the top of the dialog box. Here we define the parameters to use in a future compilation of the project.
Figure 2.5.a. Selection of project files.
Figure 2.5.b Selection of build options.
As Compilation Mode we keep, for now, the Standard option. In Merge files mode we select Single module for all, so we get a single compiled file from the three source code files that the project includes. In Message mode, the Full reports option will show the errors found when compiling, the warnings and the compiler statistics. In the Fas directory and Tmp directory edit boxes we will use their respective ellipsis buttons to select the FAS and TEMP folders we had previously created. Having made these changes, we click OK. A dialog box (see Figure 2.6) will appear in the IDE showing the names of the source code files that make up the project. These tools are also available as Project menu options.
Figure 2.6. Project dialog box.
Analysis of the problem. Now that the skeleton of our application is ready we must think how to solve the problem we are dealing with. Our friend tells us that he wants to select a text that contains a figure and thereafter place similar text entities in which that figure is increased by 10 each time. This tells us that we need a set of functions for: Reading a text in the drawing and checking that its value is a number. If it is a number, increasing it in the desired amount. Requesting a location for the new text.
Drawing in that location a text containing the new figure. In order to make it a more general purpose tool, it is also desirable that: We can type an initial figure as an alternative. Make it possible to change the increment value. That this value and the last number written shall be retained between drawing sessions. We could also store other information related to text formatting. In order not to complicate this tutorial we shall memorize, for the time being, only the text height.
2.2. A custom dictionary. To save data between sessions and retrieve them when we reopen the drawing will use a DICTIONARY object. Dictionaries are objects that the user can create at will to keep non-graphical information in them. The functions we shall use to manipulate our dictionary are extensions to the AutoLISP language that must be loaded before using them by calling the expression (vl-load-com). To create the custom dictionary, which we will name "VARS-NUMERA" we will program the user function numera-dict, whose source code is shown in Figure 2.7. This function is closely linked to the calculation of new values of the numbers and therefore we will include it in the numera-calculus.lsp file. To start editing this file just double-click its name in the project window. This way the file is opened (if not already open) and set in focus. Another way to access the file for editing is through the context menu displayed by pressing the right mouse button on the project window. Adding a comment to remind us later what this function does and how is a good habit. After writing this comment we select the text and click on the Comment block button (Also Ctrl+E+C.) Doing this, the system adds three semicolons to each line, coloring the text purple on a gray background in the editor. These lines will not be processed on loading and compiling the program. As you type the text in the editor window, we see that the names of LISP functions (or primitives) are colored in blue, integer values in green and list opening and closing parentheses in red. Syntax color coding allows us to immediately detect many typing errors. Like all LISP user functions this one is defined by means of the special form defun that is placed as the first term of the expression, after the opening parenthesis. After the defun term the function name appears, in this case
followed by a parenthesized list of arguments that the function is to receive and which in this case is empty. numera-dict
Figure 2.7. Function NUMERA-DICT.
The reason we say that defun is a special form and the way it operates will be understood as we advance in studying this book. After loading the necessary Visual LISP extensions calling (vl-load-com), we find a conditional expression if, which according to whether or not the dictionary "VARSNUMERA" exists, extracts its values or creates it with default values that the user can later change. The existence of the dictionary is checked by the expression: (dictsearch (namedobjdict) "VARS-NUMERA")
that, if the dictionary does not exist, returns nil which is equivalent to false. The dictionary is created only if the value returned by this expression is nil, thus avoiding the loss of previous values. Each value is identified by an associated key. The dictionary is created when data is added using the vlax-ldata-put function. To retrieve that data the vlax-ldata-get function can be used. (vlax-ldata-put "VARS-NUMERA" "INCREM" 10) assigns 10 to "INCREM" (vlax-ldata-get "VARS-NUMERA" "INCREM") recovers the value associated to "INCREM"
These functions are discussed in detail in Part 5, Chapter 23.
For the text height we read, using the getvar function, the current text height which we obtain from the TEXTSIZE system variable. For checking the syntax and use of these functions, we recommend using the Apropos tool. To do this, we select the function name in the Editor and press the Apropos button in the View toolbar. The Apropos results window appears showing the function name highlighted. We can then click on the Help button2.
Testing the programmed function. We said earlier that LISP is an interactive programming environment. We can immediately verify what we meant by this. It means we can proceed step by step checking our functions, without intermediate compilation phases. To do this, we must first load it by clicking on the Load Source Files button in the project window (see Figure 2.6). Once a function is loaded, the loading outcome is shown in the Visual Lisp Console. If when loading or evaluating an expression we receive an error message, we can use several tools intended to detect and correct these errors. To these debugging tools, which appear as options in the Debug menu and in the toolbar with the same name, we will dedicate an entire chapter later on. For now, in case of errors, we will carefully check if what we typed matches the code shown. Special care should be taken with the location and number of opening and closing parentheses. The term form is used in the Console messages to describe the loaded expressions (in this case the defun.). Form, used in this context means any LISP expression in condition to be evaluated. It should not be confused with the Visual BASIC usage of this term meaning the dialog box used in a graphical user interface. In this chapter we present the code in the form of screenshots that show the way they are presented in the Editor. The format shown is intended to facilitate parentheses checking through their vertical alignment and the introduction of comments that indicate to which expression each closing parentheses belongs. This can be achieved automatically using the format options offered by the Visual LISP editor. In the following chapters code listings will be presented as printed text using a more compact format so the number of pages is not unnecessarily increased. The editor automatically adds the appropriate indentation for each new
line by pressing ENTER. If the code does not adopt a format similar to that shown in the screenshots it indicates that an error has occurred, probably omitting a parenthesis or adding one in excess. The easiest way to check for the closing parenthesis that corresponds to an opening parenthesis (or vice versa) is to place the cursor next to them (on the outside) and double-click. This will highlight all the text from a parenthesis to the corresponding closing or opening one. Loading a function can reveal errors that are detected at compile time, but there may be errors that only become apparent when running the program. To test the function we just loaded we just type the expression (numera-dict) in the Console and press ENTER. This will create the dictionary object with default values. If an error occurs at run time the control is automatically transferred to the Visual LISP Console which will enter a break loop. This is verified by observing the console symbol that will show the current break loop level, for example, $ _1_. For now it is enough to know that to get out of this loop we must type CTRL+R. To check the stored values we must type in the console the expression (vlaxldata-list "VARS-NUMERA") which returns all the stored values as an association list. For a specific value we can use the vlax-ldata-get function, passing the value of the associated key as argument. For now we will settle with these initial default values. Later we will create the user interface that will allow us to change them at will.
2.3. The calculus function. The functions shown in this section will be included in the numeracalculus.lsp file. The next-num function (see Figure 2.8) is used to increment the figure for each new text. No arguments are received here either. It limits itself to adding the value stored in) is used to increment the figure for each new text. No arguments are received here either. It limits itself to adding the value stored in "NUMBER" with the value stored in "INCREM" and saving the resulting number as the new value for "NUMBER" in the "VARS-NUMERA" dictionary.
Figure 2.8. Function NEXT-NUM.
This function will be saved in the numera-calculus.lsp file, together with the above described numera-dict (See Figure 2.7). Each time next-num is called the value will be increased. We’ll check it by loading again the project and executing (next-num) from the Console (See Figure 2.9).
Figure 2.9. Evaluation of functions in the console.
This way we can see that whenever the (next-num) expression is evaluated it returns a number incremented by 10. That is the figure we will use for the consecutive numbering of our parcels.
2.4. The drawing function. We will now consider the function that draws the text (see Figure 2.10), which we include in the draw-text.lsp file. There are three ways to create new entities in the drawing. The oldest one is through the command function. This function allows the programmer to control the command system following the same sequence of keyboard inputs the user would type to execute any command. Another option that, as we will see further on, offers obvious advantages in
execution speed and reliability, is to incorporate directly into the drawing database the desired entities by means of the entmake function, which receives an association list with the parameters associated with the corresponding identifying code from the DXF system. An even more effective third option, available only since the addition of Visual LISP, is to create graphic objects using the ActiveX interface methods. In this tutorial we will use the command function, keeping in mind that conceiving the drawing function as a standalone utility will allow replacing it with another which uses a more advanced technology at any time (see Part 2, Chapter 10). For this, changing the source file referenced in the project would be enough. It will only be necessary that the other function receives the same arguments.
Figure 2.10. Text drawing function.
The drawing of the number itself is done by the expression (command "._text"… The expressions appearing before and after it disable any running object snap that may be active at the time of executing the function (which could result in a wrong location for the text). Its setting, stored in the OSMODE system variable is read by the getvar function and assigned to the local variable oldosm, so we can restore that previous value (with the setvar function) once the text is placed. That this variable has a local scope is achieved through the structure the function parameters list adopts. This list is divided into two parts by a slash. The name old-osm that appears to the right of that slash corresponds to the variable that is used for storing the running object snap value when this function is called. The object snaps are here disabled by assigning OSMODE the value 0 and the value assigned to old-osm is used for restoring the previous situation once the text has been drawn. Once the previous running object snaps have been restored that value is no longer necessary, and including it in the right side of the argument list causes it to cease to exist as soon as this function has concluded.
That such a variable has a local scope means that it will not interfere with other possible global variables of the same name used by other programs. The programmer must know thoroughly the sequence of options corresponding to the command he wishes to execute. We must be aware that some commands use a different version when called interactively from the command line than when called from an AutoLISP program. To check the command’s behavior and options it should be called from an expression like (command "TEXT"). Regarding text, we must keep in mind that AutoCAD has two different commands for it, whose names are: TEXT and MTEXT. The difference is that MTEXT is able to write text in multiple lines while TEXT is only able to do it in one line. Since in our case a single line is enough we will use TEXT. One factor to consider is that our program could be used with any localized version of AutoCAD without language being an obstacle. So the rule followed in this book will be to employ the English name preceded by an underscore character, which makes it valid for any version. If you are using a non-English version, you can obtain this universal name by using the getcname function. So (getcname "texto") in a Spanish localized version will return "_TEXT" which is the name we pass to the command function. The getcname function works for command names, but not for their parameter names. As for the parameters, it is usually enough just to run the command in AutoCAD monitoring closely the data requested and their sequence. Usually, we say because the TEXT command has different behaviors if run from the command line or from a program. The difference is that when run from the command line, once a line of text is finished, the command keeps asking for new lines, while from a program it does not, stopping once it has received the text to draw. Apart from this detail, the command’s behavior is similar from the command line or from a program. We should also take into account that Text Style settings will also affect the arguments required by the _TEXT command. First of all we must consider if the Text Style includes a fixed text height. In this case the height will not be prompted for. Command: (command "TEXT") TEXT Current text style: "Standard" Text height: 2.5000 Annotative: Specify start point of text or [Justify/Style]: 100,100 Specify height : 5 Specify rotation angle of text : 0
As before, we should not proceed without loading and testing the new function. For that we will call from the console, once loaded anew the project, the
No
expression: (draw-text (getpoint "Insertion Point: ") 5.0 "TEST")
Pressing ENTER will activate the AutoCAD graphics window asking us to select a point. The label TEST will be drawn on that point of the screen with a height of 5 units.
2.5. The user interface. As we see, the program code so far is limited to little more than twenty lines. Communication with the user may require a fairly large amount of extra code depending on the type of presentation to which we aspire. The possible options with Visual LISP are: The command line. Dialog boxes programmed in DCL language. Windows dialog boxes programmed in .NET and compiled as DLL. Dialog boxes generated by the OpenDCL plug-in. Later we will examine in detail some of these possibilities. For now we limit ourselves to the simplest, using the AutoCAD command line. Even using the command line quite complex interaction mechanisms can be attained. In the chapter on that subject we will show more sophisticated solutions using the command line interface. In an effort to simplify this first program as much as possible, we chose to create two different input functions. The first one (see Figure 2.11), which we have called C:NUMERA starts a loop repeatedly requesting the next number’s position until the user ends it by pressing ENTER or ESC.
Figure 2.11. Function C:NUMERA.
The second one (see Figure 2.12), named C:NUM-OPTIONS is used to change the parameter values stored in the dictionary. This function requires the help of num-read (See Figure 2.13) that extracts the value of a text label in a drawing. Those user functions whose names start with C: create new AutoCAD commands that are invoked by typing the name in the command line without using parentheses as in other LISP functions. All these functions should be included in the numera-input.lsp file. The parentheses are necessary if you want to invoke the function from the VLISP console. In this case you need to type (c:numera).
2.6. Placing the labels. To place the numbers, after loading the program (for now we will do it from the project window), we type NUMERA on the command line. Immediately a message requesting the point of the screen where you want the number will appear. This
message includes the number to be inserted as a label. After selecting a position and placing the label, the figure will be increased and new locations will continue to be asked for until the user presses ENTER, the space bar, ESC or the right mouse button. The first thing we want to highlight in the code of this function is the use, as in the draw-text function, a variable of local scope. The name pt-ins shown on the right side of the parameter list is a variable that is used to memorize the coordinates of the point selected by the user. That value is not necessary once finished the program’s execution and so it then ceases to exist. The call to numera-dict ensures that the dictionary exists before the values stored in it are extracted. The user input for the text’s insertion point is managed by the getpoint function. This function will display the message Position for No. #: printed on the command line, where # would be the value of the number whose turn it is to insert. The preparation of this message involves reading the current number stored in the dictionary in integer number format so it will be necessary to convert it into a string data type using the itoa function so that it can be appended using strcat to the rest of the message. The setq function assigns to the pt-ins variable the value of the point selected on the screen by the user (or if typed on the command line with the X, Y or X, Y, Z format.) The repetition is ensured by a while loop whose condition is that the pt-ins variable has a value other than nil. Within the loop, actions take place as follows: The label is drawn with a call to draw-text, Passing the insertion point, the number (as a string) and the text height as arguments. The number stored in the dictionary is increased by calling (next-num). The user is prompted for a new point selection. If this point has a value other than nil, The loop is repeated.
2.7. Updating the dictionary. The C:NUM-OPTIONS function has been designed (See Figure 2.12) to modify the dictionary settings prompting the user for the new increment, text height and initial figure values. To define a new increment the getint function is called, taking into account that we do not wish to use decimals. In the case of text height, where it is advisable to allow the introduction of real numbers (i.e., decimals) we will use the getdist function which makes it possible to introduce the value by typing or by selecting two points on the screen whose spacing is equal to the new labels' desired height. Both for the increment value
as for the text height, pressing ENTER passes to the next option without changing the stored value. For the initial figure there are two possibilities: typing the value or selecting an existing label. This selection is an example of the use of keywords accomplished by calling in succession the initget function (to define the supported keywords) and getkword that only admits as input these keywords (or their uppercase characters) or ENTER. These user input fuctions are explained in detail in Chapter 7. uses a helper function named num-read (See Figure 2.13) that returns, from any selected text entity, its value as a string of characters. As in all input functions possible user errors must be taken into account. Therefore numread first checks if the selected object is a text calling the expression: C:NUM-OPTIONS
(= (cdr (assoc 0 (entget nom-ent))) "TEXT").
If not, it reports the error and prompts again for a selection. Another possible error is that the selected text or the typed value is not a number, which is verified by the following expression, which returns nil if the values are nonnumeric: (distof (cdr (assoc 1 (entget nom-ent)))).
A third abnormal condition would be that the user pressed ENTER. The latter is not a mistake but rather the signal to move on to the next option.
Figure 2.12. Function C:NUM-OPTIONS.
The decision to type the value or to select an object is managed by the cond conditional that checks first if the selected keyword was "Type", in this case requesting the value using the getint function. This function incorporates its own error detecting mechanism, so that we can be sure it will not admit an erroneous value (the only alternative admitted is ENTER, In which case no changes are made to the dictionary’s content). The other two possibilities, choosing "Select" or pressing ENTER lead to the same result. Therefore if the conditional’s first block is not executed, the next one will always be executed, this being ensured by including the constant T
which represents the status of true (as opposed to nil which represents false). As before, we will load the project for testing it. After loading the project we can change to AutoCAD (by pressing the Activate AutoCAD button from the View toolbar) And type NUMERA to run the numbering and NUM-OPTIONS to change the parameters. After placing some text labels we can save the drawing so we can check how, on reopening it, the parameters have been stored. If no errors occur, it’s time to compile our program.
Figure 2.13. Function NUM-READ.
2.8. On error… While debugging is left for a subsequent chapter, it is inevitable that some error has crept in while typing the code. Basically, two types of errors will occur. Those that the compiler detects when loading the project and which are almost always due to errors in the location and number of parentheses, and those that are detected when you attempt to run the program. In the first case, on loading the project we will receive messages in the Console as shown in Figure 2.14.
Figure 2.14. Error messages when loading the project.
Observing these messages we will see that draw-text.lsp and numerainput.lsp were loaded without problems, so we know where to look for the error, in numera-calculus.lsp. The message Malformed list on input usually means that one or more closing parenthesis (right) are missing. If instead we would have a closing parenthesis in excess we would receive the message Additional input right parenthesis. The search for these brackets can be an arduous task for a novice. We have an aid in the way the editor automatically sets the indentation of new lines. If the resulting format is not the same as the code listings shown in the book we can suspect that we have an error in some of our parentheses. Two other ways are to use the Check edit window or Format edit window buttons having selected the file you want to check. If there is an error, In the first case the erroneous lines will be highlighted in the window and in the second case, if there are parentheses errors we will receive a warning message offering to fix the error. This usually will not solve the problem, but if we select No the text file will be highlighted beginning with the function where the error is located.
Figure 2.15. Warning about a parentheses error when formatting the code.
Another type of error can occur when trying to run the program. We have already discussed how to quit from the break loop in which we automatically enter. In situations like this we have a highly effective mechanism to identify where the error lies. It is the Error Trace which can be activated from the View menu. In this window you can see the sequence, ordered from bottom to top, of the functions called. The one which caused the error is in the second row
just below the keyword :ERROR-BREAK. To find the exact place we select this line and right-click to access the contextual menu where we can select the Call point source option which highlights the offending expression in our editor. If we encounter any run-time error it would be good opportunity for a first contact with this powerful tool.
Figure 2.16. Error Trace window with its contextual menu.
2.9. Compiling the program. After checking the correct operation of the program will proceed to compile it. This will fulfill several purposes. First, it will combine the three source code files into a single program file. In addition, our code will be encrypted so that users can not know the means we used to achieve our goals. This is essential if we wish to sell our work, or simply to avoid any modification of the code by inexperienced persons that may adversely affect its performance. Moreover, loading and executing the program will surely gain in speed. Compiling the program is as simple as selecting the Build Project FAS button in the project window . We should study carefully the messages presented in the window, which appears on compiling. This window contains detailed information about the process that will be explained in detail in a later chapter. Examining at the FAS folder we see that a new file named numera.fas has been created, which is the compiled program. In the TEMP folder a number of files with .ob and .pdb extensions remain, representing intermediate steps of the compilation process. To load the compiled program we can use the Project window’s Load Fas button. Once the compiled program is loaded we can use it in the same way as
the project whose source code we had loaded. To install this program in other computers just copy numera.fas to a folder that is in one of the AutoCAD support files search paths and type in the command line the expression (load "numera"). It can also be loaded using the Load Application button from the Applications group on the ribbon’s Manage tab or selecting the option of the same name from the Tools menu in the Classic Workspace.
Figure 2.17. Load/Unload Applications dialog box.
2.10. Demand loading the program. Once we have our entire program in a single file we can take advantage of the demand load option for programs. Demand loading means that our program will not take up memory space when not in use and will be automatically loaded when typing the command name, in this case NUMERA or NUM-OPTIONS. To accomplish this, we will resort to the acaddoc.lsp file that, as we instructed, was created in the Visual LISP projects folder. This is a special file
that will be loaded automatically for each drawing3. We will include in this file an autoload expression enabling the demand loading of program files. The AutoLISP autoload function provides access to commands without loading the full routines in memory. It takes two arguments: the name of the program file and a list with the command names defined in it (without the C: prefix), all as strings. (autoload "program-name" '("command1" "command2" ...))
If it is a LSP file it is not necessary to indicate the extension. But in our case the program commands are divided into three different .LSP files. In this case you must load the .FAS which is where on compiling all the code was grouped in a single file. But when, as in this case it is not a LSP file we'll have to include the extension. As the file numera.fas file is in the FAS folder within the Numera folder, it necessary to include its relative path also. Adding the following expression to the file acaddoc.lsp our programs NUMERA and NUM-OPTIONS will be available for each new drawing we open: (autoload "./Numera/FAS/numera.fas" '("NUMERA" "NUM-OPTIONS"))
Notice how the relative path is expressed: the Visual LISP Projects folder is the root folder and is represented by a dot. The path delimiters are slashes ("/"). In case that on opening a new drawing and typing NUMERA we were informed that: The file ./Numera/FAS/numera(.lsp/.exe/.arx) was not found in your search path folders. Check the installation of the support files and try again. we must first ascertain that our Visual LISP Projects folder is effectively included in the AutoCAD support files search paths and that the specified path is the correct one. And for Release 2014 that all the folders containing the code are included in the Trusted Locations. For this application to load in AutoCAD 2014 we would have to include the Visual LISP Projects/Numera/FAS folder to these Trusted Locations. In case we don't do so a Security concern alert box will be displayed (Figure 2.18) where we can accept or reject loading this program.
Figure 2.18. Warning about loading the application if not in a Trusted Location.
2.11. Summary. This tutorial has served to introduce us to some of the key tools of the Visual Lisp Development Environment (VLISP IDE): The Project The Editor The Visual LISP Console The Apropos utility The Error Trace window The Compiler For those familiar with classical AutoLISP, using the Dictionary object as a means of preserving the values of the program's parameters will have surely revealed the new possibilities that Visual LISP ActiveX extensions provide .
Exercises. Although we have not really started studying the programming language the reader may try his hand at extending the functionality of the application developed in this tutorial. Exercise 1. Redesign the drawing function so that the texts are justified with the MIDDLE option. The reader will recall that it is enough to pass to the command function the additional parameters that would make this adjustment when invoking TEXT from the command line. As a guide the command sequence is shown as it would appear in the AutoCAD text screen (see Figure 2.19).
Figure 2.19. Sequence for the TEXT command.
Exercise 2. Expand the program to include drawing a circle centered at the text insertion point and with a radius equal to 2 times the height of the text. The drawing of the circle will be implemented in a new function named draw-circ to be executed after draw-text. By supplying as arguments the coordinates of the text's insertion point (assigned to the local variable pt-ins) and the value of "HEIGHT" multiplied by two: (* (vlax-ldata-get "VARS-NUMERA" "HEIGHT") 2)
The options for the _CIRCLE command are: Command: CIRCLE Specify center point for circle or [3P/2P/Ttr (tan tan radius)]: Specify radius of circle or [Diameter]:
Excersise 3. The text height saved to the dictionary will be the value the TEXTSIZE system variable had when the dictionary was initially created. The program does not include an option for modifying this value. You can add this option to the C:NUM-OPTIONS function by prompting the user for a new value, in this case using the getreal function: We can use the same structure used for the increment data, but in this case we must take into account that the numeric value is a real number (see Chapter 4, section 4.5) so we must convert it to string using rtos. (setq option (getreal (strcat "\nNew text height : "))) 1 To display this dialog box we can use the graphic window’s contextual menu or type the OPTIONS command.
2 In the latest releases of AutoCAD, Help files are in HTML format, and by default the files hosted on the Autodesk server are used. But the installed configuration only searches for files in the application's language. Therefore users of localized versions will not have access to the AutoLISP reference. The only solution for them is disabling the online help. This is done in the System tab of the Options dialog box, deselecting the "Access online content when available" checkbox. 3 A file named acad.lsp, if found, will be loaded when the AutoCAD session starts. The functions loaded from it will be available only for the first drawing. A vl-load-all expression in an acad.lsp file will make sure that a LSP file will be loaded to every document opened in that session. But an autoload expression must be called on initializing each drawing. This is the reason we are using it instead of the acad.lsp file. Setting the ACADLSPASDOC system variable to 1, the acad.lsp file will be loaded into every drawing, its behavior then being the same as acaddoc.lsp.
Part 2 The Language and its Development Environment This part of the book includes the essential information needed to program with Visual LISP. This information relates primarily to the operation of the two most frequently used tools in the Visual LISP Integrated Development Environment: the Console and the Editor. We will use these two tools in learning the programming language.
This is a book that addresses a wide range of readers, from those without expertise in programming to those who, skilled in AutoLISP programming seek information about how profit in their work from the new features that Visual LISP has been adding to AutoCAD since release 2000. In this part of the book we intend to describe the language’s fundamental principles, not the detailed syntax of each of each of the functions used or the programming methodology. It should be read carefully as it certainly is a sometimes dense text, but the ideas proposed therein will serve to further understand the sometimes complex programming techniques that in some cases it will inevitable to propose. For starters, the study of this part of the book is an absolute necessity to understand the rest. However, it is also a recommended reading for those with expertise in the subject, as we address here basic conceptual aspects of the language whose understanding will lead to more effective programming. In any case, we consider that the study of Chapter 6, ActiveX Data Structures is essential for even the most experienced reader using AutoLISP versions prior to AutoCAD 2000. On a first reading, this part of the book certainly poses issues whose immediate understanding is not easy. Experience acquired in the development of programs oriented to solve practical tasks proposed in the following chapters will allow deeper second readings in which the wealth and possibilities of LISP as a programming language as will become increasingly evident. First of all, we insist on the fundamental mechanism of learning we recommend: try each one of the expressions and carefully observe its effects. This is how we learned and also how we wrote this book. It is easy to see that much of the text shows evaluation results copied directly from the console and programs written and formatted in the editor. The whole book, but especially this part, is designed to be followed on the computer, using the Visual LISP IDE. In Chapter 4 the Console will be used and from Chapter 5 on we will begin to create source code files in the Programming Editor that will configure a code modules library designed for their use in everyday programs such as those shown in the subsequent parts of this book and in the additional examples the reader can download from our website or other AutoLISP Internet sites. Each chapter also includes a summary as a reminder of the concepts that the reader will have acquired in their study and, where appropriate, self-assessment exercises are proposed.
Chapter 3 The Visual LISP IDE The Visual LISP Integrated Development Environment (VLISP IDE) includes tools that assist in writing, modifying and debugging the source code. Now we can, once we have coded the program using the Programming Editor, check its operation step by step, viewing at all times the output returned by the evaluation of each one of the expressions and the value the variables are assuming without having to include any additional expressions in the program. It also provides the means to package and distribute applications in a single executable file that contains the DCL dialog interface. These operations are performed within a single environment, allowing text editing and program debugging, plus interaction with AutoCAD and other applications.
Figure 3.1. Visual LISP Application Window
3.1. The Visual LISP IDE user interface. Visual LISP (see Figure 3.1) has its own set of windows and menus, but cannot run independently of AutoCAD. A working session is started by typing vlide or
on the command line. When activated Visual LISP shows the following components: vlisp
The Menu Bar: VLISP commands can be issued by selecting menu options. By placing the cursor over a menu item, a brief description of the action of this command appears in the Status bar at the bottom of the screen. Active options on a menu may vary depending on which window (for example, the Editor or the Console) is active at the time. The Toolbars: The toolbar buttons allow launching many of the VLISP commands. There are five toolbars: Debug, Edit, Search, Inspect and Run representing each of the different features available. Moving the cursor over any button and leaving it still for a few seconds, displays a help message (tooltip) Indicating its function. Further explanation also appears in the status bar. Console window: It is a separate window, where we can type Lisp expressions, as in the AutoCAD command line. Many VLISP commands can be launched from this window rather than using the menu or tool bars. The Console is the tool we will use for learning the fundamentals of the programming language, as it allows us to evaluate and immediately obtain the result of any LISP expression. The Status Bar: Is located at the bottom of the screen. The information displayed varies according to the task being performed. The Programming Editor: It is designed especially for use with AutoLISP code. For each source code file a new Editor window opens with the name of that file in its title bar. The name also appears in the status bar. An asterisk (*) next to the file name in the status bar indicates that you have made changes that have not been saved. Other Visual LISP Windows: Some outputs are displayed in the Console window, but certain functions create their own windows to display theirs. Some look like the console, for example when you search for text in files of a project or when a program is compiled. These windows cannot be written on, but you can copy the text that appears in them and paste it in the editor or the console.
3.2. Interactivity: The Visual LISP Console. From the Console, you can run LISP expressions to check their outcome. This can also be done from the AutoCAD command line but there are several
differences in how the two operate. For example, to display the value of a variable in LISP, we simply type the name of the variable in the Console window and click ENTER. But to obtain the value of a variable in the AutoCAD command line, we must precede the variable name with an exclamation point (!). In the Console window messages appear in case of errors and also the value returned on evaluating many expressions. For example, it shows the outputs of the print and princ functions.
Console Operation. We will use the Console to learn the basics of LISP. During this process we will use many of the techniques outlined below. Evaluating expressions. The main function of the console is to evaluate LISP expressions. The symbol _$ indicates the Console is waiting for an expression to be evaluated. The expression can be typed following this symbol. The text you type into the console is not evaluated until you explicitly press ENTER. To continue a LISP expression on a new line avoiding its evaluation you can use the key combination CTRL+ENTER. Repeating expressions. The typed text and the evaluation outputs are retained by the Console window. To re-evaluate an expression we can just press TAB. This will display on the current Console prompt line the last expression evaluated. Pressing ENTER will repeat its evaluation. If instead of repeating the last expression we should wish to repeat a previous one we can press TAB repeatedly, which will cycle through all of the expressions evaluated during this work session. Once the first expression is reached, the cycle restarts. With SHIFT+TAB we cycle the expressions in reverse. The TAB key can also be used to perform an associative search in the set of expressions for this session. To do this, type the text you want to locate pressing TAB later. If there is a previous expression that includes that text, it will appear in the current Console prompt line. Other way to repeat a previous expression would be to select it among those memorized by the Console window (which can be scrolled using the vertical
scroll bar) and pressing ENTER. This will copy the selected expression to the current Console prompt line, ready to be re-evaluated. Discarding expressions without evaluating them. If we start to write an expression in the console and decide not to continue, we can delete it by pressing ESC so it will not be saved in the session history. But if we wish to retain the expression but without evaluating it, we can press SHIFT+ESC. Although not evaluated, the expression is stored in the session history so you can retrieve it by pressing TAB. Completing words. In many ways the console operates just as the Editor. One such aspect is the functionality of the Spacebar in combination with the CTRL and SHIFT keys. Once you have started typing a word it can be completed automatically, either by resemblance to other terms used in previous expressions pressing CTRL+SPACE, or by a symbols table lookup by pressing CTRL+SHIFT+SPACE. By using the complete words by resemblance tool (CTRL+SPACE) VLISP completes a word only partially typed by comparing it with other words in the same window. If the word shown is not the desired one you can continue to press this same key combination until the required word appears. If it is a symbol (function names or variables used in the program or in the Visual LISP environment) the word can be completed with a name that exists in the symbol table without requiring that it had been used before in the console. In this case the combination CTRL+SHIFT+SPACE is used or you could select the Complete word button (See Figure 3.4) of the Standard toolbar.
Figure 3.2. Search with CTRL+SHIFT+SPACE.
This will display a contextual menu (see Figure 3.2) that includes the symbol names found. Selecting one of the names will complete the word. If no symbol has been found, the Apropos Options window will open. If the number of
possible names found is greater than 15, instead of the window shortcut menu the Apropos results window appears. In this case the symbol could be copied to the clipboard by selecting the corresponding option from the window’s context menu. This functionality is also available for the Editor windows.
Console contextual menu.
The most important VLISP console features are included in the menu that appears when you right-click (or SHIFT+F10) anywhere on the Console. According to the text selected in the Console window and the cursor’s position, some commands not relevant to the situation are disabled. The features invoked by these commands will be explained in the following chapters of this book. Table 3.1 shows all of the available commands in the console’s contextual menu, also indicating the equivalent Standard (Figure 3.4) Or View (Figure 3.9) toolbar buttons and the equivalent hotkey combination
Syntax color coding. Another aspect in which the console works similarly to the Editor is in the color coding of the different parts that make up a LISP expression. The color coded expression is the one that will be evaluated on pressing ENTER. Once evaluated, the color coding disappears.
AutoLISP functions helpfile. If we select a function name anywhere in the Editor or the console and press the Help button on the Tools toolbar (See Figure 3.6) VLISP shows the help entry available for this function. The same effect is achieved by pressing CTRL+F1. This functionality is also available in the Editor.
The Console and AutoCAD drawings. There is a single console for all open AutoCAD drawings. Scrolling the Console we will find expressions and commands that have been issued in the context of each of these documents. In this it differs from the command line that only shows those related to the current drawing. VLISP automatically changes context on activating a different drawing. The active AutoCAD drawing is always the active document in VLISP. The expressions you type in the Console always have an effect on that drawing. The VLISP window's title bar shows its name . If you switch to the AutoCAD window without evaluating an expression already typed in the console, that expression will be visible in it, but a new line will have been generated on returning to the IDE and the typed expression cannot be recovered by pressing TAB. In cases like this you can highlight the expression and press ENTER, which will automatically copy it to the current Console line. To select a LISP expression the easiest way to is double-click next to the
opening or closing parentheses, this way the entire expression will be highlighted.
3.3. The Programming Editor. If you only need to run a few AutoLISP expressions the Console might be enough. But in writing programs of some complexity it will be essential to use the Programming Editor as a way to save the source code into a permanent file. The Editor is along with the console, one of the key components of the Visual LISP environment. The Editor as well as the Console, is designed to assist in AutoLISP programming and has the ability to complete expressions, locate the matching opening and closing parentheses, syntax coloring and evaluating expressions without leaving the Editor window. In contrast to the Console, pressing ENTER generates a new line in the Editor. The new line is automatically indented. The Editor is also able to format the code according to different user selectable styles. In general, besides syntax color coding and automatic indentation, the Editor provides other utilities that assist in program development. Some of these utilities are: Checking the parenthesis closure. The ability to insert expression-closing comments. Commenting and uncommenting text blocks. Finding and replacing text. Syntax checking. Loading of LISP expressions to be evaluated in the Console. VLISP IDE allows opening any number of files. When finishing a work session, information is saved for open files which will be reopened automatically when you start a new session. Editor windows do not have their own Menu or Toolbars. When an Editor window is in focus, the Menu options and VLISP IDE tools that can be used in Editor operations will be enabled. Some of the options can also be run from the contextual menu that opens on right-clicking your mouse. You can use the usual text editor keyboard shortcuts as well as some others unique to this Editor. In the following sections we will explain these features as they are accessed from the Toolbars, the Contextual Menu and Keyboard shortcuts.
Toolbars. Visual LISP has five toolbars which can be activated or deactivated from the dialog box (see Figure 3.3) That is opened from the View>Toolbars... menu option. These five Toolbars include the essential IDE commands. For the time being we will only use the Standard, Search and Tools toolbars. The commands available in the Debug and View toolbars are used in the debugging process to which we will devote a chapter later on.
Figure 3.3. Toolbars dialog.
Figure 3.4 Standard toolbar.
Standard toolbar. It contains the usual Windows commands like New File, Open File, Save File,
Print, Cut, Copy, Paste, Undo and Redo. Undo recovers previous editor states. You can undo an unlimited number of operations, up to the last time you saved the Editor contents to disk. Once saved you cannot undo. In this case, if needed, the backup copy could be restored using the File>Revert menu option. Redo is used to cancel Undo operations, but it should be used immediately after it. Besides these most usual operations, the Editor allows dragging text from one location to another within the edit window. To do this, you must highlight the text and left-clicking on any part of the selection, drag it to the new location. In the same way it can be copied by pressing the CTRL key before releasing the left button. You can also save the selected text into a new file by pressing CTRL+E+Q. Complete word completes a partially entered word with a matching symbol name from the VLISP symbol table. This functionality is also available for the console. Search Toolbar. This toolbar includes commands for locating and bookmarking text in the Editor windows. Find and Replace (See Figure 3.5). The search for an expression can be performed both in the file being edited as in a project's files or in the files contained in a given folder (and its sub-folders). The search output is displayed in a new window called presenting the expressions where the searched term is used. The search tool can be configured so it will place bookmarks each time it finds the searched term. To enable this you should select the Mark instances checkbox. Each editor window has its own set of bookmarks, so this option will be effective for successive searches in a single file. The bookmarks are managed using the buttons described below. To the right of the Replace button a drop-down list box is displayed where previously searched terms are saved during the work session allowing to repeat searches by pressing the Find toolbar string button next to it. Toggle bookmark places a bookmark at the cursor line. Up to 32 bookmarks can be placed in an Editor window. If you have already bookmarked 32 positions, the next position to be bookmarked removes the oldest one. Next bookmark and Previous Bookmark cycle through the existing
bookmarks. Clear all bookmarks. Deletes all bookmarks in the current window. When you format a text that contains bookmarks, they move to the start of the block selected for formatting. If we format all of the Editor window's content, we lose the bookmarks, remaining only one on the window's first line, fact that we must take into account if we use this tool. Tools toolbar. The Tools toolbar (See Figure 3.6) is enabled only if the Editor window is active. Its commands are, from left to right: Load the code in the active Editor window so it can be executed. Load only the selected code block. Check the syntax of the active Editor window's content. Check only the selected code block. The syntax checking results are displayed in a new window that bears the title of . If an error is detected, the affected code will be displayed and highlighted in this window. Double-clicking on the highlighted line in the Build output window, the cursor will move to the line of code in the Editor window where the error was detected highlighting the text. Not only are obvious mistakes signaled but also some situations that should be handled with care as they may be, in certain cases, a source of errors. Format Edit window. Format selection. The Visual LISP code formatter organizes the content of the Editor's active window to improve its appearance and readability. Although intelligent indentation organizes the program code as the text is typed, it can also be explicitly invoked with the Tools toolbar buttons (See Figure 3.6) to rearrange the selected blocks or the entire Editor text . If the formatter finds an unbalanced parenthesis, it displays a warning message and offers to add the necessary parentheses. But rarely will these parentheses be placed correctly, so it is better not to accept this offer and look by oneself where the mistake has been made, which we should do starting from the portion of code that remains highlighted in the Editor’s window. There are different styles for the automatic formatting depending on the settings of several environment options. The format we used in the book’s code listings
are obtained with the settings shown in Figure 3.7.
Figure 3.5. Search Toolbar.
Figure 3.6. Tools toolbar.
To set these or other options select: Environment Options>Visual LISP Format Options... in the Tools menu. The user can experiment with these
options until a presentation of the source code that is comfortable to work with is found. Many professionals use a format similar to this book’s code listings, which for us is also imposed by the need to make it more compact, although for beginners it is often clearer to enable the Close at new line with outer indentation and Insert form-closing comment options. This comment can be customized by inserting a prefix (e.g. end of) in the edit box provided for this. These formatting options were used in the screenshots for the tutorial in Chapter 2. Comment block. Uncomment block. The comment marking introduced by the Comment block button corresponds to the three semicolon character style (;;;) that is aligned with the left margin with the automatic formatting option. In DCL files, a double slash (//) is used for commenting a line . Online help for the function on which the cursor is placed. Formatting keyboard shortcuts and contextual menu. Typing CTRL+E while an editor window is active or selecting the Edit>Extra commands menu option displays the formatting menu displaying the list of options shown in Figure 3.8. These options enable a series of operations related to the formatting of text in the editor. They have the associated shortcut keys shown, with a description of their operation, in Table 3.2.
Figure 3.7 Visual LISP Formatting Options.
Figure 3.8. Formatting menu.
View toolbar. The View Toolbar (See Figure 3.9) allows us to access AutoCAD and other specialized tool windows in the IDE. It is also available when the focus is on the Console. Most of the buttons on this toolbar are used during the debugging process. Here we limit ourselves here to describing the use they would have in the editing stage. The first button is used to access the AutoCAD graphic window. The second button (Select Window) Opens a menu where you can select the Visual LISP IDE window we want to set in focus. This is convenient because we can simultaneously open a large number of files and locating the one we wish to edit may not be easy. The third button sets the focus on the console. For the time being we will only use the second last button, Apropos.
Figure 3.9 View Toolbar.
The Apropos button opens the Apropos results window, a tool that searches the system symbols table. This table contains each of the symbols read by AutoLISP in the current session. This includes both the symbols of user programs and those implemented by the language. Apropos is a very effective way of checking function names and accessing the online documentation. Specific search criteria can be defined. For example, searching for all symbols that contain a particular string and even refining this search so that only those that identify functions are returned. If no text is highlighted a dialog box is displayed for typing the text to search. Other way to restrict the search is to establish that the search string should be the symbol name’s prefix. You can also opt for a search using wildcards as done by the wcmatch function. The Apropos results window (See Figure 3.10) has three buttons that call the following actions: Returns to the Apropos options dialog box to refine the search Copies the results of the search window content to the Trace window from which you can select text and copy it for use in any Windows application. If Trace logging is enabled, the contents are also copied to the log file. To enable logging activate the Trace window, and specify a log file name in the file dialog box displayed by the File>Toggle Trace Log... option from the File menu. Opens the on-line Help item which refers to the selected function name.
Figure 3.10 Apropos results.
The contextual menu enables copying the symbol name to the clipboard, opening the Inspect window for the selected symbol, print it in the Console, opening the Symbols service, copying it to the *obj* global variable and adding it to the Watch window so as to monitor its values at runtime. The Help option displays the online documentation (if any) for the selected symbol.
Editor contextual menu. Right-clicking anywhere in the Editor window opens a contextual menu in which, according to whether or not any text is selected, a number of often used Editor commands will be enabled. Some of these options are equivalent to those of the same name described for toolbars.
Automatic backups. Visual LISP backups the files opened in the Editor. This backup is done on saving the file for the first time. The backup has the same name as the file and its extension starts with an underscore sign (_) followed by the first two characters of the original extension. So a backup with a ._LS extension will correspond to an AutoLISP source file with an .LSP extension. Creating backups is an option that can be enabled or disabled from the Tools>Environment Options>General Options menu option. If a backup exists for the file we are editing, its original content can be restored thereby discarding all changes. To do this the File>Revert menu option should be selected.
3.4. Interaction between the Editor and the
Console. In the Console we evaluate expressions. We can operate in the Editor in a similar way, although it is necessary to highlight the differences. There is only one Console always available, while working in the Editor we must explicitly open the windows we need. It is possible to evaluate expressions from the editor as we do from the Console. We can evaluate any expression typed by selecting it and clicking the Load Selection (CTRL+SHIFT+E) button. The result of this evaluation will be displayed in the Console. The Load active edit window (CTRL+ALT+E) button is designed to load programs. For this reason, although the expressions are evaluated, in this case the output does not appear printed out on the console. You can also select one or more expressions in the editor, copy (CTRL+C) and paste (CTRL+V) them in the Console. Then by pressing ENTER they would be evaluated. Similarly, an expression in the console, once verified its operation, may be copied and pasted to the Editor as part of a program.
3.5. Summary. To start programming in Visual LISP two are the tools we must master: the Console and Editor. The simultaneous use of both, Console and Editor, is one of the keys to success in Visual LISP programming. Working in the Visual LISP Console we can understand why we say that LISP is an interactive language. If we type an expression in the Console, we'll obtain its value immediately. This way we can check the expressions that afterwards we will include in our programs. It is in the Editor where we establish the final form of our programs, saving them to the data storage units of our system.
Chapter 4 Evaluating expressions The Console is the tool, along with the Editor, that we shall use the most in the development of our programs. It is also the ideal means for learning LISP. Often we will try out an expression directly in the console taking advantage of the possibility to repeat again and again that evaluation making each time the necessary changes, either to the syntax of the expression or to its arguments. Once we find the form that achieves the intended goal, we can simply copy it to the Editor window in the position it will occupy in the finished program. In this and the following chapters we will use the term LISP to refer to those concepts, procedures and functions that match the specifications of the ANSI Common LISP standard. In the case of those that are specific to AutoCAD we will use the term AutoLISP when it refers to issues already present in release 14 or lower, and Visual LISP to those introduced in this programming environment since release 2000.
4.1. Data. Numbers are among the simpler LISP expressions. We type a number in the Console, for example 5. This number is colored green. We then press ENTER. _$ 5 5 _$
On a new line number 5 is printed. The number of the previous line loses its green color. The number on the new line is also not colored. The new _$ prompt shows that the console is ready for a new expression. Let’s explain what has happened. We have passed data to LISP. This data is of a specific type. The green color indicates a numeric data of integer type1. We shall see that other data types are identified by different colors. The 5 that appears when you press ENTER indicates a fundamental aspect of numeric data: numbers represent themselves. So when Lisp reads and evaluates 5 it should not return anything but 5. The color is only present in the line waiting to be evaluated. It is not possible to type text in previously evaluated lines or in those containing the returned results.
If instead of an integer we enter in the Console a number with decimals we see that the color is teal (a greenish blue). This indicates that this is a real number instead of an integer. As in any numerical data, the value returned is the same number. _$ 10.5 10.5 _$
When it comes to extremely large real numbers VLISP represents them using the exponential notation2. _$ 1797693134862315 1.79769e+015
When the maximum limit for the real numbers is exceeded VLISP returns 1.#INF which represents infinity. _$ 1.797693134862316e+308 1.#INF _$ (/ 1.797693134862315e+308 1.797693134862315e+308) 1.0 _$ (/ 1.797693134862315e+308 1.797693134862316e+308) 0.0
A real value can be calculated with an accuracy of up to fourteen digits. The printed representation in the console or in the AutoCAD command line truncates real numbers to five decimal places, although in calculations their highest precision is employed. To display the desired number of decimal places the number can be converted into a character string using the rtos function which is discussed in the section dealing with data type conversion. Another type of LISP data represents itself. This is the String data type that represents an alphanumerical value. _$ "abc" "abc" _$
Strings are identified by being enclosed in double quotes. Quoted text is colored in magenta. The returned value is the string itself.
4.2. Expressions. We can use numbers to perform arithmetic operations. For example if we wish
to obtain the result of adding 5 and 4: _$ (+ 5 4) 9 _$
Other colors appear. Blue for the + sign. The blue color in any expression indicates that the name identifies an AutoLISP protected symbol. This means that the system will consider trying to assign it a value other than the predefined one a mistake. Visual LISP intercepts any attempt to assign a different value to a protected symbol and displays a dialog box warning about it (see Figure 4.1). Selecting No the new value will overwrite the original, which is not recommended. On clicking Yes, we will enter a Console break loop. To exit this loop you can press CTRL+R. The topic will be discussed in detail when we explain error debugging.
Figure 4.1. Assignment to protected symbol warning dialog box.
Usually, though not exclusively, the blue color indicates that this is an operator or primitive function, meaning that (unlike the new features that the user can define) it is included with the system. Among the AutoLISP protected symbols which are not functions we have pi which represents the constant 3.1415..., pause that stops the execution of a program to wait for a user response to an AutoCAD command and T which is the symbol representing true. In expressions such as (+ 5 4) we say that the function + is applied to the other terms that make up the list to obtain a definite result. In this case the addition of numerical values. It appears as the first term of a list. LISP lists are enclosed in parentheses that the IDE colors red. The operator or function is always first term of a list. The remaining terms in the list are called arguments.
This way of organizing the terms, putting the operator at the first term of the expression is called prefix3 notation. The usefulness of this notation is understandable when we must use more than two arguments. Normally if we add three values we should repeat the operator twice: 5 + 4 + 3, while with LISP a single operator will be enough: (+ 5 4 3). In fact, the + function can receive any number of arguments, or none. Precisely the fact of being able to receive any number of arguments requires using parentheses to define where each expression begins and ends. _$ (+) 0 _$ (+ 1) 1 _$ (+ 1 2) 3 _$ (+ 1 2 3) 6 _$ (+ 1 2 3 4) 10 _$
Let us examine more closely how the expression evaluation process takes place. In LISP, + is a function and an expression like (+ 5 4) is the function call. When Lisp evaluates a function call, it does so in two steps: First, arguments are evaluated from left to right. In this case the two numbers returned themselves as values: 5 and 4. The argument values are passed to the function represented by the operator, in this case the adding function +, which returns 9. In the introduction we saw how each argument in turn may be a new expression in list form, which leads to the possibility of an unlimited number of nested expressions: (* 8 (/ (+ 3 17) 4))
The evaluation will start with that expression in which none of its terms is a list, In the example above the expression (+ 3 17). If an expression is not a list we call it an atom. All of LISP expressions are either atoms or lists. Atoms as the symbol + and integers 3 or 17, or lists consisting of zero or more expressions enclosed in parentheses. While any of the arguments is a list headed by a function, it will be evaluated following the same rule: from left to right, passing the values obtained to the function that in turn will return the resulting value. The term form is applied to any LISP expression, list or atom, apt to be evaluated.
It is important to mind the closure of parentheses. When we type a closing parenthesis on the console or in the Editor, the cursor jumps momentarily to its matching opening parenthesis. If we double-click to the right of any closing parenthesis the expression will be highlighted up to the matching opening parenthesis. If a closing parenthesis is missing, the Console will display on pressing ENTER a special symbol indicating the number of missing parentheses. For example, ('(_> indicates the lack of two parentheses. When using numeric arguments it is important to decide if we want to work with integers or real numbers. We must remember that when working with integers the decimals will be truncated: _$ (/ 5 2) 2 _$ (/ 5.0 2.0) 2.5 _$
This means that the resulting values will not be the same. It is important to remember that the conversion to integers is produced by truncation and not by rounding to the nearest number: _$ (/ 100 26) 3 _$ (/ 100.0 26) 3.84615 _$ (/ 100 30) 3 _$ (/ 100 30.0) 3.33333 _$
In the above expressions we can see that it is enough that one of the arguments is a real number so that the output belongs to that same data type.
4.3. Symbols and assignment. Let’s see what happens when we type only the sign +, not including it in a list: _$ + # _$
We obtain the value represented by the + symbol, which is none other than the internal identifier of one of the language’s own subroutines. The concept of symbol is very important in LISP. A symbol represents something. Numbers represent themselves. They cannot represent anything else. Thus the name of a LISP symbol can include numbers, but cannot be composed only by numbers. It must always include letters or some other non-numeric character. The following are valid symbol names: a a5 xyz *5*. For the same reason a symbol name will never include quotes4. The symbols colored in blue have a meaning which is assigned by the system. The other possible symbols will not have a value until the user assigns it. If we type in the Console the symbol names given above we obtain the following result: _$ a a5 xyz *5* nil nil nil nil _$
We see how pressing ENTER after typing more than one expression on the same line of the Console, Visual LISP evaluates each of them in the order they were typed and then returns their results in successive lines. It has been nil in all cases. The LISP symbol nil is equivalent to nothing. The function we use to assign a specific value to a symbol is setq. Thus the expression (setq a 100) will assign the integer 100 as the value of the symbol a. Once we have done this, evaluating a will return 100. _$ (setq a 100) 100 _$ a 100 _$
If we examine the allocation process using setq we observe that the evaluation rule explained above is not followed here. Of the two arguments passed to setq the first one, the symbol a was not evaluated5. In fact setq is the combination of two other functions: set and quote. The quote function returns its argument without evaluating it. _$ (quote a) A
_$ a 100 _$
We see that (quote a) returns A in upper case. This reveals another LISP feature: internally all the symbol names are converted to uppercase. Therefore it is indifferent to use upper or lowercase characters in them. the evaluation of a outside the quote still returns 100. The set function, like setq, assigns the value of its second argument to the first one, but in this case the first argument is always evaluated. This is avoided by using quote. The following expressions have the same effect: _$ (setq a5 "ghi") "ghi" _$ a5 "ghi" _$ (set (quote a5) "mno") "mno" _$ a5 "mno" _$
as we can see in the example above, is an abbreviation of (set (quote that saves code typing. Also quote can be abbreviated by using the apostrophe (') character: Setq
_$ (quote a5) A5 _$ 'a5 A5 _$
The apostrophe character is colored brown, which avoids confusion with similar characters such as accents. As seen from their results, the above two previous expressions are identical. has a rather exceptional use when you want to assign a value to a symbol that is represented by another or resulting from the evaluation of a particular expression: Set
_$ (setq b 'c) C _$ (set b 250) 250 _$ b C
_$ c 250 _$
Note the use of quote in the expression (setq b 'c). Without quote we would be assigning b the value nil. The integer 250 is assigned not to b, but to c which is the value represented by the symbol b. Two further aspects to consider: can receive any number of pairs of arguments . Each argument will be assigned the argument that follows, processing the pairs as usual, from left to right. Symbols representing numerical values can be used in an expression instead of the represented numbers. Setq
_$ (setq a 20 b 10) 10 _$ (/ a b) 2 _$
4.4. Lists. Lists are LISP’s most characteristic data type . They are expressed as zero or more elements enclosed in parentheses. The elements of a list can correspond to any data type, including other lists. We have seen that function calls take the form of lists. When we use a list as data we must precede it with quote to prevent the system from confusing it with a function call. The following expressions are lists. The first one composed exclusively by numbers, the second one by symbols and the third one mixing different LISP objects such as the number 3, the symbol three, the string "abc" and symbols d and e. _$ '(1 2 3 4 5) (1 2 3 4 5) _$ '(a b c d e) (A B C D E) _$ '(3 three "abc" d e) (3 THREE "abc" D E) _$
Observe two things: the symbols are converted to uppercase to be printed by the system, but not
so the string; a single initial quote protects the entire list from evaluation. If we wish to build a list from expressions that should be evaluated we cannot use quote. In that case we should employ the list function which returns a list including the result of evaluating its arguments: _$ (setq a 20 b 10 c 5) 5 _$ (list c b (+ b c) a) (5 10 15 20) _$
The fact that LISP programs are expressed as lists requires a very clear understanding of the relationship between expressions and lists. If a list is preceded by quote, Its evaluation returns that same list, otherwise it is considered an expression to be evaluated and returns its result: _$ (list '(+ 2 1) (+ 2 1)) ((+ 2 1) 3) _$
In the previous example the first argument is preceded by quote, therefore a list is returned. The second argument is taken as an expression to evaluate and returns a number. If a list is not preceded by quote and its first term is not a function, its evaluation will return an error: _$ (3 three "abc" d e) ; error: bad function: 3 _$
Before we said that a list may contain no elements. The empty list is equivalent to the nil symbol. It Is expressed by an opening parenthesis followed by a closing one. _$ '() nil _$ () nil _$ nil nil _$
Here the result is always the same, regardless of whether or not the empty list is preceded by quote since the symbol nil is representing itself.
Building lists. Lists are built using the cons function. We can describe its operation by saying that cons adds a first term to a list. To test this we propose the following exercise. First of all it is convenient to clear the console window of previously evaluated expressions and their results. This we do by right-clicking in the Console window so the contextual menu is displayed (see Figure 4.2) and selecting the Clear Console window option. After clearing the console, we will do as follows: assign to the symbols new-list and num the values nil and 0 respectively, add to the empty list by the name new-list a first element which is the sum of 1 to the value represented by num, assign the resulting list to the same new-list symbol.
Figure 4.2. Console contextual menu.
These operations in the Console are: _$ (setq new-list nil num 0) 0 _ $ (setq new-list (cons (setq num (+ 1 num)) new-list)) (1) _$
The first expression assigns the value nil (The empty list) to new-list and the value 0 to num. Note that setq only returns the result of this last assignment. Assigning to new-list the value of nil is not strictly necessary in AutoLISP, as if the symbol has no value it will be assigned nil automatically6. But it is a good practice to do so, as this ensures that the list will be empty before running the following expression. The Console can automatically complete words. If for example, we have previously typed setq it will be enough to type the s and press CTRL+SPACE and VLISP will try to complete the word. If the result is not the desired one, you can repeat this keyboard shortcut until the desired word appears. The second expression adds a first term to new-list in this case 1, resulting from (+ 1 num). Another essential aspect of LISP is revealed here: the functions are usually non-destructive. A destructive function is one that can alter the arguments passed to it. In general, LISP’s own operators are designed to be called in order to obtain the returned values. For these changes to be retained we must use functions such as setq that besides returning a value also have collateral effects. The collateral effect of setq is to assign a new value to a symbol. Many times functions are invoked only for their collateral effects and not for the value returned. In this particular case, we invoke (setq num (+ 1 num)) both for its collateral effect -changing the value assigned to num- as for the returned value, which is the argument passed to cons as a first term to be added to the list. An expression such as: (cons (setq num (+ 1 num)) new-list)
returns the value (1), but does not affect the value represented by new-list that will still be nil. To assign (1) to new-list, thus changing its value, we must reapply the setq function: (setq new-list (cons (setq num (+ 1 num)) new-list))
On evaluating the expression above we have two symbols whose value has changed from those originally assigned. Often we will use symbols to store values in our programs that will change as a consequence of calculations and other operations. Symbols used in this fashion are usually known as variables. If we repeatedly evaluate the second expression these variables take on new values. The outcome of pressing TAB and ENTER repeatedly is shown below: _ $ (setq new-list (cons (setq num (+ 1 num)) new-list)) (2 1) _ $ (setq new-list (cons (setq num (+ 1 num)) new-list)) (3 2 1) _ $ (setq new-list (cons (setq num (+ 1 num)) new-list)) (4 3 2 1) _ $ (setq new-list (cons (setq num (+ 1 num)) new-list)) (5 4 3 2 1)
The list function we saw above can be considered simply as shorthand to represent the repeated use of cons to add terms to the empty list nil.
4.5. Variables and data types. Working with data types is extremely simple in LISP. In other programming languages the variables are the ones that possess type. For this reason it would not be possible to use a variable without first declaring its type, nor assigning it a value that did not match the declared type. In LISP values are the ones which belong to a particular type, not variables. A given variable can be used to represent objects of any type. We have mentioned so far the most common types: integers and real numbers, strings, symbols, subroutines and lists. They are not all of the data types available in Visual LISP. Further on we will examine other types of data. The type function detects the type corresponding to a value. _$ (setq data 1)(type data) 1 INT _$ (setq data 1.0)(type data) 1.0 REAL _$ (setq data "abc")(type data) "abc" STR _$ (setq data 'abc)(type data) ABC SYM
_$ (setq data '(1 2 3 4 5))(type data) (1 2 3 4 5) LIST _$
In the case of a function name we’ll get different results depending on whether or not the name is preceded by quote. _$ (setq data 'setq)(type data) SETQ SYM _$ (setq data setq)(type data) SUBR _$
The setq function name is a symbol (SYM) representing a subroutine (SUBR).
Conversion between data types. Supplying an unexpected data type to a function will cause an error that will interrupt program execution. In many cases it will be necessary to convert the data type before passing it to a function as an argument. We have functions are designed especially for this. Among them several are designed to convert data to strings, necessary when writing to external files, configuring messages, etc. Among the functions capable of this the most universal is vl-princ-tostring. Below are some examples of its use. _$ (vl-princ-to-string 120) "120" _$ (vl-princ-to-string 120.8) "120.8" _$ (vl-princ-to-string (list 'a 'b 'c)) "(A B C)" _$ (vl-princ-to-string (list a b c)) "(nil nil nil)" _$ (setq a 10 b 20 c 30)(vl-princ-to-string (list a b c)) 30 "(10 20 30)" _$ (vl-princ-to-string (* a b c)) "6000" _$ (vl-princ-to-string '(* a b c)) "(* A B C)" _$ (vl-princ-to-string '*) "*" _$ (vl-princ-to-string *) _$
There are other functions for converting data into strings that will be used in programs further on. When it comes to converting strings to other types of data the most versatile LISP function is read. _$ (read "120") 120 _$ (type (read "120")) INT _$ (read "120.8") 120.8 _$ (type (read "120.8")) REAL _$ (read "(* A B C)") (* A B C) _$ (type (read "(* A B C)")) LIST _$
In the latter case it is a call to the * (multiply) function. If we also wish to evaluate the list as if we had typed it in the console and pressed ENTER we must expressly make a call to the eval function. _$ (eval (read "(* A B C)")) 6000 _$
In fact every time you press ENTER in the Console a call to eval is implicit. The read function returns the first LISP object represented in the string it receives as an argument. That object’s type will depend on its printed representation in the string. _$ (read "1 2 3 4 5") 1 _$ (type (read "1 2 3 4 5")) INT _$ (read "1.0 2.0 3.0 4.0 5.0") 1.0 _$ (type (read "1.0 2.0 3.0 4.0 5.0")) REAL _$ (read "(1 2 3 4 5)") (1 2 3 4 5) _$ (type (read "(1 2 3 4 5)")) LIST _$
Frequently we will have to convert between numeric data types. To convert a real number to an integer we can use fix and for converting the other way, from
integer to real, float. Conversion to an integer is done by truncation of the decimals. _$ (fix 3.8) 3 _$ (fix 3.2) 3 _$ (float 3) 3.0
For converting real numbers to strings it is often more convenient to use rtos because this function lets you control the format and number of decimal places in the returned string. It is also the only way available for printing on the console or the command line a real number with more than five decimal places. From the constant pi we get: _$ pi 3.14159 _$ (rtos pi 2 0) "3" _$ (rtos pi 2 1) "3.1" _$ (rtos pi 2 2) "3.14" _$ (rtos pi 2 3) "3.142" _$ (rtos pi 2 15) "3.141592653589793" _$
It is also useful for converting real numbers to integers, since instead of truncating the decimals it rounds the number to the nearest integer. Applying read to the string returned we can implement a conversion to integer which operates by rounding instead of truncation. _$ (read (rtos 3.6 2 0)) 4 _$ (read (rtos 3.5 2 0)) 4 _$ (read (rtos 3.4 2 0)) 3 _$ (type (rtos 3.4 2 0)) STR _$ (type (read (rtos 3.4 2 0))) INT _$
4.6. Manipulating the elements of a list. The basic functions that allow access to the terms of a list are car and cdr. The car of a list is its first element, the cdr is the rest of the list. Considering the list (5 4 3 2 1) assigned to the variable new-list that we obtained by evaluating the expressions explained above, _$ (car new-list) 5 _$ (cdr new-list) (4 3 2 1) _$
As we see car returns an atom and cdr returns a list. Combining these two functions we can have access to any term of the list. To access the successive terms: _$ (car (cdr new-list)) 4 _$ (car (cdr (cdr new-list))) 3 _$ (car (cdr (cdr (cdr new-list)))) 2 _$ (car (cdr (cdr (cdr (cdr new-list))))) 1 _$ (car (cdr (cdr (cdr (cdr (cdr new-list)))))) nil _$
These combinations can be abbreviated using compound function names as follows: as the first letter, then up to four letters A or D corresponding to each successive car or cdr, and ending with a R. C
So we have the equivalent functions: _$ (cadr new-list) 4 _$ (caddr new-list) 3 _$ (cadddr new-list) 2 _$
For terms beyond the fourth we would have to nest functions. _$ (cadddr (cdr new-list)) 1 _$ (cadddr (cddr new-list)) nil
But this notation is already confusing, so that in many cases it is clearer to use the nth function, which retrieves the term of the list indicated by the numeric index supplied as its first argument: _$ (nth 0 new-list) 5 _$ (nth 1 new-list) 4 _$ (nth 2 new-list) 3 _$ (nth 3 new-list) 2 _$ (nth 4 new-list) 1 _$ (nth 5 new-list) nil _$
Note that the first item always corresponds to index 0.
Lists and dotted pairs. We have seen that a list is constructed by adding an item to nil through the cons function. And that the list grows by successive calls to cons, each time adding a new first term to the previous list. Let us consider now what happens when cons receives a second argument with a value other than nil. _$ (cons 1 2) (1 . 2) _$ (cons 'a 'b) (A . B) _$
This is a dual structure in which the first term is the car and the second the cdr. It is often referred to as a dotted pair7 due to its printed format. The cdr of a dotted pair like this returns an atom, not a list: _$ (car '(a . b)) A
_$ (cdr '(a . b)) B _$
Conceptually, a dotted pair consists of two references; the first is the car and the second the cdr. The two halves of a dotted pair can point to any object, including other dotted pairs. This is the basic structure which makes up proper lists, but in a proper list the cdr of its last element is always nil. _$ '(a . nil) (A) _$
A list like the one we assigned to the variable new-list could also have been expressed in dotted pair notation. If we type in the console the following dotted pair expression: _$ '(5 . (4 . (3 . (2 . (1 . nil))))) (5 4 3 2 1) _$
Figure 4.3. List as a sequence of dotted pairs.
We get a list just like the previous one. This list is composed (see Figure 4.3) of a series of dotted pairs related in such a way so that the cdr of each pair leads us to a new pair, with the cdr of the last dotted pair pointing to nil. Most of the list handling functions cannot be applied to dotted pairs: _$ (nth 0 '(a . b)) A _$ (nth 1 '(a . b)) ; error: bad list: B
_$ (length '(a . b)) ; error: bad list: B _$
The only list functions supported for dotted pairs without generating an error are car, cdr, cons, list and assoc. Visual LISP has a function to obtain the length of a list which admits dotted pairs, avoiding errors. If we use vl-list-length instead of length we would obtain nil when passing it a dotted pair. On the other hand we have the vl-list* function which is similar to list but that will place the its argument as the last cdr of the resulting list, while list always includes a nil as its last cdr. For this reason if the last argument for vl-list* is an atom, the result is a dotted pair. _$ (vl-list-length '(a . b)) nil _$ (vl-list* 'a 'b) (A . B) _$
Association lists. An efficient way for storing and retrieving information is to structure it as association lists. This is how AutoLISP exposes the data used to define the properties of objects, both graphical and non-graphical (Layers, colors, etc.). To recover an entity’s association list we can use the entget function. This function takes as its argument an identifier or entity name for the object whose data we want to obtain. (entget entity-name)
There are several ways to obtain an entity name (ename), which is an AutoLISP data type. For now we need only the simplest, (entlast) that returns the ename for the last drawn entity: (entget (entlast ))
Immediately after drawing something such as a circle in AutoCAD’s graphic window8, we return to the VLISP IDE typing in the console the expression: (setq circle-obj (entget (entlast)))
Pressing ENTER we will obtain the association list for that last entity drawn. Setq will have assigned that list to the circle-obj variable, which will allow us to operate with it in the following series of exercises. But examining the list
in the Console is uncomfortable as it is shown as a single long line. Here we will use the Editor’s ability for automatically formatting code. We will proceed as follows: Select the list returned by setq. We can do this by double-clicking to the left of the list’s opening parenthesis. Once highlighted, we right-click and select Copy from the contextual menu or simply press CTRL+C. We then select the New File... button or CTRL+N, to open a new Editor window. With the cursor over this window right-click to select Paste in the contextual menu, or CTRL+V. Once this is done press the Format edit window button (or CTRL+ALT+F), doing which we obtain the list in the format shown below: ((-1 . ) (0 . "CIRCLE") (330 . ) (5 . "3FC") (100 . "AcDbEntity") (67 . 0) (410 . "Model") (8 . "0") (100 . "AcDbCircle") (10 100.0 100.0 0.0) (40 . 50.0) (210 0.0 0.0 1.0))
This is a list each of whose elements is another list that we will call a sublist. The first thing that calls our attention here is that some of its elements are dotted pairs and others (the sublists headed by the integers 10 and 210) are proper lists. Dotted pairs are used as a more compact storage format when the sublist includes only two terms, using proper lists as needed to include the values of point coordinates. This would mean the values X, Y, Z for 3D points or X, Y for 2D points. The meaning of the data is defined by the car (first term) of the sublist, which we call key. Thus the sublist with key 10 represents the coordinates of the circle’s center and that with key 40, Its radius. Other data are non-geometric, as the key 8 which is the name of the Layer. The meaning of each of these codes can be found in the DXF reference included in the online documentation. To extract values from an association list we use assoc: (assoc key association-list)
This function returns the first sublist from an association list, whose key matches the one passed as argument. If the argument is not found, it returns nil.
_$ (assoc 40 circle-obj) (40 . 25.0) _$ (assoc 10 circle-obj) (10 100.0 100.0 0.0) _$ (assoc 14 circle-obj) nil _$
The key need not necessarily be a number. Any LISP object can be a key in an association list. _$ (setq test '(((a . b) . "abcd")(0 . "CIRCLE")(5 . "2C"))) (((A . B) . "abcd") (0 . "CIRCLE") (5 . "2C")) _$ (assoc '(a . b) test) ((A . B) . "abcd") _$
If we want to extract the value itself, without the associated key we can use cdr. _$ (cdr (assoc 40 circle-obj)) 25.0 _$ (cdr (assoc 10 circle-obj)) (100.0 100.0 0.0) _$
Depending on the sublist being a dotted pair or a proper list the value returned by cdr will be an atom or a list. This is appropriate because the AutoCAD’s graphic commands precisely require lists for representing point coordinates.
Replacing terms in a list. The subst function replaces a list item by another. (subst new-item item-to-replace list)
It receives as first argument the new item, the item to be replaced as the second argument and the list in which the replacement should be made as the third argument. Subst returns the modified list. _$ (subst "a" 1 '(1 2 1 3 1 4)) ("a" 2 "a" 3 "a" 4) _$
The assoc function is frequently used in combination with subst when the list to be modified is an association list.
_$ (setq test '(((a . b) . "abcd")(0 . "CIRCLE")(5 . "2C"))) (((A . B) . "abcd") (0 . "CIRCLE") (5 . "2C")) _$ (setq test (subst '(x . y)(assoc '(a . b) test) test)) ((X . Y) (0 . "CIRCLE") (5 . "2C")) _$ (setq test (subst "abcd" (assoc 0 test) test)) ((X . Y) "abcd" (5 . "2C")) _$
In the examples above the item-to-replace argument is the value returned by assoc.
List processing. LISP includes a number of functions designed to process lists. These functions are characterized by receiving other function as an argument that will be applied to the list items. This is possible because in LISP functions are just another type of data. Applying functions to lists. The apply function applies a function to a list. Assuming a list of numbers, applying + adds all of the list’s terms, * multiplies them, / divides the first term by the product of the rest. In the latter case we can see the difference between the use of integers and real numbers. _$ (apply '+ '(5 4 3 2 1)) 15 _$ (apply '- '(5 4 3 2 1)) -5 _$ (apply '* '(5 4 3 2 1)) 120 _$ (apply '/ '(5 4 3 2 1)) 0 _$ (apply '/ '(5 4 3 2 1.0)) 0.208333 _$
As shown in the examples above, the names of the functions are always preceded by quote. The min and max functions are used to find the minimum and maximum values. _$ (apply 'min '(5 4 3 2 1)) 1 _$ (apply 'max '(5 4 3 2 1)) 5 _$
One of the list manipulation functions, append, joins several lists: (append [lst …])
Applying append to a nested list (i.e., a list of lists) can flatten it, returning a simple (single level) list. _$ (apply 'append '(("a" "b")("c" "d")("e" "f"))) ("a" "b" "c" "d" "e" "f") _$
In the case of characters, and knowing the existence of the strcat function, (strcat [string [string]…])
that joins strings; we also recognize the possibility of bringing them together to form a word. _$ (apply 'strcat (apply 'append '(("a" "b")("c" "d")("e" "f")))) "abcdef" _$
Notice how in this last example two apply expressions are nested, so that one takes the result returned by the other as an argument, a structure that is characteristic of the LISP functional programming style. Mapping functions on lists. In LISP the process of invoking a certain function using in succession as arguments the terms of one or more lists is known as expression mapping on lists. The mapping process ends when the end of any of the lists is reached. The results obtained are returned in a list. This process is performed in AutoLISP by the mapcar function9. Suppose we want to create an association list from the contents of two lists, the first with the keys and the second with the data: _$ (mapcar 'list '(1 2 3 4) '("a" "b" "c" "d" "e")) ((1 "a") (2 "b") (3 "c") (4 "d")) _$ (mapcar 'cons '(1 2 3 4) '("a" "b" "c" "d" "e")) ((1 . "a") (2 . "b") (3 . "c") (4 . "d")) _$
Mapping list in the first case produces sublists as proper lists. In the second case, using cons we get dotted pairs as sublists. Due to the different length of both lists, the number of terms obtained on the result always corresponds to the
shorter list. Another possibility is to perform numeric or string operations on the items of several lists. _$ (mapcar '+ '(1 2 3 4) '(10 20 30 40)) (11 22 33 44) _$
The strcat function, as shown in the example, concatenates the strings so that from "a" and "1" we obtain "a1". _$ (mapcar 'strcat '("a" "b" "c" "d") '("1" "2" "3" "4")) ("a1" "b2" "c3" "d4") _$
Figure 4.4. Mapping over a list.
Sometimes mapcar is used for the collateral effects produced by the mapped function rather than for the list returned. Suppose you want to print on different lines the terms of a list, to do which we had to resort in a previous example to the Editor's automatic formatting capability. Using mapcar we can achieve a similar effect on the console, resorting to the print function. This is one of three very similar functions (print, PRIN1 and princ) that have as their collateral effect printing an expression in the Console, the command line or in a text file. In the case of print, a new line is inserted prior to printing each expression. Mapping print on the list (see Figure 4.4) we manage to have each list item printed in a different line. We can see that after the last sublist printed, the value returned by mapcar, in this case the entire association list, is printed in the console.
4.7. Lambda. In certain cases we need to apply more complex expressions that are not available as language primitives. In those cases when we can generate the necessary combinations using lambda. A lambda function has this symbol as the first term, followed by the list of variables that the function will use internally to represent the arguments it receives. The body of the function comes next, including the expressions necessary for the intended processing. For example, if we must square a number: _$ ((lambda (x)(* x x)) 2) 4 _$
The lambda symbol communicates to LISP that the expressions in this list form a function similar to the system's primitives in everything except for the fact that in this case it lacks a name i.e., it is an anonymous function. The list that follows the lambda symbol contains the names of the variables which in that function will represent the values to be processed. In this case a single variable, which we name x. This list is usually known as the parameter list. After the parameter list comes what is usually called the body of the function consisting of all the expressions to be evaluated in order to obtain the desired result, in this case a single expression that returns the product of x multiplied by itself. A lambda expression represents a function that operates exactly like any of the language primitives when used as the first term of a list. Evaluating the lambda expression returns the function that the expression represents. This function is applied to the argument that follows in the list, the number 2, returning as a result 4. Now we will see the use of the same lambda expression together with the apply function. Also here its behavior is identical to that of the language primitives. _$ (apply '(lambda (x)(* x x)) '(2)) 4 _$
In this case the lambda expression must be preceded by quote just as any function name used with apply or with mapcar . A lambda function does not occupy memory space once processing ends. As
shown below, each time the lambda expression is evaluated it creates a new object that will cease to exist immediately after evaluation. The memory used in them is recovered through LISP’s automatic memory management feature. _$ (lambda (x)(* x x)) _$ (lambda (x)(* x x))
The type function will return, for a lambda expression, the type identifier USUBR , which means User Subroutine. _$ (type (lambda (x)(* x x))) USUBR _$
As we might expect, also with mapcar its behavior is identical to that of any other function. _$ (mapcar '(lambda (x)(* x x)) '(0 1 2 3 4 5)) (0 1 4 9 16 25) _$
It can be more interesting when several arguments are involved. If you want the processing to involve several different arguments they should be represented in the parameter list, as many variable names as arguments we wish to use. The following expression takes three character arguments combining them so that the resulting string can be read the same way in either direction _$ ((lambda (x y z)(strcat x y z y x)) "a" "x" "u") "axuxa" _$ (apply '(lambda (x y z)(strcat x y z y x)) '("a" "x" "u")) "axuxa" _$
With mapcar we could get as many combinations as terms in the lists we use. _$ (mapcar '(lambda (x y z)(strcat x y z y x)) '("a" "e" "i" "o" "u") '("v" "w" "x" "y" "z") '("u" "o" "i" "e" "a")) ("avuva" "ewowe" "ixixi" "oyeyo" "uzazu") _$
Function. Instead of quote , a lambda expression may be preceded by the special form function . The following expressions produce identical results:
_$ (mapcar '(lambda (x) (* x x)) '(1 2 3)) (1 4 9) _$ (mapcar (function (lambda (x) (* x x))) '(1 2 3)) (1 4 9) _$
The difference between quote and function lies in the way that the Visual LISP compiler will treat them. By using function we instruct the compiler to link and optimize the expression as if it were a primitive function or defined with the special form defun . In this book, to simplify the printed code, we always use quote represented by the apostrophe character, knowing this can be substituted by function for lambda expressions.
4.8. Summary. If we followed the exercises in this chapter we will have verified the following aspects: LISP programs are composed of expressions (also called forms). An expression can be an atom or a list whose first term is a function followed by zero or more arguments. Prefix notation used in LISP syntax allows a function to receive any number of arguments. LISP evaluates expressions according to the following rule: all arguments are evaluated from left to right and the results are passed to the function that appears as the first term of the list. An exception to this rule is the special form quote, Which returns its argument without evaluating it. Two data types are characteristic of LISP: symbols and lists. Lisp programs are expressed in the form of lists. Symbols can represent LISP objects of any kind. The variables used in a program are symbols representing the data on which the program operates. In LISP the values have type, not the variables. LISP functions always return a value and sometimes produce collateral effects. They can be used in a program either for the value returned, for its collateral effects or for both. We say that LISP functions are non-destructive in the sense that they do not change the value of the variables that are passed to them as arguments. To change the value assigned to a variable it will be necessary to assign it expressly the new value using setq. The new value is returned by setq that also assigns it to a symbol as its collateral effect. The basic functions for manipulating lists are cons, which builds them, car that returns its first term and cdr that returns the rest of the list. We must distinguish proper lists from dotted pairs. The dotted pair is a data structure composed of two parts, the car and the cdr. The lists are
actually chains of dotted pairs in which the last cdr points to nil. There are functions like apply and mapcar designed specifically for the processing of data structured as lists. A function can be applied to all the terms of a list by apply. The mapcar function applies a function taking in succession as arguments each item of one or more lists. The lambda symbol allows designing anonymous functions according to the user's requirements. lambda expressions can be used in a manner identical to the language primitives and are often used in combination with apply or mapcar.
Exercises Exercise 1 Analyze what happens and what is returned on evaluating the following expressions: (setq x 10 y 20) (/ (* x x) y) (setq x 1.0)(list x (setq x (/ x 2)) (/ x 2))
If we assign to the variables x and lst the following values: (setq x 1.0 lst (list x))
Describe how the following expression operates and what values are obtained when evaluated repeatedly: (setq lst (cons (setq x (/ x 2)) lst))
Exercise 2 Propose two different expressions that return (A list function and in the other the cons function.
B C)
using in one of them the
Exercise 3 Suggest a lambda expression that receiving as argument the list of numbers assigned to the nums variable, returns a list with the minimum and the maximum value. The list: (setq nums' (23 4 Value to be returned: (0 84)
9 0 17 84))
1 A programming language can use different types of integers which differ in terms of the memory space (measured in bits) they occupy. VLISP uses a single type, the unsigned 32 bit integer. Their values can be between -2147483647 and 2147483647. The numbers that exceed those limits are automatically converted to real numbers (with decimals). 2 Real numbers in VLISP are 64-bit floating point double precision numbers ranging from 1.7976931348623158e +308 to 1.7976931348623158e +308. 3 In contrast to the usual infix notation in which operators are written between the operands they act on. 4 Neither single nor double. Double quotes delimit string data while single quotes (apostrophe) represent the quote function. 5 We have seen that evaluating a before the setq expression returns nil. And as nil is a protected symbol it should not be assigned any other value. 6 This is not so in other LISP implementations where evaluating a symbol with no previously assigned value will produce an error. 7 This structure is also known as a cons, term which we will not use to avoid confusion wit the function of the same name. 8 To change to the AutoCAD graphic window we can use the Activate AutoCAD button or any of the ways Windows offers to set the focus to a different program. 9 The Common LISP standard includes in addition to mapcar several other mapping functions: mapc, mapcan, mapl, maplist and mapcon. These are not available in AutoLISP.
Chapter 5 User-defined functions Many times when writing a program it would be practical to have a custom function that receiving certain arguments would return a certain value. This can be easily done with LISP. The user can define functions designed specifically for the type of applications he is developing. The technique of building a program from small user functions aimed at solving specific problems is known as bottom-up programming. Over the time the user will have created his own library of functions that will bring the programming language closer to the type of applications that he usually writes. The functions that we propose in this book are mostly conceived with this purpose.
5.1. Defun. Working on the console we evaluated lambda expressions which were identical to the primitive functions of the language in all, except that they lacked a name. To add numbers we can call a function by the name: +. It would be convenient if we could also refer to the expression that grouped characters in a certain way: (lambda (x y z)(strcat x y z y x))
by a name instead of typing each time the entire expression. LISP functions are another kind of data. This is what makes it possible to pass them as arguments in expressions using apply or mapcar. If so, there would be no difficulty in assigning, using setq, the function represented by a lambda expression to a symbol. A descriptive name for it could be palindrome. _$ (setq palindrome (lambda (x y z)(strcat x y z y x))) # _$
Indeed if we evaluate the expression above, still using the console, we see that now we can use palindrome as we use any of the original LISP functions. _$ (palindrome "a" "x" "u") "axuxa" _$ (mapcar 'palindrome '("a" "e" "i" "o" "u") '("v" "w" "x" "y" "z")
'("u" "o" "i" "e" "a")) ("avuva" "ewowe" "ixixi" "oyeyo" "uzazu") _$
These two distinct operations1, building the function and associating it with a certain name, are unified in the LISP operator defun. LISP programs are no more (and no less) than user functions created using defun. is also a special form, like setq. This means that it doesn’t evaluate its arguments as would be normal in a LISP expression. The defun expression for the palindrome function is: defun
(defun palindrome (x y z)(strcat x y z y x))
Listing 5.1. Code for the PALINDROME function.
The resemblance to a lambda expression is evident. In this case defun receives a first argument that will be the function’s name. It is followed by the parameter list and the body of the function which are identical to those used in the equivalent lambda expression.
5.2. Loading and executing user functions. Unlike the language primitives, user functions will not be available if they have not been previously loaded into memory. Usually these functions are included in an .LSP source code file. For the exercises in this chapter it is convenient to create a folder in which to save the .LSP files. In the Chapter 2 tutorial we proposed creating a folder named Visual LISP Projects. Now we propose to create, in that folder, a new subfolder named Chapters. We will save the code for our functions to a new file. To create it we can press CTRL+N or the New File... button in the Standard toolbar. In it we will type the code for the palindrome function. Being so short, this code will occupy a single line. It is important to note that spaces, tabs and line breaks are ignored when evaluating the code unless they are part of a string delimited by double quotes. Expressions are delimited only by the parentheses. So it would the same to type the program as: (defun palindrome (x y z) (strcat
x y z y x) )
Although this obviously would not help in its comprehension. To test the program we can assign character lists to variables, so that will not be necessary to type them anew each time. In the same Editor window we will include the required setq expression: (setq lis1 '("1" "2" "3") lis2 '("4" "5" "6") lis3 '("7" "8" "9")) (defun palindrome (x y z)(strcat x y z y x))
Having the code in the Editor window we can load it by pressing the Load active edit window button (CTRL+ALT+E) in the Tools toolbar. The outcome of the loading operation is shown in the console: ; 2 forms loaded from # _$
The term forms must be understood here as expressions. The text in the title bar reminds us that the content of the Editor window has not yet been saved to disk. To keep it, we must click (with the Editor window in focus) on the Save File (CTRL+S) button. In the Save as dialog box we will search for the Chapters folder and save it as Chapter5.lsp. It is not necessary to explicitly introduce the .LSP extension, it is enough that the Save as dropdown list box reads Lisp source files. Once named the file and having added the Visual LISP projects folder to the AutoCAD support search paths, we can load it with the expression (load ". /Chapters/Chapter5") from the Console, or even from the AutoCAD command line. _ $ (Load ". /Chapters/Chapter5") PALINDROME _$
In case of using load, the value returned by the last evaluated expression evaluated, which in this case is the name of the new function created, returned by defun will be printed in the Console. To test the loaded function we can use both the console and the command line.
_$ (mapcar 'palindrome lis1 lis2 lis3) ("14741" "25852" "36963") _$
For the listings in the rest of this chapter we can use this same file.
5.3. Global and local variables. The way we have used setq so far, the variables created will have effect for any running AutoLISP program. We say that its scope is global. The variables LIS1, LIS2 and LIS3 created by the setq expression in the Chapter5.lsp file will still be available in the current drawing’s memory space even after our testing is over, and can be used for any other processing. _$ (apply 'strcat (mapcar 'strcat lis1 lis2 lis3)) "147258369" _$ (apply 'strcat (apply 'append (list lis1 lis2 lis3))) "123456789" _$
Often a program must create variables to store temporarily values it will process. The end result is often a side-effect, such as the drawing of certain shapes in the AutoCAD viewport. The variables used to achieve this goal are not normally intended to be used outside the program that created them. In this sense we say that these variables should have a purely local scope. A local variable ceases to exist once that the execution of the program that uses them ends, and do not affect any other program that runs after it. In fact, LISP can solve many problems without even assigning values to variables. In LISP all are functions, and functions return values that can be used by other functions. This is the fundamental principle of what is called the functional programming style. We cannot consider a program finished if we have not checked how many setq expressions can be deleted without affecting its operation. If programs use global variables there is always the risk that another program or even different functions within a large program will use variables with the same name causing errors that are extremely difficult to detect. If the use of a global variable, is really justified it is customary among LISP programmers to make it stand out by beginning and ending its name with asterisks. We can use the Apropos (CTRL+SHIFT+A) tool to verify the existence of global variables with such names (See Figure 5.1) in our system. In those cases where using variables cannot be avoided they must be declared as
local. Local variables are declared in the parameter list supplied to defun. If the name of a symbol appears in the parameter list to the left of a slash, it represents an argument whose value must be supplied when calling the function. If it appears to the right, it is a local variable whose initial value is nil and may take any value assigned by setq when executing the program.
Figure 5.1. Global variables. (defun function-name (arg1 arg2 … / local1 local2 …) (expression1) (expression2) …)
Both the arguments and local variables are visible only to the function in whose parameter list they are included and to the functions called from it, provided that the latter do not include the same symbol in their own parameter lists. To demonstrate how the scope of local variables is limited exclusively to the function where they are declared we can use the code in Listing 5.2. We will
save the code for these functions in the Chapter5.lsp file for loading them afterwards with the Load active edit window (CTRL+ALT+ E) button. In each of them a different value is assigned to a variable named x, while the variable z is local only to the function messages that calls message-1 and message-2, being, for this reason also visible from them. The contents of these variables is printed as part of a series of successive messages. The first and second messages are printed in the scope of the messages function. This function then calls the messages-1 and messages-2 functions, both assigning different a value to their local variable named x and printing messages using those values and the value of z whose value remains the initially assigned. (defun message-1 (/ x) (setq x "SECOND") (princ. "\n message-1 assigns to x ") (princ. x) (princ. "\n But z is still ") (princ. z)) (defun message-2 (/ x) (setq x "THIRD") (princ "\n message-2 assigns to x ") (princ x) (princ "\n But z is still ") (princ z)) (defun messages (/ x z) (setq x "FIRST" z "UNCHANGED") (princ "\n messages assigns to the variable x ") (princ x) (princ "\n and to the variable z ") (princ z) (message-1) (message-2) (princ "\n and on returning to messages, x contains ") (princ x) (princ "\n and z, as always ") (princ z) (princ)) Listing 5.2. Demonstration with local variables.
After the execution of these functions are the values of x and z are printed again, showing that within the scope of the function messages none of them has changed.
_$ (messages) messages assigns to the variable x FIRST and to the variable z UNCHANGED message-1 assigns to x SECOND But z is still UNCHANGED message-2 assigns to x THIRD But z is still UNCHANGED and on returning to messages, x contains FIRST and z, as always UNCHANGED _$
Since the princ function does not insert a new line, we include in the string the control character "\n" forcing this change of line. As x has a purely local scope in all cases, evaluating x in the console before or after running messages returns nil (Or any other value it was previously assigned). _$ x nil _$
Arguments passed by value or by reference. In other programming languages, there are two ways to pass arguments to procedures that are executed within the program: by value and by reference. When an argument is passed by value, the procedure receives a copy of the variable that acts as the data’s container. If the procedure changes the data, it just changes the value of the copy, not of the original variable. This is not the usual procedure in these languages. In a language like Visual Basic the default way of passing values is by reference, which means you pass the data container itself. So if the data is altered, the effect is permanent and the change will affect all subsequent procedures that use this data within the scope in which that data is visible. According to its effects, not taking into account those issues related to LISP systems implementation, the arguments passed to any of the AutoLISP functions act as if passed by value and not by reference. This is the reason why the texts dealing with LISP programming refer to non-destructive functions that are most of them and destructive functions, which are only a few and in Visual LISP limited largely to what actually are underlying ActiveX procedure calls. Only in these latter cases we can find a performance equivalent to passing values by reference. It is important to note that in Visual LISP there are no keywords to specify when an argument is passed by value and by reference2. It is the programmer has to be clear about which functions act in a way and which in the other.
5.4. Predicates and Conditionals. In many cases the behavior of a program is determined by conditions that occur during program execution. A program chooses how to proceed by checking if certain conditions are met. This is generally done by using expressions that return true or false.
Predicates. In LISP those functions whose purpose is to return a result that can be interpreted as true or false are known as predicates. A predicate usually returns T or nil. The T symbol represents true and the symbol nil false. Predicate names usually end with the letter p. The listp function, for example, checks if its argument is a list: _$ (listp '(a b c)) T _$ (listp '(a . b)) T _$
In case it is not, listp returns nil. _$ (listp "a b c") nil _$
Since nil is also the empty list, the predicate null that returns T for an empty list will return the same result for nil. _$ (listp nil) T _$ (null nil) T _$ (null '()) T
Or like not, which will return T if its argument is false and nil if true. _$ (not nil) T _$ (not '()) T _$
Any result other than nil will be taken as true. This is the case of member, Which checks if an object is part of a list: _$ (member 245.0 '(117.9 48.5 "abcd" 245.0 "x4" 118)) (245.0 "x4" 118) _$ (member "jkl" '(117.9 48.5 "abcd" 245.0 "x4" 118)) nil _$
The member function returns nil if the object is not part of the list. If found, it returns the list starting from that object. Thus we can use member both as a predicate and for the information returned. If we want to check for nonmembership, we can nest a member expression in a not. _$ (not (member "jkl" '(117.9 48.5 "abcd" 245.0 "x4" 118))) T _$
Equality AutoLISP includes three predicates designed to check if certain values are equal. The use of one or another depends on the objects you want to compare and what we mean by equality. The function = compares one or more numbers or strings returning T if they are numerically equal. For strings it compares the ASCII code values for the characters that compose them. It works for integer values, but not for real numbers output from calculations, since the result is influenced by numerical differences that due to its small magnitude can be negligible in practice. _$ (= 2) T _$ (= 2 2) T _$ (= 2 2 3) nil _$ (= "abc" "abc") T _$ (= '(a b c) '(a b c)) nil _$ (= '(a b c) '(x y z)) nil _$ (= 3.14159265 3.141592654) nil _$
The equal function compares any two LISP objects. It is also appropriate for lists and other data types. In this case the criterion for equality is that the printed representation of the objects is the same. For the comparison of real number values a third argument can be included, a precision factor that determines the greatest difference permissible to consider them equal. _$ (equal 3.14159265 3.141592654 0.0001) T _$ (equal '(a b c) '(a b c)) T _$ (equal '(a b c) '(x y z)) nil _$
A stricter criterion of equality is applied by eq. It considers fulfilled the condition of equality only when the compared object is the same, i.e., that occupies the same memory location. For strings and integers it is identical to =. The major difference is when we compare lists. Two lists whose printed representation is identical are not usually eq. The reason is that whenever an expression as '(a b) is evaluated new cons cells (or dotted pairs) are created that will occupy different memory locations. _$ (equal '(a b) '(a b)) T _$ (eq '(a b) '(a b)) nil _$ (setq a '(a b) b a) (A B) _$ (eq a b) T _$
For numeric values or strings we have other predicates that establish numerical comparisons discerning relationships such as: unequal, returns T if none of its arguments is equal to the one that follows. < less than, returns T if each of its arguments is less than the next one. greater than, returns T if each of its arguments is greater than the next. >= greater than or equal, returns T in case each of its arguments is greater than or equal to the next. /=
User-defined predicates
In some cases it will be convenient to define new predicates of which the system lacks. For example, we mentioned that list processing functions are usually not applicable to dotted pairs. It would be convenient to have a predicate that allowed us to verify this condition. AutoLISP provides the listp predicate, which checks if an object is a list. But listp returns T also if the argument is nil or if it is a dotted pair. Visual LISP3 provides the vl-consp predicate that will return T only if the list is not empty, but does not distinguish between proper lists and dotted pairs. The proof that the list is a dotted pair would be that its cdr was an atom instead of a list. The fact that an object is an atom is checked by the predicate atom. So to discern whether an object is a dotted pair we must combine the predicates vl-consp and atom. To combine predicates have the special forms and and or. The logical operators and and or allow us to build complex predicates, in which several conditions are tested. and returns false (nil) in case any of its arguments returns nil. When a nil value is found the evaluation stops. or will return true (T) in case any of its arguments returns a value other than nil. When a value other than nil is returned the evaluation of its arguments will stop and T will be returned.
Grouping the conditions that the object is a list, that it is not nil and that its cdr is an atom we could verify if the argument is a dotted pair: (defun dotted-pair-p (arg) (and (vl-consp arg) (cdr arg) (atom (cdr arg)))) Listing 5.3. DOTTED-PAIR-P predicate.
In the name chosen for this predicate we have adopted the convention to finish it with the letter p although, as seen in atom this rule has its exceptions. Other useful predicates are those that check the data type. AutoLISP provides some but not all. To check the data type we use type. The following function checks if the argument passed to it is a string 4. (defun stringp (arg) (eq (type arg) 'STR)) Listing 5.4. STRINGP predicate.
The equality predicate to use in this case would be eq because we would be
checking that the symbol returned by type is the same as STR the symbol identifying the character string type. In the same way we could define, if necessary, predicates for other data types. _$ (stringp "abc") T _$ (stringp 'abc) nil _$
Conditionals Decision making is a fundamental part of any program. Conditionals are specialized functions that allow the program to act one way or the other according to the result of an expression which acts as predicate. As we can build our own predicate functions we can write functions that make decisions implying any degree of complexity. The conditional IF The simplest LISP conditional is if. It takes three arguments: the predicate that returns true or false, the expression which is executed if the predicate has returned true, and the expression that is executed in case false is returned. The third argument may be omitted, not the first nor the second ones. In case the third argument is omitted, if will return nil when the predicate returns false. The following function prints a message saying if the argument it receives is or is not a list: (defun is-list? (arg / result) (if (listp arg) (setq result "Yes, it's") (setq result "No, it's not")) (princ (strcat result " a list")) (princ)) Listing 5.5. IS-LIST? function with local variables.
If the value of arg is a list, the value saved in the local variable result is "Yes, it's". In case it is not, that variable takes the value "No, it is not". Then we concatenate that value with the phrase " a list", the resulting string
being printed in the console by princ. The last princ with no arguments prevents displaying the value returned by the previous princ. This is a standard procedure used so programs end silently without printing in the Console (or in the command line) the value returned by their last expression. _$ (is-list? '(a b)) Yes, it's a list _$ (is-list? "(a b)") No, it's not a list _$
We mentioned before that in the functional programming style our job should not be considered finished without analyzing how much can we do without setq. For the function above this would lead to a new version of is-list? (See Listing 5.6) where everything is done without the use of local variables. (defun is-list? (arg) (princ (strcat (if (listp arg) "Yes, it's" "No, it's not") " a list")) (princ)) Listing 5.6. IS-LIST? function without local variables.
The if function is another of LISP’s special forms. Its purpose is precisely to leave one of its arguments unevaluated, contrary to the general evaluation rule we have stated. It will often be necessary to evaluate within one of its branches more than one expression. In these cases it will be necessary to group the expressions for each of the alternatives as a single block. The AutoLISP function to create a block of code is the special form progn. The expressions within the block are evaluated successively, returning the value output from the last one. Compare the results displayed in the console from three separate expressions: _$ (setq x "returned")(setq y 'value)(setq z 1)(list z y x) "returned" VALUE 1 (1 VALUE "returned") _$
with what we obtain from the same three expressions within a progn block.
_$ (progn (setq x "returned")(setq y 'value)(setq z 1)(list z y x)) (1 VALUE "returned") _$
The COND conditional With progn we are explicitly creating a block. Many LISP functions implicitly create blocks. Among them we have cond which, among the advantages offered is that of eliminating the need to use progn. It also allows checking multiple conditions, compared to the single test expression that if supports, thus making it far more general in its application. The expressions that will be evaluated in case one of its test expressions returns true will return their result as if they were included in a progn expression. admits zero or more arguments (which we call clauses), which should be lists in which the first item will be used as the predicate expression followed by zero or more expressions. cond
(cond ((predicate-1)(expression)…) ((predicate-2)( expression)…) …)
In a cond expression each of the predicates is evaluated in the order they appear until one returning true is found. When this happens, the expressions following this predicate are evaluated in succession to return the last expression’s value. No more clauses are evaluated. If none of the predicates returns true, cond returns nil. An example is the function type? (Listing 5.7). (defun type? (arg) (cond ((listp arg) (princ arg) (princ ": a list")) ((vl-symbolp arg) (princ arg) (princ ": a symbol")) ((and (numberp arg) (zerop arg)) (princ arg) (princ ": number zero")) ((and (numberp arg) (minusp arg)) (princ arg) (princ ": a negative number")) ((numberp arg) (princ arg) (princ ": a positive number")) (t (princ arg) (princ ": an unknown type "))) (princ)) Listing 5.7. TYPE? function.
Frequently the symbol T is used as the predicate for a last clause, so if none of the previous ones return true, this clause will always be evaluated. The following function prints a message with the type of some data. If the tests fail to define the data type, it will report this fact as part of this final clause, which is evaluated by default whenever none of the test expressions returns true. To check whether this is zero or a negative number two predicates are grouped in an and expression. As when the first expression is false the second expression is not evaluated this will avoid the error that passing a non-numeric argument to zerop or minusp would produce. To print the messages princ is used since it is capable of printing on the console any type of data and it does not introduce line breaks. _$ (type? '(a b)) (A B) is a list _$ (type? 'ab) AB is a symbol _$ (type? 0) 0 is number zero _$ (type? -1) -1 is a negative number _$ (type? 1) 1 is a positive number _$ (type? +) # is an unknown type _$
Sorting lists in Visual LISP. The acad_strlsort list sorting function supplied with AutoLISP was limited to sorting strings. There were no other procedures to sort lists. Sorting a list efficiently is not a trivial matter. Sorting algorithms have been studied by specialists since a long time ago and are well documented5. Visual LISP introduces two new list sorting functions that can be used with lists that contain not only strings, but also integers or real numbers and even other lists. These functions are vl-sort and vl-sort-i. Both functions accept as input a list and a function that compares two arguments and that will serve to set the sorting order. This function may be a system primitive, a user-defined function or a lambda expression. The difference between them lies in the values returned. With vl-sort we obtain a sorted list containing the original list elements. The preservation of all the original list elements is not guaranteed. If the list contains duplicates in some cases only one of them will be retained. This happens, for example, in the case of strings or
integer values. However, in real number lists the returned list will contain all of the elements. In a list mixing real and integer values, duplicates will only appear if they are real. In the case of strings, the behavior will be similar to the case with integers. _$ (vl-sort '(28 9.0 11 44 28 15.0 28.0 9.0 44 2 1 7 88) 'list and vlax-safearrayget-element),
and obtaining information about its structure (vlax-safearray-type, vlax-safearray-get-dim, vlax-safearray-get-l-bound, vlaxsafearray-get-u-bound), Unlike classical AutoLISP functions, those that manage safearray objects are destructive in the sense that they alter the values they receive as arguments.
Creating Safearrays To create a matrix vlax-make-safearray is used. Its syntax is: (vlax-make-safearray type '(lower-boundary . upper-boundary) '(lower-boundary . upper-boundary)…)
Arrays can be defined with up to a maximum of sixteen dimensions. The array’s elements receive the default values defined in Table 6.1.
The arguments vlax-make-safearray receives are: type,
data type the array will contain. You must specify one of the constants from Table 6.2. The integer value indicates the value returned on evaluating the constant. You should use the constant name instead of the numeric value, as this may change in future AutoCAD releases. '(lower-bound . upper-bound), dotted pair (one for each dimension) that specify the lower and upper index boundaries for that array dimension. In a two-dimensional array the first sublist indicates the number of rows, the second that of columns. Unlike other LISP functions, the lower index value is set by the programmer and can be non-zero. Of course it must be an integer smaller than the one specified for the upper index. Being lists, remembering to quote them is important.
To create the array
we use the expression:
_$ (vlax-make-safearray vlax-vbDouble '(0 . 2) '(0 . 2)) # _$
The function returns the array created that is displayed in the console as # .
Filling a group of values. After creating the array we can assign values to its elements. The values of an array can be allocated all at once or item by item. The function vlaxsafearray-fill is used to assign a group of values: (vlax-safearray-fill var 'value-list)
The argument var represents a variable that points to a previously created a safearray object. The argument value-list represents a list containing the values you want to assign. You can specify as many values as there are elements
in the safearray. If less are specified, the other values are not changed. In multidimensional arrays value-list should be a list of lists, each list corresponding to one of the array’s dimensions. _$ (setq vector (vlax-make-safearray vlax-vbDouble '(0 . 2))) # _$ (vlax-safearray-fill vector '(1 2 3)) # _$
The array will then contain the values [1.0 2.0 3.0]. Trying to assign four values to a three element array with vlax-safearray-fill will produce an error. _$ (vlax-safearray-fill vector '(1 2 3 4))
; error: vlax-safearray-fill failed. Invalid initialization list. # (1 2 3 _$
To assign values to a multidimensional array, you can specify a list of lists where each sublist corresponds to a dimension of the array. To create the array
the following expressions can be
used: _$ (setq array (vlax-make-safearray vlax-vbString '(1 . 2)'(1 . 3))) # _$ (vlax-safearray-fill array '(("a" "b" "c")("d" "e" "f"))) #
Assigning values to a single element To set the value of a particular element of an array we use vlax-safearrayput-element. In this case the function receives, besides the variable that points to a safearray object and the value to be assigned, the index that indicates the element’s position in the array. (vlax-safearray-put-element var indices… value)
For a one-dimensional array a single index is specified. For a two-dimensional array, two indices, and so on. _$ (vlax-safearray-put-element array 2 3 "X") "X"
This expression replaces the third item in the second row. In case an invalid index is specified we will get an error: _$ (vlax-safearray-put-element array 2 4 "X") ; error: ActiveX Server returned an error: Invalid index _$
We must take into account the destructive nature of the safearray modifying functions. In contrast to the LISP functions studied before, it is not necessary to reassign to the variable the value returned. This is because in reality these functions act as an interface to invoke ActiveX procedures in which the data are passed by reference and not by value. In fact, the vlax-safearray-putelement function does not return the transformed matrix, but the value that was inserted. To check the values that arrays contain we can use the functions described in the following section.
Safearrays expressed as lists The values in safearrays can also be extracted as a group or by item. To read the whole group Visual LISP converts it to a list using the vlax-safearray>list function. If we want to get a specific value we can access it passing its index to vlax-safearray-get-element. vlax-safearray->list
receives as argument a variable that points to a
safearray object: (vlax-safearray->list var)
Using the vlax-safearray->list function we can obtain the values assigned to the arrays created in the previous paragraph: _$ (vlax-safearray->list vector) (1.0 2.0 3.0) _$ (vlax-safearray->list array) (("a" "b" "c") ("d" "e" "X"))
Obtaining the value of a single element In the same way it was possible to establish the value of a given element in an array by means of the indices that indicate their position, it is also possible to read its value. The function vlax-safearray-get-element is used for this. The arguments it receives are a variable that points to a safearray object and the index of the element to be read. This function accesses an array element in a
way comparable to how it is done for a list. Only that the array can have more than one dimension and therefore as many indices as necessary must be specified to locate the element. For a one-dimensional array a single integer should be specified. (vlax-safearray-get-element var indices…)
The following expression uses vlax-safearray-get-element to retrieve the second element in the first dimension in:
_$ (vlax-safearray-get-element array 1 2) "b" _$
Structure of the safearray Many operations require obtaining information on the array's structure. The functions explained below allow us to find out the type of data stored in the safearray, its number of dimensions, and its lower and upper boundaries. These functions will return an error if the argument passed to them is not a safearray. Data types The vlax-safearray-type function returns the data type of a safearray. (vlax-safearray-type var)
If var contains a safearray, it will return one of the integers shown in Table 6.2. From the arrays created above we would obtain the following results: _$ (vlax-safearray-type vector) 5 _$ (vlax-safearray-type array) 8 _$
Safearray dimensions The number of dimensions in a safearray object can be obtained with vlaxsafearray-get-dim. This function takes a variable that points to a safearray
object as argument: (vlax-safearray-get-dim var)
It returns an integer equal to the number of dimensions in the safearray. _$ (vlax-safearray-get-dim vector) 1 _$ (vlax-safearray-get-dim array) 2
Lower boundary The vlax-safearray-get-l-bound function returns the lower boundary (initial index) of one of a safearray's dimension. (vlax-safearray-get-l-bound var dimension)
The first dimension of a safearray is identified by index number 1. If var is not a safearray or the dimension index is not valid (e.g., zero or more than its number of dimensions) an error will be returned. _$ (vlax-safearray-get-l-bound vector 1) 0 _$ (vlax-safearray-get-l-bound array 2) 1
Upper boundary The vlax-safearray-get-u-bound function returns an integer representing the upper limit of a safearray's dimension. (vlax-safearray-get-u-bound var dimension)
It will return an error in the same situations as vlax-safearray-get-l-bound would. _$ (vlax-safearray-get-u-bound vector 1) 2 _$ (vlax-safearray-get-u-bound array 2) 3
The following function checks if a matrix is square, i.e., if it has the same number of elements in all of its dimensions: (defun ax-square?
(matrix / dim tmp)
(setq dim 1) (repeat (vlax-safearray-get-dim matrix) (setq tmp (cons (- (vlax-safearray-get-u-bound matrix dim) (vlax-safearray-get-l-bound matrix dim)) tmp) dim (1+ dim))) (apply '= tmp)) Listing 6.1. AX-SQUARE? function.
As can be seen on analyzing the code, the function admits n-dimensional arrays. Checking the data. As noted in previous sections, many functions that handle objects of safearray type will fail if supplied other type of object. It is therefore advisable to ensure that the type of object passed to those functions is the correct one. For this we can implement a user-defined function to act as a predicate: (defun ax-safearrayp (datum) (eq (type datum) 'safearray)) Listing 6.2. AX-SAFEARRAYP predicate.
The ax-safearrayp function returns T if its argument is a safearray and nil otherwise. This allows us to develop safer alternatives for functions using arrays that include checking the type of data they receive. For example, a secure vlax-safearray->list version: (defun ax-matrix->list (s-arr) (if (ax-safearrayp s-arr) (vlax safearray->list s-arr))) Listing 6.3. AX-MATRIX->LIST function.
Comparison between lists and arrays. A safearray has a header which stores information about its structure, such as its number of dimensions, the length of each of these dimensions and the type of data it holds. This information makes it possible to access the values contained in arrays directly, without having to traverse the elements that compose it sequentially as in the case of the list. Being a continuous block of memory, it usually occupies less memory space and
there will be no difference in the access time to its elements. In lists we have to follow a chain of pointers to get from one cons cell (dotted pair) to the next one, so it takes longer to access the last item than the first. This efficiency in accessing its elements is the great advantage of the array in comparison to the list, and can be decisive in processing a large amount of data. But the list also has its advantages. It’s very simple to dynamically build lists of arbitrary length element by element or trim them either recursive or iteratively, while arrays are static structures. lists may contain any type of data, while arrays are created with a fixed structure regarding size and content data type. To conclude this subject we propose an exercise that will compare the access time on lists and arrays. We will design a function that creates an array from a list of any size and containing any type of data. We will use two functions to determine the data type of the array to be created. First of all we must determine if all the data in the list are the same type. This is achieved with the function ax-data-type. (defun ax-data-type (lst) (if (apply 'and (mapcar '(lambda (x y) (eq (type x) (type y))) lst (cdr lst))) (ax-type (car lst)) vlax-vbVariant)) Listing 6.4. AX-DATA-TYPE.
Verification that all data are the same type is done by mapping the expression '(lambda (x y)(eq (type x)(type y)))
on the list and its cdr. This returns a list with the values returned by eq on comparing each term with the one that follows. Applying and to this list we will obtain T if all the comparisons returned true and nil otherwise. In the case that types are not the same the type of the matrix should be vlax-vbVariant. If equal, the ax-type function is invoked in order to set the ActiveX type best suited to the data contained in the list. (defun ax-type (datum) (setq datum (type datum)) (cond ((eq datum 'INT) vlax-vbLong)
((eq datum 'REAL) vlax-vbDouble) ((eq datum 'STR) vlax-vbString) ((eq datum 'VLA-OBJECT) vlax-vbObject) (t vlax-vbVariant))) Listing 6.5. AX-TYPE function.
These two auxiliary functions are used in the main ax-list->array function. This function is designed to receive flat lists (in which none of its elements is another list) as argument. This function is very useful when creating 2D and 3D polylines using ActiveX methods, as we shall see in Part 3, Chapter 10. (defun ax-list->array (lst) (vlax-safearray-fill (vlax-make-safearray (ax-data-type lst) (cons 0 (1- (length lst)))) lst)) Listing 6.6. AX-LIST->ARRAY function.
In this function a call to vlax-make-safearray that creates the array is nested inside the call to vlax-safearray-fill that inserts the values from the list it receives as argument. To show the difference between sequential access in lists and access by index in arrays we begin by creating a very large list. To do this we will use the list of all symbols defined in VLISP, which is accessed through the atoms-family function. _$ (setq lst (acad_strlsort (atoms-family 1)))
We can ascertain the number of items in that list (sorted alphabetically by acad_strlsort) using the length function. _$ (length lst) 4264 _$
Now we will create the equivalent matrix: _$ (setq arr (ax-list->array lst)) # _$
To compare access times, we will use the nth function for the list and for the
matrix the vlax-safearray-get-element function. To measure the time we will use the millisecs system variable1, proceeding as follows: We read the value of millisecs and assign it to the variable time. Execute the function, within a repeat loop so that the time elapsed is appreciable. We obtain the new value of millisecs and subtract from it the one we saved in time. The expressions that perform these operations should be typed one after the other in the Console before pressing ENTER to evaluate them. Remember that to change to a new line without evaluating you must press SHIFT+ENTER. You can also type them in an Editor window, selecting and loading them using CTRL+SHIFT+E: _$ (setq time (getvar "millisecs")) (repeat 10000 (nth 9 lst)) (- (getvar "millisecs") time) 7838161 "*PUSH-ERROR-USING-COMMAND*" 15 _$
The results returned are: the value read from "millisecs" The tenth term of the list and the elapsed milliseconds. _$ (setq time (getvar "millisecs")) (repeat 10000 (nth 4263 lst)) (- (getvar "millisecs") time) 8044316 "VLISP-IMPORT-EXSUBRS" 156 _$
We can see that for the last term of the list the time elapsed is 10 times greater. The same operation for the array gives the following results: _$ (setq time (getvar "millisecs")) (repeat 10000 (vlax-safearray-get-element arr 9)) (- (getvar "millisecs") time) 8535906 "*PUSH-ERROR-USING-COMMAND*" 31 _$
And as we expected the time needed to access the last term is the same:
_$ (setq time (getvar "millisecs")) (repeat 10000 (vlax-safearray-get-element arr 4263)) (- (getvar "millisecs") time) 8687539 "VLISP-IMPORT-EXSUBRS" 31 _$
As we can see, in small sets, its treatment as a list can be more efficient, but when it comes to a large volume of data its treatment as an array can save a lot of time. Although this statement must be qualified because it is not the only aspect to consider. We have to consider also the efficiency of the algorithm formulated for solving a problem which has a great influence in achieving efficient solutions and greatly exceed implementation details such as those here considered. Clearly, these exercises have more to do with the desire to gain an understanding of the nature of two different data structures. As mentioned in the introduction to this chapter, the option of using arrays is also determined from the nature of the arguments that certain ActiveX methods require.
6.2. Variants The Variant data type is a container for any kind of data used for exchanging information when working with AutoCAD ActiveX objects properties and methods or other Windows applications managed from Visual LISP. Its use is limited to those cases that will be studied in following chapters. Visual LISP functions that enable working with Variants are: vlax-make-variant vlax-variant-type
that creates a Variant. which returns the type of data contained in the
Variant. vlax-variant-value which returns the value vlax-variant-change-type that changes the
of a Variant variable. data type of a Variant
variable.
Creating a Variant object The vlax-make-variant function accepts two arguments: value and type. _$ (setq var1 (vlax-make-variant 5.0 vlax-vbSingle)) # _$
A Variant is represented in the Console as # where the first number represents the type of data and the second its value. The data type is identified in the printed representation as an integer, but you should pass to the function as a constant using the names in Table 6.3. The numerical values that are passed to vlax-make-variant must be within the specified limits. Otherwise it will return an error. _$ (setq var (vlax-make-variant 1.8E+307 vlax-vbInteger)) ; error: lisp value has no coercion to VARIANT with this type: 1.8e+307 _$
If a type is not specified, vlax-make-variant assigns a default type based on the data it receives (see Table 6.4.)
Checking the data type The vlax-variant-type function receives a Variant object and returns one of the integer values specified in Table 6.3. _$ (setq var (vlax-make-variant 1.8E+307)) # _$ (vlax-variant-type var) 5 _$
Extracting values For recovering the value of a Variant for use in VLISP we can use vlaxvariant-value: _$ (vlax-variant-value var) 1.8e+307 _$
Changing the data type The type of the data contained in a Variant can be changed with vlax-variantchange-type provided that no information is lost in the conversion. _$ (setq var (vlax-make-variant 32767 vlax-vbInteger)) # _$ (setq var (vlax-variant-change-type var vlax-vbLong)) # _$ (setq var (vlax-variant-change-type var vlax-vbString)) # _$ (setq var (vlax-variant-change-type var vlax-vbSingle)) # _$ (setq var (vlax-variant-change-type var vlax-vbDouble)) #
In case the change of type is not possible the function returns nil. _$ (setq var (vlax-make-variant 1.8E+307)) #
_$ (setq var (vlax-variant-change-type var vlax-vbSingle)) nil _$
6.3. VLA Objects. Objects are the main components of an ActiveX application. In a certain way we are already familiar with this concept. We usually refer to AutoCAD drawing entities such as lines, arcs, polylines and circles as objects. When referring to AutoCAD objects within ActiveX functions, it will always be necessary to specify them as VLA-object.
ActiveX Objects. But in the ActiveX object model other AutoCAD components such as Styles (Linetypes, text, dimensioning parameters, etc.), organizational structures (Layer groups and blocks) and the different viewports are also represented as objects. Even the drawing and the application itself are considered as AutoCAD objects. The objects in an application such as AutoCAD are structured hierarchically, with the Application object as the root. This hierarchical structure (see Figure 6.2) Is known as the Object Model. To reach a particular object it is necessary to start at the Application object and then loop through each successive level until the object we wish to operate on is found. AutoCAD objects have been developed following the object-oriented programming paradigm. In object-oriented programming new object classes are created from parent classes inheriting their characteristics. To the inherited characteristics each new class adds other new ones that individualize them. For this reason AutoCAD graphic objects expose a great number of common properties and methods. These properties are inherited from the AcadObject and AcadEntity classes. The Application object The vlax-get-acad-object function returns a pointer to the AutoCAD Application object. This returned value is a characteristic Visual Lisp type called VLA-object (VLISP ActiveX Object). _$ (setq *objAcad* (vlax-get-acad-object)) # _$
This pointer is usually assigned to a global variable that will be used whenever it is necessary to return to the root of the object model. The Document object can be obtained from the Application object and also assigned to a global variable from which we can obtain references to the ModelSpace or to the PaperSpace of the various layouts in which we want to include our new graphic objects. This is convenient as repeatedly obtaining references to these objects whenever necessary would affect the performance of our programs.
Referencing the Application and Document Objects Working in a controlled environment, the procedure I use is to set global variables that refer to the application and document in a file of library functions loaded from acaddoc.lsp for each open drawing. In these cases it is necessary to avoid symbol name conflicts. To achieve this Autodesk provides an on-line prefixes registration service for the use of developers. These prefixes are composed by four characters. By applying for the registration of a particular character combination, the system checks if is not already registered. If it were not, it will be registered to the developer’s name. As an example, we have registered for this book the prefix AEVL. A symbol can be registered in the http://usa.autodesk.com/ web site, selecting the Community>Developers>Platform Technologies>Symbols Registration>Register a Symbol menu option. If the proposed prefix does not exist, the developer’s data is requested to proceed with registration. This service is free of charge.
Figure 6.1. On-line symbols registration service.
Having registered our developer prefix, we will use *aevl:acad* in this book’s code as a symbol for the Application object and *aevl:drawing* for the Document object. To provide an additional protection to these symbols so that trying to change its assigned value displays a message just like in the case of system protected symbols we use a non-documented expression: the pragma function. pragma is a compiler directive that, among other things, serves to assign a symbol the protected symbol status. We shall type in a file by the name of aevl-globals.lsp saved to the Visual LISP Projects folder the following code: (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*)))
This code works as follows: The first expression uses unprotect-assign to remove the protected symbol status to *aevl:acad* and *aevl:drawing*, the symbols to which we assign the Application and Document objects. This is necessary
in case they were already defined symbols, something which is not likely because they are loaded on opening the drawing. The setq expression assigns the objects to these symbols. The new call to pragma applies protect-assign to the symbols list, making them protected system symbols. As a second expression of our acaddoc.lsp file we will add the expression that loads the aevl-globals.lsp file: (vl-load-com) (load "aevl-globals")
From now on we will have symbols for the Application and Document objects that behave like any other protected system symbol. All of our functions that require these objects can refer to these symbols. We must remember that in case the acaddoc.lsp file is not used for this purpose we would have to assign the Application and Document objects specifically in our program. In that case I do not recommend using the pragma expression but simply assigning them with setq. This would also be essential if we create separate namespace VLX applications or in the case we want to distribute a program in FAS or LSP format to be used in an environment we do not control. We have applied the registered developer prefix only for the Application and Document global variables. In fact, it would be best to apply it to all of the functions used in our programs, especially if they are not compiled as a separate namespace VLX . We have not done this in the book’s code because we felt that could make it confusing.
6.4. Collections The objects in AutoCAD drawings are organized into groups called Collections. The rectangular boxes2 in Figure 6.2 indicate that the object is a collection. There are collections for both the graphic and the non-graphic objects. The Document object has properties that allow access to these collections. The Layers and Linetypes properties expose the Layers and Linetypes collections respectively. To obtain the graphic objects it will be necessary to access the ModelSpace collection through the ModelSpace property or the PaperSpace collection through the property named so. These collections are in fact BLOCK objects, therefore exposing a number of features belonging to this object type. These collections have an associated Layout object. All objects in these collections are also in the BLOCKS collection as part of blocks named *MODEL_SPACE and
*PAPER_SPACE.
The *MODEL_SPACE block and ModelSpace collection point to the same objects, just as the *PAPER_SPACE block and the PaperSpace collection. The ModelSpace and PaperSpace collections’ sole purpose is providing a more direct way for selecting the Model Space or Paper Space information. Besides the *MODEL_SPACE and *PAPER_SPACE blocks there are also graphic objects in the BLOCKS collection, the individual entities that make up block definitions, but these do not become visible until a block reference is created inserting it into PaperSpace or ModelSpace. As AutoCAD can have several drawings open at once, there is also a Documents collection, which includes the active document and all the others that are open at any given time. This collection is accessed through the Documents property of the Application object. Visual LISP provides specialized tools for processing objects that make up these collections. These functions, vlax-map-collection and vlax-for will be described further on. From the Document object we can refer to the collection we are interested in. Once the collection is referenced, its count property informs our application the number of items it contains, and the item method can be used to access a specific item using a name or index number.
Methods The item method employed to access a member of a collection is just one of many methods we can use with VLA objects. Unlike the properties that are data, methods are actions available for a particular type of entity. Some methods can be applied to most drawing objects. For example, the Mirror and Move methods can be applied to practically all of the drawing objects. However, the Offset method is only valid for a few classes of objects such as Arc, Circle, Ellipse,
Line and Polyline.
Properties All VLA-objects have properties. For example, a Circle object can be described by properties such as Radius, Area, or Linetype. To find out an object’s methods and properties we can use the vlax-dump-object function. This function takes two arguments, the VLA-object to be examined and a second optional argument that if not nil will also display the object’s methods. The vlax-dump-object function prints its output to the Console window. The properties and methods returned are those that belong to the ActiveX API.
Figure 6.3. Properties and methods of the Application object.
6.5. Working with methods and properties. VLA functions. In Visual LISP, we can access properties and invoke methods using the ActiveX functions. These functions are distinguished by the vla- prefix. The vlafunctions are VLISP wrappers for each of the ActiveX methods and properties. The vla-get-... functions read property values and the vla-put-...
functions change those values. The names of functions that invoke methods are created by prefixing the method name with vla-. _$ (setq *Layers* (vla-get-Layers *aevl:drawing*)) # _$ (vla-get-Name (vla-Item *Layers* 2)) "Roads" _$
The first of the preceding expressions returns a pointer to the Layers collection assigning it to the *Layers* variable. The second expression returns the third Layer object in this collection (the index 0 indicates the first item) using the vla-Item method and uses it as argument for vla-get-Name to obtain its Name property.
Generic VLAX functions. VLAX-INVOKE-METHOD. Object methods can also be invoked calling the generic function vlax-invokemethod. This function has the following syntax: (vlax-invoke-method object method arg [arg…])
where object represents the object to which it is applied, method represents the name of the method to be applied (either as a string or as a symbol) and arg, the method’s required arguments. To check whether a method is applicable to an object the predicate vlax-method-applicable-p can be used. For example, the GetBoundingBox method returns the lower left and upper right corner coordinates of the object’s bounding box and is common to all graphical objects, but cannot be applied to a non-graphical object such as the Layers collection: _$ (vlax-method-applicable-p *Layers* 'GetBoundingBox) nil _$
But although returning T, the method may fail with certain objects. To verify this we can draw an AutoCAD RAY entity. Returning to the Console we evaluate the following expressions: _$ (setq ray (vlax-ename->vla-object (entlast))) # _$ (vlax-method-applicable-p ray 'GetBoundingBox)
T _$ (vlax-invoke-method ray 'GetBoundingBox 'min-pt 'max-pt) ; error: Automation Error. Invalid extents _$
The RAY and the XLINE have an infinite extent, so it is not possible to calculate its bounding box. On trying to do so an error (also known as an exception) will be raised. Further on we will study how to prevent these errors from halting the program’s execution. VLAX-GET-PROPERTY. We also have the vlax-get-property function, used to obtain a particular property’s value whose syntax is: (vlax-get-property object property)
It takes as arguments the object and the property name. The name of a Layer can also be obtained with: _$ (setq *Layers* (vlax-get-property *aevl:drawing* "Layers")) # _$ (vlax-get-property (vlax-invoke-method *Layers* "Item" 2) "Name") "Roads" _$
VLAX-PUT-PROPERTY. To change a property’s value we have the vlax-put-property function. Its syntax is similar to that of vlax-get-property adding to it the property’s new value: (vlax-put-property object property value)
VLAX-PROPERTY-AVAILABLE-P predicate. As for methods, there is also a vlax-property-available-p predicate to check whether a property is available in an object. Note that in the following expression we will use a symbol to identify the property instead of a string. _$ (vlax-property-available-p ray 'Color) T _$
Deprecated ActiveX functions
For backward compatibility, Visual LISP for AutoCAD 2000 and above retains the functions that were used to manage ActiveX methods and properties in Release 14 VLISP. These functions are vlax-invoke for methods, together with vlax-get and vlax-put for properties. Although not documented in the current version, they are still operational and we can sometimes find them in programs posted to discussion groups and in documentation from non-AutoCAD sources. It is not recommended to use these functions. In case you have to convert old code you should take into account that the data type returned is not the same as that of the current standard functions. For example the Center property of a circle in the current functions would be obtained as follows, assuming it is the last entity drawn: _$ (setq obj (vlax-ename->vla-object (entlast))) # _$ (vlax-get-property obj "Center") #
The returned value is a Variant containing a safearray. This value would be suitable as argument for any of the other ActiveX methods. If converting this value to a list for its use in AutoLISP is necessary, we can resort to: _$ (setq ctr (vlax-get-property obj "Center")) # _$ (vlax-safearray->list (vlax-variant-value ctr)) (100.0 150.0 0.0) _$
While the deprecated function returns a list directly: _$ (vlax-get obj "Center") (100.0 150.0 0.0) _$
If we must change the circle’s center point, we could supply its coordinates to vlax-put as a list. _$ (vlax-put obj "Center" '(300.0 200.0 0.0)) nil _$
While for the vlax-put-property function you must supply its coordinates as a Variant containing a safearray. The center point’s coordinates list can be transformed into the correct data type applying the vlax-3d-point function. _$ (vlax-put-property obj "Center" (vlax-3d-point '(50 90 0))) nil
_$
Although apparently using the old functions could save labor, it is not recommended because in addition to not being officially documented, nothing guarantees its presence in future releases.
6.6. Collections Processing. Mapping functions The vlax-map-collection function operates on collections in a similar way as mapcar does on lists, applying a function or a lambda expression to each one of the objects in a collection. (vlax-map-collection collection 'function)
The collection argument must be a VLA object representing one of the document’s collections. The other argument may be a function name or a lambda expression, in both cases preceded by quote. The following function inverts the display status of the drawing: it turns on all Layers that are turned off and vice versa: (defun ax-on-off (drawing) (vlax-map-collection (vla-get-layers drawing) '(lambda (x) (if (equal (vla-get-LayerOn x) :vlax-true) (vla-put-LayerOn x :vlax-false) (vla-put-LayerOn x :vlax-true))))) Listing 6.7. Function to turn Layers on/off using ActiveX.
The argument drawing represents the active document object that we may have referenced as a global variable. The Layers collection is obtained through vla-get-Layers. The lambda expression is applied successively to each member of the collection, setting for each one its LayerOn property (that is, active or visible) as :vlax-false, keyword representing false in the VLISPActiveX environment, in case its value was :vlax-true and vice versa.
Iterating over collections Just as vlax-map-collection is to collections what mapcar is to lists, the
function would be the equivalent of foreach when dealing with collections. vlax-for
(vlax-for var-name collection [expression1 [expression2 …]])
The following function returns a list with the names of all the active drawing’s Layers in alphabetical order: (defun ax-layer-list (drawing / layers) (vlax-for lyr (vla-get-layers drawing) (setq layers (cons (vla-get-name lyr) layers))) (acad_strlsort layers)) Listing 6.8. Extracting a list of the drawing's Layers using ActiveX.
The argument drawing is a VLA-object representing the active drawing. The Layers collection returned by vla-get-Layers is traversed extracting from each item its Name property which is added to a list using cons. The Layer name list is finally passed to the acad_strlsort function that orders the strings alphabetically _$ (ax-layer-list *aevl:drawing*) ("0" "LIN1" "LIN2" "LIN3" "LIN4" "LIN5" "TEXT1") _$
A more generic function ax-names-list (Listing 6.9) which can be applied to any collection, offers the same result as the names-list function (see Listing 5.28). This function must receive as arguments the document object and the collection’s name either as a string or as a symbol. (defun ax-names-list (drawing name / collection names) (setq collection (vlax-get-property drawing name)) (vlax-for obj collection (setq names (cons (vla-get-name obj) names))) (acad_strlsort names)) Listing 6.9. Generic function to obtain the names of objects in a collection.
6.7. Managing exceptions. A program can have four different types of errors: syntactic, data type, logical and runtime. Of these, syntax errors, such as the lack of parenthesis or double quotes, or typos in function names will be immediately detected usually in the
early stages of program writing. Errors in the data type passed to a function or in the number and/or the order of its arguments will be detected as soon as you try it out even if the program is not complete. But logical errors that arise from poor algorithms or from implementation errors are not so easy to detect. They appear only when running the program and under certain circumstances. Division by zero is a classic example of this type of error, caused when the implementation of an algorithm does not take into account the possibility that the divisor can occasionally take a zero value. (defun tan~ (ang) (/ (sin ang) (cos ang))) Listing 6.10. Tangent calculation without anticipating a division by zero.
For example, implementing a function for calculating the tangent of an angle as in Listing 6.10 causes an error if (cos ang) returns 0. To avoid it such a function could be designed (See Listing 6.11) taking into account the possibility that if the number returned by (cos ang) were zero the highest floating point number possible should be returned (see Table 6.3), number which in practice approximates infinity. (defun tan (ang / cosine) (if (zerop (setq cosine (cos ang))) 1.8E+308 (/ (sin ang) cosine))) Listing 6.11. Calculation of the tangent anticipating for the division by zero.
Run-time errors do not always correspond to deficiencies in the program, but may result from situations unexpected or outside the limits for which it was designed. Erroneous entries by the user often cause such errors.
Exceptions. Run-time errors and some logic errors cause what are called exceptions, i.e., unexpected errors that cause the program to fail. Exceptions which are not managed properly will cause the program to halt unexpectedly without completing its task, leaving the system in an unpredictable state. So far in an AutoLISP program the best the developer could hope to do was to inform the user of the nature of the error and, if possible, attempt to restore the system to a known state by undoing the changes made up to the moment it occurred. With Visual LISP a set of functions intended to manage these
exceptions is included. Catching exceptions. The vl-catch-all-apply function allows a Visual LISP to catch runtime exceptions. Its use is similar to the apply function studied in a previous chapter. Using apply the program would be halted immediately. In the following example the variable result receives no value since the setq expression is not evaluated due to the error returned: _$ (setq result (apply '/ '(2 3 5 0 7))) ; error: divide by zero _$ result nil _$
But using vl-catch-all-apply we manage to catch the error and assign it to result: _$ (setq result (vl-catch-all-apply '/ '(2 3 5 0 7))) # _$ result # _$
Checking for an exception and its nature. To check if the result is as expected or if an exception was thrown we use the vl-catch-all-error-p predicate: _$ (vl-catch-all-error-p result) T _$
Once we know that it is an exception we can determine the error’s nature by examining the error message returned by vl-catch-all-error-message: _$ (vl-catch-all-error-message result) "divide by zero" _$
To use this message for anything other than informing the user we must bear in mind that they are translated to the language of the AutoCAD localized version being used. That is, it will not be the same message for a Spanish version as for an English one
Programming with exceptions. Catching exceptions we can approach the ideal of a program that never halts unexpectedly and if this were not possible, at least it could save any important information and restore the system to a known state. But exceptions can also be used to build more efficient programs, especially when using Visual LISP’s ActiveX. For example, to check if a Layer exists in the current drawing, with AutoLISP we should traverse the entire Layer collection, checking their names one by one. Using ActiveX and knowing that any collection will throw an exception if you try to access a non-existent member we can create a function to detect the existence of a Layer, a block, etc. without going through the whole collection: (defun ax-exists? (item collection / result) (if (not (vl-catch-all-error-p (setq result (vl-catch-all-apply 'vla-item (list collection item))))) result)) Listing 6.12. Checking for an item in a collection.
Testing it in a drawing in which "Layer10" does not exist: _$ (setq *Layers* (vla-get-Layers *aevl:drawing*)) # _$ (ax-exists? "0" *Layers*) T _$ (ax-exists? "Layer10" *Layers*) nil _$
A perhaps more versatile variation3 would return the VLA-object if any, somewhat in the manner of member: (defun ax-exists? (item collection / result) (if (not (vl-catch-all-error-p (setq result (vl-catch-all-apply 'vla-item (list collection item))))) result)) Listing 6.13. AX-EXISTS? function that returns the VLA-object.
6.8. Summary. Object-oriented programming exposes methods and properties with which we can attain results that by other means may require a significant programming effort. To use these methods and access these properties we must use the data types reviewed above: safearrays, Variants and VLA-objects. Safearrays can also provide a highly effective way to manage large volumes of data. Variant data types are universal containers for the exchange of information between VLISP and ActiveX servers. Although when we discuss operations on the drawing`s database we will expand on these issues, we have advanced here the basic concepts regarding: Creating and using arrays. Data conversion to and from Variants. Access to VLA-objects and use of their properties and methods. How objects are organized into collections and the way we can operate on them. Programming methods using exceptions. Recent additions to classical AutoLISP seem to point to a future in which this programming paradigm may be fully implemented without the need for ActiveX's COM interface.
Exercises. Exercise 1. State one advantage of using arrays instead of lists. State one advantage of lists instead of arrays. Exercise 2. The main diagonal in a square matrix (one having the same number of rows and columns) is the set of elements found in the line going from the upper left to the bottom right corner. A two-dimensional square matrix is called diagonal when all its elements except those in its main diagonal are null. Program the diagonal? function that returns T if a matrix represented by a safearray is diagonal and nil otherwise.
Figure 6.4. Diagonal matrix.
Exercise 3. Modify the tangent of an angle calculation function in Listing 6.11 using vlcatch-all-apply to catch and resolve potential division by zero errors. 1 The MILLISECS variable is not documented. Its use has been demonstrated in several contributions to autodesk.autocad.customization, especially those by Tony Tanzillo. 2 Rectangular shapes indicate collections and those with rounded corners are objects. The dark colored ones are not saved in the drawing. 3 Proposed by Eric Schneider in autodesk.autocad.customization, 01/09/2002.
Chapter 7 Data entry AutoLISP has a number of user data entry functions using the AutoCAD command line. These functions are identified by their names that start with the prefix get. This is not the only way to communicate with the user. Applications increasingly tend to display dialog boxes where, instead of sequentially entering the data, the user can select the appropriate options from various types of controls such as text boxes, action buttons, image buttons, etc. We will cover in this chapter the command line entries, which is how AutoCAD commands traditionally operate. Future chapters will discuss the implementation of Graphical User Interfaces (GUI) using two procedures: dialog boxes programmed in DCL language and OpenDCL, an open source application platform for Visual LISP that implements the use of Windows user interface components.
7.1. Integrated error control. The get... functions include data type control, avoiding user entries of data which are not compatible with the program’s requirements. A function like getreal aimed at obtaining a real number rejects anything that is not a number, which if integer is converted to real. Command: (getreal "Specify a real number: ") Specify a real number: a Requires numeric value. Specify a real number: 5 5.0
As shown in the previous example, get... functions accept as an optional argument a string that will be presented to the user as a prompt in the command line. As shown in the example, a loop is begun that can end in one of two ways: by entering an integer or real number, that will always be converted into real or by pressing ENTER to finish without entering any data. Command: (getstring "Specify a string: ") Specify a string: asdf "asdf" Command: (getstring "Specify a string: ") Specify a string:
""
On pressing ENTER the get... functions return nil, except getstring, that returns an empty string represented by two quotation marks with no space in between.
7.2. Default Values. This value returned on pressing ENTER can often be used to accept the program’s default values. The usual AutoCAD command prompts usually display default values surrounded by the characters "". For example, the OFFSET command presents the default offset distance this way. Command: _OFFSET Current settings: Erase source=No Layer=Source OFFSETGAPTYPE=0 Specify offset distance or [Through/Erase/Layer] :
We can develop a generic function (see Listing 7.1) to be used for all of the get... functions (except getstring) that reproduces this behavior. This function takes as arguments the name of the get... function to use (always preceded by quote), the message to be used as prompt and the default value. The function returns this value if ENTER is pressed. Otherwise, it returns the value entered if it matches the function’s expected data type. In case an incompatible data type is entered, the value is prompted for again. (defun default-value (func message value / tmp) (if (setq tmp (apply func (list (strcat message ": ")))) tmp value)) Listing 7.1. User input request including default values.
Testing it with getreal we obtain the following results, in this case entering number 22: Command: (default-value 'getreal "Specify a real number " 15.0) Specify a real number : 22 22.0 Command: (default-value 'getreal "Specify a real number " 15.0)
Specify a real number : 15.0
Other get... functions accept points picked on the screen as user input. For example getdist accepts a number or picking two points on the screen as a distance input. Command: (default-value 'getdist "Specify distance " 100.0) Specify distance : Specify second point: 63.1624
In the previous case a point was picked and getdist then prompted for a second point. In case ENTER is pressed the default value will be returned: Command: (default-value 'getdist "Specify distance " 100.0) Specify distance : 100.0
The default-value function used with getstring would not work properly because pressing ENTER would return "" instead of nil. It would therefore be necessary to design a function we shall call default-string (see Listing 7.2) for these specific cases. (defun default-string (message value / tmp) (setq tmp (apply 'getstring (list (strcat message ": ")))) (if (/= tmp "") tmp value)) Listing 7.2. Prompting for a string with default value.
With which we would get the same result as with default-value as shown in the following examples: Command: (default-string "Specify text " "SAMPLE") Specify text : abcd "abcd" Command: (default-string "Specify text " "SAMPLE") Specify text : "SAMPLE"
In this case it is not necessary to provide the function name as an argument, as it can only be getstring. If the argument value is not a string an error will be returned.
7.3. Prompting for data with options. Many AutoCAD commands in addition to providing default values, allow choosing between option keywords. The keywords that allow you to access the various options are usually surrounded by brackets ("[" and "]" characters). Command: ARC Specify start point of arc or [Center]:
Usually the options provided are alternatives to the normal input, in the case shown a starting point for the arc is prompted for and the alternative of picking instead the center of the arc is offered as an option, alternative that can be activated typing its abbreviation "C" (shown in upper case) or else the word "center" or part thereof. The options enclosed in brackets will appear in the contextual (right-click) menu from which they can also be selected. In this case as the basic request is that of a point, the input function to use would be getpoint, but with modifications to its behavior introduced by preceding it with the evaluation of the initget function which sets the desired behavior’s parameters. _$ (initget "Center") nil _$ (getpoint "Specify the arc's first point or [Center] :") (0.498113 0.751347 0.0) _$ (initget "Center") nil _$ (getpoint "Specify the arc's first point or [Center] :") "Center" _$
The first of the examples shown above displays the value returned by picking a point on the screen, the second the value returned by typing the letter "C" or selecting Center from the contextual menu. In the same way we designed the generic function default-value we can develop a function which admits options. (defun value-with-options (initget options) (if (setq tmp (apply func
(func message options / tmp)
(list (strcat message " [" (replace " " "/" options) "]: ")))) tmp)) Listing 7.3. Prompting for data including options.
When it is necessary to include several options, they are passed to initget as a single string where keywords are separated by spaces and have one or more uppercase letters that indicate their abbreviation. This abbreviation is usually the first character, but if more than one keyword begins with the same, other keyword characters (one or more consecutive characters) can be used. For example "Center ciRcumference clOsure" would be a valid initget option string. This string is represented in the listed function by the argument options. But in the prompt displayed in the command line the options should be separated by slashes. To transform the options argument string with one in which the keywords are separated by slashes we can use the vl-string-translate function that replaces a set of characters in a string with another one. This function will return the string of options properly configured for the user prompt: _$ (vl-string-translate " " "/" "Height Width Depth") "Height/Width/Depth" _$
This way we will have designed a new function which we can use whenever we must include prompts for data presenting alternative options. Command: (value-with-options 'getdist "Specify distance" "Height Width Depth") Specify distance [Height/Width/Depth]: w "Width"
An application can check the value returned to choose the appropriate action according to the user’s input. Besides the get... functions, initget defined keywords are also admitted by the entity selection functions entsel, nentsel, and nentselp, some of which will be discussed later.
Prompting for integers. We will now consider the getint function which prompts for integer values. (getint [message])
Integer values used by Visual LISP are in the range of -2147483647 to 2147483647. But getint only admits values between -32768 and 32767. Command: (getint "Specify integer value: ") Specify integer value: 2147483647 Requires an integer between -32768 and 32767. Specify integer value:
This is a vestige of old AutoLISP limitations that can be solved by means of a user-defined function (see Listing 7.4) that supports values within the current system limits. This would be achieved using getreal to obtain the number and fix to return an integer. (defun getfixnum (message / tmp) (initget 1) (setq tmp (getreal message)) (while (and tmp (not (< -2147483647.0 tmp 2147483647.0))) (prompt "Requires an integer from -2147483647 to 2147483647..\n") (initget 1) (setq tmp (getreal message))) (if (numberp tmp) (fix tmp) tmp)) Listing 7.4. Function that admits long integers.
When using integers near the limits it should be noted that numerical operations that generate values exceeding the system’s limits, if conducted solely with integers produce erroneous results: _$ (/ 2147483647 2) 1073741823 _$ (* (/ 2147483647 2) 3) -1073741827 _$
It is therefore advisable to check these limits and if necessary convert them to real numbers by using the float function.
Prompting for angular values. There are two different functions that prompt for angular values, getangle and getorient.
(getangle [point][message]) (getorient [point][ message])
In both cases the values returned are expressed in radians even if the keyboard data entry is done in AutoCAD’s current angle units format. Command: (getangle "Specify angle: ") Specify angle: 45 0.785398
The difference between them is that getangle measures the angle according to the current drawing’s angular units settings (ANGBASE and ANGDIR system variables) and getorient ignores these settings, assuming the system’s defaults. With getorient the angle is always measured from the default zero-angle (Positive direction of the X axis) and increasing counterclockwise. The value returned by getangle and getorient for the same angular direction may be different if the settings for system variables ANGDIR and ANGBASE have been changed. Command: ANGBASE Enter new value for ANGBASE : 45 Command: (getangle "Specify angle: ") Specify angle: 0,0,0 Specify second point: 100,0,0 5.49779 Command: (getorient "Specify angle: ") Specify angle: 0,0,0 Specify second point: 100,0,0 0.0
7.4. Input control through INITGET. Besides enabling keywords, initget can affect the behavior of command line input functions. These behavior modalities are determined by an optional numeric argument which can be included before the keywords string: (initget [bits] [options]). The bits argument is the result of adding the values shown in Table 7.1 according to the modalities to be set.
An initget expression only affects the get... expression that follows. Not every initget mode applies to all data entry functions. In particular, getstring doesn’t support any. Table 7.2 shows the modes that can be applied to the different functions.
7.5. Data coded as binary values. AutoCAD settings are frequently encoded in a similar way to those of initget. This encoding corresponds to the use of numerical values in binary format. Each position of a binary number represents a bit. A bit is the smallest unit of information that a computer can handle, and can only take one of two values, ON or OFF, symbolized in their numeric representation by 1 or 0. When we say that bit 0 is on, we mean it has the binary value 0000 0001; bit 1 is equal to the binary number 0000 0010 and so on. Therefore we say that the option for not accepting ENTER is enabled when bit 0 is set and the one discarding the Z value when bit 6 is set. If both were enabled that would mean that the value that is passed to initget, expressed in
binary format would be 0100
0001.
This may sound simple, but the difficulties arise when we consider that initget expects to receive, not this zeros and ones representation, but its equivalent decimal numbers. And the decimal equivalent of each bit would be given by 2n where n would be the position of the bit whose decimal value is sought. Expressed in terms of the expt LISP function that raises a number to a given power: (expt number power)
The decimal value of each bit would be (expt can verify the values expressed in Table 7.1.
2 n).
Using this expression we
_$ (expt 2 0) 1 _$ (expt 2 1) 2 _$ (expt 2 2) 4 _$ (expt 2 3) 8 _$
The same effect is achieved using the lsh1 function that shifts an integer the specified number of bits to the left if the numbits argument is positive and to the right if it is negative. (lsh [integer numbits]) _$ (lsh 1 0) 1 _$ (lsh 1 1) 2 _$ (lsh 1 3) 8 _$ (lsh 1 4) 16 _$
The binary value 0100 functions:
0001
_$ (+ (expt 2 0)(expt 2 6)) 65 _$ (+ (lsh 1 0)(lsh 1 6))
could be computed from either of these two
65 _$
Clearly we need some means of: Detecting which of these bits are enabled and which are not. Having a way to enable or disable one of them without disturbing the rest. The use of binary logical operators has been scarcely explained in AutoCAD’s LISP programming documentation. Its importance and possible applications are inversely proportional to the scarce attention devoted to it. Its use allows controlling a multitude of application parameters encoded as binary numbers of which initget is only one example. The logical operations on binary numbers available in Visual LISP are four: logand (logical binary AND) logior (logical binary OR) ~ (binary NOT) boole (general binary logical operator)
To these operations we must add the lsh function, binary shift, as a means to calculate the decimal values corresponding to each bit. Of these the most used functions are logand and logior. These functions allow us to solve the two problems we raised above.
Detecting which bits are enabled. Logand returns the logical binary and for two or more integers. We must recall that AND is a logical operator that returns true if all of its arguments are true and false when some are not. On a binary level, logand returns 0 for a bit whose value in all the arguments it receives is not always 1. Therefore we speak of a binary mask or filter. Only those values enabled for each of the filter’s bits pass.
Assuming that logand receives the values (expressed here both as binary and as its decimal equivalent) the results shown in Table 7.3 would be obtained. The order in which arguments are passed is irrelevant; the result will be the same. Exploring the enabled bits can be a tedious task, id-bits (Listing 7.5) is a small function that will ease this task. It identifies which bits are enabled for any value encoded this way. The list of values includes bits up to bit 17 which is the largest found (so far) in AutoCAD. This function operates by mapping a lambda function that applies logand to the number received as argument and each of the bit values included in a list. As disabled bits return zero, vl-remove is used to remove them from the list returned by mapcar. (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.
Changing a binary encoded value. If logand allows us to verify that a particular bit is set, adding a new value can be done using logior. We stated above that the bit encoded value is set by adding each bit’s decimal values. But in the event we must modify an existing
value either by enabling or disabling a bit, we should first determine whether that bit is already enabled or not. This can be done before deciding the action to take by using a logand expression as the condition for an if. The same can be done using the logior function.
BOOLE function. There is a binary logic function that summarizes all the others. It is the boole function. Its syntax is: (boole operator integer1 [integer2 …])
Boole’s action depends on the operator’s value. Its values are shown in Table 7.5. The value returned by (boole 8 132 4) seems strange. With the operator boole 8 returns 0 for any bit in which any of the supplied values is set, i.e., with value 1. And will return 1 for all those bits in which both values are disabled. In Table 7.5 we have only represented 8 bits, but binary numbers occupy 32 bits in present computers. In the other operations represented in the table this didn’t mean anything because all the bits from 7 on had 0 as their value. But the inversion made by the boole 8 operator means that now all those bits will have a value of 1. The result, represented as a list of 0 and 1 would be2: '(1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 0 1 1)
The negative value comes from bit 31, the last one, which is used to define the
sign. This bit’s value expressed as a decimal number can be obtained evaluating the expression (lsh 1 31). _$ (lsh 1 31) -2147483648 _$
The sum of the values of (lsh 1 n-1) where n represents the position of each of the bits set for the present case, except for bit 31, would return 2147483515. Subtracting the value returned by bit 31 we would get: _$ (+ 2147483515 -2147483648) -133.0 _$
which is the value returned by (boole 8132 4). As can be seen from this explanation, the bit 31 is used to determine the sign. In most cases we can solve our problems using logand and logior. Both logand and logior accept more than two arguments. In this case: 1. The function is applied to the first two arguments, obtaining an interim result. 2. The function is then applied to this interim result and the third argument, obtaining a new interim result. 3. This process is repeated until all of the arguments passed to the function have been processed. In case of using more than two arguments, you must take this into account, since each new operation will receive the value arising from the previous one and not the original arguments.
The following three functions (Listing 7.6) can be useful to check if certain bits are set and to enable or disable them3. (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.
Other binary encoded values. These functions can be used in any context where system settings are encoded in
this way. Other frequently used AutoCAD values are also encoded as binary values. An example of this are the values of the OSMODE system variable which establishes running object snaps settings. Values for OSMODE range from bit 0 (ENDpoint snap) to bit 13 (PARallel snap). The decimal equivalent of this last bit is 8192. _$ (lsh 1 13) 8192 _$
OSMODE values are the sum of those values identifying enabled object snap modes. These values are encoded as binary numbers. Table 7.6 shows their possible values.
According to AutoCAD’s documentation, adding 16384 to the current value temporarily disables all the running object snaps. This is equivalent to setting bit 14. The effect of setting bit 14 in OSMODE is equivalent to that obtained by pressing F3, object snaps are disabled without losing the current settings. So we can then disable OSMODE without changing its value to zero, simply by enabling or disabling bit 14. The value of bit 14 is given by: _$ (lsh 1 14) 16384 _$
The following expressions turn off or on the current running object snaps. The value returned depends on the object snaps that were active at the time of evaluating the expression. Instead of (lsh 1 14) the number 16384 could be included directly. We use this expression in order to clarify its origin.
_$ (setvar "osmode" (enable-bits (lsh 1 14)(getvar "osmode"))) 19150 _$ _$ (setvar "osmode" (disable-bits (lsh 1 14)(getvar "osmode"))) 2766 _$
This is an opportunity to demonstrate another use of the boole operator. In this case using the option 6, XOR or Exclusive OR. We shall see the definition of XOR in Part 3, Chapter 11 (Table 11.7) when dealing with the ssget selection function. Using the boole operator a variables switch can be programmed. (defun switch-sysvars (varsis bits) (setvar varsis (boole 6 (getvar varsis) bits))) Listing 7.7. Variables switch.
For disabling and restoring OSMODE without losing the current running object snap settings the following two expressions can be used. Note that the coding used in both cases is the same. This is precisely the way XOR operates: The same bit cannot have the same state in both arguments. So if it is off it will be turned on and vice versa. To make this clear, suppose that only the ENDpoint object snap is active, meaning OSMODE’s value is 1. _$ (switch-sysvars "osmode" (lsh 1 14)) 16385 _$ (switch-sysvars "osmode" (lsh 1 14)) 1 _$
The first value returned is the sum of 16384 + 1, which is 16385. Evaluating the expression again returns 1, thus recovering OSMODE’s original setting. Whenever we use the command function (Or its Visual LISP variant vl-cmdf) we must take a series of measures affecting the drawing’s environment, at least turning off running object snaps, not to mention that for newer releases we will have to take into account the 3D object snaps (3DOSMODE system variable). We have considered in the code the possibility that the program runs on an older release in which this variable does not exist. For this reason when restoring 3DOSMODE to its previous state we check if the variable holding its original value is not nil.
In reviewing the drawbacks of using command we referred to command line prompts. For this reason when using command/vl-cmdf it is advisable to take also in account the CMDECHO system variable, set using bit 1: value 0 turns off prompts and 1 turns them on. We must always turn it on when the program ends. As this would be necessary whenever we invoke command/vl-cmdf or when we must select points in the graphic window, it is convenient to group them into two functions, one to be called before entering the command (Listing 7.8) and the other (Listing 7.9) to be called on exiting it. Both functions control the errors that would occur in cases where: an AutoCAD release is used in which 3DOSMODE does not exist. OSMODE would already be disabled (= (logand 16384 (getvar "OSMODE")) 0)
OSMODE was not disabled with F3 (= (logand 16384 (getvar "OSMODE")) 16384).
Whether the CMDECHO system variable is enabled or not upon entering or exiting command. To turn off and on running object snaps and command prompts the functions bits-on? (Listing 7.6) and switch-sysvars (Listing 7.7) are used. Turning off command prompts using CMDECHO, we might find that if an error occurs or if the user cancels the command by pressing ESC or invoking other command, prompts will not be reactivated creating a situation that can be annoying for the user. To prevent this situation an error handling function should be defined that in those cases would restore the system to its previous state. (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.
7.6. File search dialog box. The exception among the get... functions is getfiled. This function instead of requesting information from the command line uses the AutoCAD file search dialog box so users can select a file name and path. Its use is extremely useful in combination with the file reading and writing functions that will be described in Chapter 8. The getfiled function displays a dialog box containing the list of available files of the specified type. It can be used to find files in different folders, to select a file or to specify the name for a new one. (getfiled title file-name extension bits)
The title argument is a string of characters that appear in the dialog’s title bar. A string with the default file name to use can be passed as the file-name argument. It can be an empty string (""). To specify the file type the extension argument, a string with the desired extension (as "dwg" for drawings), is used. If an empty string, the "*" wildcard is assumed, meaning any type of file. In case "dwg" is specified as the extension the dialog box displays a drawing preview. As with initget, the bits argument is encoded as binary values whose meaning is explained in Table 7.7. After closing the file selection dialog box, getfiled returns a string containing the name of the file specified by the user or nil in case none has been specified. The following code snippet opens the DWG file selection dialog shown in Figure 7.1. Following the expression the value returned by getfiled on selecting the bushing.dwg drawing is shown. Note that the (getenv "PUBLIC") expression which returns the path of the Users\Public folder is invoked, concatenating the string returned with the rest of the path "\\Documents\\Autodesk\\Vault 2012\\Samples" and "\\Acad 2012\\AutoCAD Tutorial\\". It should also be noted that in the localized versions of Windows the names of system folders are translated in Windows Explorer, although in these path strings the real English names should be used. _$(getfiled
"Select a drawing file" (strcat (getenv "PUBLIC") "\\Documents\\Autodesk\\Vault 2012\\Samples" "\\Acad 2012\\AutoCAD Tutorial\\") "dwg" 8)
"C:\\Users\\Public\\Documents\\Autodesk\\Vault 2012\\Samples\\Acad 2012\\AutoCAD Tutoria _$
Figure 7.1. File selection dialog box displayed by GETFILED
7.7. Summary. The usual way in which communication is established between AutoCAD and the user is through the command line. AutoLISP since its first version incorporates a number of functions that allow the programmer to implement in his programs the same type of communication that takes place when using native commands. These functions are recognized by their names that always begin with the prefix get. Almost all the get... functions accept changes in their behavior regarding the support of keywords as an alternative to their specific data type and as a way to control the permissible user input. These changes in their behavior are set using the initget function, which must be called immediately before. There are also techniques that allow the programmer to include default values that are accepted by pressing ENTER. We have shown how to define user input functions that include either default values or keywords for any of the get... functions in which this behavior is accepted. Unlike other get... functions, getfiled displays a dialog box instead of command line prompts. Both initget and getfiled support arguments encoded as binary values. This technique is widely used in the definition of AutoCAD object properties. In this chapter we have defined the procedures used in working with these binary values that will be used in the following chapters.
Exercises. Exercise 1. Using the techniques demonstrated in Listing 7.1 and Listing 7.3 develop a function that proposes both keywords and a default value. Exercise 2. Develop a function that allows selecting a LISP file by means of a dialog box. 1 Name derived from Left SHift. 2 We have published in our website two functions that can be used for the conversion of decimal numbers to binary numbers expressed as lists of zeros and ones and vice versa: http://www.togores.net/vl/curso/lisp/bases/control/binario/logand/dec-bin http://www.togores.net/vl/curso/lisp/bases/control/binario/logand/bin-dec 3 These functions were originally proposed by Vladimir Nesterovsky in the autodesk.autocad.customization discussion group.
Chapter 8 File Operations For its use in other work sessions or to transfer data to other applications it may be necessary to store information generated by programs in external files. Visual LISP provides functions to read and write external text files and for locating files or folders in our computer or in a network environment. This chapter is dedicated to exploring these operations
8.1. Opening files. To read or write to a text file we must open it first. Opening a file can be done in reading, writing or append mode with the open function. To open a file in read mode it is necessary that it previously exists. The other two modes will create the file if it does not exist. The open function should receive as arguments the file name and a letter that identifies the desired mode: (open file-name mode)
where file-name is a string that can include the path and mode can be "R" for reading, "W" for writing or "A" for appending1. If the path is not specified VLISP assumes AutoCAD’s default drawing folder. This function returns a filedescriptor object or nil if the file to be opened in reading mode does not exist. This file-descriptor must be assigned to a variable that will be used by other input/output operations performed on that file. The findfile function checks whether a file is located in one of the AutoCAD library search paths and -in Release 2014- the trusted file paths. It is typically used in combination with open: _$ (setq file-id (open (findfile "acaddoc.lsp") "r")) # _$
Note that the backslash in the path is duplicated. This is because in VLISP the backslash as part of a string is used to include control characters (escape codes) such as tab (\t) or newline (\n). The \\ sequence within a string represents the backslash character2. Release 2014 has introduced the new findtrustedfile function which
operates just as findfile, but searching for files in folders contained both in the Support and Trusted Locations lists. _$ (findtrustedfile "numera-calculus.lsp") "D:\\Visual Lisp Projects\\Numera\\numera-calculus.lsp" _$
The file-descriptor is another special data type in AutoLISP: _$ (type file-id) FILE _$
Aside from findfile there are other functions used to manage the file system which we will study further on.
8.2. File reading. A file may be read letter by letter or line by line, the latter being more usual. To read letter by letter the read-char function is used while for reading line by line we use read-line. If a file-descriptor, is not supplied both read-char and read-line will wait for keyboard input. read-char returns the ASCII numerical code for the character read. _$ (read-char) 97
In the example above the letter a was typed and the value returned was 97. When several letters are typed, calling (read-char) repeatedly will return one by one the codes for the different letters until the code 10 introduced when pressing ENTER is found. This code belongs to the new-line ("\n") character. With read-line we will recover the entire string of characters typed until ENTER is pressed. _$ (read-line) "abc def ghi jkl" _$
When a file-descriptor returned by open is supplied, the repeated call to any of these functions keeps on reading the next character or line until the end of the file, then returning nil. This can be used as the end condition for a while loop. The use of read-char would be appropriate in situations where we need to
retrieve special characters such as newline. The function in Listing 8.1 reads a file character by character and displays it in an AutoCAD message box: (defun read-letters (file / file-id txt-str) (if (setq file-id (open (findfile file) "r")) (progn (setq txt-str "") (while (setq character (read-char file-id)) (setq txt-str (strcat txt-str (chr character)))) (close file-id) (alert txt-str)))) Listing 8.1. Reading a file character by character.
Figure 8.1. Acad.mln file read with READ-LETTERS.
As read-char returns not a character, but an integer corresponding to its ASCII code, it must be converted back to a character by using the chr function. The line change occurs when you read the last character on each line, the ASCII code 10, which is \n (newline). The characters are concatenated in the string
assigned to the txt-str variable. Once finished reading the file, this string is passed as an argument to the alert function. As the string includes the newline characters, the message displayed in the AutoCAD message box exactly reproduces the original file’s configuration (see Figure 8.1). Once read, the file must be closed with the close function. More frequently files are read line by line using read-line instead of readchar. In this case the newline character is used by the function to determine where each line ends and is not included in the returned string. Supposing a text file in which each line contains the XYZ coordinates of a point, separated by spaces3: 255.4725 134.2724 25.4300 300.3431 87.9725 27.0900 198.5400 124.1443 21.5000 239.5507 82.6673 18.0000 251.6127 124.1443 18.6300 206.7422 160.3161 17.0800 145.9499 116.4276 18.2300
We can implement a function to read that file and return a list of points in a format appropriate for its use in the automatic insertion, for example, of survey points. The process as always begins opening the file in read mode. As in the previous Listing, the file will be read within a while loop. But in this case, to each line an opening and closing parentheses are added. As it is still a string it will be necessary to transform them into a list data type before they are included in the list assigned to the local variable tmp. This is accomplished by read. We must not forget to close the file it has been read. As the list has been assembled using cons, reverse is used to recover the original order. (defun file->list (file / file-id tmp) (if (setq file-id (open file "r")) (while (setq txt-line (read-line file-id)) (setq tmp (cons (read (strcat "(" txt-line ")")) tmp))) (close file-id)) (reverse tmp)) Listing 8.2. Reading a file into a list.
8.3. Writing files. The file reading functions read-char and read-line have their counterparts
for writing files: write-char and write-line. The use of write-char is appropriate when including special characters or escape codes is required. The write-line function is frequently used for writing strings composed using some of the string manipulation functions provided by Visual LISP as it automatically adds the newline. In addition to these two functions we can also use print, princ and prin1. The print function also adds a newline character, but in this case before the string, plus a trailing space, printing quotation marks to delimit the string and any escape code: _$ (progn (print "text\twith")(print "\t\"tabs\"")(print)) "text\twith" "\t\"tabs\""
In contrast prin1 prints the opening and closing quotation marks and all the escape codes, but without adding the newline character or the trailing space: _$ (progn (prin1 "text\twith")(prin1 "\t\"tabs\"")(prin1)) "text\twith""\t\"tabs\""
With princ the string’s delimiting quotation marks or any included escape code are not printed. The control sequences "\t" and "\"" print as tabs and quotation marks respectively: _$ (progn (princ "text\twith")(princ "\t\"tabs\"")(princ)) text with "tabs"
The expressions are included in a progn block and include a last call to the princ function without any argument to avoid printing the returned value. The function in Listing 8.3 writes the values contained in a list with sublists to a file, each sublist corresponding to a new line. The values are separated by the string specified in the delim argument. This function uses prin1 to ensure that the strings are enclosed in double quotes, princ for the delimiting character and write-char for the newline character. The princ function could also have been used for this. (defun list->csv (lst delim file add / file-id) (if (setq file-id (open file (if add "a" "w"))) (progn (foreach sublist lst
(while (setq value (car sublist)) (prin1 value file-id) (if (setq sublist (cdr sublist)) (princ delim file-id))) (write-char 10 file-id)) (close file-id)))) Listing 8.3. LIST->CSV Function.
If the add argument is not nil the data will be appended to the file instead of overwriting it. If the file does not exist, it will be created. Since it is not possible to define VLISP user functions that support optional arguments, we must include a value for the add argument even if it is nil.
String handling. Writing to files using write-line requires previously composing the string to be printed. Visual LISP has increased the number of functions available to handle strings. These functions allow concatenating strings, extracting or searching substrings, converting other data types into strings and vice versa or comparing them. We will use some of them in a final example dealing with file writing. In this case it writes a data file with fixed width columns. This format is usually known as SDF (for Standard Data File). To achieve this we must use some auxiliary functions. Unlike the list->csv function in which we used princ, in this one we must first convert the data to string in order to establish its length. To do this we implement the list->text function. If the values are numeric, their conversion to strings is done with rtos to control the number of decimals, value that is passed as the argument prec. The fact that the value is numeric is checked using the numberp predicate. This conversion is done by mapping to the sublist the expression '(lambda (y) (if (numberp y)(rtos y 2 prec)(vl-princ-to-string y)))
When values are not numbers, the vl-princ-to-string function that transforms into a string the printed representation of any LISP object is used. (defun list->text (lst prec) (mapcar '(lambda (x) (mapcar '(lambda (y) (if (numberp y)
(rtos y 2 prec) (vl-princ-to-string y))) x)) lst)) Listing 8.4. Conversion of list items to text.
The mapcar function is used because the resulting values are returned as a list. This is why it is also used on the list’s top level. This function is very useful when we want to pass data to applications such as spreadsheets or databases that support comma delimited files (CSV) to import values. It should be noted that converting real numbers to strings using rtos is influenced by the DIMZIN system variable which controls whether trailing zeros in decimals are removed. To obtain the number of decimal places specified by the prec argument including the trailing zeros the following procedure may be used: (setq tmpvar (getvar "dimzin")) (setvar "dimzin" 0) ... program's body ... (setvar "dimzin" tmpvar)
This is a usual practice when temporarily changing a system variable is required: assigning the current value to a local variable, setting the new value required by the program and afterwards restoring the original value. As the number of characters in each data item can vary, we must include additional space characters to complete the desired number for each column. But to achieve a more universal function (see Listing 8.5) we also consider the possibility that the string exceeds the length permitted and must be truncated. A third possibility is that the string has the required number of characters, in which case the original string should be returned. This is a typical case for using the conditional function cond to cover the three alternatives. To determine the appropriate alternative the difference between the desired length (argument long) and the string’s length (strlen string) is assigned to the variable times. The zerop predicate verifies if times is zero, then returning the original string. (defun length-string (character string long / times) (setq times (- long (strlen string))) (cond ((zerop times) string) ((minusp times) (substr string 1 long)) (t (repeat times
(setq string (strcat string character)))))) Listing 8.5. Giving a string a fixed length.
With minusp we check if times is negative, which means that the string should be truncated. For truncating it to a number of characters the substr function can be used. The expression (substr string 1 long) returns the substring starting at the first character (position 1) including the number of characters specified in the argument long. If none of these alternatives is true the default clause is executed concatenating the character specified in the character argument to the string the necessary number of times to achieve the desired length. Having defined these auxiliary functions, writing to a file would be solved in a similar way to that of Listing 8.3, always considering that the input list will be a list of sublists, each of which represents a line of the file to write. As in the previous case if the argument add is not nil, the new data is appended to the existing file. Note that in both cases the sublists must be proper lists, not dotted pairs. This case is even simpler because in CSV file writing, it was necessary to discriminate when the delimiting character should not be added, which was done in a while loop. Now we simply nest two foreach expressions to iterate over each level of the input list. The string is created using strcat and is temporarily assigned to the variable tmp. After completing each line it is written to the file using write-line. The code in Listing 8.4, Listing 8.5 and Listing 8.6 must be loaded to run the function list->sdf, so it is convenient to write them in the same file, loading it with CTRL+ALT+E. (defun list->sdf
(lst long prec character file add / file-id tmp) (if (setq file-id (open file (if add "a" "w"))) (progn (setq lst (list->text lst prec)) (foreach sublist lst (setq tmp "") (foreach value sublist (setq tmp (strcat tmp (length-string character value long))))
(write-line tmp file-id)) (close file-id)))) Listing 8.6. LIST->SDF function.
8.4. Files and Folders. Visual LISP provides a set of functions designed to manage file names and perform operations on files stored on disk. To access the locations of many of the files used in the system we can find the necessary information on the properties of the Application object or of the Preferences object. To access this information the following procedure can be followed: Obtain a reference to the application object using vlax-get-acad-object. Because of its frequent use we may assign this reference to a global variable as in: (setq *aevl:acad* (vlax-get-acad-object))
To find this object’s (or any others) available properties we can use the vlaxdump-object function (See Figure 6.3). We can see that the AutoCAD installation path is obtained from the Path property and in FullName we have that same path including the executable file name. For the rest of the paths we can read the Files property from the Preferences object as shown in Figure 8.2.
Figure 8.2. Files object properties.
Knowing the property names, we can get the path to the folder where a particular file is stored prefixing it with vla-get-. For example to get the path where texture files are stored: _$ (vla-get-TextureMapPath (vla-get-Files (vla-get-Preferences *aevl:acad*))) "C:\\Program Files (x86)\\Common Files\\Autodesk Shared\\Materials\\Textures\\1\\Mats"
It is also possible to find paths stored in system environment variables using the getenv function. For the Operating System’s default directory and the temporary files folder: _$ (getenv "SystemRoot") "C:\\Windows" _$ (getenv "TEMP") "C:\\Users\\username\\AppData\\Local\\Temp" _$
With getenv AutoCAD paths and file names can also be found, but these variables are not documented, so vlax-dump-object may be a better way to
find the information needed. As an example of some of these variables we have these that refer to default Linetype files. _$ (getenv "ANSILinetype") "acad.lin" _$ (getenv "ISOLinetype") "acadiso.lin"
The vl-directory-files function returns a list with the names of files in a folder: _$ (vl-directory-files (vla-get-Path *aevl:acad*) "acad.*" 1) ("acad.exe" "acad.exe.config" "acad.mst") _$
It receives as arguments the path obtained from the Application object’s Path property, the string "acad.*" indicating the pattern for the names to look for, where * is a wildcard that admits any extension and the integer 1 indicating that only files and not folders (if any names fit the pattern) should be returned. With vl-directory-files we can design a function that checks whether a file exists. (defun file-exists? (name folder) (vl-directory-files folder name 1)) Listing 8.7. File searching function.
The existence of a drawing template file in the AutoCAD templates folder can be checked using the expression: _$ (file-exists? "*title*" (vla-get-TemplateDwgPath (vla-get-Files (vla-get-Preferences *aevl:acad*)))) ("Architectural Title Block.dwg" "Generic 24in x 36in Title Block.dwg") _$ (file-exists? "myfile*" (vla-get-TemplateDwgPath (vla-get-Files (vla-get-Preferences *aevl:acad*)))) nil _$
The following function (Listing 8.8) returns a list of available drives. In the case of removable media (floppy, CD-ROM) they must be in the drive to be recognized. Drive letters to test are generated using the function chr that returns a character according to its ASCII code in a while loop that begins assigning to the variable code the value 65 that represents the letter a and ends with the value 90 belonging to letter Z. Values are incremented applying the 1+ function.
(defun drives (/ code tmp) (setq code 65) (while (csv function and using the semicolon ";" as the delimiter. Give the file a name generated by vlfilename-mktemp. Remember to assign the name to a global variable for subsequent operations. After creating the file, verify its existence with fileexists?. Once its existence is verified, read the file with read-letters. If you have a spreadsheet application in your system, try importing data from the CSV file. Exercise 2.
Using the same list of points write to disk a SDF file with a column width of 15 characters and a numerical precision of 5 decimals, using spaces as padding characters. Compare the original list with that obtained by using the file>list function using the SDF file as input. Try, as in the previous case, importing the file’s data to a spreadsheet. 1 Before Visual LISP these characters that indicate the aperture mode had to be always lowercase. Presently both upper and lowercase characters are admitted. 2 Other special characters also require the backslash. For example, to represent double quotes within a string \” must be used, as without the backslash it would be taken as the string’s end. 3 This fixed width columns file can be written using the function from Listing 8.6.
Chapter 9 Debugging Visual LISP code The programs do not always behave the way we want. When we get different results from those expected or the program halts because of an error, discovering what went wrong can be very difficult. VLISP offers a series of tools that can assist in the debugging process in order to find and solve any problems our programs may present.
9.1. Finding the error's origin. Immediate evaluation of the code The first option we have, and the first one to which we should resort, is the immediate evaluation of each line of code written. As we write in the editor window, we can select the code we want to check by double-clicking next to the opening or closing parentheses and click, in the Tools toolbar, on the Load Selection button (or CTRL+SHIFT+E). The highlighted code is immediately evaluated (see Figure 9.1) its result being displayed in the Console.
Figure 9.1. Loading a selected expression.
Figure 9.2 shows the code for a program used to extract from a LWPOLYLINE's entity list the coordinates of its vertices. Selecting the line (setq lst (entget (car (entsel)))) and loading it, the AutoCAD graphic window is set in focus where we are prompted to select an object. On doing so, the Visual LISP console will show the returned entity list, which will have been stored in the
lst
variable.
To verify the nature of this data we can place the cursor on the variable’s name and select the View toolbar’s Inspect button (CTRL+SHIFT+I). Thus it would be possible to check, line by line, the effects of the different expressions we use to achieve a certain goal.. What if our code had a bug? Suppose we have forgotten to include in this line of code the function car. In this case, on evaluating only (entsel) instead of an entity name a list like: ( (45.4038 41.8009 0.0))
would be returned, which used as an argument for entget would cause an error displaying in the Console the message: ; error: bad argument type: lentityp ( (48.9771 44.1075 0.0))
where lentityp is a predicate that indicates the nature of the argument that the entget function expected to receive (an entity name), and then displays the actual data received. Table 9.1 shows some predicates associated to error messages. The Visual LISP documentation does not explain the meaning of these messages.
Syntax checking. The code in an editor window can be checked for syntax errors before even evaluating an expression. To do this we have the Check edit window (CTRL+SHIFT+C) button in the Tools toolbar.
This command is can check the following errors: An incorrect number of arguments supplied to a known function. An invalid name in a variable passed to a function. For example passing a symbol preceded by quote when a variable is required. An incorrect syntax in calls to special forms such as lambda, setq and foreach. Some syntax errors can only be detected at runtime, such as passing incorrect data types, since the type of data that LISP variables hold are not previously declared. A common mistake that will be detected by this command is that of improperly balanced opening and closing parentheses. Upon detecting an error the user is notified in a special output window. The error is highlighted in blue and when you double-click on that line, the cursor moves to where the error has been detected in the code editor window and the expression is highlighted.
9.2. The debugging session. The easiest way to start debugging is to select the VLISP menu option Debug>Stop once. With this option selected, the program will pause on evaluating of the first expression. To continue the program’s execution several debugger commands that will be explained below can be used. Another way to enter the debug mode is to set a breakpoint before the expression we want to test. This may well be the expression which, as we can see in the Error trace window has caused the error. When an interruption occurs, the expression where program execution has stopped will be highlighted in the Editor. The console prompt also displays the number of the current break loop level. Using the console is possible to gain access and manipulate the program environment in which the interruption occurred. Variables can also be examined using the Watch window.
Figure 9.2. Editor window showing selected code and breakpoint.
Break loops. When running a program without the VLISP debugger’s intervention, its execution takes place in the read-eval-print loop’s top level. Also when an expression is evaluated in the console that shows the normal prompt we are working in the top level. Once the evaluation of a running program is interrupted, VLISP transfers control to the console and enters a break loop. This is a different read-eval-print loop, nested under the original one. You may also interrupt this second loop and start yet another read-eval-print loop as a new nested level. The nesting level of a break loop with reference to the top level is known as the break level. When entering a break loop, VLISP adds a number to the console prompt indicating the level in which we are working. When we enter a break loop for the first time, the console prompt reads: $_1_ While in a break loop it is not possible to transfer control to the AutoCAD graphics window. Emerging from a break loop (e.g., by selecting the Quit button from the Debug toolbar or CTRL+Q), execution at the current level ends, and the break loop one level up is resumed. If a variable’s value has been changed in a break loop, this value will be used when the program continues its execution.
Break Points.
Once a problematic function is identified, we will want to check how it works step by step. Introducing breakpoints we can pause program execution allowing us to verify its operation in detail. To place a breakpoint we can pick the Toggle Breakpoint button (See Figure 9.3), the corresponding Debug menu option or simply press the F9 function key.
Figure 9.3. Debug Toolbar.
We can control the pace at which expressions are evaluated using the Step Into button to evaluate each expression nested in the highlighted expression, the Step Over button that evaluates the highlighted expression, including its nested expressions or Step Out that evaluates all of the expressions up to the end of the function where the program is paused. Selecting the Continue button or pressing CTRL+F8, we will be taken to the next breakpoint, if any. To move up one level (as there may be multiple nested break loops) the Quit Current Level button (CTRL+Q) may be used and to return directly to the top level we can use the Reset to Top Level button or CTRL+R. During the interruption we can evaluate other expressions in the Console to get information about the content of the variables (for example, typing the variable name and pressing ENTER) and even change their content by evaluating expressions in the Console and also using the Symbols service (See Figure 9.4) selecting its button on the toolbar or through the shortcut CTRL+SHIFT+S. To keep the breakpoints set during a work session it will be necessary to use the Save Settings command in the Tools menu. Breakpoints are lost when applying automatic formatting operations.
The Animate option.
The Debug menu includes the Animate option. When this option is selected, the program will be run as if automatically entering a Step Into command for each expression. The time interval between the evaluation of expressions is configurable using the Tools>Environment>General Options (Diagnostic tab). To stop the animation you can press the PAUSE/BREAK key and thus observe and if necessary change the values of variables or place new breakpoints. When a program running in Animate mode arrives to a breakpoint the buttons Step into, Step over and Step out must be used to continue evaluation. In this case the Animate execution mode is restarted by clicking the Continue button. The Animate mode is very instructive especially in programs that include nested loops.
Non-Continuable Break Loops. The program automatically enters a break loop when a runtime error is found if the Break on Error option is selected in the Debug menu. In that state we will have access to the contents of all variables in the environment in which the error occurred, but it will not be possible to continue the program’s execution.
9.3. Data inspection tools. The Symbol Service. The Symbol service dialog box displays the current value of a symbol and its properties. Both the values assigned to the symbol and its properties flags can be modified using this dialog. This can be done through: A toolbar. A Name text box with the symbol's name. A Value edit box showing the value assigned to the symbol and in which a new value can be typed. A series of checkboxes that allow you to set various symbol flag options.
Figure 9.4. Symbol Service window.
If the specified symbol is a protected system symbol the Value edit box will be read-only and the Protect Assign checkbox will be marked. This protection will be removed unchecking this box. In the same way it can protect a userdefined symbol (e.g., a user-defined function) so that it will behave as a primitive system symbol. A protected symbol will be colored blue in the editor or the console. The toolbar displays three buttons with the functions in Table 9.2.
Symbol Service Options. The symbol flags that can be set from this dialog box are shown in Table 9.3.
The Watch window. We also have the opportunity to monitor the values returned by functions. This is done through the Watch window. To open it with the value of the last evaluated expression Watch Last Evaluation is chosen in the Debug menu. Or we can click the Add Watch button in the View toolbar (see Figure 9.3), thus opening the Add Watch dialog box. If the cursor was located on the name of a variable, it would appear in the dropdown list box. Otherwise, we can write its name or choose the default value that is the global variable *LAST-VALUE*. If the expression were highlighted, it is added automatically to the Watch window. The Watch window (See Figure 9.5) is updated as the inspected symbol values change. Thus, on entering a break loop the current values will always be displayed. These values can be copied to the Trace window by clicking on the Copy to Trace/Log button. If the Toggle Trace Log... option is enabled from the contextual menu of the Trace window this information will also be copied to a log file.
Figure 9.5. Real-time inspection of selected variables.
Trace Stack. The Trace Stack contains the historical record of the execution of functions within the program. A Stack is a programming structure that is characterized by the order of entry and exit of elements stored: the last item in is the first out (LIFO). Its inspection during a program interruption allows us to view what has happened during execution. Invoking a function includes an element in the stack. When calling other nested functions new elements can be added to it. This will be done whenever Visual LISP needs it to remember the way out. A stack may include five types of elements (see Table 9.4). The contextual menu can be used for more information about any of the elements in the stack (see Figure 9.6).
Some options of particular interest are: Local Variables: Displays the Frame Bindings dialog box to allow browsing of local variable values at the time the function was called. Source Position: Shows the source code highlighted in the editor window. Call Point Source: Shows the position of the caller expression, similar to Source Position. According to the selected item one or the other, and sometimes both options will be available .
Figure 9.6. Trace Stack with its contextual menu.
Trace Stack Keywords. A keyword indicates a specific operation taking place in the VLISP environment. Some only appear in the bottom of the stack, and others in its top. Table 9.5 shows some of the keywords that only appear at the bottom of the stack.
The most frequently displayed keywords on the top position of the stack are shown in Table 9.6.
9.4. Error Tracing When an error occurs, the state of the Trace Stack is automatically copied to the Error Trace window, which is accessed from the View menu (View>Error
Trace).
Figure 9.7. Comparison between Trace Stack and Error Trace1.
As the copy it is of the Trace Stack, all that was explained about it can be applied to the Error Trace. Both the Trace Stack and the Error Trace windows display two buttons. The one on the left is used to Refresh the window contents. When this button is clicked after exiting a break loop returning to the top level, refreshing the Trace Stack will change its contents so they reflect the current state, while refreshing the Error Trace does not change its content unless a new error has occurred. The error shown in Figure 9.7 has been raised by supplying a list including dotted pairs as sublists which as was explained in Chapter 8 is not permitted in this case. The expression that caused it appears just below the keyword :ERROR-BREAK. If it were a large program, we could have easily found the location of this error in the source code selecting in the Error Trace window’s context menu the available option, either Source position or Call point source.
9.5. Summary We have seen the main debugging tools available in the Visual LISP IDE. We
must not forget the other resources available in the editor itself, such as colored syntax (just seeing a function name that does not turn blue means it is misspelled), detection of parentheses closure, syntax checking, automatic formatting or the introduction of form closing comments (see Figure 9.7). Not to mention the subsequent verification which is done when compiling the program, as we will see further on. All this constitutes an environment that aids us in the development of complex applications. 1 When the program requires user input from the Graphic Screen the Visual LISP Console should be in AutoCAD Mode to avoid a call to (C:VLIDE) that would appear in the Trace Stack instead of the functions called from our program.
Index of Function Listings. Chapter 2. Figure 2.7. Function NUMERA-DICT. Figure 2.8. Function NEXT-NUM. Figure 2.10. Text drawing function. Figure 2.11. C:NUMERA function. Figure 2.12. C:NUM-OPTIONS function. Figure 2.13. Function NUM-READ.
Chapter 5. Listing 5.1. Code for the PALINDROME function. Listing 5.2. Demonstration with local variables. Listing 5.3. DOTTED-PAIR-P predicate. Listing 5.4. STRINGP predicate. Listing 5.5. IS-LIST? function with local variables. Listing 5.6. IS-LIST? function without local variables. Listing 5.7. TYPE? function. Listing 5.8. List sorting function. Listing 5.9. Sorting a list of points by one of its coordinates. Listing 5.10. Function that sorts strings. Listing 5.11. Function that sorts the words in a sentence. Listing 5.12. Replacement of characters in a string. Listing 5.13. Function for ordering words in sentences. Listing 5.14. Factorial of a number. Listing 5.15. Function that counts list members. Listing 5.16. Recursive definition of the member function. Listing 5.17. Function to flatten nested lists. Listing 5.18. Flatten function removing nil terms. Listing 5.19. Fibonacci function implemented with REPEAT. Listing 5.20. PALINDROME-P predicate (using repeat). Listing 5.21. Printing of a list (using FOREACH). Listing 5.22. Printing a list (using MAPCAR). Listing 5.23. Squares of a list (using mapcar). Listing 5.24. Squares of a list (recursive). Listing 5.25. Squares of a list (using foreach). Listing 5.26. Counting drawing entities. Listing 5.27. PALINDROME-P predicate (with while). Listing 5.28. Obtaining the names of items in symbol tables.
Chapter 6. Listing 6.1. AX-SQUARE? function. Listing 6.2. AX-SAFEARRAYP predicate. Listing 6.3. AX-MATRIX->LIST function. Listing 6.4. AX-DATA-TYPE. Listing 6.5. AX-TYPE function. Listing 6.6. AX-LIST->ARRAY function. Listing 6.7. Function to turn Layers on/off using ActiveX.
Listing 6.8. Extracting a list of the drawing's Layers using ActiveX. Listing 6.9. Generic function to obtain the names of objects in a collection. Listing 6.10. Tangent calculation without anticipating a division by zero. Listing 6.11. Calculation of the tangent anticipating for the division by zero. Listing 6.12. Checking for an item in a collection. Listing 6.13. AX-EXISTS? function that returns the VLA-object.
Chapter 7. Listing 7.1. User input request including default values. Listing 7.2. Prompting for a string with default value. Listing 7.3. Prompting for data including options. Listing 7.4. Function that admits long integers. Listing 7.5. Function to detect the enabled bits. Listing 7.6. Functions to check, enable or disable bits. Listing 7.7. Variables switch. Listing 7.8. Setting system variables before entering the command. Listing 7.9. Restoring the original values on exiting the command.
Chapter 8. Listing 8.1. Reading a file character by character. Listing 8.2. Reading a file into a list. Listing 8.3. LIST->CSV Function. Listing 8.4. Conversion of list items to text. Listing 8.5. Giving a string a fixed length. Listing 8.6. LIST->SDF function. Listing 8.7. File searching function. Listing 8.8. Function to recognize available disk drives. Listing 8.9. MAKE-POINT-LIST function for creating a coordinates list.
Contents of other Volumes Volume 2. PART 3. CONTROLLING AUTOAD FROM 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 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. Selecton 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 through 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.
PART 4. 3D PROGRAMMING. 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.
Volume 4. PART 5. ADVANCED PROGRAMMING. 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.
View more...
Comments