Curso de AutoLISP: Temas y Contenidos
Curso de AutoLISP: Temas y Contenidos
Tema 1: Introducción.
Tema 2: Las variables en AutoLISP.
Tema 3: Operaciones matemáticas básicas.
Tema 4: Solicitar números al usuario.
Tema 5: Funciones de usuario y nuevos comandos.
Tema 6: Introducción al entorno de Visual LISP.
Tema 7: Cargar los archivos de AutoLISP.
Tema 8: Operaciones matemáticas.
Tema 9: Obtener textos y puntos del usuario.
Tema 10: Funciones para manejar listas.
Tema 11: Ejecutar comandos de AutoCAD.
Tema 12: Operaciones de comparación.
Tema 13: Operaciones lógicas.
Tema 14: Estructuras condicionales.
Tema 15: Mostrar textos en pantalla.
Tema 16: Variables de sistema.
Tema 17: Funciones de conversión.
Tema 18: Obtener distancias y ángulos del usuario.
Tema 19: El comando deshacer en las rutinas de AutoLISP.
Tema 20: Funciones de tratamiento de errores.
Tema 21: Limitar las respuestas de los usuarios a las funciones de solicitud de datos (I).
Tema 22: Limitar las respuestas de los usuarios a las funciones de solicitud de datos (y II).
Tema 23: Estructuras repetitivas.
Tema 24: Funciones para manipular cadenas de texto.
Tema 25: Trabajar con ángulos y distancias.
Tema 26: Funciones avanzadas para trabajar con listas.
Tema 27: Aplicar funciones a los elementos de las listas.
Tema 28: Literales y otras funciones de utilidad.
Tema 29: Carga automática de archivos de AutoLISP.
Tema 30: Operaciones con archivos.
Tema 31: Leer y escribir archivos de texto.
Tema 32: La base de datos de AutoCAD
1
TEMA 1: INTRODUCCIÓN
Otro de los objetivos del curso es el de ver AutoCAD no como un programa de dibujo, sino como una potente base de
datos gráficos. Y descubrir como funciona esa base de datos de AutoCAD.
¿Es rentable el AutoLISP? Desde luego que puede serlo, puede agilizar y facilitar enormemente el trabajo con AutoCAD.
AutoLISP es un lenguaje sencillo de aprender y a diferencia de otros lenguajes de programación se pueden crear
programas interesantes y útiles casi desde el primer momento.
Muchas veces, la mayoría del tiempo empleado en crear una rutina o programa se emplea en depurar el código (corregir
errores). La planificación y el análisis previo de la rutina son de suma importancia para evitarlo. El primer paso para
escribir una rutina consiste en escribir en un papel con un lenguaje simple lo que se desea que realice el programa, a esto
se le denomina pseudocódigo. Un método algo más avanzado y eficaz es utilizar diagramas de flujo, puedes ver algo más
sobre esta técnica en la sección de artículos: Diagramas de flujo.
Cuando ya se empieces a programar en AutoLISP por ti solo, te será muy útil reunir todas las anotaciones sobre
proyectos de aplicaciones y rutinas en una libreta o bloc de notas.
Interfaces de programación
AutoCAD dispone varios entornos de programación, la selección del tipo de interfaz a emplear para crear una aplicación
dependerá de las necesidades de la aplicación y de la experiencia o conocimientos del programador/es:
AutoLISP es una adaptación del lenguaje de programación CommonLISP para AutoCAD. Es sencillo de
aprender y al mismo tiempo potente. AutoCAD cuenta con un intérprete interno de LISP que permite introducir
código desde la línea de comando o cargar programas desde archivos externos. Puedes utilizar AutoLISP para
automatizar tareas repetitivas y crear nuevos comandos de AutoCAD.
ActiveX Automation constituye una alternativa moderna al AutoLISP. Puedes acceder y controlar objetos de
AutoCAD desde cualquier aplicación que utilice un controlador Automation como Visual Basic y Delphi, o
cualquiera de las aplicaciones que dispongan de Visual Basic for applications (VBA).
VBA es un entorno de programación basado en objetos que utiliza íntegramente la sintaxis del lenguaje Visual
Basic y permite usar controles ActiveX. Permite también la integración con otras aplicaciones que utilizan VBA
como MS Office o MS Project. Las ediciones de desarrollo de MS Visual Basic, que se adquieren por separado,
complementan AutoCAD VBA con componentes adicionales como un dispositivo externo de base de datos y
funcionalidades de escritura de informes.
ObjectARX es un entorno de programación de lenguaje compilado para el desarrollo de aplicaciones de
AutoCAD. El entorno de programación ObjectARX incluye varias bibliotecas de vínculos dinámicos (DLL) que
ofrecen acceso directo a las estructuras de la base de datos, el sistema de gráficos y los dispositivos de geometría
de AutoCAD.
2
Características de AutoLISP
El LISP fue desarrollado a finales de los años 50 por John McCarthy, es el segundo lenguaje de programación más
antiguo solo precedido por Fortran. Uno de los campos en los que es más empleado es en la investigación en inteligencia
artificial. Su nombre proviene de LISt Procesing (Procesado de listas), pues se basa en el uso de listas en lugar de datos
numéricos. Aunque hay quien mantiene que su nombre proviene de "Lost In Stupid Parenthesis". AutoLISP añade al
LISP algunas funciones especialmente diseñadas para la manipulación de entidades de AutoCAD.
AutoLISP es un lenguaje evaluado. El código se convierte a lenguaje máquina (ceros y unos) y se almacena en la
memoria temporal. No es tan lento como los lenguajes interpretados, ni tan rápido como los compilados. En los
lenguajes interpretados, se va traduciendo el programa a código máquina (el idioma de los ordenadores) a medida que se
ejecuta. En los lenguajes compilados, el código fuente (texto) del programa se traduce a código máquina generando un
archivo ejecutable (.EXE) que es el que ejecutará el programa.
Hay quien dice que AutoLISP es lento comparándolo con otros lenguajes como Pascal o C, especialmente cuando se
utilizan muchas funciones matemáticas. Sin embargo, cuando se utiliza manipulando símbolos los resultados son muy
distintos. Esto hace de AutoLISP un excelente lenguaje para la programación de sistemas CAD, ya que un sistema CAD
no es más que un entorno de manipulación de símbolos gráficos.
Una de las características más importantes del AutoLISP es la posibilidad de acceder a la base de datos de los dibujos de
AutoCAD. Donde podemos acceder a las capas, estilos de texto, SCP... así como a todas las entidades del dibujo. Esta
información se puede modificar, extraer e incluso añadir más entidades al dibujo.
La mayoría de los lenguajes de programación como C o Pascal son lenguajes procedimentales, se ejecutan una serie de
instrucciones paso a paso a partir de unos datos. En AutoLISP en lugar de seguir instrucciones prefijadas, se puede
acceder a la información de los objetos para determinar de que información se dispone, preguntándole a los objetos por
ellos mismos. AutoLISP no es estrictamente un lenguaje orientado al objeto, pero contiene bastantes características de
estos tipos de lenguajes debido a que puede interactuar con los objetos contenidos en los dibujos.
AutoLISP permite crear nuevos comandos para AutoCAD, que se ejecuten como cualquier otra orden. Es posible incluso
redefinir los comandos de AutoCAD para que funcionen de forma distinta, por ejemplo se puede redefinir el comando
"POLIGONO" para que dibuje polígonos estrellados en lugar de los regulares.
Una mención especial merece el uso de AutoLISP en la realización de programas paramétricos y en el diseño
semiautomático, ya que entre el 60% y el 80% del diseño está dedicado a la modificación de diseños previos. En los
programas paramétricos, el usuario introduce una serie de datos o parámetros a partir de los cuales el programa realiza el
diseño completo de un elemento u objeto. Esta es sin duda una de las mayores aplicaciones de AutoLISP.
AutoLISP se ha mejorado con la creación de Visual LISP que ofrece un entorno de desarrollo integrado dentro de
AutoCAD. Visual LISP incluye un compilador, un depurador y diversas utilidades para facilitar la programación.
Además añade nuevos comandos para permitir la interacción con objetos que utilizan ActiveX. Otra de las novedades
que aporta Visual LISP son los reactores de objetos que permiten que AutoLISP responda a eventos.
En los dos primeros capítulos del curso se trabajará desde la ventana de comandos de AutoCAD. A partir del tercer
capítulo ya crearemos nuestras rutinas en archivos de texto utilizando el entorno de Visual LISP proporcionado por
AutoCAD. Existen dos inconvenientes al emplear AutoLISP desde la ventana de comandos de AutoCAD:
En primer lugar el reducido espacio con el que se cuenta en la ventana de comandos de AutoCAD y la dificultad
que supone el no tabular el código, es decir escribir el código todo seguido. Esto es debido a que cada vez que se
pulsa Intro, AutoCAD evalúa lo que se ha escrito.
En segundo lugar, al terminar la sesión de trabajo en AutoCAD se perderán todos los datos almacenados en la
memoria temporal.
3
Expresiones y procedimientos de evaluación
Un programa en AutoLISP consiste en una serie de expresiones del tipo "(función argumentos)". Cada expresión
comienza con un paréntesis de apertura al que sigue el nombre de una función de AutoLISP (o una función creada por el
usuario) y una serie de argumentos (a veces opcionales) que dependen de la función indicada y van separados por al
menos un espacio en blanco. Cada expresión termina con un paréntesis de cierre, esto es muy importante pues el número
de paréntesis de apertura debe ser igual al de cierre.
Cada expresión de AutoLISP devuelve un valor. Un argumento también puede ser una expresión, creándose así una
estructura formada por expresiones (listas) anidadas unas dentro de otras; de modo que la más interior devolverá su
resultado como un argumento a la lista exterior. Cuando existen listas anidadas (unas dentro de otras), primero se evalúan
las más interiores.
Los primeros ejemplos que vamos a ver son sencillos y cortitos, así que puedes teclearlos directamente en la ventana de
comandos de AutoCAD.
Ejemplo:
(+ 31 22 -3) Ejecuta la función + que realiza la suma de los argumentos 31, 22 y -3 devuelve el resultado 50.
Prueba también:
(- 17 2)
(+ 2.5 22.8)
(- 0.25 22.5)
(+ 12 -2 31 -7.5)
Ejemplo:
¿Qué sucedería si al escribir la expresión (+ 1 (/ 5.0 2)) nos olvidásemos de escribir el último paréntesis? Haz la prueba,
veras que AutoCAD te indica que falta 1 paréntesis de cierre.
Si el interprete de comandos de AutoCAD encuentra un paréntesis de apertura, supone que todo lo que vaya a
continuación hasta el paréntesis de cierre es una expresión de AutoLISP. De modo que envía esa expresión al interprete
de AutoLISP para que la evalúe.
En el tema 2 veremos algunas de las operaciones matemáticas que se pueden realizar con AutoLISP.
4
Tipos de objetos y datos
Los elementos de las expresiones de AutoLISP pueden ser símbolos, valores concretos y también otras expresiones. Se
pueden distinguir los siguientes tipos de elementos:
Símbolos: Cualquier elemento que no sea un valor concreto. Por ejemplo una variable, una función.
Enteros: Números enteros comprendidos entre -32000 y 32000.
Reales: Números reales (180 es un número entero, pero 180.0 es un número real).
Cadenas de texto: Texto con una longitud máxima de 132 caracteres.
Descriptores de archivo: Representan un archivo de texto ASCII abierto.
Nombres de entidades de AutoCAD: Nombre en hexadecimal de una entidad del dibujo.
Conjuntos designados por el usuario: Conjuntos de entidades de AutoCAD.
Funciones de usuario: Funciones definidas por el usuario.
Funciones de AutoLISP: Funciones o comandos predefinidos de AutoLISP.
Ejemplo:
(+ 5 2) devuelve 7.
En el primer caso todos los argumentos son números enteros, y el resultado de su suma es un número entero. En el
segundo caso, tenemos un número real, así que el resultado es un número real. Para que comprendas la importancia de
esta distinción, realiza el siguiente ejemplo:
(/ 5 2)
(/ 5 2.0)
Una de las mayores virtudes de AutoLISP es que pueden ejecutarse expresiones en medio de un comando de AutoCAD.
Ejemplo:
Ejecuta el comando "LINEA" e indica el primer punto, activa el forzado ortogonal (F8) y teclea... (+ 11 25) Esto
devuelve el resultado de la suma al comando que se estaba ejecutando. Por eso dibuja una línea de 36 mm de longitud en
la dirección que indicaba el cursor.
Prueba ahora indicando el radio de la circunferencia (- 70 25) al utilizar el comando llamado "CIRCULO" (mal
llamado, debería ser "circunferencia").
Podemos emplear también la constante PI = 3.14159... para realizar cálculos. Por ejemplo, ejecuta de nuevo el comando
"CIRCULO" y cuando pregunte el radio de la circunferencia, teclea (/ 6 (* 2 PI)). Obtendremos una circunferencia de
perímetro 6.
Notación empleada
Para describir las funciones de AutoLISP que se expliquen a lo largo del curso se seguirá la siguiente notación:
(FUNCIÓN Argumentos_necesarios [Argumentos_opcionales] )
Los nombres de las funciones de AutoLISP aparecen en color azul y el código a teclear en cursiva.
5
Convenciones recomendadas
En este apartado se indicarán una serie de convenciones recomendables a la hora de programar. Alguna de ellas puede
que aún no las entiendas, pero no te preocupes porque las iremos recordando a medida que avancemos en el curso.
6
TEMA 2: LAS VARIABLES DE AUTOLISP
En el ejemplo anterior logramos dibujar una circunferencia de 6 unidades de perímetro, pero que sucede si queremos
utilizar el valor obtenido por (/ 6 (* 2 PI)) para realizar otra operación, tendremos que volver a teclear la expresión
anterior.
¿Existe alguna opción para almacenar en memoria los resultados de una operación, tal como hace una calculadora?
Desde luego que si, AutoLISP permite almacenar miles de datos en variables, que se almacenan en la memoria del
ordenador.
La función de AutoLISP empleada para definir variables y asignarles un valor es SETQ y permite definir varias variables
en una misma expresión. La función SETQ devuelve el valor de la última variable definida.
Con respecto a los nombres utilizados para designar a los símbolos creados por el usuario (variables y funciones):
Ejemplo:
(SETQ a 3) Esta expresión crea la variable a y le asigna el valor 3. Devuelve el valor de la variable a.
¿Que valor crees que devolverá (SETQ b (+ 1 3) melones 23.0)? Fíjate que se han definido dos variables b y melones de
valor 4 y 23.0 respectivamente. Como melones ha sido la última variable evaluada, la expresión anterior devuelve su
valor.
Si el interprete de comandos de AutoCAD recibe una palabra precedida por el signo de exclamación "!", comprobará si
ese termino ha sido empleado como un símbolo (nombre de variable o una función de usuario) de AutoLISP. En cuyo
caso devolvera su valor y en caso contrario devolvera nil, que es lo mismo que nada o vacío. Por ejemplo: !a devuelve 3.
Prueba también las siguientes expresiones y comprueba los valores asignados a las variables:
(SETQ c (+ b 3)) Los valores de las variables también pueden utilizarse para realizar operaciones y asignar el resultado
de dichas operaciones a una variable.
(SETQ a 3.5) Se puede modificar el valor asociado a una variable, por eso se llama variable.
7
(SETQ b 2.0) ¿Qué sucede con el valor de la variable d? ¿Tomará el nuevo valor de b o seguirá con el anterior? Al
definir la variable d se le asigno el valor que tenía la variable b en ese momento, no se estableció ninguna relación entre
ambas variables.
También se puede asignar valores no numéricos a las variables. Por ejemplo, una cadena de texto. Las cadenas de texto
son secuencias de uno o más caracteres encerrados entre comillas. Dentro de una cadena de texto pueden insertarse una
serie de carácteres de control:
\\ representa al símbolo \
\" representa al símbolo "
\n representa un cambio de línea (retorno del carro)
\t representa una tabulación
En este caso además, hay que notar que en la variable a primero se almacenaba un valor entero, luego uno real y ahora
una cadena de texto. La posibilidad de reutilizar variables con distintos tipos de datos puede ser muy útil, pues dota al
programador de una gran flexibilidad al escribir el código. Pero también supone que se requiera más cuidado para no
pasar a una función una variable con un tipo de dato incorrecto. Por ejemplo: (+ a b)
(SETQ b T)
(SETQ c nil)
En este caso, a la variable a se le ha asignado una cadena de texto, a b el valor Cierto o Verdadero y a c Falso.
Para almacenar el radio de la circunferencia de perímetro 6 unidades en una variable podemos teclear:
Por último veamos porque no debemos emplear para los nombres de las variables los nombres de las funciones de
AutoLISP.
Prueba (+ 5 2.5) Ahora + no representa a la función suma, sino a una variable de valor 23. De modo que el primer
termino de la expresión anterior no es una función, por lo que da un error. Para recuperar el modo habitual de trabajo de
la función + es necesario cerrar la sesión actual de AutoCAD e iniciar una nueva sesión.
8
TEMA 3: OPERACIONES MATEMÁTICAS BÁSICAS
Suma los números indicados como argumentos y devuelve el resultado de dicha suma. Si todos los números de la lista
son enteros, el resultado también será entero.
(+ 3 9) devuelve 12
Hemos asignado a la variable a el resultado de una operación en la que usamos el anterior valor asignado a la variable a
como uno de los argumentos de la operación.
Si le pasamos a la función + un único número como argumento, nos devuelve ese número.
(+ ) devuelve 0
La expresión (+ .5 2) nos dará un error. Los números reales siempre deben comenzar por un número entero, incluso si es
cero, seguido de la parte decimal.
Resta al primer número todos los siguientes números pasados como argumentos. Si todos los números de la lista son
enteros, el resultado también será entero.
(- 11 5) devuelve 6
9
(SETQ a (- 12 5 4)) devuelve 3 y lo almacena en la variable a
Si le pasamos a la función - un único número como argumento, nos devuelve ese número cambiado de signo.
En la expresión anterior, el primer signo "-" representa a la función resta, mientras que el segundo representa el signo de
un número negativo.
(- ) devuelve 0
Multiplica los números indicados como argumentos y devuelve el resultado de dicho producto. Si todos los números de
la lista son enteros, el resultado también será entero.
(* 3 9) devuelve 27
Si le pasamos a la función * un único número como argumento, nos devuelve ese número.
(* ) devuelve 0
Divide el primer número entre el siguiente y devuelve el resultado. Si se pasan más de dos números como argumentos, el
primer número se dividirá entre el producto de los restantes números.
(/ 45 5 3) devuelve 3
10
En esta función es muy importante recordar que si todos los números de la lista son enteros, el resultado también será
entero.
(/ 7 2) devuelve 3
Si le pasamos a la función / un único número como argumento, nos devuelve ese número.
(/ ) devuelve 0
(1+ <número> )
(1+ 5) devuelve 6. Ojo entre "1" y "+" no debe haber ningún espacio, ya que el nombre de la función es "1+".
(1+ 0) devuelve 1
(SETQ i 1)
(1- <número> )
(1- 5) devuelve 4. Ojo entre "1" y "-" no debe haber ningún espacio, ya que el nombre de la función es "1-".
(1- 0) devuelve -1
11
TEMA 4. SOLICITAR NÚMEROS AL USUARIO
(GETINT [mensaje] )
Solicita del usuario un número entero. En caso de que el usuario introduzca un número real o cualquier otro dato que no
sea un número entero, AutoCAD recordará mediante un mensaje que está solicitando un número entero y no finalizará la
ejecución de la función hasta que se introduzca un valor entero, o se pulse ESC para cancelar su ejecución.
(GETINT)
Puede indicarse un mensaje de solicitud opcional, que facilite al usuario información acerca de lo que se está pidiendo.
El mensaje debe ser una cadena de texto y por tanto debe estar entre comillas.
Podemos asignar el valor introducido por el usuario a una variable y así utilizarlo después.
(SETQ edad (+ edad 1)) nos dará la edad que tendrás el próximo año.
También puedes crear una variable que contenga el mensaje de solicitud de la función GETINT
En este caso mens es una variable que almacena una cadena de texto, pero no es una cadena de texto. Por lo tanto, no
debe ir entre comillas.
Que pasará si como respuesta a la solicitud de la edad del usuario se introduce !a. Parece lógico que le estamos
indicando el valor de la variable a, que contiene el valor numérico 27, de modo que la variable edad debería tomar el
valor 27, pero observaras que no es así. Haz la prueba: nos indicará que es "Imposible volver a entrar en LISP.". Esto es
debido a que al ejecutar la función GETINT se está ejecutando el interprete de LISP, y al indicar !a como respuesta
estamos diciendo que ejecute el interprete de LISP para obtener el valor asociado a la variable a. Como el intérprete de
LISP ya está en ejecución, no puede volver a ejecutarse y nos muestra el mensaje anterior.
12
(GETREAL [mensaje] )
Solicita del usuario un número real. En caso de que el usuario introduzca un número entero, el intérprete de AutoLISP lo
considerará como un real. Si se introduce cualquier otro tipo de dato que no sea numérico, recordará mediante un
mensaje que está solicitando un número real y no finalizará la ejecución de la función hasta que se introduzca un valor
numérico, o se pulse ESC para cancelar su ejecución.
(GETREAL)
Puede indicarse un mensaje de solicitud opcional, que facilite al usuario información acerca de lo que se está pidiendo.
El mensaje debe ser una cadena de texto y por tanto debe estar entre comillas.
(SETQ peso (- peso 1)) nos dará el peso que tendrás si adelgazas un kilo.
También puedes crear una variable que contenga el mensaje de solicitud de la función GETREAL
El primer argumento representa el nombre de la funcíon de usuario que queremos definir. Hay que tener cuidado de no
emplear los nombres de las funciones de AutoLISP, ya que en dicho caso se redefinirían.
Después de indicar el nombre de la función, se deben indicar entre paréntesis los argumentos y las variables locales,
separados por una barra inclinada.
Los argumentos son valores que recibirá la función cuando sea ejecutada por el usuario, o llamada desde otras
funciones.
Las variables locales son aquellas variables que se emplearán tan solo dentro de la función que queremos definir,
de modo que restringimos su uso al entorno de la función.
Por último, se añaden las expresiones que ejecutará la función. Veamos algunos ejemplos:
En este caso hemos definido una nueva función llamada "2+", que recibe como argumento un número y realiza la suma
de ese número y 2. Las funciones de AutoLISP las ejecutabamos escribiendo (1+ 5) directamente desde la línea de
comandos, bien pues las funciones de usuario se ejecutan exactamente igual. Prueba:
(2+ 5) devuelve 7
(2+ 0) devuelve 2
13
Defun devuelve el resultado de la última expresión ejecutada, que en el caso anterior es (+ valor 2).
¿Que sucede si tratamos de ejecutar la función sin pasarle ningún argumento, o pasandole un argumento que no sea de
tipo numérico?. Veamoslo:
(2+ "texto") indica "; error: tipo de argumento erróneo: numberp: "texto""
Podrías pensar que el número indicado se almacena en una variable llamada "valor", pero no es así. Compruebalo
escribiendo !valor en la línea de comandos, lo que nos devolverá nil.
En la función anterior tenemos un argumento y no hay variables locales. Vamos a modificarla un poco:
Ahora seguro que estas totalmente convencido de que el resultado obtenido se almacena en la variable "valor". Pues
comprobemos si tienes razón:
(2+ 5) devuelve 7
Parece que estabas equivocado. La función "2+" recibe como argumento un número, que almacena en la variable "valor".
Pero el ámbito de aplicación es local, es decir una vez que salgamos de la función "2+" la variable "valor" recupera su
valor inicial, que en este caso es nil. Vamos con otra prueba...
(2+ 4) devuelve 6
Pero, que pasa si ejecutamos (SETQ valor (2+ 4)). Se repiten los puntos 1-4 anteriores, pero al salir de la función "2+" le
asignamos a "valor" el valor devuelto por la función, que es 6. De modo que "valor" = 6.
14
En este caso, dentro de la función "2+" declaramos una variable a la que se le asigna el valor que recibe como argumento
la función.
(2+ 4)
!inicial devuelve 4
Observa que "valor" se comporta como una variable local, solo se emplea dentro de la función. Sin embargo "inicial" es
de ámbito global, es decir sigue empleandose al salir de la función. Vamos a modificar un poquito más nuestra función:
Ahora hemos añadido la variable "inicial" a la lista de variables locales, con lo que su ámbito será local. Para
comprobarlo...
!inicial devuelve 4
(2+ 3)
(DEFUN 2+ ( / valor ) (SETQ valor (GETINT "Número: ")) (SETQ valor (+ valor 2)))
Ahora la función "2+" no tiene argumentos, así que para ejecutarla tan solo debemos poner su nombre entre paréntesis:
(2+)
La variable "valor" es de ámbito local y se emplea GETINT para solicitar un número al usuario.
Lo habitual es que se trate de evitar el uso de variables globales innecesarias. De modo que las variables que no sean
globales, se deberan añadir a la lista de variables locales dentro de las definiciones de las funciones de usuario.
15
Crear nuevos comandos de AutoCAD
La función de AutoLISP DEFUN no solo nos permite crear funciones de usuario, también nos permite crear nuevos
comandos de AutoCAD.
(DEFUN C:2+ ( / valor ) (SETQ valor (GETINT "Número: ")) (SETQ valor (+ valor 2)))
Si anteponemos al nombre de la función a definir "C:" en lugar de crear una función de usuario, se crea un nuevo
comando de AutoCAD. La función seguirá funcionando exactamente igual, la única diferencia está en el modo de
ejecutarse. Ahora no es necesario poner el nombre de la función entre paréntesis, sino que se escribe directamente.
2+
También puede ejecutarse poniendo el nombre de la función precedido de "C:" entre paréntesis.
(C:2+)
En caso de que el nuevo comando creado necesite algún argumento, tan solo podrá ejecutarse del último modo.
(DEFUN C:2+ ( valor / inicial ) (SETQ inicial valor valor (+ valor 2)))
(C:2+ 2) devuelve 4
La función que hemos creado solo nos servirá para la sesión actual de AutoCAD. Es más, tan solo está definida la
función 2+ en el dibujo actual, de modo que si hay más dibujos abiertos, en ellos no está definida. En el siguiente tema
veremos como guardar nuestras funciones en archivos de texto, a la vez que comenzamos a trabajar con el entorno de
Visual LISP.
De modo que en la ventana de comandos de AutoCAD tan solo se escribiran pequeñas líneas de código que no interese
guardar. Suele emplearse por tanto para hacer pruebas y depurar código, aunque también se puede utilizar como una
ayuda más para el diseño con AutoCAD.
El código de las funciones de AutoLISP se escribe en un editor de textos ASCII, para que así se puedan almacenar. Se
puede emplear cualquier editor de texto que permita tan solo códigos ASCII, por ejemplo el bloc de notas de Windows.
Otros editores, como MS Word, insertan códigos para diferenciar los estilos de texto, las tabulaciones, etc. y no se
pueden emplear para escribir las rutinas de AutoLISP, ya que el interprete de AutoLISP no sabe interpretar esos códigos.
16
Además del bloc de notas, existen muchos otros editores de texto que se pueden emplear para escribir código fuente en
AutoLISP. Algunos de estos editores, disponen de utilidades para la programación (permitiendo incluso emplearlos para
distintos lenguajes de programación). Otros editores están ya enfocados a la programación en LISP o AutoLISP.
Desde la versión 14, AutoCAD incorpora un editor para AutoLISP, en el que tenemos entre otras las siguientes
utilidades:
Nosotros seguiremos utilizando la ventana de comandos de AutoCAD para ver como trabajan las funciones de AutoLISP,
y nos iremos al editor de Visual Lisp para crear nuevas funciones y comandos.
Comentarios en el código
Funciones de AutoLISP
Números
Textos
FUNCIONES DE USUARIO Y NUEVOS COMANDOS
Nombres de variables, paréntesis, etc...
El interprete de AutoLISP no evalúa los retornos de carro (Intros), de modo que el código se podía haber escrito todo
seguido, en una misma línea: (defun RAG ( ang ) (/ (* ang 180.0) pi) ) pero así es más difícil de leer. Esta función tiene
muy poco código, pero imaginate una función mucho mayor escrita toda en la misma línea...
Fíjate en que la segunda línea se presenta tabulada, de modo que no comienza a la misma altura que el resto, sino que
está desplazada hacia la derecha. Esto nos indica que está incluida dentro de una lista de nivel superior, la de la función
defun. No es necesario tabular el código, pero facilita su lectura y además nos permite detectar posibles errores con los
paréntesis (Regla número 1). Pi es una constante que ya está definida en AutoLISP, pi = 3.141592...
17
En la primera línea "(defun RAG ( ang )" definimos la función RAG (Radianes A Grados) que recibe un argumento
"ang" (el ángulo en radianes).
Veamos como funciona la segunda línea: Por la Regla número 2, primero se evaluará la lista interior (* ang 180.0) que
multiplica el ángulo en radianes "ang" por 180.0 y devuelve el resultado a la lista de nivel superior (recuerda la Regla
número 3) que lo divide entre pi = 3.141592... devolviendo a su vez el resultado de la división (el ángulo en grados
decimales) a la lista de nivel superior, defun.
La última línea cierra la lista de la función defun, verificandose así la Regla número 1.
Nuestra función RAG se ejecutaría así: (RAG 1.57) siendo 1.57 el ángulo en radianes, y devolvería ese ángulo en grados
decimales (aproximadamente 90º). No es necesario poner el nombre de la función RAG en mayúsculas, ya que AutoLISP
no diferencia las mayúsculas de las minúsculas (salvo en contadas excepciones que ya explicaremos). Aunque al poner
los nombres de las funciones en mayúsculas se diferencian perfectamente del resto del código.
Tal vez penseis que no es tan importante añadir explicaciones en el código. Pero si teneis que leer una rutina que
creasteis hace meses os serán muy útiles, por que seguramente no recordareis muy bien como funciona. Además, si
alguien va a leer alguna de vuestras rutinas le facilitariais mucho la labor. Al igual que a vosotros os será más sencillo
leer y entender una rutina con abundantes comentarios.
;;; Esta función recibe el valor de un ángulo en radianes y lo devuelve en grados decimales.
18
El editor de Visual LISP
El editor de Visual Lisp se inicia desde AutoCAD de varias formas:
Al abrir el editor veremos dos ventanas, "consola de Visual LISP" y "Rastreo". De momento no vamos a explicar para
que se usan ni como funcionan estas ventanas. Estamos ante un editor de textos, como otro cualquiera, pero con
utilidades específicas para la programación en AutoLISP. De modo que para crear un nuevo archivo hacemos lo mismo
que en cualquier editor de textos: "Archivo-->Nuevo archivo" (o pulsa Control + N). Aparece una nueva ventana, tal
como puede verse en la siguiente imagen:
19
Es en esta pantalla donde vamos a escribir el código de nuestra rutina RAG, pero antes un pequeño comentario...
Es bastante habitual añadir al principio de los archivos de AutoLISP unas líneas de comentarios indicando el nombre del
autor, fecha, nombre de los comandos y/o funciones definidas en el archivo, y una breve descripción de estos. De modo
que al código anterior le añadiremos unas líneas en la cabecera del archivo:
;;;________________________MecaniCAD__________________________;;;
;;;_____________http://www.peletash.com/mecanicad_____________;;;
;;;_________________________RAG.LSP___________________________;;;
;;;_______________________Versión 1.0_________________________;;;
;;;________________________21/02/2002_________________________;;;
;;; Esta función recibe el valor de un ángulo en radianes y lo devuelve en grados decimales.
El editor de Visual LISP realiza las tabulaciones automáticamente. Y aunque se pueden eliminar o modificar a vuestro
gusto (añadiendo o quitando espacios y tabulaciones), lo recomendable es mantener el formato por defecto para el
código. Veamos como queda la función GAR en el editor:
20
En primer lugar observamos que el código tiene distintos colores. Esto es simplemente una ayuda visual para diferenciar
los diferentes elementos de nuestras rutinas:
El formato coloreado del código se puede desactivar, o se pueden modificar los colores predefinidos para los diferentes
elementos. Pero el mantener el formato de colores para el código, nos puede ayudar a detectar errores. Por ejemplo, si
ponemos "(SETW radianes ...", SETW aparecerá en negro y no en azul, por que la función de AutoLISP es SETQ, de
modo que nos indica que hemos escrito mal el nombre de la función.
En cuanto a la tabulación, tal vez llame la atención el último paréntesis (el de cierre de la función defun), ya que no está
a la misma altura que su paréntesis de apertura. Hay editores para AutoLISP que insertan los paréntesis de cierre a la
misma altura que sus correspondientes paréntesis de apertura y hay otros editores que insertan los paréntesis de cierre
tabulados, tal como hace (por defecto) el editor de Visual LISP.
Una vez escrito el código de la función RAG, tan solo nos queda guardarlo en un archivo. Es recomendable crear un
directorio (carpeta) en el que guardar todas nuestras rutinas. Se le suele dar a los archivos de AutoLISP el mismo nombre
del comando o función que esté definida en él, aunque podemos guardar esta rutina en el archivo "klhsduif.lsp" luego no
la reconoceriamos. Así que lo mejor será guardar el código de la función GAR en el archivo "RAG.lsp", dentro del
directorio que hemos creado para almacenar nuestras rutinas. Así que selecciona "Archivo --> Guardar como" e indica la
ruta y el nombre del archivo.
Ahora puedes intentar crear una función llamada GAR que reciba como argumento un ángulo en grados decimales y que
devuelva ese ángulo en radianes. Es practicamente igual a la función que acabamos de ver. Pero recuerda... Antes de
empezar a programar que hay q hacer? Pues pensar en lo que hay que hacer y en como lo vamos a hacer. Como esta
rutina es muy sencilla no es necesario escribir el pseudocódigo (ni hacer un diagrama de flujo), tan solo hay que pensar
un poco en lo que se va a hacer. Guarda el código de la función GAR en un archivo llamado "GAR.lsp" en el directorio
donde tengas tus rutinas.
21
TEMA 7: CARGAR LOS ARCHIVOS DE AUTOLISP
Para que la función RAG que creamos en el tema anterior se pueda ejecutar en AutoCAD, hay que cargar el archivo de
AutoLISP en el que está definida. Existen varias formas de cargar los archivos de AutoLISP, pero de momento tan solo
vamos a ver tres:
Si estamos en el editor de Visual LISP y tenemos abierto un archivo con una rutina, para cargarla en AutoCAD podemos:
Seleccionar en los menús desplegables del Visual LISP "Herramientas --> Cargar texto en editor".
Pulsando sobre el icono .
Tecleando la combinación de teclas CTRL+ALT+E.
Al cargar el archivo, aparece la Consola de Visual LISP mostrando un mensaje parecido al siguiente: "; N formularios
cargado de #<editor "RUTA/rag.lsp">. En caso de que se produzca algún error en el proceso de carga del archivo, en la
consola de Visual LISP se nos indicaría el tipo de error que se ha producido. De modo que habria que modificar el
código para corregir ese error antes de cargar la rutina.
Una vez cargada la rutina en AutoCAD, podemos probar si funciona. Teclea directamente en la ventana de comandos de
AutoCAD:
(rag 0)
(rag pi)
(rag (/ pi 4))
El segundo método para cargar un archivo de AutoLISP en AutoCAD (seleccionando en el menú de AutoCAD "Herr--
>AutoLISP-->Cargar") muestra un letrero de diálogo en el que se selecciona el archivo a cargar (también se pueden
seleccionar más de un archivo) y se pulsa el botón "Cargar".
La función LOAD permite cargar en AutoCAD el archivo de AutoLISP que se indique. Por ejemplo para cargar el
archivo RAG sería:
22
(load "rag")
No hace falta indicar la extensión del archivo. Escribiendo así el nombre del archivo solo cargará el archivo RAG.LSP si
está en uno de los directorios de soporte de AutoCAD o en el directorio actual. En caso contrario hay q indicar la ruta
completa:
(load "c:\rutinas\rag.lsp")
Pero esto nos daría un error, ya que AutoCAD no reconoce el caracter "\", de modo que hay que escribirlo de forma algo
especial. Para AutoLISP el caracter "\" hay que indicarlo de cualquiera de esas 2 formas: "\\" o "/". Por lo tanto:
(load "c:\\rutinas\\rag.lsp")
(load "c:/rutinas/rag.lsp")
En caso de que no se encuentre el archivo, la expresión load puede ejecutar lo que se indique en [resultado_si_falla]. Por
ejemplo:
23
En este caso, si no se encuentra el archivo RAG.lsp a la variable test se le asigna el valor cero. Suele emplearse para que,
cuando se ejecute LOAD desde un archivo de AutoLISP, podamos indicarle al usuario que no se ha encontrado el
archivo. Como indica el siguiente pseudocódigo:
Esto es todo, de momento, sobre la carga de archivos de AutoLISP en AutoCAD. Más adelante veremos métodos mucho
mejores para cargar nuestras rutinas.
;;;________________________MecaniCAD__________________________;;;
;;;_____________http://www.peletash.com/mecanicad_____________;;;
;;;_________________________GAR.LSP___________________________;;;
;;;_______________________Versión 1.0_________________________;;;
;;;________________________21/02/2002_________________________;;;
;;; Esta función recibe el valor de un ángulo en grados decimales y lo devuelve en radianes.
Inicia AutoCAD. En el menú desplegable "Herr" selecciona "Opciones". Así aparece un letrero de diálogo que nos
permitirá configurar AutoCAD. En la primera pestaña "Archivos" tenemos una opción denominada "Ruta de búsqueda
de archivos de soporte". Si no está expandida, para ver su contenido pulsamos con el ratón sobre el + que aparece a la
izquierda. Para añadir un nuevo directorio de soporte pulsamos el botón "Añadir" que se encuentra a la derecha del
cuadro de diálogo. Esto creará una nueva etiqueta, en la que podemos escribir la ruta del directorio o pulsar el botón
"Examinar" para seleccionarlo. Ya hemos añadido "C:\Rutinas" a los directorios de soporte de AutoCAD.
24
También podemos subir o bajar el nuevo directorio en la lista de directorios de soporte. Esto se hace para definir las
prioridades, es decir donde buscar primero. De modo que si subimos nuestro directorio hasta el primer lugar (como en la
imagen), este será el primer directorio en el que busque algo AutoCAD
(ABS numero)
Esta función devuelve el valor absoluto del número que se le pase como argumento. Por ejemplo:
Si el número que recibe como argumento es entero, devuelve un número entero y si es un número real, devuelve un real.
(abs 0) devuelve 0
(FIX numero)
Esta función devuelve la parte entera de un número. De modo que devuelve un número entero.
(fix 15.8) devuelve 15. Ojo! no redondea, sino que elimina lo que está detras del punto decimal.
25
(rem 3 2) devuelve 1
Cuando se indica más de 2 números (rem 1 2 3) es equivalente a (rem (rem 1 2) 3). Es decir, primero calcula el resto de
la división entre 1 y 2, que es 1, y después lo divide entre 3 y devuelve su resto, que es 1.
Si todos los números que recibe como argumentos son enteros, devuelve un número entero y si alguno de ellos es un
número real, devuelve un real.
26
(SIN angulo)
(COS angulo)
Funciona igual que la anterior, pero devuelve el coseno del ángulo, que hay que pasarselo en radianes.
Si se indica un segundo número (ATAN num1 num2) lo que hace es dividir num1 entre num2 y devuelve el arco cuya
tangente sea el resultado de la división. Esto se hace para facilitar lo siguiente...
(SQRT numero)
Esta función devuelve la raiz cuadrada del numero que recibe como argumento. Siempre devuelve un número real, no
entero.
(expt 2 2) devuelve 4
(expt 2 3) devuelve 8
Si todos los números que recibe como argumentos son enteros, devuelve un número entero y si alguno de ellos es un
número real, devuelve un real.
27
(EXP num)
Devuelve el número e (e = 2.71828... ) elevado al número num. Siempre devuelve un número real.
(LOG numero)
Esta función devuelve el logaritmo neperiano del número que recibe como argumento.
Esta función recibe dos números enteros y devuelve su máximo común divisor (o denominador). Siempre devuelve un
número entero.
(gcd 15 5) devuelve 5
(max 2 4 1 3 6) devuelve 6
Si todos los números que recibe como argumentos son enteros, devuelve un número entero y si alguno de ellos es un
número real, devuelve un real.
(min 2 3 6) devuelve 2
Si todos los números que recibe como argumentos son enteros, devuelve un número entero y si alguno de ellos es un
número real, devuelve un real.
28
TEMA 9: SOLICITAR TEXTOS AL USUARIO
Recuerdas las funciones GETINT y GETREAL? Nos sirven para solicitar al usuario un número entero y real,
respectivamente. Pues la función que se utiliza para solicitar textos al usuario es muy parecida.
Se puede ejecutar sin parámetros (getstring) pero no es recomendable. Se suele indicar un mensaje de texto que explique
al usuario lo que se le está solicitando. Por ejemplo:
Supongamos q te llamas Pepe, a (getstring "Cual es tu nombre?") responderias Pepe y ya está. Incluso se podria asignar
el nombre que indique el usuario a una variable:
Pero que sucede si te llamas Jose Luis? Pues que en cuanto pulses el espacio es como si hubieras pulsado Intro. No nos
permite insertar textos con espacios. Para que admita textos con espacios, debemos hacer un pequeño cambio:
Le estamos indicando el argumento [modo] = T. Este argumento puede ser cualquier expresión de AutoLISP, en este caso
le pasamos el valor T = Cierto, verdadero. Si no se indica el modo, o si al evaluarlo devuelve nil (nil = Falso, vacío)
entonces no admite espacios. Y si se pone cualquier expresión que al evaluarse no devuelva nil, permite espacios.
(setq nombre (getstring (+ 1 2) "Cual es tu nombre?")) permite responder con espacios, ya que (+ 1 2) devuelve 3
que es distinto de nil.
(setq nombre (getstring (setq var1 nil) "Cual es tu nombre?")) no permite responder con espacios, ya que (setq var1
nil) devuelve nil.
Esta función le pide un punto al usuario y devuelve una lista con las coordenadas del punto indicado. El usuario puede
indicar el punto en pantalla con el digitalizador (ratón) o tecleando sus coordenadas, tal y como se haría al dibujar en
AutoCAD.
Se puede ejecutar sin parámetros (getpoint) pero no es recomendable. Se suele indicar un mensaje de texto que explique
al usuario lo que se le está solicitando. Por ejemplo:
Así asignamos a la variable pto algo parecido a lo siguiente: (120.56 135.88 0.0)
Veamos ahora para que sirve el argumento opcional [pto_base] aprovechando que tenemos el punto pto definido.
29
(getpoint pto "Siguiente punto:")
Aparece una línea elástica entre el punto pto y la posición del cursor.
Esta función se utiliza también para solicitar puntos al usuario. En este caso el punto base no es opcional, hay que
indicarlo. Veamos la diferencia entre las dos expresiones siguientes:
Al utilizar getpoint, se muestra una línea elástica entre el punto pto y la posición del cursor. Si se utiliza getcorner, en
lugar de una línea elástica, aparece un rectángulo.
Fijemonos un momento en lo que devuelven tanto getpoint como getcorner: (125.68 117.68 0.0). Se trata de una lista. En
el próximo tema veremos algunas funciones para manejar listas.
(CAR lista)
Esta función devuelve el primer elemento de la lista que recibe como argumento.
De modo que si (siguiendo con el ejemplo del tema anterior) en la variable pto hemos asignado el valor devuelto por
getpoint, tenemos una lista con las coordenadas X, Y y Z del punto designado. Supongamos que pto = (10.0 20.0 0.0).
(CDR lista)
Esta función devuelve la lista que recibe como argumento pero sin el primer elemento.
(cdr pto) devolverá una lista formada por las coordenadas Y y Z del punto pto. Es decir, (20.0 0.0)
De modo que (cdr pto) devuelve (Y Z). Así que para obtener la coordenada Y:
30
En resumen, las coordenadas del punto pto se obtendrian mediante:
Si en las coordenadas X, Y y Z ponemos las letras A y D de cAr y cDr en mayúsculas, lo anterior quedaría:
Las funciones CAR y CDR se pueden agrupar. Para ello, existen una serie de funciones que se denominan juntando las
Aes y las Des de cAr y cDr respectivamente. El ejemplo anterior, queda:
Esto nos servirá como regla mnemotécnica para recordar el nombre de estas funciones. Tan solo se permiten 4 niveles de
anidación, así que entre la c y la r solo puede haber 4 letras (Aes o Des).
Supongamos que tenemos la siguiente lista asignada a la variable lst = ((a b) (c d) (e f)). OJO!! es una lista en la q sus
elementos son a su vez listas.
Como obtendríamos a ??
(car lst) devuelve (a b) que es el primer elemento de lst, y a es el primer elemento de (a b) así que:
y el elemento c???
(cDr lst) devuelve ((c d) (e f)). Ahora si hacemos (cAr (cDr lst)) devuelve (c d). Así que:
Cómo obtener d ??
(cDr lst) devuelve ((c d) (e f)) y (cAr (cDr lst)) el primer elemento de ((c d) (e f)), es decir devuelve (c d).
Si ahora hacemos (cDr (cAr (cDr lst))) obtenemos (d), que no es lo mismo que d. Ya que se trata de una lista cuyo
primer elemento es d. Así que:
Y cómo obtener e ??
31
(cDr lst) devuelve ((c d) (e f)) y (cDr (cDr lst)) devuelve ((e f)). Ojo! se trata de una lista cuyo primer (y único) elemento
es a su vez otra lista con dos elementos.
Así que (cAr (cDr (cDr lst))) devuelve (e f), y para obtener e:
(cAr (cDr (cDr lst))) devuelve (e f) tal como se vio en el ejemplo anterior. Así que (cDr (cAr (cDr (cDr lst)))) devuelve
(f), que no es lo mismo que f. Por tanto:
(cAr (cDr (cAr (cDr (cDr lst))))) devuelve f. Podriamos pensar que:
(cADADDr lst) también devuelve f. Pero al ejecutar esta línea AutoCAD nos dice que la función cADADDr no está
definida. Ya dijimos antes que se pueden agrupar hasta 4 funciones cAr y cDr, pero aqui estamos intentando agrupar 5, y
logicamente no podemos. Para obtener f podriamos hacer, por ejemplo:
(LENGTH lista)
En la variable pto teniamos una lista con las coordenadas de un punto, pero si solo trabajamos en 2D, la Z no nos
interesa. Así que muchas veces los puntos tan solo tendran 2 coordenadas (X Y). Pero para un programa no es lo mismo
que tenga 2 que 3 coordenadas, a lo mejor va a buscar la coordenada Z y no existe produciendose un error en nuestra
rutina.
Así que necesitamos conocer el número de elementos que tienen las listas. Para ello se utiliza la función length, que
devuelve el número de elementos de la lista que recibe como argumento. Por ejemplo:
Y qué devolvería (length lst) ??? siendo lst = ((a b) (c d) (e f)). Pues devolvería 3, ya que lst es una lista con 3 elementos
que a su vez son listas.
Qué devolvería (length (car lst)) ??? El número de elementos del primer elemento de lst, es decir el número de
elementos de (a b), que es 2.
Esta función devuelve una lista formada por los elementos indicados. De modo que se utiliza para crear listas. Por
ejemplo:
(list 1 2 3) devuelve (1 2 3)
Veamos (list "a" "b") devuelve ("a" "b") y (list "d" "e") devuelve ("d" "e"). Así que (list (list "a" "b") "c" (list "d" "e") "f")
devuelve (("a" "b") "c" ("d" "e") "f").
32
TEMA 11: EJECUTAR COMANDOS DE AUTOCAD
Una de las mayoresventajas de la programación en AutoLISP es la posibilidad de ejecutar comandos de AutoCAD en
nuestras rutinas.
Esta es la función que nos permite ejecutar comandos de AutoCAD. Hay que destacar que siempre devuelve nil. Los
nombres de los comandos de AutoCAD, y sus opciones, se indican como textos por lo que van incluidos entre comillas.
(command "linea" (list 0.0 0.0) (list 100.0 200.0)) Dibujará una línea desde el origen al punto 100,200. Pero, nos falta
algo: Al dibujar líneas en AutoCAD se van indicando puntos y siempre pide "Siguiente punto: " de modo que para
terminar el comando "LINEA" hay que pulsar INTRO. Pues ese Intro también hay que pasarselo a command:
(command "linea" (list 0.0 0.0) (list 100.0 200.0) "") o así (command "linea" (list 0.0 0.0) (list 100.0 200.0) " ")
Lo realmente potente de COMMAND es que podemos ejecutar casi todos los comandos de AutoCAD. Cuales no? Son
muy pocos, por ejemplo "Nuevo" para empezar un dibujo nuevo. Pero todos los comandos de dibujo, edición, etc se
pueden ejecutar.
Los datos dependeran del comando de AutoCAD indicado. Por ejemplo para el comando "circulo", será:
(command "circulo" (list 0.0 0.0) 25.0) Esto dibujará una circunferencia de radio 25 con centro en el origen.
Por suerte se puede solucionar, ya que sino un programa realizado para AutoCAD en Inglés sólo serviría para las
versiones en Inglés. AutoCAD en realidad no "habla" un único idioma, sino que es bilingue, dispone de una lengua que
es la que muestra (que corresponde con la versión idiomática del programa: Castellano, Francés, etc) y una lengua
interna, el Inglés.
De modo que los comandos de AutoCAD (y sus opciones) se pueden escribir en Castellano o en inglés. Pero para
diferenciar unos de otros a los comandos en la lengua nativa de AutoCAD (Inglés) se les antepone un guión bajo:
Las opciones de los comandos también se deben indicar en inglés anteponiendoles un guión bajo. Por ejemplo:
(command "_circle" (list 0.0 0.0) "_d" 25.0) Esta línea dibuja una circunferencia de Diámetro 25 con centro en el
origen.
33
Comandos de AutoCAD redefinidos
Por otra parte, ya se ha dicho anteriormente que los comandos de AutoCAD se podrán redefinir para que funcionen de
forma distinta. Así se puede cambiar el comando "circulo" para que dibuje pentagonos y el comando "linea" para que
dibuje circulos.
Si redefinimos el comando línea para que dibuje circulos, entonces debemos indicar (command "linea" centro radio) y no
(command "linea" pt0 pt1"") que no funcionaría, puesto que le estamos pasando unos parámetros que no espera. Pero
cómo hacemos ahora para dibujar una línea?
Para ejecutar los comandos originales de AutoCAD, y no los redefinidos (si lo están) debemos anteponer al nombre del
comando un punto, por ejemplo:
Podemos además indicar los comandos en Inglés anteponiendoles un guión bajo así que también podriamos escribirlo
así:
1. Ejecutar el comando "ANULADEF" (En Inglés "undefine") indicando el nombre del comando a redefinir. De
este modo se elimina la definición del comando de AutoCAD, y la única forma de ejecutarlo será anteponiendo
al nombre del comando un punto.
2. Crear y cargar una rutina en la que esté definido un nuevo comando con el nombre del comando que acabamos
de anular.
Veamoslo con un ejemplo: Primero anulamos la definición del comando línea. Podemos hacerlo desde AutoCAD con el
comando "ANULADEF" (En inglés "undefine") o desde AutoLISP: (command "_undefine" "linea")
Ahora podemos comprobar que el comando línea no funciona en AutoCAD, y la única forma de ejecutarlo es
anteponiendo a su nombre un punto ".linea".
(defun C:LINEA ( )
Para recuperar el valor original del comando podemos hacer dos cosas:
34
1. Cerrar AutoCAD y abrirlo de nuevo de modo que la rutina que hemos creado se borre de la memoria del
ordenador.
2. Ejecutar el comando de AutoCAD "redefine" (En Inglés es igual) e indicarle el nombre del comando del que
queremos recuperar su definición original, es decir "linea".
Bueno, por último un ejercicio: Crear un nuevo comando llamado CIRCPERI que dibuje una circunferencia indicando su
centro y la longitud de su perímetro
Compara si expr1 devuelve el mismo resultado que expr2, en caso afirmativo devuelve T y en caso ontrario devuelve nil.
(= 5 (+ 1 4.0)) devuelve T aunque (+ 1 4.0) devuelve 5.0 y no 5. Pero 5 y 5.0 valen lo mismo, no?
(= 5 5.0) devuelve T
(= txt1 "Curso de LISP") devuelve nil. No es lo mismo un texto en mayúsculas que en minúsculas.
(= 6 (+ 1 5) 6.0 (/ 12 2)) devuelve T, pues las cuatro expresiones devuelven 6 o 6.0 (que vale lo mismo).
Es muy similar a la anterior. Devuelve T si las expresiones no devuelven el mismo valor y devuelve nil si todas las
expresiones devuelven el mismo valor.
(/= 6 6.0) devuelve nil, porque 6 y 6.0 no son distintos (valen lo mismo).
35
(< expr1 expr2 ...)
Si se ponen más de 2 expresiones, se comprueba que esten ordenadas de menor a mayor y devuelve T si lo están y nil si
no lo están.
(<1 2 3) devuelve T
También podemos comparar textos. El interprete de AutoLISP evalua los códigos ASCII de los textos. Es decir los
ordena alfabéticamente de la "a" a la "z".
(< "C" "c") devuelve T, puesto que los códigos ASCII de la mayúsculas son inferiores a los de las minúsculas.
Los códigos ASCII, son una serie de números que se asignaron a las letras del alfabeto y a algunos de los caracteres más
usuales (al menos en Occidente).
El caracter "a" tiene asociado el código 97, la "b" el 98, etc hasta la "z".
El caracter "A" tiene asociado el código 65, la "B" el 66, etc hasta la "Z".
Supongo que ya os imaginais como funciona, no? Comprueba si las expresiones están ordenadas de mayor a menor.
(> 5 3) devuelve T
(> 5 4 3) devuelve T
(> "a" "c") devuelve nil, pues el código ASCII de "a" es menor que el de "c".
36
(<= expr1 expr2 ...)
(>= 3 3 3 3) devuelve T
Veamos como se haría el ejercicio propuesto en el tema anterior: Crear un nuevo comando llamado CIRCPERI que
dibuje una circunferencia indicando su centro y la longitud de su perímetro.
Qué es lo primero que hay que hacer ??? Esta respuesta tiene que ser instintiva, como un acto reflejo: El pseudocódigo.
Siempre comenzaremos nuestras rutinas escribiendo el pseudocódigo (o haciendo un diagrama de flujo) de lo que se
pretende hacer. Bueno, cómo podria ser el pseudocódigo de esta rutina, vamos a ver:
Una vez que terminamos el pseudocódigo, ya tenemos el 80% de la rutina. Si el pseudocódigo es correcto, el resto es de
lo más simple.
(defun C:CIRCPERI ( )
1) Pedir al usuario el centro de la circunferencia. Podriamos poner (getpoint "Centro de la circunferencia") pero eso no
serviría de nada, por que no almacenamos el punto que indica el usuario en ningún sitio. Tendriamos que hacer,
entonces...
(setq pto (getpoint "Centro de la circunferencia")) asi almacenamos el punto indicado en la variable pto.
2) Pedir al usuario el perímetro de la circunferencia. Por ejemplo (setq peri (getint "Perímetro:")) pero al usar getint, solo
permite obtener número enteros. Así que podriamos cambiarlo por:
3) Calcular el radio de la circunferencia a partir de su perímetro. Peri = 2* pi * rad así que rad = Peri / ( 2 * pi).
Traduciendolo a código:
37
4) Dibujar la circunferencia
Sólo nos falta una cosa. Pista: El número de paréntesis de apertura tiene que ser igual al número de paréntesis de cierre.
Así que:
(defun C:CIRCPERI ( )
También deberias añadir comentarios al código y una cabecera con varias líneas de comentarios en el archivo LSP
indicando El nombre de la rutina, fecha, autor, etc.
Te das cuenta de la importancia del pseudocódigo? Al programar nos ha guiado paso a paso.
Esta función devuelve T si ninguna de las expresiones que recibe como argumento es (devuelve) nil. Si una sola de las
expresiones devuelve nil, la función AND devolverá nil. Es decir, comprueba que se cumplan todas las expresiones que
recibe como argumento.
En el ejemplo anterior, como la primera expresión (> 3 4) devuelve nil, ya no se continuan evaluando el resto de
expresiones. Cómo hay una expresión que devuelve nil, AND devolverá nil. De modo que la expresión (= 5 5.0) ya no se
evalúa.
38
Preguntándolo de otra forma: Alguna de las expresiones que recibe AND como argumentos es nil? No, así que AND
devuelve T.
(and a b) devolverá T
(and a d)
Devuelve nil si todas las expresiones son nil. En caso de que alguna de las expresiones no devuelva nil, OR devuelve T.
Es decir, comprueba si alguna de las expresiones se cumple.
(or (/= 5 5.0) (> 3 4)) devuelve nil, porque ambas expresiones son nil
En el ejemplo anterior, como la primera expresión (= 5 5.0) devuelve T, ya no se continuan evaluando el resto de
expresiones. Cómo hay una expresión que devuelve T, OR devolverá T. De modo que la expresión (> 3 4) ya no se
evalúa.
(or a b) devuelve T
En el tema anterior vimos la función de comparación = que nos sirve para determinar si dos números o textos son
iguales. Pero que pasa si queremos comparar otra cosa, por ejemplo dos listas (como dos puntos).
(setq pt1 (list 10.0 20.0 0.0) pt2 (list 10.0 (* 10.0 2) 0.0) pt3 (list 9.99 20.0 0.0) pt4 (list 9.99 20.02 0.0))
Al comparar estas listas (puntos) con la función = siempre nos devuelve nil. Aunque pt1 y pt2 sean iguales, y muy
parecidos a pt3 y pt4. Por tanto, la función = no nos servirá para comparar listas. Para comparar dos listas se utilizará la
función EQUAL.
(equal 5 5.0) devuelve T, al igual q la función =, porque 5 vale lo mismo que 5.0
(equal 4.99 5.0 0.1) devuelve T, porque compara 4.99 y 5 pero con una precisión de 0.1. Así que con esa precisión 4.99
== 5.0
39
Sin embargo, si subimos la precisión...
¿Y que pasa con las listas? ¿También podemos indicarle una precisión? Veamoslo..
(equal pt1 pt3 0.1) devuelve T, porque compara los elementos de las listas (coordenadas) con la precisión que hemos
indicado, 0.1
Si subimos la precisión...
(equal pt1 pt4 0.02) devuelve T, porque la precisión es 0.02 que es exactamente la diferencia entre 20.02 y 20
El utilizar una precisión muy elevada no implica que todas las comparaciones devuelvan T, todo dependerá de los valores
a comparar:
(NOT expr)
A esta función le gusta llevarle la contraria a la expresión que recibe como argumento.
Si hacemos...
(and (not c) 5) Como c es T, (not c) devuelve nil. Al no cumplirse la primera expresión de AND, esta devuelve nil y no
continúa evaluando.
40
Mostrar textos en pantalla
Hay varias funciones para mostrar textos en pantalla. De momento tan solo vamos a ver un par de ellas, pero habrá más.
(PROMPT mensaje)
(ALERT mensaje)
Muestra el texto que recibe como argumento en un letrero. También devuelve nil. Se utiliza principalmente para avisar al
usuario en caso de error, o para mostrarle alguna información importante.
(TERPRI)
En el tema 12, creamos un nuevo comando para AutoCAD denominado CIRCPERI. Si habeis cargado y ejecutado el
comando, habreis observado que los mensajes para solicitar el centro de la circunferencia y su perímetro aparecen en la
misma línea y pegados.
Para que los mensajes aparezcan en líneas distintas, se puede emplear la función TERPRI. Ya que esta función mueve el
cursor a una nueva línea en la ventana de comandos de AutoCAD, y devuelve nil. El código de la rutina quedaría así:
(defun C:CIRCPERI ( )
Creo que ya se dijo que AutoLISP no reconoce una serie de caracteres, por ejemplo al indicar la ruta de un archivo no
reconoce el caracter "\" y hay que indicarselo así: "\\". Pues existen más caracteres de control predefinidos:
\\ Equivale al caracter \
\" Equivale al caracter "
\e Equivale a ESCape
\n Equivale al retorno de carro
\r Equivale a pulsar Intro
\t Equivale a pulsar la tecla del tabulador
\" nos permite escribir las comillas dentro de un texto. Por ejemplo:
41
(alert "Esto son un par de comillas \" o no?")
La única comilla q se tiene q ver es la que está escrita así: \" . Las otras nos indican donde comienza y termina el texto,
nada más.
Si lo ponemos así, añade un espacio en blanco al principio de la segunda línea. Por eso se pone todo seguido.
Entonces, cómo quedaría el código de la rutina CIRCPERI utilizando caracteres de control, en lugar de la función
(TERPRI)???
(defun C:CIRCPERI ( )
Un par de cosas más con respecto a esta rutina... Cuando se crea un archivo LISP en el que está definido un nuevo
comando es bastante útil añadir al final de todo el código algo similar a...
Esta línea se pondría después del paréntesis de cierre de defun. Es decir, que cuando se ejecuta CIRCPERI desde
AutoCAD esta línea no se evalúa. Para qué ponerla entonces?? Pues muy sencillo... para que cuando se cargue el archivo
en AutoCAD muestre en pantalla: Nuevo comando CIRCPERI cargado. Así el usuario sabe cual es el nombre del
comando definido en el archivo que se acaba de cargar. De modo que el mensaje sólo se mostrará al cargar el archivo.
Por otro lado... si recordamos la estructura de la función DEFUN: (DEFUN nombre_función ( argumentos /
variables_locales ) expr1 expr2 ...)
Veremos que en la rutina CIRCPERI no hemos indicado variables locales, así que todas las variables serán globales. Es
decir que al ejecutar CIRCPERI y dibujar un círculo, luego nos quedan accesibles los valores de las variables pto peri y
rad desde AutoCAD, ocupando y malgastando memoria. Así que vamos a ponerlas como locales. Sólo habría que
cambiar la siguiente línea...
OJO! la barra inclinada / hay que ponerla, sino serían argumentos y no variables locales.
42
El código completo de la rutina es el siguiente:
;;;________________________MecaniCAD__________________________;;;
;;;_____________http://www.peletash.com/mecanicad_____________;;;
;;;_______________________CIRCPERI.LSP________________________;;;
;;;_______________________Versión 1.1_________________________;;;
;;;________________________26/02/2002_________________________;;;
;;; de su perímetro.
La función IF es una de las más empleadas al programar. Devuelve el valor de la última expresión evaluada. Si condición
es distinto de nil, entonces evalúa la expr_si_cumple. Si condición devuelve nil evalúa la expr_no_cumple, si existe y si
no existe no hace nada. Veamos algunos ejemplos:
La condición a evaluar es: (= 2 2.0) en la que tenemos un número entero 2 y uno real 2.0, pero su valor es el mismo.
Aunque sean de distinto tipo 2 y 2.0 valen igual. Así que la condición devuelve T, evaluandose la condición si cumple
43
que muestra un mensaje de alerta. La función IF devuelve el valor de la última expresión evaluada, es decir alert, que es
nil.
En este caso la condición devuelve nil y al no existir expresión no cumple, no haría nada más. ¿Qué valor devolvería IF?
El de siempre, el valor de la última expresión evaluada, que en este caso ha sido la propia condición que devuelve nil. De
modo que IF devolverá nil.
(if (= 2 3)
En este caso el código se ha escrito en varias líneas y tabulado para facilitar su comprensión. Ahora si tenemos una
expresión no cumple, que será evaluada ya que la condición devuelve nil.
(alert "El límite superior debe ser mayor que el inferior") (setq limsup (getint "\nLímite superior")) (setq
intervalo (- limsup liminf))
Viendo el código anterior, tal vez pienses que si la condición (> liminf limsup) se cumple, entonces se evaluará la línea
siguiente de código. Pero no es así, se evalúa la expresión si cumple, que es la primera expresión (alert "El límite
superior debe ser mayor que el inferior"). Si la condición no se cumple, devuelve nil, se evaluará la expresión no cumple,
que en este caso será (setq limsup (getint "\nLímite superior")).
Tanto la expresión si cumple, como la no cumple solo pueden ser una única expresión. El ejemplo de código anterior nos
daría un error ya que IF no puede tener más que 3 expresiones:
La condición
La expresión si cumple
La expresión no cumple
44
(PROGN expr1 expr2 ...)
Para que se pueda indicar más de una expresión si cumple, o no cumple, en la función IF se suele emplear la función
PROGN. El valor devuelto por PROGN es el de la última expresión que recibe como argumento. Esta función en
realidad no hace nada, tan solo nos permite agrupar una serie de expresiones.
(progn
(progn
Si se cumple la condición, se evalúa la condición si cumple, es decir el progn. De modo que se van evaluando las
expresiones contenidas en la función PROGN, que devuelve el valor de la última expresión (setq intervalo (- limsup
liminf)), que será el valor de la variable intervalo.
En caso de que la condición no se cumpla, se evalúa la condición no cumple, (setq intervalo (- limsup liminf)) que
curiosamente también devuelve el valor de la variable intervalo.
45
Pasemos ahora a unos ejemplos más de estructuras condicionales:
En este caso la condición es (and (= 2 2.0) (< 2 3)) que en este caso devolvería T, ya que se verifican las dos expresiones
que recibe la función AND.
En este caso si var1 es nil, (not var1) devolverá T, indicando que la variable no se ha definido. En caso contrario, (not
var1) devolverá nil evaluandose la expresión no cumple. Otro método para hacer lo mismo, sería:
(if var1
Si var1 es distinto de nil, se evalúa la expresión si cumple. En caso de que var1 sea nil, se evaluaría la expresión no
cumple.
(COND (condicion1 [expr1_1] [expr1_2] ... ) [ (condicion2 [expr2_1] [expr2_2] ... ) ] ... )
Esta función es similar a IF en cuanto a que se indican condiciones y una serie de expresiones que se evaluaran si se
verifica cada condición. n este caso no existe la limitación de indicar tan sólo una única expresión si cumple, se pueden
indicar tantas como se desee. COND evaluará la primera condición encontrada, si se verifica evaluará las expresiones si
cumple de dicha condición. En caso de que no se verifique, evaluará la siguiente expresión.
46
Por lo tanto, COND evaluará las expresiones si cumple de la primera condición que se verifique. Y devolverá el
resultado de evaluar la última expresión si cumple. Pongamos un ejemplo:
(cond
((= a b)
((< a c)
((< a b))
(T
Supongamos ahora que antes de la expresión COND, hemos definido (setq a 2 b 2.0 c 3.5). En este caso, al entrar en
COND se evaluará la primera condición (= a b) que se verifica, de modo que se evaluaran las expresiones si cumple de
esta condición: (alert "A y B son iguales") y (setq b (getreal "Introduzca de nuevo B: ")) finalizando la expresión COND
que devuelve el nuevo valor de b.
Aunque la siguiente condición (< a c) se cumple, no se evalúa, ya que existe una condición anterior que también se
cumplía.
En caso de que se hubiera definido (setq a 2 b 2.5 c 3.5) la primera condición (= a b) no se verifica, de modo que se
evalúa la segunda condición (< a c) que si se cumple. Evaluándose sus expresiones si cumple (alert "A es menor que C")
que devuelve nil.
Si fuera (setq a 2 b 2.5 c 1.5) la primera condición en cumplirse sería la tercera (< a b) que no tiene expresiones si
cumple. ¿Qué devolverá entonces la función COND? Pues el valor de la última expresión evaluada, es decir el valor
devuelto por la condivión (< a b) que es T.
Por último, si tenemos (setq a 2 b 1.0 c 1.5) ninguna de las tres condiciones anteriores se verifica, de modo que pasamos
a la siguiente condición T, que lógicamente siempre devuelve T, así que siempre se verifica. Esto se suele utilizar mucho
en la función COND, añadir como última condición una que se verifique siempre. En lugar de T se podía haber puesto
(not nil) o (= 1 1.0) que también son expresiones que siempre se cumplen. ¿Para qué añadir una expresión que siempre se
cumple? Muy sencillo, para incluir el código que se desea ejecutar en caso de que no se verifique ninguna de las
condiciones anteriores.
47
¿Y qué sucede si no se pone la condición T como última condición? Pues sucede que las que estén a continuación nunca
se evaluarán, ya que T siempre se cumplirá. Si en el ejemplo anterior hubieramos puesto:
(cond
(T
((= a b)
((< a c)
((< a b))
Independientemente de los valores de las variables a b y c siempre nos diría que A no es igual a B, A no es menor que C
y A no es menor que B.
Activada / Desactivada. Muchas variables de sistema sólo admiten dos opciones: Activada y Desactivada.
Normalmente tienen asignado el valor "0" cuando están desactivadas, y "1" cuando están activadas. Un ejemplo
de este tipo de variables es "blipmode".
Números enteros. Otras variables tienen más de dos posibilidades, para lo que asignan un número entero para
cada opción. Normalmente emplean números correlativos, empezando desde el cero. Una variable que utiliza
este tipo de datos es "coords".
48
Códigos binarios. Algunas variables pueden emplear varias opciones a la vez, para lo que suelen emplear
códigos binarios. A cada opción se le asigna el número resultante de elevar 2 a n. Asignando a n números entero
correlativos a partir del cero. Es decir, los valores para las distintas opciones serán: 1,2,4,8,16,32,etc. De modo
que para seleccionar la primera y cuarta opciones, hay que asignar a la variable la suma de sus valores: 1+8 = 9.
Un ejemplo muy interesante de este tipo de variables es "osmode".
Números reales. Las variables que almacenan valores de ángulos o distancias, por ejemplo, utilizan este tipo de
valores. Un ejemplo de este tipo es la variable "chamfera".
Puntos. Este tipo de entidades almacenan las coordenadas de un punto, un buen ejemplo es "ucsorg".
Cadenas de texto. Hay bastantes variables que almacenan cadenas de texto, como nombres de archivos o rutas
de directorios. Ejemplos de este tipo de variables son "acadver" y "acadprefix".
No guardadas. La información asignada a este tipo de entidades no se guarda. Un ejemplo de este tipo de
variables es "acadver".
En el dibujo. La mayoría de las variables de sistema son de este tipo, de modo que cada dibujo trabajará con
unos valores determinados para las variables de sistema. Esto hace sumamente importante la definición de los
valores de las variables de sistema en las plantillas utilizadas para crear nuevos dibujos. Un ejemplo de variable
guardada en el dibujo es "luprec".
En el registro. Algunas variables de sistema se guardan en el registro de Windows. Por ejemplo "attdia".
La mayoría de las variables de sistema de AutoCAD pueden editarse, modificando el valor que tengan asignado. Pero
algunas variables son de solo lectura, de modo que no se pueden modificar, tan solo leer. Un ejemplo de variable de solo
lectura es "cdate".
Hay varios métodos para modificar los valores asignados a las variables de sistema de AutoCAD:
Esto puede ocasionar cambios en los valores asignados a algunas de las variables de sistema. Al programar deberías
seguir los siguientes consejos para que esto no suceda:
Deberías guardar los valores iniciales de las variables de sistema que se necesite modificar, y asignarles sus valores
iniciales al terminar el programa.
Crear una función de tratamiento de errores, de modo que si se produce algún error al ejecutar el programa se
restablezcan los valores iniciales de las variables de sistema. La creación de funciones de tratamiento de errores la
trataremos más adelante.
49
(GETVAR variable)
Esta función devuelve el valor asociado a la variable que recibe como argumento. Por ejemplo:
(getvar "osmode")
(getvar "blipmode")
(getvar "acadver")
(setvar "blipmode" 0)
Veamos un ejemplo combinando las funciones GETVAR y SETVAR. Escribe lo siguiente en la línea de comandos de
AutoCAD:
(getvar "luprec")
(setvar "luprec" 2)
(getvar "luprec")
(ITOA entero)
(ATOI texto)
Hace justo lo contrario que la función anterior. Convierte un texto en un número. Atom TO Integer
Y que pasa si hacemos... (atoi "soy un texto") pues que devuelve cero. Siempre que escribes algo que no sea un número
devuelve 0.
50
(FLOAT numero)
pero tb puedo hacer... (float 5.36) devuelve 5.36 lo cual sería una tontería porque no hace nada.
(ATOF texto)
La última función de este tipo que vamos a ver es algo más complicada, pero no mucho. Convierte un real en texto. Real
TO String.
Veamos para qué sirven los argumentos opcionales [modo [precisión]]. Modo, permite indicar un tipo de expresar los
números, es decir, permite seleccionar el formato utilizado para los números. Hay 5 opciones:
1. Formato científico.
2. Decimal. Es el que se usa habitualmente.
3. Pies y pulgadas.
4. Pies y pulgadas en fracciones.
5. Fracciones.
Precisión nos permite definir el número de decimales que deseamos.. por ejemplo:
51
TEMA 18: OBTENER DISTANCIAS Y ÁNGULOS DEL USUARIO
Hemos visto como se obtienen números, puntos y textos del usuario. Ahora le vamos a solicitar directamente una
distancia o un ángulo.
Solicita una distancia y devuelve esa distancia como un número real. El usuario podrá indicar la distancia por medio de
un número o indicando 2 ptos en pantalla.
El mensaje es opcional, pero casi siempre se pone. También podemos asignar el resultado a una variable...
En muchas ocasiones se puede reemplazar a la función GETREAL por GETDIST si lo que se pide se puede relacionar
con alguna distancia del dibujo. Por ejemplo, en nuestra rutina CIRCPERI podríamos dibujar una circunferencia de
perímetro la longitud de una recta.
El argumento opcional [pto_base] funciona de modo similar a como lo hace en GETPOINT permitiendo indicar la
distancia a partir de un punto de origen ya predefinido:
Solicita un ángulo al usuario que puede escribirlo, o designarlo por medio de dos puntos. Devuelve el ángulo en radianes
aunque el usuario lo escribiría en las unidades actuales (generalmente grados decimales). Por ejemplo, a la expresión:
(getangle "Angulo: ") el usuario responde... 180.0 y getangle, devuelve pi.
Toma como origen de ángulos el actual, que suele coincidir con el sentido positivo del eje X del SCU (Sistema de
Coordenadas Universal). Pero siempre considerará como sentido positivo de los ángulos el antihorario.
El parámetro opcional [pto_base] permite indicar un punto a partir del cual indicar el ángulo.
Es casi igual a GETANGLE, y también se utiliza para solicitar un ángulo al usuario. La única diferencia es que el origen
de ángulos siempre es el del sentido positivo del eje X del SCU (Sistema de Coordenadas Universal).
52
)
Si sustituimos la función getreal por getdist además de poder escribir un perímetro directamente, también podremos
indicarlo mediante dos puntos .
Por otra parte, Si habeis ejecutado el comando CIRCPERI vereis que lo de los mensajes queda mejor que al principio
pero tampoco es perfecto ya que aparecen unos textos en la ventana de comandos que no se sabe de dónde salen:
Centro de la circunferencia:
Perímetro: Designe segundo punto: _.circle Precise punto central para círculo o
Esto queda bastante feo y como hay que ser profesionales, vamos a ponerlo bien. Hemos visto las funciones GETVAR y
SETVAR que nos permiten acceder a las variables de sistema de AutoCAD. Pues hay una variable que controla el "eco
de mensajes", es decir, el que aparezcan esos mensajes en pantalla. La variable se llama CMDECHO y solo admite dos
valores posibles:
0 Desactivada
1 Activada
Por defecto debería estar activada (1) así que se verían los mensajes raros de antes. ¿Cómo evitamos que aparezcan estos
mensajes? Pues desactivando la variable.
Pero no conviene modificar los valores de las variables de sistema, porque tal vez el usuario los quiera mentener como
estaban. De modo que se desactivará momentaneamente y al terminar la rutina se dejará con su valor inicial, es decir tal
como estaba.
Esto es más bien una filosofía de vida: "Si al entrar en un sitio, la puerta estaba cerrada, vuelve a cerrarla"
53
Efectivamente los mensajes raros desaparecen, pero... ¿Qué pasa si al entrar en una habitación, la puerta ya estaba
abierta? ¿la cerramos o la dejamos abierta de nuevo?. Lo mejor es que todo quede "tal como lo encontramos". Así nadie
nos dirá... "¿Por qué cerraste la puerta? No ves que aqui no hay quien respire...". Y si lo dice, le respondes: "Perdona,
pero mi rutina, deja las cosas tal y como estaban, así que o estaba cerrada antes o la cerraron después".
Si cmdecho está inicialmente desactivada nuestra rutina la descativa, o lo intenta, y luego la activa. Quedando por lo
tanto cmdecho activada. Así que vamos a modificar el código para que cmdecho quede con el valor que tenía antes de
ejecutar la rutina...
Lo primero que tenemos que saber es si la puerta está cerrada, si está cerrada la abrimos y si ya está abierta no hacemos
nada, simplemente pasamos.
(getvar "cmdecho") me dirá si cmdecho está activada o desactivada. Veamos que es lo que hay que hacer:
El apartado 1 suena claramente a una estructura condicional, así que emplearemos la función IF:
(setvar "cmdecho" 0)
¿Y cómo haríamos al final de la rutina en el apartado 3? Sería otro condicional, pero necesitamos conocer el valor que
tenía inicialmente la variable cmdecho para saber si estaba activada o desactivada. De modo que hay que modificar el
código anterior para que en el apartado 1 se almacene el valor de cmdecho.
(setvar "cmdecho" 0)
De este modo la variable cmd0 almacena el valor inicial de cmdecho. Es bastante habitual almacenar los valores iniciales
en variables cuyo nombre sea del tipo cmd0, osm0 o blip0 pues el 0 nos indica que almacena el valor inicial.
Ahora ya podemos poner el código del apartado 3. Podríamos hacerlo de dos formas:
(if (= cmd0 1)
(setvar "cmdecho" 1)
En este caso, si cmdecho estába inicialmente activada entonces la activa. Si no estaba inicialmente activada, es decir
estaba desactivada, entonces no hace nada porque cmdecho ya está desactivada, al igual que al principio.
54
El código quedaría así:
(setvar "cmdecho" 0)
(if (= cmd0 1)
(setvar "cmdecho" 1)
Aún nos queda una cosa... al ejecutar ahora la rutina muestra el siguiente texto:
Centro de la circunferencia
Aparece un 1 al final del texto. Ese 1 es el resultado de la evaluación de la última expresión de CIRCPERI ( setvar
"cmdecho" 1). Para que la salida de nuestras rutinas sea "limpia" añadiremos al final del código de esta una función que
devuelva nil, así no escribirá nada. Por ejemplo, se podría añadir (prompt "") aunque suele emplearse la función (princ)
que aún no hemos visto, pero que también devuelve nil.
55
Finalmente el código resultante será:
(setvar "cmdecho" 0)
(if (= cmd0 1)
(setvar "cmdecho" 1)
(princ)
En este tema no vamos a ver ninguna función de AutoLISP. Vamos a crear un nuevo comando y a repasar un poco el
comando CIRCPERI.
Comenzaremos creando un nuevo comando... Crear un comando para dibujar arandelas en 2D. El programa solicitará al
usuario el centro de la arandela, el diámetro interior y el exterior. Se dibujarán dos circunferencias concéncricas con los
diámetros indicados. Y antes de ponerse a escribir código hay que ... escribir el pseudocódigo. Veamos, podíamos hacer
algo así:
AutoCAD ya tiene un comando que se llama arandela (en inglés donuts) así que buscaremos otro nombre para nuestra
rutina, por ejemplo ARAND. Es mejor utilizar nombres más bien cortos y que evoquen a la función que tiene el
comando.
(defun C:ARAND ( )
56
(setq pto (getpoint "\nCentro de la arandela: "))
(defun C:ARAND ( )
57
El código después de realizar las mejoras anteriores sería:
(setvar "cmdecho" 0)
(if (= cmd0 1)
(setvar "cmdecho" 1)
(princ)
Además de las mejoras indicadas anteriormente, ahora la variable rad se utiliza tanto para almacenar el radio de la
circunferencia interior como el de la exterior. ¿Puede dar lugar esto a algún problema? Veamoslo:
El radio de la circunferencia interior no lo vamos a volver a utilizar en nuestra rutina, de modo que podemos reutilizar
esa variable y asignarle otro valor, el radio de la circunferemcia exterior.
Ahora la variable rad almacena el radio de la circunferencia exterior y pto el centro de las dos circunferencias, de modo
que dibujamos la circunferencia exterior.
58
(command "_.circle" pto rad)
De este modo nos ahorramos una variable. Si es posible conviene reutilizar las variables.
Si cargamos la rutina y ejecutamos el comando ARAND dibujaremos una arandela formada por dos círculos. ¿Pero qué
pasa si después de dibujarla ejecutamos el comando "H" (deshacer)? Pues que "deshace el último comando ejecutado"
que no es ARAND sino (command "_.circle" pto rad) de modo que deshace el círculo exterior. Pero el interior no.
La opción por defecto es indicar el número de operaciones a deshacer, que por defecto tiene el valor 1. Esta opción
funciona exactamente igual que el comando "H". Pero, podemos indicarle un número de operaciones superior a 1
(cualquier número entero entre 1 y 32767). Esta opción es útil para deshacer los cambios realizados por los últimos "n"
comandos.
"Control"
Al seleccionar "Control" nos ofrece las siguientes posibilidades: "Indique una opción de control DESHACER
[Todas/Ninguna/Una] <Todas>:"
Con la opción "Todas" seleccionada (es la opción por defecto), AutoCAD almacena en el archivo temporal
"UNDO.ac$" la información sobre los comandos ejecutados en el dibujo actual y por tanto que se pueden
deshacer. También almacena en el archivo temporal "REDO.ac$" la información sobre los comandos del dibujo
actual que se han deshecho. Estos archivos se almacenan en el directorio temporal del sistema operativo. Esta
opción permite deshacer todos los comandos realizados en el dibujo durante la sesión actual.
Si se selecciona la opción "Una", tan sólo se podrá deshacer el último comando ejecutado. Quedando
desactivadas todas las opciones del comando "DESHACER" excepto "Control" e "Indique el número de
operaciones a deshacer <1>:"
Seleccionando la opción "Ninguna" se elimina la capacidad de revocar de los comandos "H" y "DESHACER",
quedando estos desactivados. Y ya no se utilizan los archivos temporales anteriormente citados.
"Marca y Retorno"
Estas dos opciones funcionan en pareja. Supongamos que vamos a ejecutar una serie de comandos en el dibujo actual,
pero no sabemos si el resultado obtenido será el deseado. En este caso, antes de comenzar puedes ejecutar el comando
"DESHACER" y seleccionar la opción "Marca". De este modo activas una marca, a la que podrás volver en cualquier
momento ejecutando "DESHACER" con la opción "Retorno". Al encontrar una marca AutoCAD mostrará el mensaje
"Marca encontrada".
Si en lugar de volver a la marca lo que quieres es deshacer un número determinado de comandos, puedes ejecutar el
comando "H" o "DESHACER" indicando el número de comandos a deshacer.
Además, puedes definir tantas marcas como desees, y cada vez que ejecutes "DESHACER" "Retorno" volverás a la
marca anterior. Si no existen más marcas, o si no se ha definido ninguna marca, AutoCAD preguntará si se desea
deshacer todo.
59
"Auto"
Algunos comandos de AutoCAD, están formadas por un grupo de órdenes. De modo que el comando "H" no anularía
todo el grupo de comandos ejecutados, sino sólo el último. Activando esta opción se agrupan estos comandos en uno
sólo, a efectos de la aplicación de los comandos "H" y "DESHACER".
"Inicio y Fin"
Estas dos opciones también funcionan juntas. Con ellas podemos agrupar una serie de comandos, de modo que sean
tratados como uno solo al ejecutar "H" o "DESHACER".
Con la opción "Auto" desactivada, las opciones "Inicio" y "Fin" se ejecutan de forma análoga a como se hace con
"Marca" y "Retorno". Si se vuelve a ejecutar la opción "Inicio" sin haber ejecutado la opción "Fin" para cerrar un grupo
anterior, AutoCAD entiende que se quiere cerrar el grupo anterior y abrir uno nuevo.
Añadiremos al inicio de nuestra rutina "deshacer" "inicio" y al final de la rutina "deshacer" "fin". Veamoslo:
(setvar "cmdecho" 0)
(if (= cmd0 1)
(setvar "cmdecho" 1)
60
(princ)
¿Deberíamos añadir estas dos líneas en la rutina CIRCPERI? Pues no es necesario, puesto que en CIRCPERI tan solo
utilizamos un comando. Así que el comando de AutoCAD "H" deshara ese comando.
En este tema se estudiará el primero de los dos apartados anteriores, y en el siguiente tema veremos el segundo apartado.
Fijémonos en el código de la rutina ARAND:
(setvar "cmdecho" 0)
(if (= cmd0 1)
(setvar "cmdecho" 1)
(princ)
61
¿Qué sucede si el usuario como respuesta a la petición del radio interior pulsa Intro? Pues que se asigna a la variable rad
el valor nil. Produciendose un error al evaluar la siguiente línea de código: (command "_.circle" pto rad)
En el siguiente tema veremos cómo se puede evitar que el usuario introduzca datos erroneos. Por ejemplo, evitar que
como respuesta a la petición del radio interior se introduzca Intro. Pero una cosa está clara, no siempre vamos a poder
controlar todos los posibles errores en nuestras rutinas. De modo que necesitamos una función de tratamiento de errores
que informe al usuario del tipo de error que se produce. Por ejemplo, indicarle al usuario que ha introducido un dato
incorrecto. AutoCAD en este caso nos dice: "; error: tipo de argumento erróneo: numberp: nil"
AutoLISP dispone de una función de tratamiento de errores por defect. Dicha función se llama *error* y recibe como
argumento una cadena de texto con la descripción del error que se ha producido.
Podemos redefinir la función de tratamiento de errores que ofrece AutoLISP, personalizándola a nuestro gusto en función
de nuestros intereses. Vamos a crear una función de tratamiento de errores a la que llamaremos ERRORES:
(princ)
(princ)
No nos paremos demasiado aqui, ya lo veremos bien más adelante. Hemos creado una función llamada ERRORES para
el tratamiento de errores en nuestras rutinas. Ahora habrá que decirle al comando ARAND que utilice esta función de
tratamiento de errores.
Antes, conviene explicar cómo se pueden redefinir las funciones de AutoLISP. Hay métodos:
1. Crear una función con el mismo nombre. Por ejemplo, (defun SIN ... Esto redefiniría la función SIN de
AutoLISP.
2. Asignarle un valor distinto mediante setq. Por ejemplo, si hacemos (setq sin cos) la función SIN de AutoLISP
pasará a funcionar como la función COS, devolviendo el coseno de un ángulo en lugar de el seno. (sin 0.0)
ahora devolvería 1.0 en lugar de 0.
(setvar "cmdecho" 0)
62
(setq rad (getreal "\nRadio interior: "))
(if (= cmd0 1)
(setvar "cmdecho" 1)
(princ)
En la línea (setq error0 *error* *error* errores) se asigna a la variable error0 la antigua función de tratamiento de errores,
*error*. Y se define la función de tratamiento de errores como nuestra función ERRORES. Por supuesto, le decimos que
use nuestra función de tratamiento de errores al principio del código. Pero siempre después de desactivar el eco de
mensajes.
Hemos redefinido la función de tratamiento de errores, pero al terminar la rutina debemos ponerlo todo como estaba
antes. Así que recuperamos la función de tratamiento de errores inicial, la que ofrece AutoLISP por defecto. Lo haríamos
con la siguiente línea: (setq *error* erro0) que se debe poner antes de volver a activar el eco de mensajes.
(setvar "cmdecho" 0)
(if (= cmd0 1)
63
(setvar "cmdecho" 1)
(princ)
Repasemos un poco el funcionamiento de la rutina CIRCPERI: Lo primero que hacemos es desactivar el eco de
mensajes, a continuación redefinimos la función de tratamiento de errores, después viene el código de la función, se
restituye la función de tratamiento de errores de AutoLISP y se recupera el valor inicial de "cmdecho" (que controla el
eco de mensajes).
¿Qué diferencia existe entre nuestra función de tratamiento de errores y la ofrecida por defecto por AutoLISP? Casi
ninguna, de momento tan solo se diferencia en que si el error que se ha producido tiene por descripción "quitar / salir
abandonar", que equivale a pulsar la tecla de ESCape no hace nada. Si el error no se ha producido por pulsar ESC, sino
por otra causa, entonces mostrará un mensaje indicando: "Error: Descripción del error". Un "gran" cambio, no? Bueno
los cambios vamos a hacerlos ahora que ya sabemos como funciona la función ERRORES.
¿Qué sucede si el usuario introduce un dato incorrecto al solicitarle el perímetro? En ese caso, en la siguiente línea (setq
rad (/ peri (* pi 2))) se produciría un error, iniciandose la función de tratamiento de errores. Cuando se produce el error,
ya se ha evaluado la línea (setq error0 *error* *error* errores) en CIRCPERI, de modo que hemos redefinido la función
de tratamiento de errores. Así que se ejecuta la función ERRORES, que muestra la descripción del error que se produjo.
Pero, salimos de la función ERRORES y no hemos recuperado el valor de la función de tratamiento de errores que nos
ofrece por defecto AutoLISP. Y también se ha desactivado el eco de mensajes, pero no recuperamos su valor inicial.
(princ)
(if (= cmd0 1)
(setvar "cmdecho" 1)
(princ)
Ahora nuestra función ERRORES restituye el valor inicial del eco de mensajes y la función de tratamiento de errores
inicial. Puede extrañar que se utilicen las variables cmd0 y error0 dentro de la función ERRORES, ya que estaban
definidas como variables locales en CIRCPERI.
64
Cuando definimos una variable local en una función, esta variable se puede utilizar sólo dentro de esa función. Pero, si
desde CIRCPERI "llamamos" a otra función, por ejemplo ERRORES, en realidad estamos dentro de ERRORES, que
está dentro de CIRCPERI. Así que seguimos dentro de CIRCPERI.
Otra de las principales aplicaciones de redefinir la función de tratamiento de errores tiene que ver con el tema anterior,
los comandos deshacer en las rutinas de AutoLISP.
Si nos fijamos en el código de la rutina ARAND, vemos que la primera línea es (command "_.undo" "_begin") Qué
sucedería si se produce un error después de dibujar el segundo círculo? Daría un error, y terminaría el comando sin
ejecutar la línea (command "_.undo" "_end"). Con lo cual para deshacer la arandela deberiamos ejecutar el comando "H"
(deshacer) dos veces, una por cada círculo. Es más, imagina que nuestra rutina no dibuja 2 círculos sino 120 y que el
error se produce al dibujar en círculo enésimo...
(princ)
(if (= cmd0 1)
(setvar "cmdecho" 1)
(princ)
Ahora, bastaría con ejecutar el comando deshacer una única vez para que se deshaga todo lo que hizo el comando
ARAND. Podemos incluso decirle a la función ERRORES que si se produce un error, ejecute el comando deshacer
directamente:
(princ)
65
(command "_.undo" "")
(if (= cmd0 1)
(setvar "cmdecho" 1)
(princ)
En este caso si se produce un error ni siquiera hace falta ejecutar el comando deshacer, la función ERRORES ya lo hace
por nosotros.
Para terminar el tema, vamos a añadirle una nueva opción a CIRCPERI. Se trata de que ofrezca por defecto el perímetro
del último circulo dibujado. Podríamos hacer lo siguiente:
(setvar "cmdecho" 0)
(progn
(if (= cmd0 1)
(setvar "cmdecho" 1)
66
)
(princ)
Ponemos la variable rad como global, así se puede recuperar el valor que tenía en la anterior ejecución de CIRCPERI.
Veamos ahora cómo funciona el siguiente trozo de código:
(progn
Si rad es igual a nil, no se ha definido, significa que es la primera vez que se ejecuta el comando CIRCPERI en el dibujo
actual. En este caso se muestra un mensaje solicitando el perímetro del círculo.
Si no es la primera vez que se ejecuta CIRCPERI, la variable rad tendrá asociado un valor, correspondiente al radio del
circulo creado en la última ejecución de CIRCPERI. También muestra un mensaje solicitando el perímetro, pero entre los
caracteres "<" y ">" se indica además el valor de la variable global rad.
A continuación se solicita una distancia. No se ha indicado ningún mensaje en la función getdist, ya que el mensaje de
solicitud se muestra en las líneas anteriores. Si se indica un perímetro, ya sea por medio de dos puntos o escribiendolo
directamente, entonces se calcula su radio. Si como respuesta a getdist se pulsa Intro, a la variable peri se asigna el valor
nil, que es devuelto por setq. De modo que en este caso no hace nada, por lo tanto la variable rad sigue almacenando el
radio del último círculo creado con CIRCPERI.
AutoCAD dispone de una variable de sistema llamada CIRCLERAD en la que almacena el valor del último circulo
dibujado. Así que podemos utilizar esta variable de sistema para obtener el radio del último círculo, en lugar de utilizar la
variable rad como global. El código sería el siguiente:
67
(if (= (setq cmd0 (getvar "cmdecho")) 1)
(setvar "cmdecho" 0)
(progn
(if (= cmd0 1)
(setvar "cmdecho" 1)
(princ)
Ahora ofrecerá por defecto el perímetro del último círculo dibujado en AutoCAD, independientemente de si dicho
círculo se creo con la rutina CIRCPERI, con el comando CIRCULO o por cualquier otro medio. Veamos como funcionan
los cambios que hemos realizado:
(progn
68
(prompt "\nPerímetro <")
Si aún no se ha creado ningún círculo en el dibujo, la variable circlerad tendrá asociado el valor 0.0. En este caso solicita
el perímetro sin ofrecer ningún valor por defecto, y en caso contrario ofrece por defecto el perímetro del último círculo
creado.
Si se introduce un perímetro, por medio de dos puntos o escribiendolo, se calcula el radio correspondiente. En caso de
que se pulse Intro, se asocia a la variable rad el radio del último círculo dibujado.
TEMA 21: Limitar las respuestas de los usuarios a las funciones de solicitud de
datos (I)
En tema anterior vimos las funciones de tratamiento de errores, que nos permiten cotrolar lo que sucede cuando se
produce un error en nuestras rutinas. Hoy intentaremos que no se produzcan alguno de los posibles errores en nuestras
rutinas.
Los errores pueden deberse a que el código no funciona bien por que se ha empleado mal alguna función de AutoLISP o
se halla ejecutado incorrectamente un comando de AutoCAD, por ejemplo, pero estos no son los tipos de errores que
corregiremos en este tema. M ás adelante veremos un tema en el que se explicarán métodos de depuración de nuestras
rutinas, en ese tema corregiremos los errores debidos a un código fuente incorrecto.
En este tema trataremos otro tipo de errores, los que se producen cuando el usuario introduce datos erroneos. Por
ejemplo, cuando se solicita un número positivo y el usuario indica cero o un número negativo.
Esta función se utiliza para modificar el funcionamiento de otras funciones de AutoLISP, en concreto aquellas funciones
en las que se solicitan datos al usuario. Por ejemplo: GETpoint, GETreal, GETint, ... casi todas comienzan por GET así
que se suelen denominar funciones de tipo GET...
Initget siempre devuelve nil. Y si se indica solo, sin argumentos, no hace nada. Tan solo devuelve nil. Así que vamos a
ver para que sirven los argumentos de Initget:
"modo"
Es un número entero que nos permitirá limitar los datos que se puedan introducir en la siguiente solicitud de datos al
usuario. Initget NUNCA funciona por si sola, siempre se utiliza para modificar el funcionamiento de otra función.
69
El argumento modo es en realidad un código binario, que puede tener los siguientes valores:
1 --> No admite valores nulos, es decir que se indique Intro como respuesta
2 --> No admite el 0 como respuesta
4 --> No admite valores negativos como respuesta
8 --> Permite indicar un punto fuera de los límites del dibujo. Aún cuando estos están activados.
16 --> Este valor no se utiliza actualmente
32 --> Dibuja la línea o rectángulo elásticos con líneas discontínuas en lugar de contínuas
64 --> Hace que GETdist devuelva la distancia en 2D. Es como si proyectase la distancia real sobre el plano XY.
128 --> Permite introducir como respuesta una expresión de AutoLISP.
Bien, veamos como se utiliza initget... por ejemplo, si queremos que el usuario introduzca un número entero y que no
pueda pulsar Intro como respuesta, haríamos lo siguiente:
(initget 1)
Si además queremos que no pueda indicar 0 como respuesta, entonces sumamos sus respectivos códigos, el 1 para que no
se pueda indicar Intro como respuesta y el 2 para que no se pueda indicar 0.
(initget (+ 1 2))
También podemos indicar directamente (initget 3) en lugar de (initget (+ 1 2)). Si el usuario indica como respuesta 0 o
Intro, AutoCAD le dirá que ese dato no es válido y que introduzca un dato correcto.
(initget 7)
Ya que 7 = 1 + 2 + 4
Veamos ahora como funciona el código 8 como argumento modo de Initget. 8 --> Permite indicar un punto fuera de los
límites del dibujo. Aún cuando estos están activados.
Supongamos que tenemos los límites del dibujo de AutoCAD activados (comando LIMITES) en ese caso no podemos
indicar puntos fuera de esos límites. De modo que si se solicita un punto al usuario con GETPOINT debera indicarlo
dentro de los límites del dibujo. Pero si antes de solicitar el punto se ejecuta (initget 8) entonces si nos dejaría.
El código 16 no se utiliza.
El código 32 se utiliza en funciones de solicitud en las que se indica un punto base, por ejemplo:
70
En estos casos aparece una línea, o un rectángulo, elástico con origen en el punto base pto. Estas líneas y rectángulos
elásticos se muestran con línea continua. Pero si antes de la función de solicitud se añade (initget 32) se mostraran con
líneas discontínuas. Por ejemplo:
(initget 32)
En este ejemplo aparece una línea discontinua. Y en el siguiente un rectángulo con líneas discontinuas.
(initget 32)
Veamos ahora como funciona el código 64 --> Hace que GETdist devuelva la distancia en 2D. Es como si proyectase la
distancia real sobre el plano XY.
Getdist solicita una distancia, que se puede escribir directamente, o se pueden indicar dos puntos en pantalla. En este
caso, getdist devolverá la distancia real entre esos dos puntos. Si lo que nos interesa obtener es la distancia de sus
proyecciones sobre el plano XY actual se añadirá (initget 64) antes de la ejecutar getdist. Por ejemplo:
(initget 64)
Por último, el código 128 permite indicar una expresión de AutoLISP como respuesta. Por ejemplo, podemos utilizar
nuestra rutina RAG (Radianes A Grados decimales) para indicar un ángulo en grados decimales cuando nosotros lo
tenemos en radianes.
(initget 128)
En este caso el usuario podría indicar como respuesta a la solicitud del ángulo: (RAG (/ pi 4)) Es decir, un ángulo de 45º.
Pues llegados a este punto, antes de ver el segundo argumento de (INITGET [modo] [palabras_clave]), es decir, las
palabras clave. Vamos a modificar nuestras rutinas ARAND y CIRCPERI. Comenzamos con CIRCPERI:
(setvar "cmdecho" 0)
71
(prompt "\nPerímetro: ")
(progn
(if (= cmd0 1)
(setvar "cmdecho" 1)
(princ)
Tenemos la siguiente línea: (setq pto (getpoint "\nCentro de la circunferencia: ")) le añadiríamos alguna limitación
como respuesta del usuario? Veamoslo:
Si el usuario pulsa Intro como respuesta a la variable pto se le asocia el valor nil, que es lo que devolvería Getpoint.
Después al intentar dibujar el círculo (command "_.circle" pto rad) se produciría un error. Así que debemos impedir que
el usuario introduzca Intro como respuesta, de modo que añadiríamos (initget 1) antes de la función getpoint. Como
estamos solicitando un punto, el código 2 no tiene sentido y lo mismo sucede con el 4.
El código 8 permite indicar un punto fuera de los límites del dibujo, aún cuando estos están activados. Este código si
podríamos utilizarlo, aunque si el usuario trabaja con los límites activados, están activados y ya está. Si quiere que los
desactive él, no? Porque supongo que los tendrá activados por algún motivo. Así que no le añadimos a initget el código
8.
El código 32 no tiene sentido aqui, pues no aparece la línea base. Y el código 64 tampoco ya que estamos solicitando un
punto, no una distancia.
El código 128 permite introducir como respuesta una expresión de AutoLISP. Este código también se podría indicar, pero
lo habitual es no hacerlo. Se podría indicar en casos en los que el usuario pudiera utilizar una expresión de AutoLISP
para calcular el dato. Como en el ejemplo que vimos antes, si tiene un ángulo en radianes y lo tiene que indicar en grados
decimales. Aqui nos pide un punto, así que no tiene demasiado sentido.
72
Definitivamente el código quedaría:
...
(initget 1)
...
Sigamos modificando la rutina CIRCPERI... Unas líneas después de la sollicitud del centro de la circunferencia, se
solicita su perímetro:
Añadimos el código 1 antes de la función getdist? Veamos como funciona esta parte del código: Si el usuario introduce
una distancia, se evalúa la condición si cumple del IF es decir:
Y si el usuario pulsa Intro, se asigna a peri el valor nil y evalúa la condición no cumple del IF, es decir:
Si ponemos (initget 1), el usuario no podrá indicar Intro, así que nunca se ejecutaría la expresión no cumple. Por tanto no
añadimos el código 1 a Initget.
El perímetro del cículo no puede ser ni cero ni un número negativo, de modo que podemos añadir a initget los códigos 2
y 4. También podríamos añadirle el código 64 para que GETdist devuelva la distancia en 2D. Pero normalmente no
conviene añadir este código, excepto cuando la distancia "debe" siempre ser en 2D.
Una última nota sobre CIRCPERI... Cuando ejecutamos la rutina por primera vez en un dibujo en el que no se halla
dibujado ningún círculo, la variable de sistema "circlerad" tiene el valor 0.0. En este caso, no ofrecemos la opción de
seleccionar el radio del último círculo dibujado pulsando Intro, ya que no existe ningún círculo dibujado previamente. En
este caso, no deberíamos indicar el modo 6 a Initget, sino el 7 para que tampoco permita al usuario indicar Intro como
respuesta. Veamos como se soluciona en el código completo de la rutina:
(setvar "cmdecho" 0)
(initget 1)
73
(progn
(initget 7)
(progn
(initget 6)
(if (= cmd0 1)
(setvar "cmdecho" 1)
(princ)
74
Vamos ahora a modificar la rutina ARAND:
(setvar "cmdecho" 0)
(if (= cmd0 1)
(setvar "cmdecho" 1)
(princ)
La primera solicitud que tenemos en ARAND es la del centro de la arandela. Practicamente es igual que la solicitud del
centro del cículo en CIRCPERI, así que le añadimos también el código 1 a Initget:
(initget 1)
75
A continuación solicita los radios interior y exterior de la arandela. En los que podemos evitar que el usuario indique
como respuesta Intro, 0 o un número negativo. De modo que añadiriamos Initeget con el modo 7. Veamos el código
completo:
(setvar "cmdecho" 0)
(initget 1)
(initget 7)
(initget 7)
(if (= cmd0 1)
(setvar "cmdecho" 1)
(princ)
76
TEMA 22: Limitar las respuestas de los usuarios a las funciones de solicitud de
datos (y II)
En el último tema comenzamos a ver la función INITGET y vimos como funcionaba el argumento [modo] ahora vamos
con [palabras_clave]...
[palabras_clave] nos permite indicar una serie de textos (palabras) que también seran aceptados como respuesta en la
siguiente función de solicitud de datos. Por ejemplo:
como respuesta a getdist podemos indicar una distancia, ya sea escribiendola o mediante dos puntos, pero ahora también
aceptaría como respuesta Diametro y Perimetro. El modo 7 impide que se indique como respuesta Intro, cero o un
número negativo.
Si indicamos como respuesta una distancia, asocia esa distancia a la varible rad.
Si indicamos como respuesta Diametro, asocia a la varible rad la cadena de texto "Diametro".
Si indicamos como respuesta Perimetro, asocia a la varible rad la cadena de texto "Perimetro".
En [palabras_clave] indicamos una serie de palabras, separadas por espacios, que serviran como respuesta a la siguiente
función de solicitud de datos. No hace falta escribir el nombre completo de la palabra, como hicimos antes, basta con que
el usuario introduzca como respuesta las letras que aparecen en mayúsculas. Es decir, la D o la P. En el siguiente
ejemplo...
Para seleccionar la opción Diametro habrá que escribir al menos la D. Pero para seleccionar DEShacer al menos habrá
que escribir DES. También aceptaría dia para el diámetro y desha para deshacer. Fíjate que al menos deben indicarse las
letras en mayúsculas.
En este caso getdist aceptaría como respuestas a la petición del radio del círculo "Diametro" y "Perimetro". Pero al no
indicar estas opciones en el mensaje de getdist, el usuario no sabra que existen. De modo que lo deseable es indicar al
usuario las opciones que puede seleccionar:
77
Como mejor se ve es con un ejemplo así que... Vamos a modificar la rutina ARAND añadiendo una opción para indicar
los diámetros en lugar del radio. Teníamos el siguiente código:
(setvar "cmdecho" 0)
(initget 1)
(initget 7)
(initget 7)
(if (= cmd0 1)
(setvar "cmdecho" 1)
(princ)
(initget 7)
(initget 7 "Diametro")
78
Y a continuación le decimos al usuario que existe una opción llamada Diametro que puede seleccionar como respuesta:
De esta forma ya hemos añadido la opción Diametro, pero ¿Qué sucede si el usuario indica como respuesta la opción
Diametro? Pues que asignariamos a la variable rad la cadena de texto "Diametro" y en la siguiente línea (command
"_.circle" pto rad) al intentar dibujar el círculo, nos daría un error.Así que hay que modificar la rutina añadiendo a
continuación algo parecido a: Si el usuario selecciona la opción Diametro --> Preguntamos por el diámetro. Pues esto en
AutoLISP, sería:
Hay que tener especial cuidado con la condición del IF (= rad "Diametro") ya que hay que indicar la palabra clave tal y
como aparece en la lista de palabras clave de initget. Es decir, no funcionaría si ponemos (= rad "diametro") o (= rad
"Diámetro") ya que en el primer caso no ponemos la "D" en mayúsculas y en el segundo hemos tildado la "a".
Si el usuario indica una distancia la asigna a la variable rad y luego (if (= rad "Diametro")... devuelve nil, puesto que rad
es distinto de "Diametro".
Si el usuario indica D o Diam, o diametro, entonces asigna a la variable rad la cadena de texto "Diametro". Luego al
entrar en el IF, (= rad "Diametro") devuelve T así que evalúa la expresión si cumple (setq rad (getreal "\nDiámetro
interior: ")) que pide un diámetro y lo asignamos a la variable rad.
Pero rad viene de radio, porque en esta variable almacenamos el radio del círculo y no el diámetro. Así que al dibujar el
círculo (command "_.circle" pto rad) dibujaría un circulo del doble del diámetro de lo que ha dicho el usuario. El código
debería ser:
(initget 7 "Diametro")
79
Pues que asignaría a la variable rad el resultado de dividir -50 o 0 entre 2.0. Por tanto tendriamos un círculo con radio
negativo o cero. Recuerda que initget solo tiene efecto sobre la siguiente función de solicitud de datos, de modo que
tenemos que añadir de nuevo la función Initget antes de preguntar por el diámetro:
(initget 7 "Diametro")
(progn
(initget 7)
Se ha añadido la función Progn ya que sino, solo puedo ejecutar una expresión.
Para el radio o diámetro exterior se haría exactamente lo mismo. Por tanto el código completo sería:
(setvar "cmdecho" 0)
(initget 1)
(initget 7 "Diametro")
(progn
(initget 7)
80
(command "_.circle" pto rad)
(initget 7 "Diametro")
(progn
(initget 7)
(if (= cmd0 1)
(setvar "cmdecho" 1)
(princ)
(GETKWORD [mensaje])
Esta función de AutoLISP se utiliza para obtener una opción indicada por el usuario. Se utiliza en combinación con
INITGET, por ejemplo:
En este caso el usuario tan solo podrá indicar como respuesta una de las palabras clave de la función Initget y se la
asigna a la variable opc.
No hemos indicado el modo en Initget, tal solo las palabras clave. En la rutina ARAND nos interesaba que apareciera el
modo 7 para que no se indique como respuesta Intro, 0 o un número negativo. Pero no es obligatorio indicar siempre un
modo.
81
Un ejemplo bastante habitual en las rutinas es el siguiente:
(cond
(T
1. Haz esto
2. Ahora esto otro
3. ...
Luego vimos las estructuras condicionales IF y COND que ya nos permiten jugar un poco más y hacer que nuestros
programas no sean tan lineales. Ahora vamos a ver funciones que nos permitiran crear repeticiones de código y algo que
tal vez te suene, los bucles, que se utilizan mucho en programación.
La función while ejecuta las expresiones indicadas MIENTRAS se cumpla la condición, y devuelve el valor de la última
expresión evaluada. Por ejemplo:
(setq i 0)
(prompt "\n")
82
(setq i (1+ i)
Mientras i sea menor que 10, ejecuta las expresiones. Es decir, escribiría en la ventana de comandos de AutoCAD los
números del 0 al 9. Cuando (setq i (1+ i) asigna a la variable i el valor 10, la condición del bucle no se verifica, de modo
que termina el bucle. La función While devolverá el valor de la última expresión evaluada, es decir 10.
Este es un típico ejemplo de bucle, una estructura repetitiva con un índice o contador, en este caso i, que puede ir
aumentando o disminuyendo.
Fíjate que una de las expresiones que se ejecutan dentro del bucle es (setq i (1+ i) es decir, movemos el contador. Si no lo
hicieramos, i siempre sería menor que 10 y se entraría en un bucle sin fin, que da lugar a un error.
(setq i 10)
(prompt "\n")
(setq i (1+ i)
En este caso i tiene asignado el valor 10 antes de entrar en el bucle, de modo que la condición (< i 10) no se cumpliría y
por tanto no se ejecutarian las expresiones siguientes. While devuelve el valor de la última expresión evaluada, que en
este caso es la condición así que devuelve nil.
Las expresiones en While son opcionales, de modo que podemos crear un bucle en el que solo se indique la condición:
En este caso la condición es (not (setq pt (getpoint "\nPunto inicial: "))) es decir, pide un punto al usuario y lo almacena
en la variable pt.
Si el usuario indica un punto, pt = (X Y Z) que es distinto de nil, de modo que (not (setq pt (getpoint "\nPunto
inicial: "))) devolverá nil y saldrá de la función While.
Si el usuario indica Intro, getpoint devolverá nil y lo almacenará en la variable pt, de modo que (not (setq pt
(getpoint "\nPunto inicial: "))) devolvería T, y preguntaría de nuevo por un punto.
83
Otro ejemplo típico de bucles es en el que se utilizan algunas variables como flags (banderas o banderillas) para indicar
si algo está activado o desactivado o para controlar distintos valores. Veamos un ejemplo, supongamos que a y b son dos
variables que almacenan dos números reales:
(if (< a b)
(setq flag1 T)
(while flag1
(if (< a b)
En este caso:
Luego la función While evalúa la condición falg1 que devolvera su valor nil o T.
Si es flag1 = nil (a es menor que b) no evalúa las expresiones pues no se verifica la condición.
Si es flag1 = T (a NO es menor que b) se verifica la condición así que se ejecutan las expresiones. Primero nos
dice que a no es menor que b, nos vuelve a pedir el valor de b y comprueba si a es menor que el nuevo valor de
b en cuyo caso asigna a la banderilla flag1 el valor nil para salir del bucle.
En la función While, al igual que vimos con IF y COND, como condición podemos utilizar expresiones lógicas. Por
ejemplo:
En este ejemplo, el bucle se ejecutará hasta que se indique un valor para b positivo y mayor que a.
84
(REPEAT cantidad [expr1] [expr2])
La función repeat ejecuta las expresiones indicadas el número de veces que se indique en cantidad. Devuelve el resultado
de la última expresión evaluada.
(repeat Bart_Simpson (prompt "\nNo volveré a hacer pompas con ácido sulfúrico en clase"))
En este caso escribiría en la pizarra, es decir la ventana de comandos de AutoCAD, esa frase tantas veces como le
indiquemos.
También podemos obtener la cantidad por medio de una función de AutoLISP, como resultado de una operación:
(setq i 0)
(repeat (+ a b)
(propmt "\t")
¿Pero qué sucede si a o b son un número real y no un entero? ¿repetirá las expresiones 2.5 veces? Pues no, nos dará un
error. Por eso hay que estar bien seguro de que se la cantidad indicada es un número entero y no un real. Incluso si como
cantidad indicamos un número entero sin decimales, como 2.0, nos daría un error.
Vamos a modifuicar la rutina ARAND para hacer que el segundo radio sea mayor que el primero. El código
correspondiente al círculo exterior era el siguiente:
(initget 7 "Diametro")
(progn
(initget 7)
85
)
Lo primero que vamos a cambiar es el nombre de las variables. En lugar de utilizar la variable rad tanto para el radio
interior como para el exterio, vamos a utilizar la variable radi para el radio interior y la variable rade para el exterior. Así
podremos comparar si rade es mayor que radi.
(initget 7 "Diametro")
(progn
(initget 7)
La primera vez que se evalúa la condición del bucle, no se ha asignado aún ningún valor al radio exterior. De modo que
rade = nil y (not rade) devolverá T. (or (not rade) (not (< radi rade))) comprueba que al menos se verifique una de las dos
condiciones. Al verificarse la primera condición (not rade) la segunda ni siquiera se evalúa (por suerte puesto que al no
estar definida rade nos daría un error). La condición se verifica y ejecuta las expresiones que están a continuación, que
nos piden un valor para el radio exterior.
Es decir, mientras no se indique el radio exterior o este sea menor que el radio interior se ejecuta el bucle, que nos pide
un nuevo valor para el radio exterior. Al salir del bucle ya tenemos un radio exterior válido así que dibujamos el círculo
exterior. El código completo sería:
(setvar "cmdecho" 0)
(initget 1)
86
(setq pto (getpoint "\nCentro de la arandela: "))
(initget 7 "Diametro")
(progn
(initget 7)
(initget 7 "Diametro")
(progn
(initget 7)
(if (= cmd0 1)
(setvar "cmdecho" 1)
(princ)
87
TEMA 24: FUNCIONES PARA MANIPULAR CADENAS DE TEXTO
En este tema veremos las funciones que incorpora AutoLISP para manipular cadenas de texto.
(CHR entero)
Esta función devuelve el caracter al que le corresponde el código ASCII inidicado. Por ejemplo:
(ASCII texto)
Devuelve el código ASCII (un número entero) correspondiente al primer caracter de la cadena de texto indicada.
(ascii "A") también devuelve 65 porque lo único que importa es el primer caracter de la cadena de texto.
Esta función devuelve la longitud (número de caracteres) de la cadena de texto que recibe como argumento.
También podemos asignar una cadena de texto a una variable y comprobar después su longitud con strlen:
(strlen txt1)
Si se indica el argumento opcional [modo] con un valor distinto de nil, entonces devuelve el texto en minúsculas.
88
(STRCAT texto1 [texto2] ...)
Para que las dos palabras anteriores aparezcan separadas se pueden hacer dos cosas:
Añadir el espacio como otra cadena de texto: (strcat "Hola" " " "Mundo")
Esta función se utiliza mucho para concatenar los mensajes que se muestran al usuario. Por ejemplo:
(setq i 10)
Esta función devuelve parte de la cadena de texto que recibe como argumento, a partir de la posición inicial que se
indique en adelante.
(substr "AutoLISP" 5) devuelve el texto a partir del quinto caracter, es decir "LISP".
(substr "AutoLISP" 15) devuelve "" es decir, una cadena de texto vacía puesto que el texto indicado tiene menos de 15
caracteres.
También se puede indicar la longitud, es decir el número de caracteres que se desean obtener.
(substr "AutoLISP" 5 2) devuelve "LI". A partir del quinto caracter devuelve 2 caracteres.
(substr "AutoLISP" 5 10) devolverá "LISP", porque tan solo tenemos 4 caracteres más, a partir del quinto, y no diez.
Esta función se utiliza para comparar si una cadena de texto verifica o cumple un patrón, en caso de que lo verifique
devuelve T y si no lo verifica devuelve nil.
(wcmatch "AutoLISP" "A*") devolverá T ya que comprueba si "AutoLISP" comienza por "A".
(wcmatch "AutoLISP" "a*") devolverá nil, puesto que "A" es distinto de "a".
Los patrones son cadenas de texto en las que se pueden emplear determinados símbolos comodín:
# Equivale a un dígito.
89
* Equivale a una cadena de caracteres.
~ Equivale a una negación. Por ejemplo (wcmatch "AutoLISP" "~B*") comprueba que el texto no empieza por "B".
[...] Nos permite indicar varios caracteres. Por ejemplo (wcmatch "AutoLISP" "[AB]*") comprueba si el texto comienza
por "A" o por "B".
También podríamos indicar un rango: (wcmatch "AutoLISP" "[A-F]*") comprueba si el texto comienza por "A", "B",
"C", ... hasta la "F".
Incluso podemos comprobar con dos patrones distintos, separandolos por una coma: (wcmatch "AutoLISP" "A*,*LISP")
devolvería T.
¿Pero cómo hariamos entonces para saber si un texto tiene una coma? no podemos hacer los siguiente: (wcmatch "Curso,
de AutoLISP" "*,*") ya que en este caso le estamos indicando dos patrones, como en el ejemplo anterior. Tendremos que
hacerlo así: (wcmatch "Curso, de AutoLISP" "*',*") al anteponer el apóstrofe ' delante de uno de los caracteres
comodines de los patrones, le estamos diciendo que queremos usar el literal, es decir, lo que ponemos a continuación tal
cual está.
Pues tema visto. Así que pasamos ahora a Modificar CIRCPERI. Teníamos el siguiente código:
(setvar "cmdecho" 0)
(initget 1)
(progn
(initget 7)
(progn
(initget 6)
90
)
(if (= cmd0 1)
(setvar "cmdecho" 1)
(princ)
Podriamos concatenar las cadenas de texto y ejecutar una única vez la función prompt:
(prompt (strcat "\nPerímetro <" (rtos (* (getvar "circlerad") 2 pi) 2 2) ">: "))
Vamos ahora a crear un nuevo comando llamado CIRCOLOM que nos permitirá dibujar múltiples círculos concéntricos.
Y comenzaremos como siempre por el pseudocódigo. Podría ser algo así:
91
Al escribir el código, lo primero es definir el nuevo comando:
(defun C:CIRCULOM ( / )
De momento no ponemos las variables locales, porque aún no tenemos ni variables ni nada. Las añadiremos al terminar
la rutina.
Tal vez pienses... ¿no deberiamos ponerle antes un INITGET? Pues seguramente, pero no nos paremos ahora con eso.
Primero a ver si hacemos que funcione la rutina, después ya nos preocuparemos de que funcione bien.
(while (setq rad (getdist pto "\nRadio [Intro para terminar]: "))
Pide puntos o distancias, para usarlas como radio, hasta que se pulse Intro. Fijate que no he cerrado el while, porque aún
tenemos que añadir las expresiones que van dentro.
nos faltan dos cosas para terminar: El paréntesis de cierre de While y el de la función. El código completo queda así:
(defun C:CIRCULOM ( / )
(while (setq rad (getdist pto "\nRadio [Intro para terminar]: "))
Ahora es cuando vamos a introducir las mejoras y a retocar el código. Vamos a comenzar limitando las posibles
respuestas de los usuarios con Initget:
(defun C:CIRCULOM ( / )
(initget 1)
(initget 6)
(while (setq rad (getdist pto "\nRadio [Intro para terminar]: "))
92
(initget 6)
Añadimos (initget 1) antes de la solicitud Getpoint, para que el usuario no pueda indicar Intro como respuesta.
Y añadimos (initget 6) antes de getdist para que no permita ni cero ni un número negativo.
Observaras que (initget 6) se vuelve a poner otra vez dentro de While. Si no lo hicieramos, al indicar el primer radio no
permitiría responder con cero ni con un número negativo. Sin embargo, para el segundo radio y todos los siguientes
tenemos si nos dejaría. Para que no lo permita, hay que incluir la función Initget antes de que se ejecute de nuevo
Getdist, de modo que se añade dentro de While.
(setvar "cmdecho" 0)
(initget 1)
(initget 6)
(while (setq rad (getdist pto "\nRadio [Intro para terminar]: "))
(initget 6)
(if (= cmd0 1)
(setvar "cmdecho" 1)
93
(princ)
Esta función devuelve el ángulo formado por la línea que va desde pt1 hasta pt2. El origen de angulos será el eje X y el
sentido antihorario se considera positivo. Por ejemplo:
Hay que fijarse en cual es el punto que se indica primero en la función angle, porque si hacemos:
Obtendremos un ángulo distinto, el anterior más pi o menos pi. Por lo tanto: No es lo mismo el ángulo de pt1 a pt2 que el
ángulo de pt2 a pt1.
Si los puntos están en 3D, los proyecta sobre el plano XY y devuelve el ángulo formado por sus proyecciones
Devuelve la distancia en 3D entre los puntos pt1 y pt2. Si uno de los puntos está en 2D, es decir no tiene coordenada Z,
se ignora la coordenada Z del otro punto devolviendo la distancia en 2D.
En este caso, (setq dist21 (distance pt2 pt1)) sería igual a dist12. Ya que la distancia del punto pt1 al pt2 es igual que la
distancia del punto pt2 al pt1.
Esta función se utiliza para obtener un punto por medio de coordenadas polares a partir de un punto base. Por ejemplo:
94
Esto asignaría a la variable pt2 un punto que está a 50 unidades en la dirección del eje X a partir del punto base pt1. Si
hicieramos:
Hay que observar que en (polar pt1 0.0 50.0) se indica primero el punto base, luego un ángulo en radianes y por último
una distancia. De modo que:
Devolverá un punto que está a 50 unidades en la dirección del eje Y a partir del punto base pt1. Ya que en este caso se ha
indicado un ángulo de (/ pi 2.0) es deirc, 90º.
(LAST lista)
Esta función devuelve el último elemento de la lista que recibe como argumento. De modo que si hacemos:
En la variable z almacenamos la coordenada Z del punto pto, siempre que el punto esté en 3D porque si está en 2D
almacenaría la coordenada Y. Los puntos son listas del tipo (10.0 20.0 0.0). El ejemplo anterior no sería muy útil para
obtener la coordenada Y, ya que si indicamos un punto en 3D devolverá la coordenada Z. Entonces, ¿cómo podemos
obtener la coordenada Y? ya vimos un método:
(CADR pto)
Pero supongamos que tenemos una lista de 27 elementos, y queremos obtener el elemento 22. ¿Cómo lo haríamos? No
podemos utilizar (cADDD.....DDDr pto) ya que solo nos permite agrupar 4 funciones cAr y cDr. De modo que
tendriamos que hacer (cADDD (cDDDD (cDDDDr ..... pto). Pero hacerlo así bastante engorroso.
(nth 2 pto) devolverá la coordenada Y, no? Pues no!. Los elementos de una lista se empiezan a numerar desde cero. Por
ejemplo:
(nth 2 pto) devolverá la coordenada Z del punto pto, si existe y si no existe devolverá nil.
95
Supongamos que tenemos una lista como la siguiente:
Con esta función, si el elemento indicado pertenece a la lista devolverá la lista a partir de ese elemento. Por ejemplo:
Esta función reemplaza un elemento de una lista por su nuevo valor. Por ejemplo, para reemplazar "perros" por
"naranjas":
(subst "naranjas" "perros" lst) devuelve la lista con el elemento que se ha modificado ("peras" "melones" "sandias"
"naranjas" "platanos"). Pero la variable lst sigue almacenando la lista anterior ("peras" "melones" "sandias" "perros"
"platanos"). Para modificar la lista almacenada en la variable lst tenemos que asignarle el valor devuelto por SUBST:
(setq lst (list (car lst) (cadr lst) (caddr lst) "limones"))
Esta función añade un elemento a la lista indicada. El nuevo elemento se sitúa en el primer lugar. Es decir:
Y al igual que sucedía con SUBST, si solo hacemos (CONS "limones" lst) no estamos asignando la lista resultante a la
variable lst. Deberiamos hacerlo así:
Pero... y si lo que queremos es añadir un elemento al final de la lista? Antes de ver como sería, veamos otra función:
96
(REVERSE lista)
Veamos como funciona la línea de código anterior: (reverse lst) devuelve la lista en orden inverso, (cons "platanos"
(reverse lst)) añade "platanos" como primer elemento de la lista invertida, y por último volvemos a invertir el orden de la
lista: (setq lst (reverse (cons "platanos" (reverse lst))))
(ACAD_STRLSORT lista)
Esta función devuelve una lista con sus elementos, que deberan ser cadenas de texto, ordenados alfabéticamente. Por
ejemplo:
Los pares punteados son simplemente listas de dos elementos separadas por un punto. Por ejemplo: (0 . 27)
1. Aunque el punto nos pueda engañar, tienen solo dos elementos. En este caso 0 y 27.
2. Podemos obtener el primer elemento, como en cualquier lista, mediante (car par_punteado). Pero, si intentamos
obtener el segundo elemento mediante (cadr par_punteado) nos indicará "error: tipo de argumento erróneo:"
Para obtener el segundo elemento utilizaremos CDR en lugar de CADR (cdr par_punteado). Ojo! (cdr
par_punteado) devolverá 27 y no (27).
¿Para qué se usan los pares punteados? Pues principalmente para crear listas de asociaciones. ¿Y qué es una lista de
asociaciones? Pues una lista cuyos elementos son pares punteados. Por ejemplo: (("perro" . 0) ("gato" . 1) ("ratón" . 5))
Al primer elemento de los pares punteados le denominaremos código, y al segundo elemento valor. En el ejemplo
anterior "perro", "gato" y "ratón" son los códigos y 0, 1 y 5 sus respectivos valores.
En una lista de asociaciones, generalmente no existen códigos duplicados. Un ejemplo de lista de asociación con un
código duplicado sería:
97
(("perro" . 0) ("gato" . 1) ("ratón" . 5) ("gato" . 7))
¿Cómo se crear los pares punteados? Con la función que ya hemos visto, CONS. Antes se explico (CONS elemento lista)
que nos permite añadir un elemento nuevo como primer elemento de la lista indicada. Si en lugar de una lista se indica
otro elemento creamos un par punteado.
(setq lst (list (cons "perro" 0) (cons "gato" 1) (cons "ratón" 5)))
Por último veamos una función que utilizaremos mucho, sobre todo al trabajar con las entidades de AutoCAD...
ASSOC nos servirá para obtener el par punteado, cuyo código se indique, de la lista de asociaciones que recibe como
argumento. Por ejemplo:
Si lo que queremos obtener es el valor asociado a ese código, es decir de ("gato" . 1) queremos obtener el 1. Entonces,
podemos hacer lo siguiente:
Pero, ¿Por qué son tan importantes las listas de asociaciones y los pares punteados? Por lo siguiente:
((-1 . <Nombre de entidad: 19e0958>) (0 . "LINE") (330 . <Nombre de entidad: 19e08f8>) (5 . "2B") (100 .
"AcDbEntity") (67 . 0) (410 . "Model") (8 . "0") (100 . "AcDbLine") (10 0.0 0.0 0.0) (11 100.0 100.0 0.0) (210 0.0 0.0
1.0))
Esta lista de asociaciones, compuesta por pares punteados, es la lista de asociaciones de una entidad linea que se
almacena en la base de datos de AutoCAD.
En dicha lista de asociaciones, el código 0 nos indica el tipo de entidad. De modo que:
El código 8 nos indica la capa en la que está la entidad, en este caso en la capa 0. El código 10 indica el punto inicial de
la línea y el 11 el punto final. Por eso son tan importantes las listas de asociaciones y los pares punteados.
98
TEMA 27: APLICAR FUNCIONES A LOS ELEMENTOS DE LAS LISTAS
Supongamos que tenemos una lista de puntos:
(setq lstptos (list (list 0.0 0.0) (list 10.0 0.0) (list 10.0 10.0) (list 0.0 10.0)))
Es decir, lstptos es una lista con cuatro elementos correspondientes a los vértices de un cuadrado de lado 10.
Supongamos que queremos dibujar un círculo en cada uno de los puntos de lstptos. Hasta ahora podiamos hacerlo así:
La variable i nos servirá como índice o contador en el bucle While, por eso le asignamos inicialmente el valor 0, porque
los elementos de una lista se numeran empezando desde cero. Es bastante habitual que los nombres de las variables
utilizadas como contadores sean i j k ... También asignamos a nent el número de elementos de la lista lstptos, porque sino
no sabremos como salir del bucle ni hasta que elemento llegar.
El bucle se ejecutará mientras el contador sea menor que nent es decir, se ejecutará para cada elemento de lstptos. (setq
pt (nth i lstptos)) se obtiene el elemento número i de lstptos, que es un punto, y se dibuja un círculo de radio 1.0 en él.
Finalmente aumentamos el contador para que pase al siguiente punto (setq i (1+ i)). Esta última línea es muy importante,
si estuviera i sería siempre igual a cero y el bucle no tendrá fin. Por lo que dibujaría infinitos círculos en el primer punto
de lstptos y da un error.
Veamos otro método para dibujar los círculos en los vértices de lstptos:
(while lstptos
En este caso, el bucle se ejecutará mientras tengamos puntos en la lista es decir, mientras lstptos sea distinto de nil. Se
define pt como el primer punto de la lista lstptos, se dibuja un círculo de radio 1.0 con centro en pt y por último se mueve
el contador. Pero, ¿qué contador? Si no existe! Pues, (setq lstptos (cdr lstptos)) eliminamos el primer elemento de la lista
lstptos. De modo que lstptos va perdiendo un elemento (punto) cada vez que se ejecuta el bucle. Llega un momento en el
que lstptos = ((0.0 10.0)) y al evaluarse (setq lstptos (cdr lstptos)) se asignará a lstptos el valor devuelto por CDR, es
decir nil. Con lo cual deja de ejecutarse el bucle.
El inconveniente de este segundo método es que se varía lstptos, que finalmente valdrá nil. Por lo que la lista de puntos
inicial se perderá. Por eso solo se utilizará este segundo método si no importa perder la lista lstptos.
99
Bien, pero tenemos algunos métodos que son mejores, y eso es lo que vamos a ver en este tema.
Esta función te permite aplicar una expresión para cada elemento de la lista que recibe como argumento. La variable
simboliza a un elemento de dicha list, por lo que se suele utilizar en la expresión. Por ejemplo:
La línea de código anterio representa el tercer método para dibujar los círculos en los puntos de lstptos. A la variable la
llamamos p y la expresión a ejecutar para cada elemento de la lista es: (command "_.circle" p 1.0) dónde p es un
elemento de la lista.
Con lo cual, escribiría todos los textos en la ventana de comandos de AutoCAD. Se ha utilizado el mismo nombre de
variable (p), pero podria haberse cambiado:
Inicialmente definimos la variable total como 0.0, el bucle se ejecutará para cada elemento de lst y se define num como
el número que está en la posición i sumamos a total ese número y movemos el contador. Cuando se entra por primera vez
en el bucle, num = 5.0 de modo que total = 0.0 + 5.0. Al volver a entrar en el bucle num = -1.5 de modo que total = 5.0 +
(-1.5) y así hasta que se llega al último elemento de lst.
100
También podria hacerse sin emplear contadores, como hicimos antes. Y también podemos hacerlo con FOREACH,
veamoslo:
Todo lo que antes poniamos en un bucle se pone ahora en una sola línea de codigo.
Aplica la función que recibe como argumento a todos los elementos de la lista. El nombre de la función a utilizar en
APPLY se indicará precedida por el símbolo ' para que AutoCAD interprete que le estamos pasando ese texto tal cual. El
ejemplo anterior sería:
En (apply '+ lst) estamos aplicando la función + a todos los elemento de lst, equivale a (+ 5.0 -1.5 2.6 3.8 9.7), y el
resultado se lo asignamos a la variable total.
Digamos que '+ es el nombre que utilizamos en AutoLISP para hacer referencia a la función +, ya que + es la función
propiamente y no su nombre. Prueba lo siguiente en la ventana de comando de AutoCAD:
(list "pepe" + 5.0) y fijate en lo que devuelve ("pepe" #<SUBR @0244f5a8 +> 5.0)
Y ahora prueba con el literal, es decir con el caracter ' delante del +
¿Entiendes ahora porque ponemos '+? Para que no evalue la función, ya que devolveria #<SUBR @0244f5a8 y nos daría
un error.
101
(MAPCAR nombre_función lista1 [lista2]...)
Esta función aplica la función de AutoLISP que recibe como argumento a los elementos de las listas. Como así no se va a
entender... ejemplos:
Vamos a determinar el vector con origen en pt1 y final en pt2. Tenemos que calcular las coordenadas X Y Z del vector,
que son el resultado de restar las respectivas coordenadas X Y Z de pt1 a las de pt2. Podemos obtener el vector así:
(setq vector
Siendo la coordenada X del vector: (- (car pt2) (car pt1)) la Y (- (cadr pt2) (cadr pt1)) y la Z (- (caddr pt2) (caddr pt1))
La función que recibe mapcar es - y lo que hace aplicarla a los elementos de pt2 y pt1, de modo que resta sus
coordenadas X, las Y y las Z.
Vamos a complicar un poco el tema... si quisieramos obtener el módulo de un vector tendriamos que calcular la raíz
cuadrada de la suma de sus coordenadas al cuadrado.
(mapcar '* vector vector) devolverá una lista con las coordenadas del vector al cuadrado, ya que las estamos
multiplicando por si mismas.
102
Veamos otro ejemplo en el que se combinan APPLY y MAPCAR. Sean v1 y v2 dos vectores vamos a calcular su
producto escalar.
El producto escalar de los vectores v1 y v2 es la suma de los productos de las coordenadas de v1 por las de v2.
Podriamos hacerlo así:
(setq pescalar (+
Pero resulta más sencillo utilizando MAPCAR y APPLY. Primero multiplicamos las coordenadas de ambos vectores:
Tal vez el formato de la función LAMBDA recuerde algo a DEFUN. LAMBDA también se utiliza para definir una
función, pero a diferencia de DEFUN la función no se almacena en ningún lugar, en este caso es temporal. Por tanto solo
se puede ejecutar donde se defina. Además la función creada no tiene nombre, por lo que tampoco podriamos llamarla
desde otra parte de nuestro código.
Utilizarla sola sin APPLY o MAPCAR no tiene sentido. Supongamos que queremos obtener el punto medio de dos
puntos...
Las coordenadas del punto medio serán las coordenadas de pt1 más las de pt2 divididas por 2.0. Para sumar sus
coordenadas...
Bien... pero ahora, ¿Cómo las dividimos por 2.0? Podemos hacer lo siguiente:
(mapcar '/ (mapcar '+ pt1 pt2) (list 2.0 2.0 2.0))
103
Donde creamos una lista (list 2.0 2.0 2.0) y dividimos los elementos de (mapcar '+ pt1 pt2) entre los elementos de la lista
anterior (2.0 2.0 2.0).
Recordemos... (mapcar '+ pt1 pt2) devuelve la suma de las coordenadas de pt1 y pt2. Ahora necesitariamos dividila por
2.0, pues podemos crear una función que reciba un número y lo divida por 2.0 devolviendo el resultado:
Y ahora pasarle a MAPCAR la nueva función 2/ para que divida cada elemento de (mapcar '+ pt1 pt2) por 2.0
Pero también podemos hacerlo de otra forma, utilizando la función LAMBDA en lugar de DEFUN. Primero veamos
como sería nuestra función definida mediante LAMDA:
Es muy parecido a lo que hicimos con DEFUN. Pero la función definida mediante LAMBDA no tiene nombre y no se
almacena en ningún sitio, es temporal, de modo que no podemos llamarla. ¿Dónde debemos utilizar LAMBDA? Pues
directamente en MAPCAR, donde hay que indicar el nombre de la función:
Por tanto, cuando queremos aplicar APPLY o MAPCAR a una o varias listas y ejecutar una función que no existe en
AutoLISP, podemos crearla previamente con DEFUN o utilizar la función LAMBDA para definirla in situ.
(VER)
Esta función devuelve una cadena de texto con la versión de AutoLISP que se está ejecutando. Por ejemplo: "Visual LISP
2000 (es)". Entre paréntesis indica la versión idiomática, en este caso Español.
(TEXTSCR)
Se utiliza para pasar a pantalla de texto y siempre devuelve nil. Se suele emplear cuando se quiere mostrar mucha
información en pantalla de texto.
(GRAPHSCR)
Pasa a pantalla gráfica y también devuelve nil. Se utiliza para asegurarnos que el usuario está viendo la pantalla gráfica,
por ejemplo para indicar un punto. Especialmente se utilizará si antes se ha pasado a pantalla de texto.
(TEXTPAGE)
Esta función es análoga a TEXTSCR. Pasa a pantalla de texto y también devuelve nil.
Tal vez estes preguntandote... ¿Cuántas funciones nos quedan aún? Pues entre otras cosas, la siguiente función nos
servirá para ver las funciones de AutoLISP que hemos visto y las que nos quedan.
104
(ATOMS-FAMILY formato [lista_simbolos])
Esta función devuelve una lista con los símbolos que se han definido en el dibujo actual. ¿Qué es un símbolo? Pues un
nombre de variable de AutoLISP, el nombre de una función de usuario que hallamos creado y también todos los nombres
de las funciones propias de AutoLISP.
0 para que devuelva una lista con los nombres de los símbolos.
1 para que devuelva una lista, pero siendo sus elementos cadenas de texto.
Veamos ahora algún ejemplo. Al escribir la línea siguiente sabras cuantas funciones faltan...
(atoms-family 0) esto mostrará una lista con los nombres de todos los símbolos definidos en el dibujo actual.
En las listas anteriores es dificil encontrar algo. ¿Recuerdas la función acad_strlsort? Permitía organizar alfabéticamente
una lista de cadenas de texto.
Estan todas las funciones de AutoLISP, pero hay otras muchas funciones que aparecen en la lista y no son funciones de
AutoLISP. Así que no te asustes, que no son tantas.
Recordemos el formato de esta función: (ATOMS-FAMILY formato [lista_simbolos]) y veamos que es eso de la lista de
símbolos...
Para saber si unas funciones determinadas existen, es decir si están definidas, creamos una lista con sus nombres y se lo
pasamos como argumento a atoms-family
(atoms-family 1 (list "car" "cdr")) devolverá una lista con sus nombres ("CAR" "CDR")
Aunque lo habitual no es emplear esta función para detectar si están definidas las funciones de AutoLISP, sino para
detectar nuestras propias funciones y variables:
(atoms-family 1 (list "car" "cdr" "variable")) devuelve ("CAR" "CDR" nil) ya que el símbolo "variable" no tiene
asociado ninguna función ni variable de AutoLISP.
Si definimos:
(atoms-family 1 (list "car" "cdr" "variable")) devolverá ("CAR" "CDR" "VARIABLE") devuelve el nombre de la
variable, no su valor.
105
(QUOTE expresión)
Esta función recibe una expresión y devuelve su literal, es decir devuelve la expresión tal cual, sin evaluar.
(quote +) devolverá +
Pero no podremos escribir esta última línea en la ventana de comandos de AutoCAD, por que el interprete de comandos
no detecta el paréntesis en primer lugar y piensa que no es una expresión de AutoLISP sino un comando de AutoCAD.
Podemos utilizar un truco para evaluarla desde la ventana de comandos:
PROGN en realidad no hace nada, simplemente nos servía para salvar la limitación de IF de indicar más de 1 expresión,
ya que evalúa las expresiones que contiene y devuelve el resultado de la última expresión evaluada. Ahora si devolverá
(SETQ A "pepe" B 10.0). Pero no hemos asignado valores a las variables, ya que (SETQ A "pepe" B 10.0) no se ha
evaluado.
(quote (15.0 10.6 9.2)) devolverá (15.0 10.6 9.2) es decir, devuelve una lista. También funcionaría con '(15.0 10.6 9.2)
podemos poner:
y también:
De modo que podemos usar QUOTE y ' para crear listas. Pero con excepciones... si hacemos:
(setq a 2.0)
(apply '+ '(a 3.5 6.8)) indicará: ; error: tipo de argumento erróneo: numberp: A
a es el nombre de una variable, es un símbolo, cuya variable tiene asociado el valor 2.0
106
(apply '+ (list a 3.5 6.8)) esto si funcionará porque (list a 3.5 6.8) devolverá (2.0 3.5 6.8), LIST se evalúa pero QUOTE
no evalua la expresión que recibe.
Resumiendo: Podemos crear listas con QUOTE o con ' pero siempre que conozcamos los elementos de dichas listas, y
que estos sean valores concretos no determinados a partir de expresiones o almacenados en variables.
(foreach p '((0.0 0.0 0.0) (10.0 10.0 0.0)) (command "_.circle" p 1.0))
(setq vector (mapcar '- '(10.0 10.0 0.0) '(5.0 0.0 0.0)))
(setq ptomed (mapcar '(lambda ( num ) (/ num 2.0)) (mapcar '+ '(10.0 10.0 0.0) '(5.0 0.0 0.0))))
(EVAL expresión)
(eval "Soy una cadena de texto") analogamente devolverá "Soy una cadena de texto"
(setq a 15.5)
¿Qué valor tendrá asignado b? (quote a) es el nombre de la variable a, es decir el símbolo. Así que b tendrá asociado el
nombre de la variable a.
(eval '(+ 10.0 5.5)) devolverá 15.5 ya que '(+ 10.0 5.5) devuelve (+ 10.0 5.5) y eval, lo evalúa.
(READ texto)
(read "(15.2 9.3 15.5)") en este caso devolverá (15.2 9.3 15.5) porque al detectar el paréntesis lo considera un mismo
término. Es algo similar a escribir en la ventana de comandos de AutoCAD, si no se pone un paréntesis delante no dejará
escribir espacios en blanco. Pues este es otro método para crear una lista, por tanto:
107
(apply 'max (read "(15.2 9.3 15.5)")) devolverá el máximo de la lista de números indicados
¿Para qué sirve esto? Para solicitarle al usuario una expresión de AutoLISP y evaluarla:
En este caso asignamos a valor el resultado de evaluar una expresión de AutoLISP introducida por el usuario.
Esta función es muy parecida a SETQ, se diferencia en que espera el literal de un símbolo. Veamoslo con ejemplos:
Si probamos:
(setq (read "num1") 5.0) nos da un error, ya que SETQ no acepta una expresión como símbolo.
El archivo ACAD.LSP se carga al iniciar AutoCAD y el ACADDOC.LSP se carga siempre que se abre un dibujo, o se
crea un dibujo nuevo. De modo que las funciones cotenidas en el ACAD.LSP tan solo estarán cargadas en el dibujo que
se abre al iniciar AutoCAD, mientras que las funciones contenidas en el ACADDOC.LSP estarán cargadas en todos los
dibujos.
El ACAD.LSP se utilizará tan solo para almacenar aquellas funciones que nos interese ejecutar al cargar AutoCAD. Por
ejemplo, podemos utilizar el archivo ACAD.LSP para mostrar una imagen, foto, al iniciar AutoCAD.
108
Si tenemos alguna rutina que se utilice mucho, o que se suele ejecutar en todos los dibujos la meteremos en el
ACADDOC.LSP
¿Qué riutinas meteremos en el ACADDOC.LSP? Conviene que no sean demasiadas, para que dicho archivo sea más
manejable y además para no ocupar demasiado espacio en la memoria del ordenador. Por tanto, solo incluiremos en el
ACADDOC.LSP las rutinas más empleadas. Por ejemplo una función de tratamiento de errores, o las funciones GAR y
RAG que se utilizan bastante, y además son bastante pequeñas.
¿Qué pasa si metemos también nuestra función C:CIRCPERI? Pues no pasaría nada pero... ¿Vas a dibujar círculos dado
su perímetro en "todos" los dibujos? no creo, por eso no merece la pena añadirla al ACADDOC.LSP, ya que si hacemos
lo mismo con todas nuestras rutinas... el archivo ACADDOC.LSP tendria un tamaño descomunal.
En cambio, podemos guargar la rutina CIRCPERI en un archivo independiente y cargarlo desde el ACADDOC.LSP. En
el caso de la rutina CIRCPER, podemos guardarla en el archivo CIRCPERI.LSP, dentro de uno de los directorios de
soporte de AutoCAD, e incluir la siguiente línea en el ACADDOC.LSP:
De este modo tenemos la rutina CIRCPERI en un archivo independiente, lo que nos soluciona parte del problema, ya que
tendremos el archivo ACADDOC.LSP será bastante corto y si queremos modificar la rutina CIRCPERI tan solo tenemos
que manipular un archivo en el que únicamente está definida dicha función.
Pero seguimos teniendo algunos problemas... Si nuestra colección de rutinas es muy extensa tenemos que añadir una
línea de código como la anterior para cada rutina, lo que se traducirá en un archivo ACADDOC.LSP bastante grande y
dificil de manipular. Además, el mayor inconveniente es que todas nuestras rutinas se cargarian en memoria
automáticamente para cada archivo de dibujo, con lo que estaremos sobrecargando la memoria del ordenador.
La función AUTOLOAD es similar a LOAD, también nos permite cargar archivos de AutoLISP pero solo aquellos
archivos que contengan comandos, por ejemplo C:CIRCPERI pero no servirá para archivos en los que solo tengamos
funciones como GAR y RAG.
¿Qué ventaja tiene el utilizar AUTOLOAD en lugar de LOAD? Al utilizar AUTOLOAD los archivos no se cargan
automáticamente, simplemente se predispone a AutoCAD para que los cargue en cuanto se ejecute uno de los comandos
indicados como argumentos en la lista de comandos.
Al iniciar un dibujo, se carga el ACADDOC.LSP y se evalúa la línea anterior, pero archivo.lsp no se cargará hasta que se
ejecute el comando1 o el comando2. Sin emargo no se cargará si ejecutamos el comando3, ya que no lo hemos incluido
en la lista de comandos. Al ejecutar uno de los dos comandos anteriores se carga el archivo, de modo que la función
nosoyuncomando también se cargará, al igual que el comando3.
109
¿Y qué pasa con los archivos de AutoLISP que no tienen comandos, es decir en los que solo hay funciones? Pues
tendremos que cargarlos con LOAD, con lo cual se cargarian todos directamente en la memoria y si tenemos bastantes
funciones seguimos teniendo el mismo problema que antes.
¿Cómo podemos solucionarlo? Pues cargando las funciones solo cuando hacen falta. Por ejemplo, supongamos que en el
comando C:CIRPERI se utiliza la función RAG... tenemos dos opciones:
Incluir la función RAG en el archivo Circperi.lsp de este modo al cargarse el archivo se cargará la función RAG. Esto se
suele hacer para las funciones que solo se utilizan en un comando determinado. Pero si la función RAG deseamos
utilizarla en otros comandos, es mejor utilizar el siguiente método...
Incluir la función RAG en un archivo independiente Rag.lsp y añadir la siguiente línea de código dentro del archivo
Circperi.lsp:
De este modo, el archivo Circperi.lsp se cargará cuando se ejecute uno de los comandos indicados en la función
Autoload del archivo ACADDOC.LSP. Y al cargarse el archivo Circperi.lsp se cargará mediante la función Load el
archivo Rag.lsp. Utilizando este método podemos utilizar una misma función en multiples rutinas, tan solo debemos
asegurarnos de que dicha función esté definida, es decir que se halla cargado el archivo en el que se encuentre.
Los archivos ACAD.LSP y ACADDOC.LSP no tienen porque existir. Si no existen pueden crearse, teniendo en cuenta
que se deben guardar en uno de los directorios de soporte de AutoCAD. En caso de que ya existan, se pueden editar para
incluir el código deseado. Conviene hacer una copia de seguridad de estos archivos porque muchos programadores crean
sus propios archivos ACAD.LSP y ACADDOC.LSP de modo que al instalar un módulo o aplicación para AutoCAD,
podeis sobreescribir vuestros archivos.
Existen otros dos métodos para cargar automáticamente rutinas de AutoLISP. El primero consiste en utilizar la opción
disponible en el menú de AutoCAD "Herr --> AutoLISP --> Cargar" ya que en el letrero de dialogo que aparece se
pueden cargar rutinas y se pueden seleccionar las rutinas que se desean cargan al inicio.
El tercer método para cargar automáticamente las rutinas de AutoLISP es editando los menús de AutoCAD, pero esto lo
veremos más adelante...
Para ejecutarla, tan solo debemos añadir en dicho archivo la siguiente línea de código:
(GAR)
(GAR 3.141592)
Una vez cargado el comando, para ejecutarlo desde la ventana de comandos de AutoCAD podemos escribir directamente
su nombre CIRCPERI para ejecutarlo. Sin embargo, desde un archivo de AutoLISP no podemos ejecutarlo así. Tenemos
que hacerlo de esta otra forma:
110
(C:CIRCPERI)
111
De modo que podriamos incluir en el archivo ACADDOC.LSP las dos siguientes líneas de código:
¿Sería mejor utilizar AUTOLOAD en lugar que LOAD en el ejemplo anterior? Pues no. Utilizar AUTOLOAD no tendría
mucho sentido, puesto que al ejecutar el comando el archivo en el que esté definido se cargaría, por tanto podemos
cargarlo ya antes.
Existe otro método para ejecutar código directamente. Se trata de escribirlo directamente, es decir sin meterlo dentro de
ninguna función o comando. Por ejemplo, si incluimos las siguientes líneas de código en el archivo ACADDOC.LSP nos
permitirá dibujar un circulo dado su centro y su perímetro:
Pero en este caso, el código anterior solo se ejecuta una vez. Si queremos dibujar otro círculo dado su perímetro, no
tendriamos la función CIRCPERI definida.
Además de mensajes de bienvenida podeis hacer otras cosas más útiles, como asignar valores iniciales a algunas
variables:
(setvar "osmode" 7)
S::STARTUP
Existe otro método para ejecutar código automáticamente. Hemos visto que AutoCAD utiliza una función de tratamiento
de errores por defecto que se llamaba *error* y también hemos visto que podiamos redefinirla creando nuesta propia
función de tratamiento de errores. Bien, pues AutoCAD también tiene una función interna que se ejecuta
automáticamente se trata de S::STARTUP.
El que las funciones *error* y S::STARTUP tengan estos nombres tan raros es para evitar que a alguien se le ocurra
denominar así a alguna función de otro tipo.
112
Para definir la función S::STARTUP se utiliza DEFUN, al igual que para cualquier otra función.
(defun S::STARTUP ( / )
La función S::STARTUP podemos utilizarla para los mismos ejemplos que se daban antes.
Por último, para terminar el tema, un consejo... Al programar hay que tener bastante cuidado para no cometer errores, ya
que el omitir un simple paréntesis o unas comillas, por ejemplo, modificaran totalmente nuestras funciones. Cuando
además estas funciones se van a ejecutar automáticamente, como en los ejemplos que se han expuesto, la precaución al
programar debe ser máxima.
CARGALISP recibirá una lista como la siguiente: (("c:\\rutinas\\lisp1.lsp" ("gar" "rag")) ("c:\\rutinas\\lisp2.lsp"
("inverso" "tg")))
En la que cada elemento es a su vez otra lista formada por el nombre de un archivo de AutoLISP (con su ruta, si es
necesario) y una lista con los nombres de las funciones (o variables globales) definidas en dicho archivo. Por ejemplo, en
el caso anterior se supone que en el archivo "c:\\rutinas\\lisp1.lsp" están definidas las funciones "gar" y "rag" y que en el
archivo "c:\\rutinas\\lisp2.lsp" están definidas las funciones "inverso" y "tg".
CARGALISP cargará los archivos de AutoLISP indicados si no estaban definidas, o cargadas que es lo mismo, las
funciones indicadas para cada archivo.
(while lst
(progn
113
Tal como está funciona perfectamente. Pero... puede producirse un pequeño error. ¿Qué pasa si no existe, o no se
encuentra, el archivo lisp1.lsp o cualquiera de los archivos de la lista que reciba CARGALISP?
(FINDFILE archivo)
Esta función nos permite comprobar la existencia de un archivo. Si el archivo existe devuelve su nombre y si no existe, o
no lo encuentra en la ruta indicada, devolverá nil.
En caso de que no se indique la ruta en la que debe buscar el archivo, lo buscará en los directorios de soporte de
AutoCAD.
(findfile "c:\\autoexec.bat") debería devolver "c:\\autoexec.bat", supongo que vuestro PC tiene Autoexec.bat, claro...
FINDFILE en realidad no hace nada, tan solo se utiliza para comprobar la existencia de un archivo. Entonces,...¿Cuando
se utilizará FINDFILE? Pues os voy a poner tres ejemplos en los que se suele utilizar:
Por tanto, deberiamos modificar el código de la función CARGALISP para comprobar la existencia de los archivos a
cargar.
(while lst
;; están cargadas
;; No se encuentra el archivo
(progn
;; Carga el archivo
114
;; Se encuentra el archivo
Esta función nos permitirá seleccionar un archivo, ya veremos más adelante como seleccionar varios archivos.
GETFILED muestra un letrero de gestión de archivos tipico de Windows.
Podemos pasarle a GETFILED el título del letrero, el archivo seleccionado por defecto y la extensión de archivos que se
buscará por defecto. GETFILED devuelve el nombre y ruta del archivo seleccionado.
No pasa nada al pulsar en Abrir, ya que no se abre el archivo. GETFILED tan solo nos deja seleccionarlo y devuelve su
nombre.
Al utilizar archivos de texto en nuestras rutinas debemos siempre seguir los siguientes pasos:
Esta función nos permite abrir un archivo de texto y devuelve un "descriptor de archivo".
¿Qué es un descriptor de archivo? Es algo similar a un canal por el que se comunica AutoCAD con dicho archivo.
Podemos abrir varios archivos, es decir abrir varios canales de comunicación, de modo que al leer o escribir en un
115
archivo, tenemos que saber cual es el canal de comunicación por el que vamos a recibir o enviar datos, es decir
necesitamos conocer el descriptor del archivo.
Hay tres formas de abrir un archivo de texto, en función de lo que se quiera hacer a continuación con dicho archivo. El
argumento modo es el encargado de diferenciar estas tres formas distintas de abrir archivos:
Aunque en la línea anterior se abre el archivo autoexec.bat en modo lectura, luego no podriamos hacer nada con él ya
que no hemos guardado el descriptor de archivo. Es decir, no podemos decirle a AutoCAD que lea texto por el canal de
comunicación que hemos abierto, ya que no conocemos el descriptor del archivo. ¿Cómo se utiliza entonces la función
OPEN?
De este modo el valor devuelto por OPEN, el descriptor de archivo, se almacena en la variable darch.
Antes de ver como leer o escribir en los archivos de texto, veamos como tendriamos que cerrarlos...
(CLOSE descriptor_archivo)
Cierra el archivo cuyo descriptor se indica y devuelve nil. Fijate en lo importante que es guardar el descriptor del archivo
en una variable, ya que si no lo hacemos no solo no podremos leer o escribir en el archivo, tampoco podremos cerrarlo.
Hay que tener una cosa en cuenta al trabajar con archivos de texto: Al abrir un archivo de texto debemos indicar el modo
(lectura, escritura, o aditivo) de modo que debemos saber de antemano lo que vamos a hacer con el archivo, y SOLO
podremos hacer una cosa o leer o escribir. Aunque podemos abrir un archivo en modo escritura, escribir en él, cerrarlo,
abrirlo en modo lectura, leer y volver a cerrarlo.
Otra cuestión de especial interes es que si abrimos un archivo de texto existente en modo escritura "w", nos habremos
cargado todo lo que tenía anteriormente dicho archivo. Así que mucho cuidado con los archivos que se abren en modo
escritura.
Devuelve #<file "c:\\nuevo.txt"> el descriptor del archivo abierto, que se almacena en la variable darch.
También podemos abrir el archivo en modo aditivo, para continuar escribiendo a partir de la última línea de texto del
archivo.
116
Bien, ya tenemos abierto nuestro archivo de texto, vamos a escribir en él:
Esta función escribe una expresión en el archivo cuyo descriptor se indique. Si no se indica el descriptor de archivo, la
expresión se escribirá en la ventana de comandos de AutoCAD. Veamos algunos ejemplos:
(prin1 a) escribirá el valor de la expresión a en la ventana de comandos de AutoCAD, es decir, escribirá 5.5
Si ejecutas las dos líneas de código anteriores desde la ventana de comandos de AutoCAD, veras que (prin1 a) parece
devolver 5.55.5 en realidad devuelve 5.5 pero el eco de mensajes, repite el valor devuelto por la expresión (prin1 a) es
decir 5.5 Por eso, aparece 5.55.5
(prin1 b)
Fijate que PRIN1 puede escribir tanto cadenas de texto como números. Sin embargo la función PROMPT tan solo puede
recibir como argumeto una cadena de texto.
Hemos indicado una expresión como argumento, si utilizamos PROMPT en lugar de PRIN1 dará un error.
(prin1)
Por eso se suele emplear como última expresión de los comandos, para que la salida de nuestras rutinas sea limpia y no
se vea el eco de mensajes de la última expresión evaluada.
(progn (print a) (print a)) escribe 5.5 pasa a otra línea y escribe 5.5
Es decir, PRINT salta de línea antes de escribir la expresión indicada. PRINT escribe la expresión en una línea nueva.
(print) devuelve una cadena nula, por eso también se emplea como última expresión de los comandos
(print (strcat "\t" b)) devuelve "\tCurso de AutoLISP", es decir PRINT tampoco entiende los caracteres de control.
117
(PRINC [expresión [descriptor_archivo]])
(princ) devuelve una cadena nula, por eso también se emplea como última expresión de los comandos
(princ (strcat "\t" b)) ahora escribe "Curso de AutoLISP" pero tabulado.
Por tanto, PRINC se diferencia de las anteriores en que si interpreta el significado de los caracteres de control.
Esta función permite escribir un caracter, recibe como argumento su código ASCII. Si se indica el descriptor de archivo
lo escribirá en el archivo y sino lo escribirá en la ventana de comandos de AutoCAD.
Esta función escribe una línea de texto entera. El primer argumento debe ser una cadena de texto no una expresión. Y si
se indica un descriptor de archivo, escribirá la línea de texto en el archivo, en caso contrario la escribe en la ventana de
comandos de AutoCAD.
(READ-CHAR [descriptor_archivo])
Lee un caracter del archivo cuyo descriptor se indica. Si no se indica el descriptor, lo lee de la ventana de comandos de
AutoCAD.
(READ-LINE [descriptor_archivo])
Esta función lee una línea del archivo de texto indicado. Si no se indica el descriptor de archivo, la leera de la ventana de
comandos de AutoCAD.
(read-line) escribir una palabra o frase y devolverá una cadena de texto con lo que has escrito
118