Está en la página 1de 123

Introducción al estudio de AutoLISP

Índice de temas

• 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.

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.

Características de AutoLISP
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.

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.

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.

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.

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:

(+ 1 2) Ejecuta la función + que realiza la suma de los argumentos 1 y 2 devuelve el


resultado 3.

(+ 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:

(+ (* 2 3) 2) devuelve 8. Primero evalúa la lista interior y devuelve su resultado a la


exterior.

(+ 7 (/ 5.0 2) -3) devuelve 6.5.

¿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.

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.

(+ 5 2.0) devuelve 7.0.

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.

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.

• Para los comentarios incluidos en el código, se recomienda utilizar el


siguiente método:
o ;;; Antes del código de las funciones, explicando su funcionamiento.
o ;; En medio del código del programa.
o ; Para explicar una línea de código. A diferencia de las anteriores, esta
no se inserta en la columna 1, se insertará al terminar el código de la
línea que comenta.
• Es muy recomendable utilizar un formato tabulado para el código.
• Evitar el uso de variables globales innecesarias.
• Utilizar los comandos de AutoCAD y sus opciones en Inglés y precedidos por
"._"
• No abusar de la función "SETQ".
• No utilizar T, MIN, MAX, LAST como símbolos (nombres de variables y
funciones).
• Recuperar el valor inicial de las variables de AutoCAD que han sido
modificadas.
• Añadir unas líneas al final del programa para indicar el nombre del nuevo
comando, autor...
• No introducir demasiado código en la función principal.
• Incluir una función de tratamiento de errores.
• Evitar que el usuario pueda introducir datos erróneos.
• En general es recomendable que, tras ejecutar el nuevo comando, si se
ejecuta el comando "DESHACER" ("H") se deshagan todos los cambios
realizados por el comando.

¿Qué es una variable?


¿Que no sabes que es una variable? Recuerdas cuando en la escuela te decían
"Tengo 3 melones, le doy uno a Juan y después de comprar otros 2, me comí uno
porque tenía hambre". Pues los melones son una variable. Nosotros hacíamos: 1º
tengo 3 melones x=3 (x es nuestra variable). Luego le doy uno a Juan x = 3-1=2.
Compro otros dos x = 2+2=4 y me comí uno x=4-1. Así que x=3. "x" no es más que
un valor que varía (o puede hacerlo) a lo largo del tiempo. Pero podíamos haber
llamado a la variable "y"o "z", y ¿por qué no "melones"?

(SETQ Variable1 Valor1 [Variable2 Valor2 ... ] )

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):
• No son sensibles a las mayúsculas. Es lo mismo bloque, BLOQUE y Bloque.
• No pueden contener espacios en blanco, ni los caracteres " ( ) . ; '
• Pueden contener números, pero no estar formados únicamente por ellos.
• No utilizaremos nombres de variables que coincidan con los nombres de las
funciones de AutoLISP ni de las variables de sistema de AutoCAD.
• No existe límite en cuanto al número de caracteres, pero es recomendable
emplear nombre cortos y descriptivos.
• No es necesario declarar previamente las variables, como sucede en otros
lenguajes de programación. En AutoLISP una misma variable puede contener
primero un número entero, luego uno real, después una cadena de texto, etc.

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 d b) Se puede definir una variable igual al valor de otra variable.

(SETQ a 3.5) Se puede modificar el valor asociado a una variable, por eso se llama
variable.

(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

(SETQ a "\tBienvenido al tema de AutoLISP" )


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)

Existen una serie de símbolos predefinidos:

• T Representa al valor lógico Cierto o Verdadero.


• nil Representa al valor lógico Falso.

(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:

(SETQ rad (/ 6 (* 2 PI)))

Ejemplo: ¿Por qué las siguientes expresiones están mal?

(SETQ 157 25)

(SETQ rad 5 Rad 4)

(SETQ b Curso de AutoLISP)

Por último veamos porque no debemos emplear para los nombres de las variables
los nombres de las funciones de AutoLISP.

(SETQ + 23) En este caso asignamos a la variable + el valor 23.

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.

Operaciones matemáticas básicas


En el tema anterior ya vimos algún ejemplo de como funcionan las funciones
matemáticas básicas (suma, resta, división y multiplicación). Ahora explicaremos su
funcionamiento con mayor profundidad.

(+ [número1 número2 ... ] )

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
(+ 3.0 9) devuelve 12.0

(SETQ a (+ 3 9 4)) devuelve 16 y lo almacena en la variable a

(+ 3.5 -1) devuelve 2.5

Prueba ahora la siguiente expresión:

(SETQ a (+ a 2.5)) devuelve 18.5

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.

(+ 12.5) devuelve 12.5

(+ -7.0) devuelve -7.0

Si ejecutamos la función suma sin argumentos, devuelve 0.

(+ ) 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.

(+ 0.5 2) devuelve 2.5

(+ 3 -0.6) devuelve 2.4

(- [número1 número2 ... ] )

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.0 5) devuelve 6.0

(- 11 5) devuelve 6

(SETQ a (- 12 5 4)) devuelve 3 y lo almacena en la variable a

(- 3.5 -1) devuelve -4.5

(SETQ a (- a 2.5)) devuelve 0.5 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.

(- 12.5) devuelve -12.5

(- -7.0) devuelve 7.0


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.

Si ejecutamos la función resta sin argumentos, devuelve 0.

(- ) devuelve 0

(* [número1 número2 ... ] )

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

(* 3.0 9) devuelve 27.0

(SETQ a (* 3 9 4)) devuelve 108 y lo almacena en la variable a

(* 3.5 -1) devuelve -3.5

Prueba ahora la siguiente expresión:

(SETQ a (* a 2.5)) devuelve 270.0

Si le pasamos a la función * un único número como argumento, nos devuelve ese


número.

(* 12.5) devuelve 12.5

(* -7.0) devuelve -7.0

Si ejecutamos la función * sin argumentos, devuelve 0.

(* ) devuelve 0

(/ [número1 número2 ... ] )

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

(/ 11 5.5) devuelve 2.0

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

(/ 7 2.0) devuelve 3.5


(SETQ a (/ 12.5 4 2)) devuelve 1.5625 y lo almacena en la variable a

(/ 3.5 -1) devuelve -3.5

(SETQ a (/ a 0.5)) devuelve 3.125 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.

(/ 12.5) devuelve 12.5

(/ -7.0) devuelve -7.0

Si ejecutamos la función / sin argumentos, devuelve 0.

(/ ) devuelve 0

(1+ <número> )

Esta función incrementa en una unidad el número indicado como argumento.

(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+ 2.5) devuelve 3.5

(1+ 0) devuelve 1

(1+ -7) devuelve -6

Una aplicación bastante habitual de esta función es la de incrementar índices o


contadores:

(SETQ i 1)

(SETQ i (1+ i)) devuelve 2

(1- <número> )

Esta función reduce en una unidad el número indicado.

(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- 2.5) devuelve 1.5

(1- 0) devuelve -1

(1- -1) devuelve -2


Solicitar números al usuario
En este apartado veremos dos funciones de AutoLISP que no permitirán solicitar al
usuario un número entero o real. Esto nos permitirá interactuar con el usuario y
pedirle información.

(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.

(GETINT "Cuantos años tienes:")

Podemos asignar el valor introducido por el usuario a una variable y así utilizarlo
después.

(SETQ edad (GETINT "Cuantos años tienes:"))

(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

(SETQ mens "Cuantos años tienes:")

(SETQ edad (GETINT mens))

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.

Prueba ahora el siguiente ejemplo:

(SETQ a 27 mens "Cuantos años tienes:")

(SETQ edad (GETINT mens))

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 interprete de LISP ya está en ejecución, no puede volver a ejecutarse y nos
muestra el mensaje anterior.

(GETREAL [mensaje] )

Solicita del usuario un número real. En caso de que el usuario introduzca un número
entero, el interprete 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 (GETREAL "Cuantos kilos pesas:"))

(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

(SETQ mens "Cuantos kilos pesas:")

(SETQ peso (GETREAL mens))

Definir funciones de usuario


Hemos visto tan solo algunas de las funciones de AutoLISP, pero también es posible
crear nuestras propias funciones. Es más, podemos redefinir las funciones de
AutoLISP e incluso los comandos de AutoCAD. La instrucción de AutoLISP que nos
permitirá crear nuestras propias funciones, denominadas funciones de usuario, se
llama DEFUN.

(DEFUN <función> ( [argumentos] / [variables_locales] ) [expr1] [expr2] ...)

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:

(DEFUN 2+ ( valor ) (+ valor 2))

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

(2+ -2) devuelve 0

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+ ) indica "; error: argumentos insuficientes"

(2+ "texto") indica "; error: tipo de argumento erróneo: numberp: "texto""

(2+ 1 2) indica "; error: demasiados argumentos"

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:

(DEFUN 2+ ( valor ) (SETQ valor (+ valor 2)))

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

!valor devuelve nil

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...

(SETQ valor 5) devuelve 5

(2+ 4) devuelve 6
¿Que valor crees que tendrá la variable valor? Pensemos un poco.

1. Inicialmente valor = nil


2. Al ejecutar (SETQ valor 5) valor = 5
3. Cuando llamamos a la función "2+" tenemos que "valor" = 4
4. Dentro de la función "2+" asignamos (SETQ valor (+ valor 2)) de modo que
"valor" = 4 + 2 = 6
5. Pero al salir de la función "2+" recuperamos el valor que tenía la variable
"valor" antes de llamar a la función, valor = 5

Por tanto, si tecleamos !valor devolvera 5.

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.

Retoquemos un poco más la función "2+" y borremos el contenido de "valor"

(DEFUN 2+ ( valor ) (SETQ inicial valor valor (+ valor 2)))

(SETQ valor nil)

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)

¿Que valor tendrán ahora las variables "valor" e "inicial"? Compruebalo:

!valor devuelve nil

!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:

(DEFUN 2+ ( valor / inicial ) (SETQ inicial valor valor (+ valor 2)))

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

(SETQ inicial nil)

!inicial devuelve nil

(2+ 3)

!inicial devuelve nil

Bueno, vamos con la última modificación de la función "2+"


(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.

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.

Siguiendo con el ejemplo anterior...

(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.

Archivos de código fuente en AutoLISP


Ya hemos visto que las funciones de AutoLISP se pueden ejecutar directamente
desde la ventana de comandos de AutoCAD. Pero el escribir el código directamente
en AutoCAD tiene varios inconvenientes, como ya dijimos en el primer tema del
curso:

1. El reducido tamaño de la ventana de comandos de AutoCAD.


2. La dificultad de escribir todo el código seguido, sin tabular. Esto es debido a
que cada vez que se pulsa Intro, AutoCAD evalúa lo que se ha escrito.
3. El código no se almacena en ningún sitio, así que se perderá al cerrar el
dibujo actual o AutoCAD.

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.

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:

• Evaluación de paréntesis durante la programación.


• Posibilidad de compilar el código, consiguiendo así aumentar su velocidad
de ejecución.
• Depurador de código específico para AutoLISP con opciones para: Ejecutar
el código paso a paso, indicar puntos de parada, evaluar expresiones y
valores de variables, etc.
• Diferenciar el código fuente con distintos colores.
• Tabulado automático del código.
• Utilidades para gestionar proyectos con varios archivos de código.
• Carga de los archivos en AutoCAD.

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.

Los archivos de AutoLISP son archivos de texto con extensión LSP.

Nuestra primera función de usuario


Antes de comenzar con el editor de Visual LISP, vamos a crear nuestra primera
función de usuario: Se trata de una función para convertir un ángulo de radianes a
grados decimales. El código de la función sería el siguiente:

(defun RAG ( ang )


(/ (* ang 180.0) pi)

El código de las rutinas publicadas en el curso se escribirá con el siguiente formato:

• Comentarios en el código
• Funciones de AutoLISP
• Números
• Textos
• FUNCIONES DE USUARIO Y NUEVOS COMANDOS
• Nombres de variables, paréntesis, etc...

Hay tres reglas básicas que sigue AutoLISP:

1. El número de paréntesis de apertura debe ser igual al número de paréntesis


de cierre.
2. Primero se evaluan las listas más interiores.
3. Toda función de AutoLISP devuelve un resultado.

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...

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.

Recordemos la estructura de la función de AutoLISP defun: (DEFUN <función>


( [argumentos] / [variables_locales] ) [expresión1] [expresión2] ...) . En la función
RAG tenemos un argumento y no se han declarado variables locales (por lo que no
es necesario poner el caracter "/"), además solo tenemos una expresión (/ (* ang
180.0) pi) . Como se dijo en el tema anterior, defun devuelve el resultado de la última
expresión, que en nuestro caso resulta ser el ángulo ya convertido a grados
decimales.

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.

Los comentarios en el código


Es imprescindible añadir comentarios al código para explicar que es lo que hace y
como se hace. En AutoLISP todo lo que en una línea va después de un punto y coma
(como este ;) se considera un comentario, de modo que no se evalúa. Da igual poner
uno, dos o 45 punto y comas seguidos, al poner el primero AutoLISP ya sabe que
todo lo que está a continuación, en esa línea, es un comentario.

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.

Para los comentarios incluidos en el código, se recomienda utilizar el siguiente


método:

• ;;; Antes del código de las funciones, explicando su funcionamiento.


• ;; En medio del código del programa.
• ; Para explicar una línea de código. A diferencia de las anteriores, esta no se
inserta en la columna 1, sino al terminar el código de la línea que comenta.

Por ejemplo, la función RAG con comentarios podría quedar así:

;;; Esta función recibe el valor de un ángulo en radianes y lo devuelve en grados


decimales.

(defun RAG ( ang ) ; Recibe un ángulo en radianes

(/ (* ang 180.0) pi)

;; Multiplica el ángulo en radianes por 180.0 y lo divide por pi.

)
El editor de Visual LISP
El editor de Visual Lisp se inicia desde AutoCAD de varias formas:

• Ejecutando el comando de AutoCAD "VLIDE".


• Desde el menú desplegable "Herr-->AutoLISP-->Editor de Visual Lisp"

Mostrará algo similar a la siguiente imagen:

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:
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.

(defun RAG ( ang ) ; Recibe un ángulo en radianes


(/ (* ang 180.0) pi)

;; Multiplica el ángulo en radianes por 180.0 y lo divide por pi.

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:

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:

• Las funciones de AutoLISP se muestran de color azul.


• Los comentariosen morado, con gris de fondo.
• Los números en verde.
• Etc.

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.

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:

1. Cargar un archivo desde el editor de Visual LISP.


2. En el menú desplegable de AutoCAD "Herr-->AutoLISP-->Cargar".
3. Utilizando la función de AutoLISP LOAD.

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".

(LOAD archivo [resultado_si_falla])

La función LOAD permite cargar en AutoCAD el archivo de AutoLISP que se indique.


Por ejemplo para cargar el archivo RAG sería:

(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:

(LOAD "rag" (setq test 0))

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:

Si test = 0 ==> Mensaje al usuario "Archivo no encontrado"

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.

Veamos como sería la función GAR propuesta en el tema anterior:

;;;________________________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.

(defun GAR ( ang )

(/ (* ang pi) 180.0)

Es muy muy parecida a la función RAG, no?


Crear un directorio para los archivos de AutoLISP
Supongamos que el directorio que hemos creado para almacenar nuestras rutinas
es "C:\Rutinas". Veamos como añadirlo a los directorios de soporte de AutoLISP:

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.

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.

Operaciones matemáticas
Hemos visto las operaciones matemáticas básicas: suma, resta, multiplicación y
división y las funciones 1+ y 1-. Ahora vamos con otras funciones de AutoLISP que
nos permitiran realizar casi cualquier operación matemática en nuestras rutinas.

(ABS numero)

Esta función devuelve el valor absoluto del número que se le pase como argumento.
Por ejemplo:

(abs 23.8) devuelve 23.8

(abs -23.8) tb devuelve 23.8

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 -7) devuelve 7

(abs -7.0) devuelve 7.0

(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.

(fix -15.8) devuelve -15

(fix 0.99) devuelve 0

(REM numero1 numero2 [numero3] ...)

Esta función devuelve el resto de la división de numero1 entre numero 2.

(rem 2.5 2) devuelve 0.5

(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.

(rem 3 2) devuelve 1.0

(SIN angulo)
Devuelve el seno de un ángulo indicado en radianes.

(sin 0) devuelve 0.0

(sin (/ pi 2)) devuelve 1.0

(COS angulo)

Funciona igual que la anterior, pero devuelve el coseno del ángulo, que hay que
pasarselo en radianes.

(cos 0) devuelve 1.0

(cos pi) devuelve -1.0

(ATAN numero 1 [numero2])

Devuelve el arco cuya tangente sea numero1. Por ejemplo

(atan 0) devuelve 0.0 ya que el ángulo que tiene tangente 0 es el 0.0

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...

(atan (sin angulo) (cos angulo)) devuelve angulo

(SQRT numero)

Esta función devuelve la raiz cuadrada del numero que recibe como argumento.
Siempre devuelve un número real, no entero.

(sqrt 4) devuelve 2.0

(sqrt 2.0) devuelve 1.41..

(EXPT num exp)

Devuelve el número num elevado al exponente exp.

(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.

(expt 3 2.0) devuelve 9.0

(EXP num)

Devuelve el número e (e = 2.71828... ) elevado al número num. Siempre devuelve un


número real.
(exp 1) devuelve 2.71828

(exp 2) devuelve 7.38906

(LOG numero)

Esta función devuelve el logaritmo neperiano del número que recibe como
argumento.

(log 1) devuelve 0.0

(log 2) devuelve 0.693147

(GCD entero1 entero2)

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

(gcd 9 27) devuelve 9

(MAX num1 num2 ...)

Devuelve el mayor de los números que recibe como argumentos.

(max 2 4 1 3 6) devuelve 6

(max 8 4 -9) 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.

(max 8 4.0 -9) devuelve 8.0

(MIN num1 num2 ...)

Devuelve el menor de los números que recibe como argumentos.

(min 2 3 6) devuelve 2

(min 8 4 -9) devuelve -9

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 8 4.0 -9) devuelve -9.0

Pues ya están vistas todas las funciones matemáticas... Enhorabuena

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.

(GETSTRING [modo] [mensaje])

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:

(getstring "Cual es tu nombre?")

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:

(setq nombre (getstring "Cual es tu nombre?"))

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:

(setq nombre (getstring T "Cual es tu nombre?"))

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.

Solicitar puntos al usuario


Para solicitar puntos se utilizan dos funciones que también son parecidas a GETINT
y a GETREAL.

(GETPOINT [pto_base] [mensaje])

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:

(getpoint "Punto inicial")

Lo habitual es que además ese punto se almacene en una variable


(setq pto (getpoint "Punto inicial"))

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.

(getpoint pto "Siguiente punto:")

Aparece una línea elástica entre el punto pto y la posición del cursor.

(GETCORNER pto_base [mensaje])

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:

(getpoint pto "Siguiente punto:")

(getcorner pto "Siguiente punto:")

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.

Funciones para manejar listas


AutoLISP es un lenguaje de programación basado en listas, así que es lógico que el
tratamiento que reciban las listas de elementos sea muy bueno. Vamos a ver ahora
una serie de funciones para manipular listas de elementos.

(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).

(car pto) devuelve la coordenada X del punto pto. Es decir 10.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)

(CADR lista) y otras


Cómo se obtendría la coordenada Y del punto pto??? Veamos:

• CDR devuelve la lista sinel primer elemento


• CAR devuelve el primer elemento de una lista

De modo que (cdr pto) devuelve (Y Z). Así que para obtener la coordenada Y:

(car (cdr pto)) devuelve la coordenada Y del punto pto.

y como obtenemos la coordenada Z??

(car (cdr (cdr pto))) devuelve la coordenada Z del punto pto.

En resumen, las coordenadas del punto pto se obtendrian mediante:

• X ==> (car pto)


• Y ==> (car (cdr pto))
• Z ==> (car (cdr (cdr pto)))

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:

• X ==> (cAr pto)


• Y ==> (cAr (cDr pto))
• Z ==> (cAr (cDr (cDr pto)))

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:

• X ==> (cAr pto)


• Y ==> (cAr (cDr pto)) == (cADr pto)
• Z ==> (cAr (cDr (cDr pto))) == (cADDr pto)

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:

(car (car lst)) devuelve a, o lo que es lo mismo:

(cAAr lst) devuelve a

y el elemento c???

(cDr lst) devuelve ((c d) (e f)). Ahora si hacemos (cAr (cDr lst)) devuelve (c d). Así
que:
(cAr (cAr (cDr lst))) devuelve c, o lo que es lo mismo:

(cAADr lst) devuelve c

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:

(cAr (cDr (cAr (cDr lst)))) devuelve d, o lo que es lo mismo:

(cADADr lst) devuelve c

Y cómo obtener e ??

(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 (cAr (cDr (cDr lst)))) devuelve e, o lo que es lo mismo:

(cAADDr lst) devuelve e

Por último, veamos cómo se obtiene f.

(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:

(cAr (cDADDr lst)) o (cADADr (cDr lst))

(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:

(length pto) devuelve 3. y si el pto estuvuiera en 2D (X Y) devolvería 2.


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.

(LIST elemento1 elemento2 ...)

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)

(list "pepe" 2 "Juan") devuelve ("pepe" 2 "Juan")

Veamos que hace la siguiente expresión:

(list (list "a" "b") "c" (list "d" "e") "f")

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").

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.

(COMMAND "comando" [datos])

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.
Los idiomas de AutoCAD
Supongamos que no disponemos de una versión en castellano de AutoCAD, sino
que está en inglés, o en Francés, o Chino Mandarín... Qué pasa si ejecutamos esto...

(command "linea" (list 0.0 0.0) (list 100.0 200.0) "")

AutoCAD no conocerá el comando "linea", así que nos dará un error.

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:

(command "_circle" (list 0.0 0.0) 25.0)

(command "_line" (list 0.0 0.0) (list 100.0 200.0) "")

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.

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:

(command ".circulo" (list 0.0 0.0) 25.0)

(command ".linea" (list 0.0 0.0) (list 100.0 200.0) "")

Podemos además indicar los comandos en Inglés anteponiendoles un guión bajo así
que también podriamos escribirlo así:

(command "._circle" (list 0.0 0.0) 25.0)

(command "_.circle" (list 0.0 0.0) 25.0)


Da igual si se pone antes el punto o el guión bajo.

Para redefinir un comando de AutoCAD:

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".

Cargamos la siguiente función:

(defun C:LINEA ( )

(setq pt (getpoint "Centro del círculo: "))

(setq rad (getreal "Radio del círculo"))

(command "._circle" pt rad)

Ahora el comando "linea" dibujará círculos.

Para recuperar el valor original del comando podemos hacer dos cosas:

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.

Operaciones de comparación
Qué es una operación de comparación?? Pues comparar, por ejemplo, si algo es
mayor que algo, o menor, o si es igual.

(= expr1 expr2 ...)

Compara si expr1 devuelve el mismo resultado que expr2, en caso afirmativo


devuelve T y en caso ontrario devuelve nil.

(= 5 (+ 1 4)) devuelve T porque (+ 1 4) devuelve 5


(= 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

No solo podemos evaluar números, también textos:

(setq txt1 "Curso de Lisp")

(= txt1 "Curso de Lisp") devuelve T

(= txt1 "Curso de LISP") devuelve nil. No es lo mismo un texto en mayúsculas que en


minúsculas.

(= "LISP" "Lisp") devuelve nil

Estamos comparando expresiones, así que:

(= (+ 1 5) (/ 12 2)) devuelve T porque ambas expresiones devuelven como resultado


6.

La función = puede aceptar más de dos expresiones:

(= 6 (+ 1 5) 6.0 (/ 12 2)) devuelve T, pues las cuatro expresiones devuelven 6 o 6.0


(que vale lo mismo).

(/= expr1 expr2 ...)

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).

(/= (+ 5 5) (/ 12 2)) devuelve T, pues la primera expresión devuelve 10 y la segunda 6.

(/= "LISP" "Lisp") devuelve T

(< expr1 expr2 ...)

Compara si expr1 es menor q expr2

(< 4 5) devuelve T, ya que 4 es menor que 5

(< 4 -5) devuelve nil

(< 5 5.0) devuelve nil

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

(< 1 2 0) devuelve nil


(< (+ 1 2) (* 2 2.0) (/ 12 2)) 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".

(< "Albacete" "Barcelona") devuelve T

(< "a" "c") devuelve T

(< "d" "c") devuelve nil

(< "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".

(> expr1 expr2 ...)

Supongo que ya os imaginais como funciona, no? Comprueba si las expresiones


están ordenadas de mayor a menor.

(> 5 3) devuelve T

(> -2 3) devuelve nil

(> 5 2 3) devuelve nil

(> 5 4 3) devuelve T

Y ahora con los textos:

(> "a" "c") devuelve nil, pues el código ASCII de "a" es menor que el de "c".

(<= expr1 expr2 ...)

Comptrueban si las expresiones son menores o iguales que las anteriores.

(<= 1 2 2.0 3) devuelve T

(<= 1 3.0 2 3) devuelve nil

(>= expr1 expr2 ...)

Comptrueban si las expresiones son mayores o iguales que las anteriores

(>= 3 2 2.0 1) devuelve T


(>= 3 1.0 2 1) devuelve nil

(>= 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:

1. Pedir al usuario el centro de la circunferencia.


2. Pedir al usuario el perímetro de la circunferencia.
3. Calcular el radio de la circunferencia a partir de su perímetro.
4. Dibujar la circunferencia.

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.

Primero hay que añadir la definición del nuevo comando CIRCPERI:

(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:

(setq peri (getreal "Perímetro:"))

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:

(setq rad (/ peri (* pi 2)))

4) Dibujar la circunferencia

(command "_.circle" pto rad)

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:

)
El código completo de la rutina sería:

(defun C:CIRCPERI ( )

(setq pto (getpoint "Centro de la circunferencia"))

(setq peri (getreal "Perímetro:"))

(setq rad (/ peri (* pi 2)))

(command "_.circle" pto rad)

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.

Operaciones lógicas
Exste una serie de funciones de AutoLISP que nos permiten realizar operaciones
lógicas. Suelen emplearse en combinación con las operaciones de comparación.

(AND expr1 expr2 ...)

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.

(and (< 3 4) (= 5 5.0)) devuelve T, porque las dos expresiones devuelven T

(and (> 3 4) (= 5 5.0)) devuelve nil, porque (> 3 4) devuelve nil

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.

Vamos a complicarlo un poco... Qué devolverá la siguiente expresión?

(and (= 5 5.0) (< 3 4) "Soy un texto" 5.8)

Preguntándolo de otra forma: Alguna de las expresiones que recibe AND como
argumentos es nil? No, así que AND devuelve T.

(setq a "Soy otro texto" b 15 c T d nil)

(and a b) devolverá T

(and a d)
(and a b c d) devolverá nil, porque d es nil

(OR expr1 expr2 ...)

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

(or (= 5 5.0) (> 3 4)) devuelve T, pues la primera expresión se cumple

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.

(setq a "Soy otro texto" b 15 c T d nil)

(or a b) devuelve T

(or c d) devuelve nil

(or d d) devuelve nil

(EQUAL expr1 expr2 [precision])

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 pt1 pt2) devuelve T

EQUAL además ofrece un argumento opcional... [precisión]. Veamos como


funciona :

(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

Sin embargo, si subimos la precisión...

(equal 4.99 5.0 0.001) devuelve nil

¿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 pt3 0.001) devuelve nil

(equal pt1 pt4 0.01) devuelve nil

(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:

(equal 15 20 100) devuelve T

(equal 5000 4200 100) devuelve nil

(NOT expr)

A esta función le gusta llevarle la contraria a la expresión que recibe como


argumento.

• Si la expresión devuelve nil, entonces NOT devuelve T.


• Si la expresión devuelve cualquier cosa que no sea nil, entonces NOT
devuelve nil.

(not 5) devuelve nil

(not "Texto") devuelve nil

(not (+ 5 1)) devuelve nil

Si hacemos...

(setq a 5 b nil c T d "Nuevo texto")

(not b) devolverá T, porque b es nil

(not c) devolverá nil.

Veamos que devuelve la siguiente expresión:

(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.

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)

Muestra el texto indicado como argumento en pantalla, y siempre devuelve nil.

(prompt "Bienvenidos al Curso")

(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.

(alert "Error: Dato no válido")

(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 ( )

(setq pto (getpoint "Centro de la circunferencia")) (terpri)

(setq peri (getreal "Perímetro:")) (terpri)

(setq rad (/ peri (* pi 2)))

(command "_.circle" pto rad)

Así los mensajes de solicitud aparecerán en líneas separadas.

Los caracteres de control

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:
(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.

(alert "Texto en línea 1\nTexto en línea 2")

Se pone 1\nTexto todo junto. Para qué poner espacios?

(alert "Texto en línea 1\n Texto en línea 2")

Si lo ponemos así, añade un espacio en blanco al principio de la segunda línea. Por


eso se pone todo seguido.

(alert "Texto en línea 1\n\tTexto en línea 2")

En este ejemplo la segunda línea está tabulada.

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 ( )

(setq pto (getpoint "\nCentro de la circunferencia"))

(setq peri (getreal "\nPerímetro:"))

(setq rad (/ peri (* pi 2)))

(command "_.circle" pto rad)

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...

(prompt "\nNuevo comando CIRCPERI cargado")

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...
(defun C:CIRCPERI ( / pto peri rad )

OJO! la barra inclinada / hay que ponerla, sino serían argumentos y no variables
locales.

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_________________________;;;

;;; Comando para dibujar una circunferencia indicando su centro y la longitud

;;; de su perímetro.

(defun C:CIRCPERI ( / pto peri rad )

(setq pto (getpoint "\nCentro de la circunferencia"))

(setq peri (getreal "\nPerímetro:"))

(setq rad (/ peri (* pi 2)))

(command "_.circle" pto rad)

(prompt "\nNuevo comando CIRCPERI cargado")

Estructuras condicionales simples


Hasta ahora nuestro código ha sido completamente lineal, las líneas de código que
escribíamos se ejcutaban después una tras otra en el mismo orden. En este tema
veremos un tipo de funciones que nos permitiran bifurcar el código, de modo que ya
no exista un único camino sino dos o más. Veamos un ejemplo en pseudocódigo:

1. Introducir el límite inferior


2. Introducir el límite superior
3. ¿El límite superior es menor que el inferior?
1. SI--> Mensaje "El límite superior debe ser mayor que el inferior"
2. NO --> intervalo = límite superior - límite inferior

(IF condición expr_si_cumple [expr_no_cumple])


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:

(if (= 2 2.0) (alert "Los números son iguales"))

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 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.

(if (= 2 3) (alert "Los números son iguales"))

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)

(alert "Los números son iguales")

(alert "Los números son distintos")

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.

Veamos el siguiente ejemplo:

(setq liminf (getint "\nLímite inferior"))

(setq limsup (getint "\nLímite superior"))

(if (> liminf limsup)

(alert "El límite superior debe ser mayor que el inferior") (setq limsup (getint
"\nLímite superior")) (setq intervalo (- limsup liminf))

(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

(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.

¿Cómo quedaría el ejemplo anterior?

(setq liminf (getint "\nLímite inferior"))

(setq limsup (getint "\nLímite superior"))

(if (> liminf limsup)

(progn

(alert "El límite superior debe ser mayor que el inferior")

(setq limsup (getint "\nLímite superior"))

(setq intervalo (- limsup liminf))

(setq intervalo (- limsup liminf))

En este caso la condición si cumple es todo lo siguiente:

(progn

(alert "El límite superior debe ser mayor que el inferior")

(setq limsup (getint "\nLímite superior"))

(setq intervalo (- limsup liminf))

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.

Pasemos ahora a unos ejemplos más de estructuras condicionales:

(if (and (= 2 2.0) (< 2 3))

(alert "Las dos condiciones se cumplen")

(alert "Al menos una condición no se cumple")

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.

(if (not var1)

(alert "Variable no definida")

(alert "Variable definida")

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

(alert "Variable definida")

(alert "Variable no definida")

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.

Estructuras condicionales múltiples


Con la función IF tan solo podemos indicar una condición y dos opciones, si cumple
y no cumple. Pero también tenemos la posibilidad de indicar varias condiciones con
sus respectivas expresiones si 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.

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)

(alert "A y B son iguales")

(setq b (getreal "Introduzca de nuevo B: "))

((< a c)

(alert "A es menor que C")

((< a b))

(T

(alert "A no es igual a B")

(alert "A no es menor que C")

(alert "A no es menor que B")

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.

¿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

(alert "A no es igual a B")

(alert "A no es menor que C")

(alert "A no es menor que B")

((= a b)

(alert "A y B son iguales")

(setq b (getreal "Introduzca de nuevo B: "))

((< a c)

(alert "A es menor que 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.

¿Qué es una variable de sistema?


El modo en que funciona el entorno de AutoCAD, y la forma de trabajar de muchos
de sus comandos, se ven afectados por los valores asignados a las variables de
sistema. Podría decirse que controlan como trabaja AutoCAD. Por lo tanto, se
pueden modificar los valores asignados a las variables de sistema para personalizar
AutoCAD para un usuario en concreto, para un tipo de trabajo determinado, e
incluso para un archivo de dibujo. De modo que nos facilite el trabajar con AutoCAD.

Tipos de variables de sistema


Las variables de sistema pueden almacenar distintos tipos de datos, en función de
la información que contengan. Podrían clasificarse del siguiente modo:

• 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".
• 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".

Modificar los valores de las variables de sistema


Podríamos clasificar las variables de sistema en función del lugar en el que se
guardan:

• 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:

• Tecleando el nombre de la variable, como si fuese un comando más de


AutoCAD.
• Ejecutando el comando "MODIVAR" e indicando la variable a modificar.
• Algunos comandos de AutoCAD permiten modificar los valores asignados a
algunas variables de sistema. Por ejemplo el comando "MARCAAUX" permite
modificar el valor asignado a la variable "blipmode".
• Algunas variables se modifican al ejecutar algún comando de AutoCAD. Por
ejemplo "circlerad" almacena el radio de la última circunferencia creada con
el comando "círculo".

Las variables de sistema en los programas de AutoLISP


Los programadores suelen leer y modificar el contenido de algunas variables de
sistema de AutoCAD, ya que esto les permite definir el modo en el que desean que
se comporte AutoCAD o algunos comandos 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.

(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 variable nuevo_valor)

Asigna a la vaiable indicada un nuevo valor, y devuelve dicho valor.

(setvar "blipmode" 0)

(setvar "osmode" 32)

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")

Funciones de conversión de datos


Vamos a ver una serie de funciones que nos permitirán pasar un entero a real, a un
texto o al revés...

(ITOA entero)

Convierte un entero en un texto. Integer TO Atom.

(itoa 24) devuelve "24"

(ATOI texto)

Hace justo lo contrario que la función anterior. Convierte un texto en un número.


Atom TO Integer

(atoi "24") devuelve 24

(atoi "-7") devuelve -7

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.

(atoi "15.3") devuelve 15

(atoi "15.99999") devuelve 15

(FLOAT numero)

Convierte un número en real, así que lo lógico es pasarle un entero.

(float 5) devuelve 5.0

pero tb puedo hacer... (float 5.36) devuelve 5.36 lo cual sería una tontería porque no
hace nada.

(ATOF texto)

Convierte un texto en real.

(atof "15.7") devuelve 15.7

(atof "15") devuelve 15.0

(atof "-15.7") devuelve -15.7

(atof "soy un texto") devuelve 0.0

(RTOS numero [modo [precision]])


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.

(rtos 2.5) devuelve "2.5"

(rtos 2) devuelve "2" y no "2.0"

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.

Por ejemplo... (rtos 2.5 5) devuelve "2 1/2"

(rtos 2.5 1) devuelve "2.5000E+00"

Precisión nos permite definir el número de decimales que deseamos.. por ejemplo:

(rtos 1.23456789 2 3) devuelve 1.235 así que redondea...

(rtos 9.99 2 0) devuelve 10

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.

(GETDIST [pto_base] [mensaje])

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.

(getdist "Distancia de desplazamiento:")

El mensaje es opcional, pero casi siempre se pone. También podemos asignar el


resultado a una variable...

(setq dist1 (getdist "Distancia de desplazamiento:"))

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:

(setq pto (getpoint "Punto base"))


(setq dist1 (getdist pto "Distancia de desplazamiento:"))

(GETANGLE [pto_base] [mensaje])

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.

(GETORIENT [pto_base] [mensaje])

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).

Vamos ahora a modificar un poco la rutina CIRCPERI. Teníamos el siguiente código:

(defun C:CIRCPERI ( / pto peri rad )

(setq pto (getpoint "\nCentro de la circunferencia: "))

(setq peri (getreal "\nPerímetro: "))

(setq rad (/ peri (* pi 2)))

(command "_.circle" pto rad)

(prompt "\nNuevo comando CIRCPERI cargado")

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

[3P/2P/Ttr (tangente tangente radio)]:

Precise radio de círculo o [Diámetro]: 23.00636765228087


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"

Así que nuestro código quedaría...

(defun C:CIRCPERI ( / pto peri rad )

(setvar "cmdecho" 0) ; Desactiva el eco de mensajes

(setq pto (getpoint "\nCentro de la circunferencia: "))

(setq peri (getdist "\nPerímetro: "))

(setq rad (/ peri (* pi 2)))

(command "_.circle" pto rad)

(setvar "cmdecho" 1) ; Vuelve a activar el eco de mensajes

(prompt "\nNuevo comando CIRCPERI cargado")

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:

1. Si la puerta está cerrada, la abro. Es decir, si cmdecho está activada, la


desactivo.
2. Aqui metemos el código de la rutina CIRCPERI
3. Si antes de entrar la puerta estaba cerrada , entonces la cierro. Es decir, si
cmdecho antes de entrar estaba activada, entonces la activo.

El apartado 1 suena claramente a una estructura condicional, así que emplearemos


la función IF:

(if (= (getvar "cmdecho") 1)

(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.

(if (= (setq cmd0 (getvar "cmdecho")) 1)

(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:

(setvar "cmdecho" cmd0)

Esta línea de código asignaría a cmdecho el valor que tenía inicialmente.

(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.

El código quedaría así:

(defun C:CIRCPERI ( / pto peri rad cmd0 )


(if (= (setq cmd0 (getvar "cmdecho")) 1)

(setvar "cmdecho" 0)

(setq pto (getpoint "\nCentro de la circunferencia: "))

(setq peri (getdist "\nPerímetro: "))

(setq rad (/ peri (* pi 2)))

(command "_.circle" pto rad)

(if (= cmd0 1)

(setvar "cmdecho" 1)

(prompt "\nNuevo comando CIRCPERI cargado")

Aún nos queda una cosa... al ejecutar ahora la rutina muestra el siguiente texto:

Centro de la circunferencia

Perímetro: Designe segundo punto: 1

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.

Finalmente el código resultante será:

(defun C:CIRCPERI ( / pto peri rad cmd0 )

(if (= (setq cmd0 (getvar "cmdecho")) 1)

(setvar "cmdecho" 0)

(setq pto (getpoint "\nCentro de la circunferencia: "))

(setq peri (getdist "\nPerímetro: "))

(setq rad (/ peri (* pi 2)))

(command "_.circle" pto rad)


(if (= cmd0 1)

(setvar "cmdecho" 1)

(princ)

(prompt "\nNuevo comando CIRCPERI cargado")

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í:

1. Obtener el centro del cículo


2. Obtener el radio del círculo interior
3. Dibujar el circulo interior
4. Obtener el radio del círculo exterior
5. Dibujar el circulo exterior

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.

La primera línea de código es la definición de la función:

(defun C:ARAND ( )

Más adelante le añadirémos las varibles locales si es que existen.

1) Obtener el centro del cículo. Podría ser algo así:

(setq pto (getpoint "\nCentro de la arandela: "))

2) Obtener el radio del círculo interior:

(setq radi (getreal "\nRadio interior: "))

3) Dibujar el circulo interior

(command "_.circle" pto radi)

4) Obtener el radio del círculo exterior:

(setq rade (getreal "\nRadio exterior: "))

5) Dibujar el circulo exterior


(command "_.circle" pto rade)

Y por último cerramos el paréntesis de la función defun

El código completo queda así:

(defun C:ARAND ( )

(setq pto (getpoint "\nCentro de la arandela: "))

(setq radi (getreal "\nRadio interior: "))

(command "_.circle" pto radi)

(setq rade (getreal "\nRadio exterior: "))

(command "_.circle" pto rade)

Ahora vamos a retocar un poco el comando, le añadiremos las siguientes mejoras:

• Desactivar el eco de mensajes, es decir desactivar la variable cmdecho al


iniciar la rutina.
• Recuperar el valor inicial de cmdecho al terminar la rutina.
• Añadir una línea al final del código para que muestre un mensaje indicando el
nombre del nuevo comando al cargar la función.
• Poner las variables como locales.
• En lugar de utilizar GETREAL para obtener el radio, usaremos GETDIST con
el centro de las circunferencias como punto base.
• Añadir una línea al final de la función para que la salida del programa sea
limpia.

El código después de realizar las mejoras anteriores sería:

(defun C:ARAND ( / pto rad cmd0 )

(if (= (setq cmd0 (getvar "cmdecho")) 1)

(setvar "cmdecho" 0)

(setq pto (getpoint "\nCentro de la arandela: "))

(setq rad (getreal "\nRadio interior: "))

(command "_.circle" pto rad)

(setq rad (getreal "\nRadio exterior: "))

(command "_.circle" pto rad)


(if (= cmd0 1)

(setvar "cmdecho" 1)

(princ)

(prompt "\nNuevo comando ARAND cargado")

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:

En primer lugar obtenemos el centro de la circunferencia y la asignamos a la


variable pto.

(setq pto (getpoint "\nCentro de la arandela: "))

Solicitamos el radio de la circunferencia interior y lo asignamos a la variable rad.

(setq rad (getreal "\nRadio interior: "))

y dibujamos la circunferencia interior

(command "_.circle" pto rad)

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.

(setq rad (getreal "\nRadio 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.

(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.

Control de deshacer comandos


El comando "DESHACER" es distinto al comando "H". En realidad, "H" es una
opción de "DESHACER". Al ejecutar este comando, AutoCAD muestra el siguiente
mensaje: "Indique el número de operaciones a deshacer o
[Auto/Control/Inicio/Fin/Marca/Retorno] <1>:". Veamos como funcionan estas
opciones:

"Número de operaciones a deshacer"

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.

Lo recomendable es tener siempre activada la opción "Todas".

"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.

"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".

Si "DESHACER" "Auto" está activada, AutoCAD coloca automáticamente un


"DESHACER" "Inicio" y un "DESHACER" "Fin" al principio y final de las opciones de
los menús, y deja inutilizadas estas opciones de forma manual.

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.

El comando deshacer en las rutinas de AutoLISP


Utilizando las opciones anteriores del comando deshacer, se puede lograr que todo
el código de nuestras rutinas funcione como si se tratase de un único comando,
aunque en realidad se utilice más de una llamada a la función command de
AutoLISP.

Añadiremos al inicio de nuestra rutina "deshacer" "inicio" y al final de la rutina


"deshacer" "fin". Veamoslo:

(defun C:ARAND ( / pto rad cmd0 )

(command "_.undo" "_begin")

(if (= (setq cmd0 (getvar "cmdecho")) 1)

(setvar "cmdecho" 0)

(setq pto (getpoint "\nCentro de la arandela: "))

(setq rad (getreal "\nRadio interior: "))

(command "_.circle" pto rad)

(setq rad (getreal "\nRadio exterior: "))

(command "_.circle" pto rad)

(if (= cmd0 1)

(setvar "cmdecho" 1)
)

(command "_undo" "_end")

(princ)

(prompt "\nNuevo comando ARAND cargado")

¿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.

Funciones de tratamiento de errores


Las rutinas que hemos creado (CIRCPERI y ARAND) no está terminadas todavía.
Para terminarlas debemos realizar dos modificaciones:

1. Controlar lo que hace la rutina en caso de que se produzca un error en su


ejecución.
2. Impedir que el usuario introduzca datos erroreos. Por ejemplo que indique 0
como radio de un círculo.

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:

(defun C:ARAND ( / pto rad cmd0 )

(command "_.undo" "_begin")

(if (= (setq cmd0 (getvar "cmdecho")) 1)

(setvar "cmdecho" 0)

(setq pto (getpoint "\nCentro de la arandela: "))

(setq rad (getreal "\nRadio interior: "))

(command "_.circle" pto rad)

(setq rad (getreal "\nRadio exterior: "))

(command "_.circle" pto rad)

(if (= cmd0 1)

(setvar "cmdecho" 1)

)
(command "_.undo" "_end")

(princ)

(prompt "\nNuevo comando ARAND cargado")

¿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:

(defun ERRORES ( mens )

(if (= mens "quitar / salir abandonar")

(princ)

(princ (strcat "\nError: " mens " "))

(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.

Veamos ahora el código de la función ARAND:

(defun C:ARAND ( / pto rad cmd0 error0 )

(command "_.undo" "_begin")

(if (= (setq cmd0 (getvar "cmdecho")) 1)

(setvar "cmdecho" 0)

(setq error0 *error* *error* errores)

(setq pto (getpoint "\nCentro de la arandela: "))

(setq rad (getreal "\nRadio interior: "))

(command "_.circle" pto rad)

(setq rad (getreal "\nRadio exterior: "))

(command "_.circle" pto rad)

(setq *error* error0)

(if (= cmd0 1)

(setvar "cmdecho" 1)

(command "_.undo" "_end")

(princ)

(prompt "\nNuevo comando ARAND cargado")

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.
En la rutina CIRCPERI hariamos exactamente lo mismo:

(defun C:CIRCPERI ( / pto peri rad cmd0 error0 )

(if (= (setq cmd0 (getvar "cmdecho")) 1)

(setvar "cmdecho" 0)

(setq error0 *error* *error* errores)

(setq pto (getpoint "\nCentro de la circunferencia: "))

(setq peri (getdist "\nPerímetro: "))

(setq rad (/ peri (* pi 2)))

(command "_.circle" pto rad)

(setq *error* error0)

(if (= cmd0 1)

(setvar "cmdecho" 1)

(princ)

(prompt "\nNuevo comando CIRCPERI cargado")

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.

De modo que vamos a modificar la función ERRORES:

(defun ERRORES ( mens )

(setq *error* error0)

(if (= mens "quitar / salir abandonar")

(princ)

(princ (strcat "\nError: " mens " "))

(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.

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...

Podemos modificar la función de errores:

(defun ERRORES ( mens )


(setq *error* error0)

(if (= mens "quitar / salir abandonar")

(princ)

(princ (strcat "\nError: " mens " "))

(command "_.undo" "_end")

(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:

(defun ERRORES ( mens )

(setq *error* error0)

(if (= mens "quitar / salir abandonar")

(princ)

(princ (strcat "\nError: " mens " "))

(command "_.undo" "_end")

(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:

(defun C:CIRCPERI ( / pto peri cmd0 )

(if (= (setq cmd0 (getvar "cmdecho")) 1)

(setvar "cmdecho" 0)

(setq pto (getpoint "\nCentro de la circunferencia: "))

(if (not rad)

(prompt "\nPerímetro: ")

(progn

(prompt "\nPerímetro <")

(prompt (rtos (* rad 2 pi) 2 2))

(prompt ">: ")

(if (setq peri (getdist))

(setq rad (/ peri (* 2 pi)))

(command "_.circle" pto rad)

(if (= cmd0 1)

(setvar "cmdecho" 1)

(princ)

(prompt "\nNuevo comando CIRCPERI cargado")


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:

(if (not rad)

(prompt "\nPerímetro: ")

(progn

(prompt "\nPerímetro <")

(prompt (rtos (* rad 2 pi) 2 2))

(prompt ">: ")

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.

(if (setq peri (getdist))

(setq rad (/ peri (* 2 pi)))

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.

(command "_.circle" pto rad)

Por último dibuja el círculo.

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:

(defun C:CIRCPERI ( / pto rad peri cmd0 )


(if (= (setq cmd0 (getvar "cmdecho")) 1)

(setvar "cmdecho" 0)

(setq pto (getpoint "\nCentro de la circunferencia: "))

(if (= (getvar "circlerad") 0.0)

(prompt "\nPerímetro: ")

(progn

(prompt "\nPerímetro <")

(prompt (rtos (* (getvar "circlerad")2 pi) 2 2))

(prompt ">: ")

(if (setq peri (getdist))

(setq rad (/ peri (* 2 pi)))

(setq rad (getvar "circlerad"))

(command "_.circle" pto rad)

(if (= cmd0 1)

(setvar "cmdecho" 1)

(princ)

(prompt "\nNuevo comando CIRCPERI cargado")

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:

(if (= (getvar "circlerad") 0.0)


(prompt "\nPerímetro: ")

(progn

(prompt "\nPerímetro <")

(prompt (rtos (* (getvar "circlerad")2 pi) 2 2))

(prompt ">: ")

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.

(if (setq peri (getdist))

(setq rad (/ peri (* 2 pi)))

(setq rad (getvar "circlerad"))

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.

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.

(INITGET [modo] [palabras_clave])

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.

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)

(setq numero (getint "\nNúmero entero: "))

Initget modifica a la siguiente función de solicitud de datos, es decir, getint.

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))

(setq numero (getint "\nNúmero entero: "))

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.

Si queremos que además se indique un número positivo, entoces deberiamos poner:

(initget 7)

(setq numero (getint "\nNúmero entero: "))


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:

(getpoint pto "\nIndicar punto:")

(getcorner pto "\nIndicar punto:")

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:

(setq pt1 (getpoint "\nPunto base: "))

(initget 32)

(setq pt2 (getpoint pt1 "\nSegundo punto: "))

En este ejemplo aparece una línea discontinua. Y en el siguiente un rectángulo con


líneas discontinuas.

(setq pt1 (getpoint "\nPunto base: "))

(initget 32)

(setq pt2 (getcorner pt1 "\nSegundo punto: "))

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:

(setq pt1 (getpoint "\nPunto base: "))

(initget 64)

(setq dist12 (getdist pt1 "\nSegundo punto: "))


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)

(setq ang (getreal "\nIntroducir ángulo: "))

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:

(defun C:CIRCPERI ( / pto rad peri cmd0 )

(if (= (setq cmd0 (getvar "cmdecho")) 1)

(setvar "cmdecho" 0)

(setq pto (getpoint "\nCentro de la circunferencia: "))

(if (= (getvar "circlerad") 0.0)

(prompt "\nPerímetro: ")

(progn

(prompt "\nPerímetro <")

(prompt (rtos (* (getvar "circlerad")2 pi) 2 2))

(prompt ">: ")

(if (setq peri (getdist))

(setq rad (/ peri (* 2 pi)))

(setq rad (getvar "circlerad"))

(command "_.circle" pto rad)

(if (= cmd0 1)
(setvar "cmdecho" 1)

(princ)

(prompt "\nNuevo comando CIRCPERI cargado")

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.

Definitivamente el código quedaría:

...

(initget 1)

(setq pto (getpoint "\nCentro de la circunferencia: "))

...

Sigamos modificando la rutina CIRCPERI... Unas líneas después de la sollicitud del


centro de la circunferencia, se solicita su perímetro:

(if (setq peri (getdist))

(setq rad (/ peri (* 2 pi)))

(setq rad (getvar "circlerad"))


)

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:

(setq rad (/ peri (* 2 pi)))

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:

(setq rad (getvar "circlerad"))

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:

(defun C:CIRCPERI ( / pto rad peri cmd0 )

(if (= (setq cmd0 (getvar "cmdecho")) 1)

(setvar "cmdecho" 0)

(initget 1)

(setq pto (getpoint "\nCentro de la circunferencia: "))

(if (= (getvar "circlerad") 0.0)

(progn

(prompt "\nPerímetro: ")

(initget 7)

(progn

(prompt "\nPerímetro <")


(prompt (rtos (* (getvar "circlerad")2 pi) 2 2))

(prompt ">: ")

(initget 6)

(if (setq peri (getdist))

(setq rad (/ peri (* 2 pi)))

(setq rad (getvar "circlerad"))

(command "_.circle" pto rad)

(if (= cmd0 1)

(setvar "cmdecho" 1)

(princ)

(prompt "\nNuevo comando CIRCPERI cargado")

Vamos ahora a modificar la rutina ARAND:

(defun C:ARAND ( / pto rad cmd0 )

(command "_.undo" "_begin")

(if (= (setq cmd0 (getvar "cmdecho")) 1)

(setvar "cmdecho" 0)

(setq pto (getpoint "\nCentro de la arandela: "))

(setq rad (getreal "\nRadio interior: "))

(command "_.circle" pto rad)

(setq rad (getreal "\nRadio exterior: "))

(command "_.circle" pto rad)


(if (= cmd0 1)

(setvar "cmdecho" 1)

(command "_undo" "_end")

(princ)

(prompt "\nNuevo comando ARAND cargado")

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)

(setq pto (getpoint "\nCentro de la arandela: "))

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:

(defun C:ARAND ( / pto rad cmd0 )

(command "_.undo" "_begin")

(if (= (setq cmd0 (getvar "cmdecho")) 1)

(setvar "cmdecho" 0)

(initget 1)

(setq pto (getpoint "\nCentro de la arandela: "))

(initget 7)

(setq rad (getreal "\nRadio interior: "))

(command "_.circle" pto rad)

(initget 7)

(setq rad (getreal "\nRadio exterior: "))

(command "_.circle" pto rad)

(if (= cmd0 1)
(setvar "cmdecho" 1)

(command "_undo" "_end")

(princ)

(prompt "\nNuevo comando ARAND cargado")

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]...

(initget [modo] [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:

(initget 7 "Diametro Perimetro")

(setq rad (getdist "\nRadio del circulo / Diametro / Perimetro :"))

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...

(initget 7 "Diametro DEShacer")

(setq rad (getdist "\nRadio del circulo / Diametro / DEShacer: "))

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.
Supongamos que tenemos el siguiente código:

(initget 7 "Diametro Perimetro")

(setq rad (getdist "\nRadio del circulo: "))

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:

(initget 7 "Diametro Perimetro")

(setq rad (getdist "\nRadio del circulo / Diametro / Perimetro :"))

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:

(defun C:ARAND ( / pto rad cmd0 )

(command "_.undo" "_begin")

(if (= (setq cmd0 (getvar "cmdecho")) 1)

(setvar "cmdecho" 0)

(initget 1)

(setq pto (getpoint "\nCentro de la arandela: "))

(initget 7)

(setq rad (getreal "\nRadio interior: "))

(command "_.circle" pto rad)

(initget 7)

(setq rad (getreal "\nRadio exterior: "))

(command "_.circle" pto rad)

(if (= cmd0 1)

(setvar "cmdecho" 1)

(command "_undo" "_end")

(princ)
)

(prompt "\nNuevo comando ARAND cargado")

Tenemos que modificar las siguientes líneas:

(initget 7)

(setq rad (getreal "\nRadio interior: "))

Primero añadimos "Diametro" a la lista de palabras clave de initget:

(initget 7 "Diametro")

Y a continuación le decimos al usuario que existe una opción llamada Diametro que
puede seleccionar como respuesta:

(setq rad (getreal "\nRadio interior / Diametro: "))

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:

(if (= rad "Diametro")

(setq rad (getreal "\nDiámetro interior: "))

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".

Fijemonos en lo que hace el código anterior:

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")
(setq rad (getreal "\nRadio interior / Diametro: "))

(if (= rad "Diametro")

(setq rad (/ (getreal "\nDiámetro interior: ") 2.0))

Pero, ¿Qué pasa si el usuario hace lo siguiente?

1. Indica D como respuesta al radio interior.


2. Indica -50 o 0 como diámetro.

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")

(setq rad (getreal "\nRadio interior / Diametro: "))

(if (= rad "Diametro")

(progn

(initget 7)

(setq rad (/ (getreal "\nDiámetro interior: ") 2.0))

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:

(defun C:ARAND ( / pto rad cmd0 )

(command "_.undo" "_begin")

(if (= (setq cmd0 (getvar "cmdecho")) 1)

(setvar "cmdecho" 0)

(initget 1)

(setq pto (getpoint "\nCentro de la arandela: "))


(initget 7 "Diametro")

(setq rad (getreal "\nRadio interior / Diametro: "))

(if (= rad "Diametro")

(progn

(initget 7)

(setq rad (/ (getreal "\nDiámetro interior: ") 2.0))

(command "_.circle" pto rad)

(initget 7 "Diametro")

(setq rad (getreal "\nRadio exterior / Diametro: "))

(if (= rad "Diametro")

(progn

(initget 7)

(setq rad (/ (getreal "\nDiámetro exterior: ") 2.0))

(command "_.circle" pto rad)

(if (= cmd0 1)

(setvar "cmdecho" 1)

(command "_undo" "_end")

(princ)

(prompt "\nNuevo comando ARAND cargado")

(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:

(initget "DEShacer Nuevo Repetir")

(setq opc (getkword "\nDEShacer / Nuevo / Repetir: ")

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.

Un ejemplo bastante habitual en las rutinas es el siguiente:

(initget "DEShacer Nuevo Repetir")

(setq opc (getkword "\nDEShacer / Nuevo / Repetir: "))

(cond

((= opc "DEShacer")

(alert "Has seleccionado la opción DEShacer")

((= opc "Nuevo")

(alert "Has seleccionado la opción Nuevo")

(T

(alert "Has seleccionado la opción Repetir")

Así en función de lo que indique el usuario se hace una cosa u otra.

Estructuras repetitivas
Hasta hace poco tan solo podiamos crear programas cuya ejecución fuera lineal:

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.

(WHILE condición [expr1] [expr2] ...)

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)

(while (< i 10)

(prompt "\n")

(prompt (itoa i))

(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.

Veamos el siguiente ejemplo:

(setq i 10)

(while (< i 10)

(prompt "\n")

(prompt (itoa i))

(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:

(while (not (setq pt (getpoint "\nPunto inicial: "))))

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.

Por lo tanto, mientras no se indica un punto, sigue preguntando.

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 nil)

(setq flag1 T)

(while flag1

(prompt "\na NO es menor que b")

(setq b (getreal "\nIntroduzca un número: "))

(if (< a b)

(setq flag1 nil)

En este caso:

• Si a es menor que b --> flag1 = nil


• Si a NO es menor que b --> flag1 = T

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.

Es decir, mientas a NO sea menor que b, nos pedirá un nuevo valor de b.

En la función While, al igual que vimos con IF y COND, como condición podemos
utilizar expresiones lógicas. Por ejemplo:

(while (or (< a b) (< b 0.0))

(prompt "\na NO es menor que b, o b es negativo")

(setq b (getreal "\nIntroduzca un número positivo: "))

En este ejemplo, el bucle se ejecutará hasta que se indique un valor para b positivo
y mayor que a.

(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 10 (propmt "Este curso es demasiado fácil para mi"))

También podríamos asignar la cantidad a repetir a una variable:

(setq Bart_Simpson (getint "\nNúmero de repeticiones para Bart: "))

(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:

(repeat (+ 4 5) (propmt "Este curso me parece poco serio"))

o suponiendo que las variables a y b tengan asignados dos números enteros:

(propmt "Inspector Gadget, este mensaje se autodestruirá en...")

(setq i 0)

(repeat (+ a b)

(propmt "\t")

(propmt (itoa i))


(setq i (1+ i))

¿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")

(setq rad (getreal "\nRadio exterior / Diametro: "))

(if (= rad "Diametro")

(progn

(initget 7)

(setq rad (/ (getreal "\nDiámetro exterior: ") 2.0))

(command "_.circle" pto rad)

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.

Podriamos sustituir el código anterior por el siguiente:

(while (or (not rade) (not (< radi rade)))

(initget 7 "Diametro")

(setq rad (getreal "\nRadio exterior / Diametro: "))

(if (= rad "Diametro")

(progn

(initget 7)

(setq rad (/ (getreal "\nDiámetro exterior: ") 2.0))

)
)

(command "_.circle" pto rade)

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:

(defun C:ARAND ( / pto radi rade cmd0 )

(command "_.undo" "_begin")

(if (= (setq cmd0 (getvar "cmdecho")) 1)

(setvar "cmdecho" 0)

(initget 1)

(setq pto (getpoint "\nCentro de la arandela: "))

(initget 7 "Diametro")

(setq radi (getreal "\nRadio interior / Diámetro: "))

(if (= radi "Diametro")

(progn

(initget 7)

(setq radi (/ (getreal "\nDiámetro interior: ") 2.0))

(command "_.circle" pto radi)

(while (or (not rade) (not (< radi rade)))

(initget 7 "Diametro")
(setq rade (getreal "\nRadio exterior / Diámetro: "))

(if (= rade "Diametro")

(progn

(initget 7)

(setq rade (/ (getreal "\nDiámetro exterior: ") 2.0))

(command "_.circle" pto rade)

(if (= cmd0 1)

(setvar "cmdecho" 1)

(command "_.undo" "_end")

(princ)

(prompt "\nNuevo comando ARAND cargado")

Estructuras repetitivas
Hasta hace poco tan solo podiamos crear programas cuya ejecución fuera lineal:

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.

(WHILE condición [expr1] [expr2] ...)

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)

(while (< i 10)


(prompt "\n")

(prompt (itoa i))

(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.

Veamos el siguiente ejemplo:

(setq i 10)

(while (< i 10)

(prompt "\n")

(prompt (itoa i))

(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:

(while (not (setq pt (getpoint "\nPunto inicial: "))))

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.
Por lo tanto, mientras no se indica un punto, sigue preguntando.

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 nil)

(setq flag1 T)

(while flag1

(prompt "\na NO es menor que b")

(setq b (getreal "\nIntroduzca un número: "))

(if (< a b)

(setq flag1 nil)

En este caso:

• Si a es menor que b --> flag1 = nil


• Si a NO es menor que b --> flag1 = T

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.

Es decir, mientas a NO sea menor que b, nos pedirá un nuevo valor de b.

En la función While, al igual que vimos con IF y COND, como condición podemos
utilizar expresiones lógicas. Por ejemplo:

(while (or (< a b) (< b 0.0))

(prompt "\na NO es menor que b, o b es negativo")

(setq b (getreal "\nIntroduzca un número positivo: "))


)

En este ejemplo, el bucle se ejecutará hasta que se indique un valor para b positivo
y mayor que a.

(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 10 (propmt "Este curso es demasiado fácil para mi"))

También podríamos asignar la cantidad a repetir a una variable:

(setq Bart_Simpson (getint "\nNúmero de repeticiones para Bart: "))

(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:

(repeat (+ 4 5) (propmt "Este curso me parece poco serio"))

o suponiendo que las variables a y b tengan asignados dos números enteros:

(propmt "Inspector Gadget, este mensaje se autodestruirá en...")

(setq i 0)

(repeat (+ a b)

(propmt "\t")

(propmt (itoa i))

(setq i (1+ i))

¿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")
(setq rad (getreal "\nRadio exterior / Diametro: "))

(if (= rad "Diametro")

(progn

(initget 7)

(setq rad (/ (getreal "\nDiámetro exterior: ") 2.0))

(command "_.circle" pto rad)

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.

Podriamos sustituir el código anterior por el siguiente:

(while (or (not rade) (not (< radi rade)))

(initget 7 "Diametro")

(setq rad (getreal "\nRadio exterior / Diametro: "))

(if (= rad "Diametro")

(progn

(initget 7)

(setq rad (/ (getreal "\nDiámetro exterior: ") 2.0))

(command "_.circle" pto rade)

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:

(defun C:ARAND ( / pto radi rade cmd0 )

(command "_.undo" "_begin")

(if (= (setq cmd0 (getvar "cmdecho")) 1)

(setvar "cmdecho" 0)

(initget 1)

(setq pto (getpoint "\nCentro de la arandela: "))

(initget 7 "Diametro")

(setq radi (getreal "\nRadio interior / Diámetro: "))

(if (= radi "Diametro")

(progn

(initget 7)

(setq radi (/ (getreal "\nDiámetro interior: ") 2.0))

(command "_.circle" pto radi)

(while (or (not rade) (not (< radi rade)))

(initget 7 "Diametro")

(setq rade (getreal "\nRadio exterior / Diámetro: "))

(if (= rade "Diametro")

(progn

(initget 7)

(setq rade (/ (getreal "\nDiámetro exterior: ") 2.0))

)
)

(command "_.circle" pto rade)

(if (= cmd0 1)

(setvar "cmdecho" 1)

(command "_.undo" "_end")

(princ)

(prompt "\nNuevo comando ARAND cargado")

Trabajar con ángulos y distancias


AutoLISP nos proporciona una serie de funciones para trabajar con ángulos y
distancias, comenzaremos viendo los angulos:

(ANGLE pt1 pt2)

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:

(setq pt1 (getpoint "\nPunto1: "))

(setq pt2 (getpoint pt1 "\nPunto2: "))

(setq ang (angle pt1 pt2))

Hay que fijarse en cual es el punto que se indica primero en la función angle, porque
si hacemos:

(setq ang2 (angle pt2 pt1))

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

(DISTANCE pt1 pt2)

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.
(setq pt1 (getpoint "\nPunto1: "))

(setq pt2 (getpoint pt1 "\nPunto2: "))

(setq dist12 (distance pt1 pt2))

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.

(POLAR ptobase ang dist)

Esta función se utiliza para obtener un punto por medio de coordenadas polares a
partir de un punto base. Por ejemplo:

(setq pt1 (getpoint "\nPunto1: "))

(setq pt2 (polar pt1 0.0 50.0))

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:

(command "_.line" pt1 pt2 "")

Dibujaríamos una línea horizontal de 50 unidades, desde pt1 hasta pt2.

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:

(setq pt2 (polar pt1 (/ pi 2.0) 50.0))

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º.

Funciones avanzadas para trabajar con listas


Ya hemos visto algunas funciones para manejar listas: CAR, CDR, LIST... pero hay
muchas más. En este tema veremos algunas de las que nos faltan.

(LAST lista)

Esta función devuelve el último elemento de la lista que recibe como argumento. De
modo que si hacemos:

(setq pto (getpoint "\nPunto de inserción: "))

(setq z (last pto))

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 numero lista)

Devuelve el elemento cuyo número de orden se indica de la lista.

(nth 2 pto) devolverá la coordenada Y, no? Pues no!. Los elementos de una lista se
empiezan a numerar desde cero. Por ejemplo:

(nth 0 pto) devolverá la coordenada X del punto pto.

(nth 1 pto) devolverá la coordenada Y del punto pto.

(nth 2 pto) devolverá la coordenada Z del punto pto, si existe y si no existe devolverá
nil.

Supongamos que tenemos una lista como la siguiente:

(setq lst (list "peras" "melones" "sandias" "perros" "platanos"))

para comprobar si "perros" pertenece a la lista lst se utiliza la función MEMBER.

(MEMBER elemento lista)

Con esta función, si el elemento indicado pertenece a la lista devolverá la lista a


partir de ese elemento. Por ejemplo:

(member "perros" lst) devolverá ("perros" "platanos")

Si el elemento no pertenece a la lista, devolverá nil.

(member "gatos" lst) devuelve nil

(SUBST elem_nuevo elem_antiguo lista)

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 (subst "naranjas" "perros" lst))

Supongamos que queremos añadir un nuevo elemento "limones" a la siguiente lista:

(setq lst (list "peras" "naranjas" "manzanas"))


Podriamos hacerlo con list, pero sería un lio:

(setq lst (list (car lst) (cadr lst) (caddr lst) "limones"))

Para una lista de 3 elementos se puede hacer, pero... ¿Y si tenemos 30 elementos?

(CONS elemento lista)

Esta función añade un elemento a la lista indicada. El nuevo elemento se sitúa en el


primer lugar. Es decir:

(cons "limones" lst) devolverá ("limones" "peras" "naranjas" "manzanas")

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í:

(setq lst (cons "limones" lst))

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:

(REVERSE lista)

Devuelve la lista que recibe como argumento pero en orden inverso.

(reverse lst) devolverá ("manzanas" "naranjas" "peras" "limones")

Bien, pues para añadir un elemento al final de una lista...

(setq lst (reverse (cons "platanos" (reverse lst))))

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:

((acad_strlsort lst) devolverá ("limones" "manzanas" "naranjas" "peras" "platanos")

Si tenemos dos listas:

(setq lst1 (list "peras" "naranjas" "manzanas"))

(setq lst2 (list "fresas" "limones"))

y que queremos unirlas...

(APPEND lista1 [lista2] ...)


Esta función une dos o más listas y devuelve la lista resultante.

(append lst1 lst2) devolverá ("peras" "naranjas" "manzanas" "fresas" "limones")

Los pares punteados

Los pares punteados son simplemente listas de dos elementos separadas por un
punto. Por ejemplo: (0 . 27)

¿Qué diferencia a un par punteado de una lista normal? Veamoslo:

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:

(("perro" . 0) ("gato" . 1) ("ratón" . 5) ("gato" . 7))

Siendo "gato" el código que está duplicado.

¿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.

(CONS código valor)

(setq par_punteado (cons 7 "sandias")) asignará a par_punteado (7 . "sandias")

Para crear una lista de asociaciones, por ejemplo:

(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 código lista_de asociaciones)

ASSOC nos servirá para obtener el par punteado, cuyo código se indique, de la lista
de asociaciones que recibe como argumento. Por ejemplo:
(assoc "gato" lst) devolverá ("gato" . 1)

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:

(cdr (assoc "gato" lst))

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:

(assoc 0 lista_de_entidad) devolverá (0 . "LINE")

(cdr (assoc 0 lista_de_entidad)) devolverá "LINE"

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.

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í:

(setq i 0 nent (length lstptos))

(while (< i nent)

(setq pt (nth i lstptos))

(command "_.circle" pt 1.0)

(setq i (1+ i))

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

(setq pt (car lstptos))

(command "_.circle" pt 1.0)

(setq lstptos (cdr 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.

Bien, pero tenemos algunos métodos que son mejores, y eso es lo que vamos a ver
en este tema.

(FOREACH variable lista expresion)

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:

(foreach p lstptos (command "_.circle" p 1.0))

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.

En caso de tener una lista de cadenas de texto, podria hacerse lo siguiente:


(setq lsttxt (list "Curso" "de" "AutoLISP"))

(foreach p lsttxt (prompt p))

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:

(foreach pepe lsttxt (princ pepe))

Supongamos ahora que tenemos una lista de números:

(setq lst (list 5.0 -1.5 2.6 3.8 9.7))

Para sumar los números podriamos hacer lo siguiente:

(setq i 0 nent (length lst) total 0.0)

(while (< i nent)

(setq num (nth i lst))

(setq total (+ total num))

(setq i (1+ i))

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.

También podria hacerse sin emplear contadores, como hicimos antes. Y también
podemos hacerlo con FOREACH, veamoslo:

(setq total 0.0)

(foreach p lst (setq total (+ total p)))

Todo lo que antes poniamos en un bucle se pone ahora en una sola línea de codigo.

(APPLY nombre_función lista)

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:

(setq total (apply '+ lst))

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 +

(list "pepe" '+ 5.0) devuelve ("pepe" + 5.0)

¿Entiendes ahora porque ponemos '+? Para que no evalue la función, ya que
devolveria #<SUBR @0244f5a8 y nos daría un error.

Veamos un par de ejemplos más:

(apply 'max lst) devolverá el mayor número de lst

(apply 'min lst) devolverá el menor número de lst

Si tuvieramos una lista cuyos elementos fueran textos:

(setq lsttxt (list "Curso" "de" "AutoLISP"))

para unir los textos:

(setq txt (apply 'strcat lsttxt))

De modo que txt = "CursodeAutoLISP" todo junto.

(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:

Supongamos que tenemos dos puntos:

(setq pt1 (getpoint "\nPunto 1: "))

(setq pt2 (getpoint pt1 "\nPunto 2: "))

Los puntos son listas con 2 o 3 elementos, en función de si tienen 2 o 3


coordenadas.

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

(list (- (car pt2) (car pt1))

(- (cadr pt2) (cadr pt1))

(- (caddr pt2) (caddr pt1))


)

Siendo la coordenada X del vector: (- (car pt2) (car pt1)) la Y (- (cadr pt2) (cadr pt1)) y
la Z (- (caddr pt2) (caddr pt1))

Bien, pues esto mismo lo podemos hacer con MAPCAR

(setq vector (mapcar '- pt2 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.

APPLY devuelve un elemento y MAPCAR una lista. Si tenemos:

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.

Para sumarlas utilizamos APPLY:

(apply '+ (mapcar '* vector vector))

Para terminar solo nos falta hacer la raiz cuadrada de lo anterior:

(setq modulo (sqrt (apply '+ (mapcar '* vector vector))))

Veamos otro ejemplo en el que se combinan APPLY y MAPCAR. Sean v1 y v2 dos


vectores vamos a calcular su producto escalar.

(setq v1 (list 10.0 10.0 0.0))

(setq v2 (list 5.0 0.0 0.0))

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 (+

(* (car v1) (car v2))

(* (cadr v1) (cadr v2))

(* (cadr v1) (cadr v2))

)
Pero resulta más sencillo utilizando MAPCAR y APPLY. Primero multiplicamos las
coordenadas de ambos vectores:

(mapcar '* v1 v2)

y luego las sumamos, de modo que...

(setq pescalar (apply '+ (mapcar '* vec1 vec2)))

(LAMBDA (lista_argumentos / variables_locales) expresión1


[expresión2] ...)

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...

(setq pt1 (getpoint "\nPunto 1: "))

(setq pt2 (getpoint pt1 "\nPunto 2: "))

Las coordenadas del punto medio serán las coordenadas de pt1 más las de pt2
divididas por 2.0. Para sumar sus coordenadas...

(mapcar '+ pt1 pt2)

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))

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:

(defun 2/ ( num ) (/ num 2.0))

Y ahora pasarle a MAPCAR la nueva función 2/ para que divida cada elemento de
(mapcar '+ pt1 pt2) por 2.0

(mapcar '2/ (mapcar '+ pt1 pt2))

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:

(lambda ( num ) (/ num 2.0))


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:

(mapcar '(lambda ( num ) (/ num 2.0)) (mapcar '+ pt1 pt2))

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.

Pantalla de texto y pantalla gráfica


En AutoCAD podemos pasar de pantalla gráfica a la pantalla de texto, y al revés,
pulsando la tecla de función F2. En AutoLISP también existen algunas funciones
para realizarlo directamente desde el código de nuestras rutinas.

(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.

(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.

El argumento formato puede tener dos valores:

• 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.

(atoms-family 1) así los elementos de la lista anterior serán cadenas de texto.

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.

(acad_strlsort (atoms-family 1)) devolverá la lista anterior ordenada

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:

(setq variable 12.5)

(atoms-family 1 (list "car" "cdr" "variable")) devolverá ("CAR" "CDR" "VARIABLE")


devuelve el nombre de la variable, no su valor.

Si definimos una función de usuario:

(defun 2+ ( numero / ) (+ numero 2.0))

La función 2+ recibe un número y le suma 2.0.

(atoms-family 1 (list "car" "cdr" "2+")) devolverá ("CAR" "CDR" "2+")

(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á +
Esto es lo mismo que haciamos en APPLY y MAPCAR:

(apply '+ (list 2.0 3.5 6.8))

pues el apóstrofe ' es el diminutivo o el alias de la función QUOTE.

(quote (setq a "texto" b 10.0)) devolverá (SETQ A "texto" B 10.0)

sería lo mismo escribir:

'(setq a "texto" b 10.0)

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 '(setq a "texto" b 10.0))

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)

Por tanto en lugar de:

(apply '+ (list 2.0 3.5 6.8))

podemos poner:

(apply '+ (quote (2.0 3.5 6.8)))

y también:

(apply '+ '(2.0 3.5 6.8))

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

(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.

Algunos ejemplos más:

(setq maximo (apply (quote max) (quote (10.5 15.2 9.3))))

(setq minimo (apply 'min '(10.5 15.2 9.3)))

(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)

Esta función evalúa la expresión que recibe.

(eval 2.5) devuelve el resultado de evaluar 2.5, es decir devuelve 2.5

(eval "Soy una cadena de texto") analogamente devolverá "Soy una cadena de texto"

Pero veamos un ejemplo algo más complicado:

(setq a 15.5)

(setq b (quote a))

¿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.

(+ b 10.0) dará un error

(eval b) devolverá 15.5 de modo que podemos hacer lo siguiente:

(+ (eval b) 10.0) devolverá 25.5

(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)

Esta función lee el texto que recibe y devuelve la primera palabra.

(read "AutoLISP") devuelve "AutoLISP"

(read "Curso de AutoLISP") devolverá "Curso"

(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:

(apply 'max (read "(15.2 9.3 15.5)")) devolverá el máximo de la lista de números
indicados

Y que pasa si hacemos:

(setq txt "(setq a 5.5)")

(read txt) devolverá (setq a 5.5)

y ahora podemos evaluarlo con EVAL

(eval (read txt)) devolverá 5.5 y asigna a la variable a el valor 5.5

¿Para qué sirve esto? Para solicitarle al usuario una expresión de AutoLISP y
evaluarla:

(setq txt (getstring T "\nExpresión de AutoLISP: "))

(setq valor (eval (read txt)))

En este caso asignamos a valor el resultado de evaluar una expresión de AutoLISP


introducida por el usuario.

(SET literal_de_símbolo expresión)

Esta función es muy parecida a SETQ, se diferencia en que espera el literal de un


símbolo. Veamoslo con ejemplos:

(setq num1 5.0) asigna a la variable num1 5.0

(set num2 5.5) da un error, ya que espera un literal

(set 'num2 5.5) asigna a la variable num2 el valor 5.5

Si probamos:

(setq (read "num1") 5.0) nos da un error, ya que SETQ no acepta una expresión
como símbolo.

Sin embargo SET si lo acepta:

(set (read "num2") 15.0) asigna a la variable num2 el valor 15.0

Carga automática de los archivos de AutoLISP


AutoCAD carga automáticamente dos archivos de AutoLISP, si es que existen y se
encuantran en los directorios de soporte. Se trata del ACAD.LSP y el
ACADDOC.LSP.
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.

Lo lógico será entonces guardar nuestras funciones en el ACADDOC.LSP y no en el


ACAD.LSP.

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.

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:

(load "circperi.lsp" (alert "Archivo Circperi.lsp no encontrado"))

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.

(AUTOLOAD archivo lista_comandos)

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.

Por ejemplo, si tenemos un archivo con 3 comandos:

(defun C:comando1 ( / ) ....)

(defun C:comando2 ( / ) ....)

(defun nosoyuncomando ( / ) ....)

(defun C:comando3 ( / ) ....)

En el archivo ACADDOC.LSP podriamos añadir la siguiente línea:

(autoload "archivo.lsp" '("comando1" "comando2"))

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.

¿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:

(load "Rag.lsp" (alert "Archivo Rag.lsp no encontrado"))

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...

Ejecución automática de código de AutoLISP


Hemos visto que el archivo ACADDOC.LSP se carga automáticamente en todos los
dibujos pero... ¿Cómo podemos ejecutar automáticamente una función al iniciarse
un dibujo?

Supongamos que tenemos definida la función GAR dentro del ACADDOC.LSP:

(defun GAR ...)

Para ejecutarla, tan solo debemos añadir en dicho archivo la siguiente línea de
código:

(GAR)

En caso de que dicha función tenga argumentos, estos deben indicarse:

(GAR 3.141592)

¿Cómo ejecutariamos automaticamente el comando C:CIRCPERI?

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:

(C:CIRCPERI)

De modo que podriamos incluir en el archivo ACADDOC.LSP las dos siguientes


líneas de código:

(load "circperi.lsp" (alert "Archivo Circperi.lsp no encontrado")) ; Carga el comando

(C:CIRCPERI) ; Ejecuta el comando

¿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:

(setq pto (getpoint "\nCentro del círculo: "))

(setq peri (getdist pto "\nPerímetro: "))

(command "_.circle" pto (/ peri (* 2.0 PI)))

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.

¿Para qué se suele utilizar este método?

Por ejemplo, para mostrar mensajes informativos o de bienvenida:

(alert "¿Estas seguro de que quieres utilizar AutoCAD?")

(alert "¿Estas realmente seguro?")

(alert "¿Seguro, seguro?")

Aunque no os aconsejo que utilieis este ejemplo, os cansariais de él a los 5 minutos.

Además de mensajes de bienvenida podeis hacer otras cosas más útiles, como
asignar valores iniciales a algunas variables:

(setq directorio "c:\\rutinas\\")

También podeis modificar el valor de alguna variable de sistema de AutoCAD:

(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.

Para definir la función S::STARTUP se utiliza DEFUN, al igual que para cualquier otra
función.

(defun S::STARTUP ( / )

(setq directorio "c:\\rutinas\\")

)
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.

Operaciones con archivos


Antes de comenzar con el tema de lectura y escritura de archivos, veamos una
pequeña rutina... CARGALISP

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.

(defun CARGALISP ( lst / )

(while lst

(if (not (apply 'and (atoms-family 1 (cdar lst))))

(progn

(load (caar lst))

(prompt (strcat "\nCargando archivo " (caar lst) "..."))

(setq lst (cdr lst))

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 "c:\\noexisto.bat") debería devolver nil

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:

1. Antes de cargar un archivo con LOAD o AUTOLOAD


2. Antes de abrir un fichero de texto, ya sea para leerlo o escribir en él.
3. Antes de "abrir" un archivo de dibujo en AutoCAD.
4. Antes de cargar y utilizar un archivo DCL (Letrero de dialogo) en AutoLISP.

Por tanto, deberiamos modificar el código de la función CARGALISP para


comprobar la existencia de los archivos a cargar.

(defun CARGALISP ( lst / )

(while lst

(if (not (apply 'and (atoms-family 1 (cdar lst))))

;; Comprueba si todas las funciones del primer elemento de la lista

;; están cargadas

(if (not (findfile (caar lst)))

(alert (strcat "No se ha encontrado el archivo " (caar lst)))

;; No se encuentra el archivo

(progn

(load (caar lst))

;; Carga el archivo

(prompt (strcat "\nCargando archivo " (caar lst) "..."))

;; Indica que se ha cargado el archivo

;; Se encuentra el archivo
)

(setq lst (cdr lst))

;; Pasa al siguiente elemento de la lista

;; Para cada elemento de la lista

(GETFILED titulo_letrero archivo_defecto extensión_defecto modo)

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.

El argumento modo es de tipo binario y ofrece los siguientes valores:

• 1 --> Para designar un archivo nuevo, es decir, que no exista.


• 2 --> Desactiva la casilla "Teclearlo".
• 4 --> Permite indicar cualquier extensión.
• 8 --> Devuelve el nombre del archivo, sin la ruta

(getfiled "Selecciona un archivo" "c:\\autoexec.bat" "bat" 0)

No pasa nada al pulsar en Abrir, ya que no se abre el archivo. GETFILED tan solo
nos deja seleccionarlo y devuelve su nombre.

(getfiled "Selecciona un archivo nuevo" "" "" 1)

Lectura y escritura de archivos de texto


AutoLISP nos permite leer y escribir archivos de texto ASCII.

Al utilizar archivos de texto en nuestras rutinas debemos siempre seguir los


siguientes pasos:

1. Comprobar si existe el archivo.


2. Abrir el archivo.
3. Leer o escribir en el archivo.
4. Cerrar el archivo.

(OPEN archivo modo)

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 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:

• Podemos abrir un archivo para leerlo --> modo "r"


• Podemos abrir un archivo de texto para escribir en él comenzando desde la
primera línea --> modo "w"
• Podemos abrir un archivo de texto para escribir en él a partir de la última
línea --> modo "a"

Para abrir el archivo autoexec.bat en modo lectura:

(open "c:\\autoexec.bat" "r")

Devuelve #<file "c:\\autoexec.bat"> es decir, el descriptor del archivo.

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?

(setq darch (open "c:\\autoexec.bat" "r"))

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.

Escritura de archivos de texto


Para crear un archivo de texto debemos abrirlo en modo escritura:

(setq darch (open "c:\\nuevo.txt" "w"))

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.

Bien, ya tenemos abierto nuestro archivo de texto, vamos a escribir en él:

(PRIN1 [expresión [descriptor_archivo]])

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:

(setq a 5.5 b "Curso de AutoLISP")

(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)

Escribirá en la ventana de comandos "Curso de AutoLISP".

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.

(prin1 'a) escribe a

(prin1 (+ 15 5.5)) escribe 20.5

Hemos indicado una expresión como argumento, si utilizamos PROMPT en lugar de


PRIN1 dará un error.

(prin1 '(+ 15 5.5)) escribe (+ 15 5.5)

Si no se indica la expresión a escribir, escribe una cadena nula

(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.

(prin1 (strcat "\t" b)) devuelve "\tCurso de AutoLISP"


PRIN1 no entiende los caracteres de control como "\n" "\t" etc

(PRINT [expresión [descriptor_archivo]])

Esta función es muy parecida a PRIN1, veamos en que se diferencian...

(progn (prin1 a) (prin1 a)) escribe 5.55.5

(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.

(PRINC [expresión [descriptor_archivo]])

Esta función también es muy parecida a las anteriores.

(progn (princ a) (princ a)) escribe 5.55.5

(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.

(WRITE-CHAR código_ASCII [descriptor_archivo])

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.

(write-char 65) escribe A. Es lo mismo que escribir (write-char (ascii "A"))

(WRITE-LINE texto [descriptor_archivo])

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.

(write-line "Curso de AutoLISP")

Lectura de archivos de texto


Para leer un archivo de texto debemos abrirlo en modo lectura:
(setq darch (open "c:\\autoexec.bat" "r"))

(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-char) escribe un caracter y devolverá su código ASCII

(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

También podría gustarte