Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Í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:
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.
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.
Los primeros ejemplos que vamos a ver son sencillos y cortitos, así que puedes
teclearlos directamente en la ventana de comandos de AutoCAD.
Ejemplo:
Prueba también:
(- 17 2)
(+ 2.5 22.8)
(- 0.25 22.5)
(+ 12 -2 31 -7.5)
Ejemplo:
• 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)
(/ 5 2.0)
Ejemplo:
Notación empleada
Para describir las funciones de AutoLISP que se expliquen a lo largo del curso se
seguirá la siguiente notación:
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.
¿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.
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:
¿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.
Prueba también las siguientes expresiones y comprueba los valores asignados a las
variables:
(SETQ c (+ b 3)) Los valores de las variables también pueden utilizarse para realizar
operaciones y asignar el resultado de dichas operaciones a una variable.
(SETQ a 3.5) Se puede modificar el valor asociado a una variable, por eso se llama
variable.
(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 b T)
(SETQ c nil)
Por último veamos porque no debemos emplear para los nombres de las variables
los nombres de las funciones de AutoLISP.
(+ 3 9) devuelve 12
(+ 3.0 9) devuelve 12.0
(+ ) devuelve 0
Resta al primer número todos los siguientes números pasados como argumentos. Si
todos los números de la lista son enteros, el resultado también será entero.
(- 11 5) devuelve 6
(- ) devuelve 0
(* 3 9) devuelve 27
(* ) devuelve 0
(/ 45 5 3) devuelve 3
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
(/ ) devuelve 0
(1+ <número> )
(1+ 5) devuelve 6. Ojo entre "1" y "+" no debe haber ningún espacio, ya que el
nombre de la función es "1+".
(1+ 0) devuelve 1
(SETQ i 1)
(1- <número> )
(1- 5) devuelve 4. Ojo entre "1" y "-" no debe haber ningún espacio, ya que el nombre
de la función es "1-".
(1- 0) devuelve -1
(GETINT [mensaje] )
(GETINT)
Podemos asignar el valor introducido por el usuario a una variable y así utilizarlo
después.
(SETQ edad (+ edad 1)) nos dará la edad que tendrás el próximo año.
En este caso mens es una variable que almacena una cadena de texto, pero no es
una cadena de texto. Por lo tanto, no debe ir entre comillas.
Que pasará si como respuesta a la solicitud de la edad del usuario se introduce !a.
Parece lógico que le estamos indicando el valor de la variable a, que contiene el
valor numérico 27, de modo que la variable edad debería tomar el valor 27, pero
observaras que no es así. Haz la prueba: nos indicará que es "Imposible volver a
entrar en LISP.". Esto es debido a que al ejecutar la función GETINT se está
ejecutando el interprete de LISP, y al indicar !a como respuesta estamos diciendo
que ejecute el interprete de LISP para obtener el valor asociado a la variable a. Como
el 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)
(SETQ peso (- peso 1)) nos dará el peso que tendrás si adelgazas un kilo.
• Los argumentos son valores que recibirá la función cuando sea ejecutada
por el usuario, o llamada desde otras funciones.
• Las variables locales son aquellas variables que se emplearán tan solo
dentro de la función que queremos definir, de modo que restringimos su uso
al entorno de la función.
Por último, se añaden las expresiones que ejecutará la función. Veamos algunos
ejemplos:
En este caso hemos definido una nueva función llamada "2+", que recibe como
argumento un número y realiza la suma de ese número y 2. Las funciones de
AutoLISP las ejecutabamos escribiendo (1+ 5) directamente desde la línea de
comandos, bien pues las funciones de usuario se ejecutan exactamente igual.
Prueba:
(2+ 5) devuelve 7
(2+ 0) devuelve 2
(2+ "texto") indica "; error: tipo de argumento erróneo: numberp: "texto""
Podrías pensar que el número indicado se almacena en una variable llamada "valor",
pero no es así. Compruebalo escribiendo !valor en la línea de comandos, lo que nos
devolverá nil.
(2+ 5) devuelve 7
(2+ 4) devuelve 6
¿Que valor crees que tendrá la variable valor? Pensemos un poco.
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.
(2+ 4)
!inicial devuelve 4
Observa que "valor" se comporta como una variable local, solo se emplea dentro de
la función. Sin embargo "inicial" es de ámbito global, es decir sigue empleandose al
salir de la función. Vamos a modificar un poquito más nuestra función:
Ahora hemos añadido la variable "inicial" a la lista de variables locales, con lo que
su ámbito será local. Para comprobarlo...
!inicial devuelve 4
(2+ 3)
Ahora la función "2+" no tiene argumentos, así que para ejecutarla tan solo
debemos poner su nombre entre paréntesis:
(2+)
(DEFUN C:2+ ( / valor ) (SETQ valor (GETINT "Número: ")) (SETQ valor (+ valor 2)))
2+
(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.
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:
• Comentarios en el código
• Funciones de AutoLISP
• Números
• Textos
• FUNCIONES DE USUARIO Y NUEVOS COMANDOS
• Nombres de variables, paréntesis, etc...
En la primera línea "(defun RAG ( ang )" definimos la función RAG (Radianes A
Grados) que recibe un argumento "ang" (el ángulo en radianes).
Veamos como funciona la segunda línea: Por la Regla número 2, primero se evaluará
la lista interior (* ang 180.0) que multiplica el ángulo en radianes "ang" por 180.0 y
devuelve el resultado a la lista de nivel superior (recuerda la Regla número 3) que lo
divide entre pi = 3.141592... devolviendo a su vez el resultado de la división (el
ángulo en grados decimales) a la lista de nivel superior, defun.
La última línea cierra la lista de la función defun, verificandose así la Regla número
1.
Nuestra función RAG se ejecutaría así: (RAG 1.57) siendo 1.57 el ángulo en radianes,
y devolvería ese ángulo en grados decimales (aproximadamente 90º). No es
necesario poner el nombre de la función RAG en mayúsculas, ya que AutoLISP no
diferencia las mayúsculas de las minúsculas (salvo en contadas excepciones que ya
explicaremos). Aunque al poner los nombres de las funciones en mayúsculas se
diferencian perfectamente del resto del código.
Tal vez penseis que no es tan importante añadir explicaciones en el código. Pero si
teneis que leer una rutina que creasteis hace meses os serán muy útiles, por que
seguramente no recordareis muy bien como funciona. Además, si alguien va a leer
alguna de vuestras rutinas le facilitariais mucho la labor. Al igual que a vosotros os
será más sencillo leer y entender una rutina con abundantes comentarios.
)
El editor de Visual LISP
El editor de Visual Lisp se inicia desde AutoCAD de varias formas:
;;;________________________MecaniCAD__________________________;;;
;;;_____________http://www.peletash.com/mecanicad_____________;;;
;;;_________________________RAG.LSP___________________________;;;
;;;_______________________Versión 1.0_________________________;;;
;;;________________________21/02/2002_________________________;;;
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.
Si estamos en el editor de Visual LISP y tenemos abierto un archivo con una rutina,
para cargarla en AutoCAD podemos:
• (rag 0)
• (rag pi)
• (rag (/ pi 4))
(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")
;;;________________________MecaniCAD__________________________;;;
;;;_____________http://www.peletash.com/mecanicad_____________;;;
;;;_________________________GAR.LSP___________________________;;;
;;;_______________________Versión 1.0_________________________;;;
;;;________________________21/02/2002_________________________;;;
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 0) devuelve 0
(FIX numero)
(fix 15.8) devuelve 15. Ojo! no redondea, sino que elimina lo que está detras del
punto decimal.
(rem 3 2) devuelve 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.
(SIN angulo)
Devuelve el seno de un ángulo indicado en radianes.
(COS angulo)
Funciona igual que la anterior, pero devuelve el coseno del ángulo, que hay que
pasarselo en radianes.
Si se indica un segundo número (ATAN num1 num2) lo que hace es dividir num1
entre num2 y devuelve el arco cuya tangente sea el resultado de la división. Esto se
hace para facilitar lo siguiente...
(SQRT numero)
Esta función devuelve la raiz cuadrada del numero que recibe como argumento.
Siempre devuelve un número real, no entero.
(expt 2 2) devuelve 4
(expt 2 3) devuelve 8
Si todos los números que recibe como argumentos son enteros, devuelve un
número entero y si alguno de ellos es un número real, devuelve un real.
(EXP num)
(LOG numero)
Esta función devuelve el logaritmo neperiano del número que recibe como
argumento.
Esta función recibe dos números enteros y devuelve su máximo común divisor (o
denominador). Siempre devuelve un número entero.
(gcd 15 5) devuelve 5
(max 2 4 1 3 6) devuelve 6
Si todos los números que recibe como argumentos son enteros, devuelve un
número entero y si alguno de ellos es un número real, devuelve un real.
(min 2 3 6) devuelve 2
Si todos los números que recibe como argumentos son enteros, devuelve un
número entero y si alguno de ellos es un número real, devuelve un real.
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 (setq var1 nil) "Cual es tu nombre?")) no permite responder
con espacios, ya que (setq var1 nil) devuelve nil.
Esta función le pide un punto al usuario y devuelve una lista con las coordenadas
del punto indicado. El usuario puede indicar el punto en pantalla con el digitalizador
(ratón) o tecleando sus coordenadas, tal y como se haría al dibujar en AutoCAD.
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.
Aparece una línea elástica entre el punto pto y la posición del cursor.
Esta función se utiliza también para solicitar puntos al usuario. En este caso el
punto base no es opcional, hay que indicarlo. Veamos la diferencia entre las dos
expresiones siguientes:
Al utilizar getpoint, se muestra una línea elástica entre el punto pto y la posición del
cursor. Si se utiliza getcorner, en lugar de una línea elástica, aparece un rectángulo.
(CAR lista)
Esta función devuelve el primer elemento de la lista que recibe como argumento.
De modo que si (siguiendo con el ejemplo del tema anterior) en la variable pto
hemos asignado el valor devuelto por getpoint, tenemos una lista con las
coordenadas X, Y y Z del punto designado. Supongamos que pto = (10.0 20.0 0.0).
(CDR lista)
Esta función devuelve la lista que recibe como argumento pero sin el primer
elemento.
(cdr pto) devolverá una lista formada por las coordenadas Y y Z del punto pto. Es
decir, (20.0 0.0)
De modo que (cdr pto) devuelve (Y Z). Así que para obtener la coordenada Y:
Las funciones CAR y CDR se pueden agrupar. Para ello, existen una serie de
funciones que se denominan juntando las Aes y las Des de cAr y cDr
respectivamente. El ejemplo anterior, queda:
Esto nos servirá como regla mnemotécnica para recordar el nombre de estas
funciones. Tan solo se permiten 4 niveles de anidación, así que entre la c y la r solo
puede haber 4 letras (Aes o Des).
Como obtendríamos 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:
Cómo obtener d ??
(cDr lst) devuelve ((c d) (e f)) y (cAr (cDr lst)) el primer elemento de ((c d) (e f)), es
decir devuelve (c d).
Si ahora hacemos (cDr (cAr (cDr lst))) obtenemos (d), que no es lo mismo que d. Ya
que se trata de una lista cuyo primer elemento es d. Así que:
Y cómo obtener e ??
(cDr lst) devuelve ((c d) (e f)) y (cDr (cDr lst)) devuelve ((e f)). Ojo! se trata de una
lista cuyo primer (y único) elemento es a su vez otra lista con dos elementos.
Así que (cAr (cDr (cDr lst))) devuelve (e f), y para obtener e:
(cAr (cDr (cDr lst))) devuelve (e f) tal como se vio en el ejemplo anterior. Así que (cDr
(cAr (cDr (cDr lst)))) devuelve (f), que no es lo mismo que f. Por tanto:
(cAr (cDr (cAr (cDr (cDr lst))))) devuelve f. Podriamos pensar que:
(cADADDr lst) también devuelve f. Pero al ejecutar esta línea AutoCAD nos dice que
la función cADADDr no está definida. Ya dijimos antes que se pueden agrupar hasta
4 funciones cAr y cDr, pero aqui estamos intentando agrupar 5, y logicamente no
podemos. Para obtener f podriamos hacer, por ejemplo:
(LENGTH lista)
En la variable pto teniamos una lista con las coordenadas de un punto, pero si solo
trabajamos en 2D, la Z no nos interesa. Así que muchas veces los puntos tan solo
tendran 2 coordenadas (X Y). Pero para un programa no es lo mismo que tenga 2
que 3 coordenadas, a lo mejor va a buscar la coordenada Z y no existe
produciendose un error en nuestra rutina.
Así que necesitamos conocer el número de elementos que tienen las listas. Para ello
se utiliza la función length, que devuelve el número de elementos de la lista que
recibe como argumento. Por ejemplo:
Qué devolvería (length (car lst)) ??? El número de elementos del primer elemento de
lst, es decir el número de elementos de (a b), que es 2.
Esta función devuelve una lista formada por los elementos indicados. De modo que
se utiliza para crear listas. Por ejemplo:
(list 1 2 3) devuelve (1 2 3)
Veamos (list "a" "b") devuelve ("a" "b") y (list "d" "e") devuelve ("d" "e"). Así que
(list (list "a" "b") "c" (list "d" "e") "f") devuelve (("a" "b") "c" ("d" "e") "f").
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) " ")
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...
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.
(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.
Si redefinimos el comando línea para que dibuje circulos, entonces debemos indicar
(command "linea" centro radio) y no (command "linea" pt0 pt1"") que no funcionaría,
puesto que le estamos pasando unos parámetros que no espera. Pero cómo
hacemos ahora para dibujar una línea?
Para ejecutar los comandos originales de AutoCAD, y no los redefinidos (si lo están)
debemos anteponer al nombre del comando un punto, por ejemplo:
Podemos además indicar los comandos en Inglés anteponiendoles un guión bajo así
que también podriamos escribirlo así:
(defun C:LINEA ( )
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.
(= 5 5.0) devuelve T
(/= 6 6.0) devuelve nil, porque 6 y 6.0 no son distintos (valen lo mismo).
(< 1 2 3) devuelve T
(< "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".
(> 5 3) devuelve T
(> 5 4 3) devuelve T
(> "a" "c") devuelve nil, pues el código ASCII de "a" es menor que el de "c".
(>= 3 3 3 3) devuelve T
Qué es lo primero que hay que hacer ??? Esta respuesta tiene que ser instintiva,
como un acto reflejo: El pseudocódigo.
(defun C:CIRCPERI ( )
4) Dibujar la circunferencia
Sólo nos falta una cosa. Pista: El número de paréntesis de apertura tiene que ser
igual al número de paréntesis de cierre. Así que:
)
El código completo de la rutina sería:
(defun C:CIRCPERI ( )
También deberias añadir comentarios al código y una cabecera con varias líneas de
comentarios en el archivo LSP indicando El nombre de la rutina, fecha, autor, etc.
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.
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.
Preguntándolo de otra forma: Alguna de las expresiones que recibe AND como
argumentos es nil? No, así que AND devuelve T.
(and a b) devolverá T
(and a d)
(and a b c d) devolverá nil, porque d es nil
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 a b) devuelve T
(setq pt1 (list 10.0 20.0 0.0) pt2 (list 10.0 (* 10.0 2) 0.0) pt3 (list 9.99 20.0 0.0) pt4 (list
9.99 20.02 0.0))
Al comparar estas listas (puntos) con la función = siempre nos devuelve nil. Aunque
pt1 y pt2 sean iguales, y muy parecidos a pt3 y pt4. Por tanto, la función = no nos
servirá para comparar listas. Para comparar dos listas se utilizará la función EQUAL.
(equal 5 5.0) devuelve T, al igual q la función =, porque 5 vale lo mismo que 5.0
(equal 4.99 5.0 0.1) devuelve T, porque compara 4.99 y 5 pero con una precisión de
0.1. Así que con esa precisión 4.99 == 5.0
¿Y que pasa con las listas? ¿También podemos indicarle una precisión? Veamoslo..
(equal pt1 pt3 0.1) devuelve T, porque compara los elementos de las listas
(coordenadas) con la precisión que hemos indicado, 0.1
Si subimos la precisión...
(equal pt1 pt4 0.02) devuelve T, porque la precisión es 0.02 que es exactamente la
diferencia entre 20.02 y 20
El utilizar una precisión muy elevada no implica que todas las comparaciones
devuelvan T, todo dependerá de los valores a comparar:
(NOT expr)
Si hacemos...
(ALERT mensaje)
Muestra el texto que recibe como argumento en un letrero. También devuelve nil. Se
utiliza principalmente para avisar al usuario en caso de error, o para mostrarle
alguna información importante.
(TERPRI)
Para que los mensajes aparezcan en líneas distintas, se puede emplear la función
TERPRI. Ya que esta función mueve el cursor a una nueva línea en la ventana de
comandos de AutoCAD, y devuelve nil. El código de la rutina quedaría así:
(defun C:CIRCPERI ( )
Creo que ya se dijo que AutoLISP no reconoce una serie de caracteres, por ejemplo
al indicar la ruta de un archivo no reconoce el caracter "\" y hay que indicarselo así:
"\\". Pues existen más caracteres de control predefinidos:
• \\ Equivale al caracter \
• \" Equivale al caracter "
• \e Equivale a ESCape
• \n Equivale al retorno de carro
• \r Equivale a pulsar Intro
• \t Equivale a pulsar la tecla del tabulador
\" nos permite escribir las comillas dentro de un texto. Por ejemplo:
(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.
(defun C:CIRCPERI ( )
Un par de cosas más con respecto a esta rutina... Cuando se crea un archivo LISP
en el que está definido un nuevo comando es bastante útil añadir al final de todo el
código algo similar a...
Esta línea se pondría después del paréntesis de cierre de defun. Es decir, que
cuando se ejecuta CIRCPERI desde AutoCAD esta línea no se evalúa. Para qué
ponerla entonces?? Pues muy sencillo... para que cuando se cargue el archivo en
AutoCAD muestre en pantalla: Nuevo comando CIRCPERI cargado. Así el usuario
sabe cual es el nombre del comando definido en el archivo que se acaba de cargar.
De modo que el mensaje sólo se mostrará al cargar el archivo.
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.
;;;________________________MecaniCAD__________________________;;;
;;;_____________http://www.peletash.com/mecanicad_____________;;;
;;;_______________________CIRCPERI.LSP________________________;;;
;;;_______________________Versión 1.1_________________________;;;
;;;________________________26/02/2002_________________________;;;
;;; de su perímetro.
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 "El límite superior debe ser mayor que el inferior") (setq limsup (getint
"\nLímite superior")) (setq intervalo (- limsup liminf))
Viendo el código anterior, tal vez pienses que si la condición (> liminf limsup) se
cumple, entonces se evaluará la línea siguiente de código. Pero no es así, se evalúa
la expresión si cumple, que es la primera expresión (alert "El límite superior debe
ser mayor que el inferior"). Si la condición no se cumple, devuelve nil, se evaluará la
expresión no cumple, que en este caso será (setq limsup (getint "\nLímite
superior")).
Tanto la expresión si cumple, como la no cumple solo pueden ser una única
expresión. El ejemplo de código anterior nos daría un error ya que IF no puede tener
más que 3 expresiones:
• La condición
• La expresión si cumple
• La expresión no cumple
(progn
(progn
En este caso la condición es (and (= 2 2.0) (< 2 3)) que en este caso devolvería T, ya
que se verifican las dos expresiones que recibe la función AND.
En este caso si var1 es nil, (not var1) devolverá T, indicando que la variable no se ha
definido. En caso contrario, (not var1) devolverá nil evaluandose la expresión no
cumple. Otro método para hacer lo mismo, sería:
(if var1
Si var1 es distinto de nil, se evalúa la expresión si cumple. En caso de que var1 sea
nil, se evaluaría la expresión no cumple.
Por lo tanto, COND evaluará las expresiones si cumple de la primera condición que
se verifique. Y devolverá el resultado de evaluar la última expresión si cumple.
Pongamos un ejemplo:
(cond
((= a b)
((< a c)
((< a b))
(T
Supongamos ahora que antes de la expresión COND, hemos definido (setq a 2 b 2.0
c 3.5). En este caso, al entrar en COND se evaluará la primera condición (= a b) que
se verifica, de modo que se evaluaran las expresiones si cumple de esta condición:
(alert "A y B son iguales") y (setq b (getreal "Introduzca de nuevo B: ")) finalizando la
expresión COND que devuelve el nuevo valor de b.
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.
(cond
(T
((= a b)
((< a c)
((< a b))
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 "blipmode" 0)
(getvar "luprec")
(setvar "luprec" 2)
(getvar "luprec")
(ITOA entero)
(ATOI texto)
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.
(FLOAT numero)
pero tb puedo hacer... (float 5.36) devuelve 5.36 lo cual sería una tontería porque no
hace nada.
(ATOF texto)
Veamos para qué sirven los argumentos opcionales [modo [precisión]]. Modo,
permite indicar un tipo de expresar los números, es decir, permite seleccionar el
formato utilizado para los números. Hay 5 opciones:
1. Formato científico.
2. Decimal. Es el que se usa habitualmente.
3. Pies y pulgadas.
4. Pies y pulgadas en fracciones.
5. Fracciones.
Precisión nos permite definir el número de decimales que deseamos.. por ejemplo:
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.
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.
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
• 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"
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".
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:
(setvar "cmdecho" 0)
(setvar "cmdecho" 0)
(if (= cmd0 1)
(setvar "cmdecho" 1)
(setvar "cmdecho" 0)
(if (= cmd0 1)
(setvar "cmdecho" 1)
Aún nos queda una cosa... al ejecutar ahora la rutina muestra el siguiente texto:
Centro de la circunferencia
(setvar "cmdecho" 0)
(setvar "cmdecho" 1)
(princ)
En este tema no vamos a ver ninguna función de AutoLISP. Vamos a crear un nuevo
comando y a repasar un poco el comando CIRCPERI.
AutoCAD ya tiene un comando que se llama arandela (en inglés donuts) así que
buscaremos otro nombre para nuestra rutina, por ejemplo ARAND. Es mejor utilizar
nombres más bien cortos y que evoquen a la función que tiene el comando.
(defun C:ARAND ( )
(defun C:ARAND ( )
(setvar "cmdecho" 0)
(setvar "cmdecho" 1)
(princ)
Además de las mejoras indicadas anteriormente, ahora la variable rad se utiliza tanto
para almacenar el radio de la circunferencia interior como el de la exterior. ¿Puede
dar lugar esto a algún problema? Veamoslo:
De este modo nos ahorramos una variable. Si es posible conviene reutilizar las
variables.
"Control"
"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".
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".
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.
(setvar "cmdecho" 0)
(if (= cmd0 1)
(setvar "cmdecho" 1)
)
(princ)
(setvar "cmdecho" 0)
(if (= cmd0 1)
(setvar "cmdecho" 1)
)
(command "_.undo" "_end")
(princ)
¿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"
(princ)
(princ)
No nos paremos demasiado aqui, ya lo veremos bien más adelante. Hemos creado
una función llamada ERRORES para el tratamiento de errores en nuestras rutinas.
Ahora habrá que decirle al comando ARAND que utilice esta función de tratamiento
de errores.
Antes, conviene explicar cómo se pueden redefinir las funciones de AutoLISP. Hay
métodos:
1. Crear una función con el mismo nombre. Por ejemplo, (defun SIN ... Esto
redefiniría la función SIN de AutoLISP.
2. Asignarle un valor distinto mediante setq. Por ejemplo, si hacemos (setq sin
cos) la función SIN de AutoLISP pasará a funcionar como la función COS,
devolviendo el coseno de un ángulo en lugar de el seno. (sin 0.0) ahora
devolvería 1.0 en lugar de 0.
(setvar "cmdecho" 0)
(if (= cmd0 1)
(setvar "cmdecho" 1)
(princ)
(setvar "cmdecho" 0)
(if (= cmd0 1)
(setvar "cmdecho" 1)
(princ)
(princ)
(if (= cmd0 1)
(setvar "cmdecho" 1)
(princ)
Ahora nuestra función ERRORES restituye el valor inicial del eco de mensajes y la
función de tratamiento de errores inicial. Puede extrañar que se utilicen las variables
cmd0 y error0 dentro de la función ERRORES, ya que estaban definidas como
variables locales en CIRCPERI.
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.
(princ)
(if (= cmd0 1)
(setvar "cmdecho" 1)
(princ)
Ahora, bastaría con ejecutar el comando deshacer una única vez para que se
deshaga todo lo que hizo el comando ARAND. Podemos incluso decirle a la función
ERRORES que si se produce un error, ejecute el comando deshacer directamente:
(princ)
(if (= cmd0 1)
(setvar "cmdecho" 1)
(princ)
)
En este caso si se produce un error ni siquiera hace falta ejecutar el comando
deshacer, la función ERRORES ya lo hace por nosotros.
Para terminar el tema, vamos a añadirle una nueva opción a CIRCPERI. Se trata de
que ofrezca por defecto el perímetro del último circulo dibujado. Podríamos hacer lo
siguiente:
(setvar "cmdecho" 0)
(progn
(if (= cmd0 1)
(setvar "cmdecho" 1)
(princ)
(progn
Si rad es igual a nil, no se ha definido, significa que es la primera vez que se ejecuta
el comando CIRCPERI en el dibujo actual. En este caso se muestra un mensaje
solicitando el perímetro del círculo.
(setvar "cmdecho" 0)
(progn
(if (= cmd0 1)
(setvar "cmdecho" 1)
(princ)
Ahora ofrecerá por defecto el perímetro del último círculo dibujado en AutoCAD,
independientemente de si dicho círculo se creo con la rutina CIRCPERI, con el
comando CIRCULO o por cualquier otro medio. Veamos como funcionan los
cambios que hemos realizado:
(progn
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 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)
(initget (+ 1 2))
(initget 7)
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.
En estos casos aparece una línea, o un rectángulo, elástico con origen en el punto
base pto. Estas líneas y rectángulos elásticos se muestran con línea continua. Pero
si antes de la función de solicitud se añade (initget 32) se mostraran con líneas
discontínuas. Por ejemplo:
(initget 32)
(initget 32)
Veamos ahora como funciona el código 64 --> Hace que GETdist devuelva la
distancia en 2D. Es como si proyectase la distancia real sobre el plano XY.
(initget 64)
(initget 128)
En este caso el usuario podría indicar como respuesta a la solicitud del ángulo:
(RAG (/ pi 4)) Es decir, un ángulo de 45º.
Pues llegados a este punto, antes de ver el segundo argumento de (INITGET [modo]
[palabras_clave]), es decir, las palabras clave. Vamos a modificar nuestras rutinas
ARAND y CIRCPERI. Comenzamos con CIRCPERI:
(setvar "cmdecho" 0)
(progn
(if (= cmd0 1)
(setvar "cmdecho" 1)
(princ)
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 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.
...
(initget 1)
...
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:
Si ponemos (initget 1), el usuario no podrá indicar Intro, así que nunca se ejecutaría
la expresión no cumple. Por tanto no añadimos el código 1 a Initget.
El perímetro del cículo no puede ser ni cero ni un número negativo, de modo que
podemos añadir a initget los códigos 2 y 4. También podríamos añadirle el código 64
para que GETdist devuelva la distancia en 2D. Pero normalmente no conviene añadir
este código, excepto cuando la distancia "debe" siempre ser en 2D.
Una última nota sobre CIRCPERI... Cuando ejecutamos la rutina por primera vez en
un dibujo en el que no se halla dibujado ningún círculo, la variable de sistema
"circlerad" tiene el valor 0.0. En este caso, no ofrecemos la opción de seleccionar el
radio del último círculo dibujado pulsando Intro, ya que no existe ningún círculo
dibujado previamente. En este caso, no deberíamos indicar el modo 6 a Initget, sino
el 7 para que tampoco permita al usuario indicar Intro como respuesta. Veamos
como se soluciona en el código completo de la rutina:
(setvar "cmdecho" 0)
(initget 1)
(progn
(initget 7)
(progn
(initget 6)
(if (= cmd0 1)
(setvar "cmdecho" 1)
(princ)
(setvar "cmdecho" 0)
(setvar "cmdecho" 1)
(princ)
(initget 1)
(setvar "cmdecho" 0)
(initget 1)
(initget 7)
(initget 7)
(if (= cmd0 1)
(setvar "cmdecho" 1)
(princ)
[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:
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:
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:
Como mejor se ve es con un ejemplo así que... Vamos a modificar la rutina ARAND
añadiendo una opción para indicar los diámetros en lugar del radio. Teníamos el
siguiente código:
(setvar "cmdecho" 0)
(initget 1)
(initget 7)
(initget 7)
(if (= cmd0 1)
(setvar "cmdecho" 1)
(princ)
)
(initget 7)
(initget 7 "Diametro")
Y a continuación le decimos al usuario que existe una opción llamada Diametro que
puede seleccionar como respuesta:
De esta forma ya hemos añadido la opción Diametro, pero ¿Qué sucede si el usuario
indica como respuesta la opción Diametro? Pues que asignariamos a la variable rad
la cadena de texto "Diametro" y en la siguiente línea (command "_.circle" pto rad) al
intentar dibujar el círculo, nos daría un error.
Así que hay que modificar la rutina añadiendo a continuación algo parecido a: Si el
usuario selecciona la opción Diametro --> Preguntamos por el diámetro. Pues esto
en AutoLISP, sería:
Hay que tener especial cuidado con la condición del IF (= rad "Diametro") ya que hay
que indicar la palabra clave tal y como aparece en la lista de palabras clave de
initget. Es decir, no funcionaría si ponemos (= rad "diametro") o (= rad "Diámetro")
ya que en el primer caso no ponemos la "D" en mayúsculas y en el segundo hemos
tildado la "a".
Si el usuario indica una distancia la asigna a la variable rad y luego (if (= rad
"Diametro")... devuelve nil, puesto que rad es distinto de "Diametro".
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: "))
Pues que asignaría a la variable rad el resultado de dividir -50 o 0 entre 2.0. Por tanto
tendriamos un círculo con radio negativo o cero. Recuerda que initget solo tiene
efecto sobre la siguiente función de solicitud de datos, de modo que tenemos que
añadir de nuevo la función Initget antes de preguntar por el diámetro:
(initget 7 "Diametro")
(progn
(initget 7)
Se ha añadido la función Progn ya que sino, solo puedo ejecutar una expresión.
Para el radio o diámetro exterior se haría exactamente lo mismo. Por tanto el código
completo sería:
(setvar "cmdecho" 0)
(initget 1)
(progn
(initget 7)
(initget 7 "Diametro")
(progn
(initget 7)
(if (= cmd0 1)
(setvar "cmdecho" 1)
(princ)
(GETKWORD [mensaje])
Esta función de AutoLISP se utiliza para obtener una opción indicada por el usuario.
Se utiliza en combinación con INITGET, por ejemplo:
En este caso el usuario tan solo podrá indicar como respuesta una de las palabras
clave de la función Initget y se la asigna a la variable opc.
No hemos indicado el modo en Initget, tal solo las palabras clave. En la rutina
ARAND nos interesaba que apareciera el modo 7 para que no se indique como
respuesta Intro, 0 o un número negativo. Pero no es obligatorio indicar siempre un
modo.
(cond
(T
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.
(setq i 0)
(prompt "\n")
(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.
Fíjate que una de las expresiones que se ejecutan dentro del bucle es (setq i (1+ i) es
decir, movemos el contador. Si no lo hicieramos, i siempre sería menor que 10 y se
entraría en un bucle sin fin, que da lugar a un error.
(setq i 10)
(prompt "\n")
(setq i (1+ i)
En este caso i tiene asignado el valor 10 antes de entrar en el bucle, de modo que la
condición (< i 10) no se cumpliría y por tanto no se ejecutarian las expresiones
siguientes. While devuelve el valor de la última expresión evaluada, que en este caso
es la condición así que devuelve nil.
Las expresiones en While son opcionales, de modo que podemos crear un bucle en
el que solo se indique la condición:
En este caso la condición es (not (setq pt (getpoint "\nPunto inicial: "))) es decir,
pide un punto al usuario y lo almacena en la variable pt.
Otro ejemplo típico de bucles es en el que se utilizan algunas variables como flags
(banderas o banderillas) para indicar si algo está activado o desactivado o para
controlar distintos valores. Veamos un ejemplo, supongamos que a y b son dos
variables que almacenan dos números reales:
(if (< a b)
(setq flag1 T)
(while flag1
(if (< a b)
En este caso:
Luego la función While evalúa la condición falg1 que devolvera su valor nil o T.
En la función While, al igual que vimos con IF y COND, como condición podemos
utilizar expresiones lógicas. Por ejemplo:
En este ejemplo, el bucle se ejecutará hasta que se indique un valor para b positivo
y mayor que a.
(repeat Bart_Simpson (prompt "\nNo volveré a hacer pompas con ácido sulfúrico en
clase"))
También podemos obtener la cantidad por medio de una función de AutoLISP, como
resultado de una operación:
(setq i 0)
(repeat (+ a b)
(propmt "\t")
Vamos a modifuicar la rutina ARAND para hacer que el segundo radio sea mayor
que el primero. El código correspondiente al círculo exterior era el siguiente:
(initget 7 "Diametro")
(progn
(initget 7)
(initget 7 "Diametro")
(progn
(initget 7)
)
)
La primera vez que se evalúa la condición del bucle, no se ha asignado aún ningún
valor al radio exterior. De modo que rade = nil y (not rade) devolverá T. (or (not rade)
(not (< radi rade))) comprueba que al menos se verifique una de las dos condiciones.
Al verificarse la primera condición (not rade) la segunda ni siquiera se evalúa (por
suerte puesto que al no estar definida rade nos daría un error). La condición se
verifica y ejecuta las expresiones que están a continuación, que nos piden un valor
para el radio exterior.
Es decir, mientras no se indique el radio exterior o este sea menor que el radio
interior se ejecuta el bucle, que nos pide un nuevo valor para el radio exterior. Al
salir del bucle ya tenemos un radio exterior válido así que dibujamos el círculo
exterior. El código completo sería:
(setvar "cmdecho" 0)
(initget 1)
(initget 7 "Diametro")
(progn
(initget 7)
(initget 7 "Diametro")
(setq rade (getreal "\nRadio exterior / Diámetro: "))
(progn
(initget 7)
(if (= cmd0 1)
(setvar "cmdecho" 1)
(princ)
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.
(setq i 0)
(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.
Fíjate que una de las expresiones que se ejecutan dentro del bucle es (setq i (1+ i) es
decir, movemos el contador. Si no lo hicieramos, i siempre sería menor que 10 y se
entraría en un bucle sin fin, que da lugar a un error.
(setq i 10)
(prompt "\n")
(setq i (1+ i)
En este caso i tiene asignado el valor 10 antes de entrar en el bucle, de modo que la
condición (< i 10) no se cumpliría y por tanto no se ejecutarian las expresiones
siguientes. While devuelve el valor de la última expresión evaluada, que en este caso
es la condición así que devuelve nil.
Las expresiones en While son opcionales, de modo que podemos crear un bucle en
el que solo se indique la condición:
En este caso la condición es (not (setq pt (getpoint "\nPunto inicial: "))) es decir,
pide un punto al usuario y lo almacena en la variable pt.
Otro ejemplo típico de bucles es en el que se utilizan algunas variables como flags
(banderas o banderillas) para indicar si algo está activado o desactivado o para
controlar distintos valores. Veamos un ejemplo, supongamos que a y b son dos
variables que almacenan dos números reales:
(if (< a b)
(setq flag1 T)
(while flag1
(if (< a b)
En este caso:
Luego la función While evalúa la condición falg1 que devolvera su valor nil o T.
En la función While, al igual que vimos con IF y COND, como condición podemos
utilizar expresiones lógicas. Por ejemplo:
En este ejemplo, el bucle se ejecutará hasta que se indique un valor para b positivo
y mayor que a.
(repeat Bart_Simpson (prompt "\nNo volveré a hacer pompas con ácido sulfúrico en
clase"))
También podemos obtener la cantidad por medio de una función de AutoLISP, como
resultado de una operación:
(setq i 0)
(repeat (+ a b)
(propmt "\t")
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: "))
(progn
(initget 7)
(initget 7 "Diametro")
(progn
(initget 7)
La primera vez que se evalúa la condición del bucle, no se ha asignado aún ningún
valor al radio exterior. De modo que rade = nil y (not rade) devolverá T. (or (not rade)
(not (< radi rade))) comprueba que al menos se verifique una de las dos condiciones.
Al verificarse la primera condición (not rade) la segunda ni siquiera se evalúa (por
suerte puesto que al no estar definida rade nos daría un error). La condición se
verifica y ejecuta las expresiones que están a continuación, que nos piden un valor
para el radio exterior.
Es decir, mientras no se indique el radio exterior o este sea menor que el radio
interior se ejecuta el bucle, que nos pide un nuevo valor para el radio exterior. Al
salir del bucle ya tenemos un radio exterior válido así que dibujamos el círculo
exterior. El código completo sería:
(setvar "cmdecho" 0)
(initget 1)
(initget 7 "Diametro")
(progn
(initget 7)
(initget 7 "Diametro")
(progn
(initget 7)
)
)
(if (= cmd0 1)
(setvar "cmdecho" 1)
(princ)
Esta función devuelve el ángulo formado por la línea que va desde pt1 hasta pt2. El
origen de angulos será el eje X y el sentido antihorario se considera positivo. Por
ejemplo:
Hay que fijarse en cual es el punto que se indica primero en la función angle, porque
si hacemos:
Si los puntos están en 3D, los proyecta sobre el plano XY y devuelve el ángulo
formado por sus proyecciones
Devuelve la distancia en 3D entre los puntos pt1 y pt2. Si uno de los puntos está en
2D, es decir no tiene coordenada Z, se ignora la coordenada Z del otro punto
devolviendo la distancia en 2D.
(setq pt1 (getpoint "\nPunto1: "))
En este caso, (setq dist21 (distance pt2 pt1)) sería igual a dist12. Ya que la distancia
del punto pt1 al pt2 es igual que la distancia del punto pt2 al pt1.
Esta función se utiliza para obtener un punto por medio de coordenadas polares a
partir de un punto base. Por ejemplo:
Esto asignaría a la variable pt2 un punto que está a 50 unidades en la dirección del
eje X a partir del punto base pt1. Si hicieramos:
Hay que observar que en (polar pt1 0.0 50.0) se indica primero el punto base, luego
un ángulo en radianes y por último una distancia. De modo que:
Devolverá un punto que está a 50 unidades en la dirección del eje Y a partir del
punto base pt1. Ya que en este caso se ha indicado un ángulo de (/ pi 2.0) es deirc,
90º.
(LAST lista)
Esta función devuelve el último elemento de la lista que recibe como argumento. De
modo que si hacemos:
(nth 2 pto) devolverá la coordenada Y, no? Pues no!. Los elementos de una lista se
empiezan a numerar desde cero. Por ejemplo:
(nth 2 pto) devolverá la coordenada Z del punto pto, si existe y si no existe devolverá
nil.
Esta función reemplaza un elemento de una lista por su nuevo valor. Por ejemplo,
para reemplazar "perros" por "naranjas":
(setq lst (list (car lst) (cadr lst) (caddr lst) "limones"))
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í:
(REVERSE lista)
Veamos como funciona la línea de código anterior: (reverse lst) devuelve la lista en
orden inverso, (cons "platanos" (reverse lst)) añade "platanos" como primer
elemento de la lista invertida, y por último volvemos a invertir el orden de la lista:
(setq lst (reverse (cons "platanos" (reverse lst))))
(ACAD_STRLSORT lista)
Esta función devuelve una lista con sus elementos, que deberan ser cadenas de
texto, ordenados alfabéticamente. Por ejemplo:
Los pares punteados son simplemente listas de dos elementos separadas por un
punto. Por ejemplo: (0 . 27)
1. Aunque el punto nos pueda engañar, tienen solo dos elementos. En este
caso 0 y 27.
2. Podemos obtener el primer elemento, como en cualquier lista, mediante (car
par_punteado). Pero, si intentamos obtener el segundo elemento mediante
(cadr par_punteado) nos indicará "error: tipo de argumento erróneo:" Para
obtener el segundo elemento utilizaremos CDR en lugar de CADR (cdr
par_punteado). Ojo! (cdr par_punteado) devolverá 27 y no (27).
¿Para qué se usan los pares punteados? Pues principalmente para crear listas de
asociaciones. ¿Y qué es una lista de asociaciones? Pues una lista cuyos elementos
son pares punteados. Por ejemplo: (("perro" . 0) ("gato" . 1) ("ratón" . 5))
¿Cómo se crear los pares punteados? Con la función que ya hemos visto, CONS.
Antes se explico (CONS elemento lista) que nos permite añadir un elemento nuevo
como primer elemento de la lista indicada. Si en lugar de una lista se indica otro
elemento creamos un par punteado.
(setq lst (list (cons "perro" 0) (cons "gato" 1) (cons "ratón" 5)))
Por último veamos una función que utilizaremos mucho, sobre todo al trabajar con
las entidades de AutoCAD...
ASSOC nos servirá para obtener el par punteado, cuyo código se indique, de la lista
de asociaciones que recibe como argumento. Por ejemplo:
(assoc "gato" lst) devolverá ("gato" . 1)
Pero, ¿Por qué son tan importantes las listas de asociaciones y los pares
punteados? Por lo siguiente:
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.
(setq lstptos (list (list 0.0 0.0) (list 10.0 0.0) (list 10.0 10.0) (list 0.0 10.0)))
Es decir, lstptos es una lista con cuatro elementos correspondientes a los vértices
de un cuadrado de lado 10. Supongamos que queremos dibujar un círculo en cada
uno de los puntos de lstptos. Hasta ahora podiamos hacerlo así:
La variable i nos servirá como índice o contador en el bucle While, por eso le
asignamos inicialmente el valor 0, porque los elementos de una lista se numeran
empezando desde cero. Es bastante habitual que los nombres de las variables
utilizadas como contadores sean i j k ... También asignamos a nent el número de
elementos de la lista lstptos, porque sino no sabremos como salir del bucle ni hasta
que elemento llegar.
El bucle se ejecutará mientras el contador sea menor que nent es decir, se ejecutará
para cada elemento de lstptos. (setq pt (nth i lstptos)) se obtiene el elemento número
i de lstptos, que es un punto, y se dibuja un círculo de radio 1.0 en él. Finalmente
aumentamos el contador para que pase al siguiente punto (setq i (1+ i)). Esta última
línea es muy importante, si estuviera i sería siempre igual a cero y el bucle no tendrá
fin. Por lo que dibujaría infinitos círculos en el primer punto de lstptos y da un error.
Veamos otro método para dibujar los círculos en los vértices de lstptos:
(while lstptos
Bien, pero tenemos algunos métodos que son mejores, y eso es lo que vamos a ver
en este tema.
Esta función te permite aplicar una expresión para cada elemento de la lista que
recibe como argumento. La variable simboliza a un elemento de dicha list, por lo que
se suele utilizar en la expresión. Por ejemplo:
La línea de código anterio representa el tercer método para dibujar los círculos en
los puntos de lstptos. A la variable la llamamos p y la expresión a ejecutar para cada
elemento de la lista es: (command "_.circle" p 1.0) dónde p es un elemento de la
lista.
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:
Todo lo que antes poniamos en un bucle se pone ahora en una sola línea de codigo.
Aplica la función que recibe como argumento a todos los elementos de la lista. El
nombre de la función a utilizar en APPLY se indicará precedida por el símbolo ' para
que AutoCAD interprete que le estamos pasando ese texto tal cual. El ejemplo
anterior sería:
En (apply '+ lst) estamos aplicando la función + a todos los elemento de lst, equivale
a (+ 5.0 -1.5 2.6 3.8 9.7), y el resultado se lo asignamos a la variable total.
Digamos que '+ es el nombre que utilizamos en AutoLISP para hacer referencia a la
función +, ya que + es la función propiamente y no su nombre. Prueba lo siguiente
en la ventana de comando de AutoCAD:
(list "pepe" + 5.0) y fijate en lo que devuelve ("pepe" #<SUBR @0244f5a8 +> 5.0)
Y ahora prueba con el literal, es decir con el caracter ' delante del +
¿Entiendes ahora porque ponemos '+? Para que no evalue la función, ya que
devolveria #<SUBR @0244f5a8 y nos daría un error.
Esta función aplica la función de AutoLISP que recibe como argumento a los
elementos de las listas. Como así no se va a entender... ejemplos:
Vamos a determinar el vector con origen en pt1 y final en pt2. Tenemos que calcular
las coordenadas X Y Z del vector, que son el resultado de restar las respectivas
coordenadas X Y Z de pt1 a las de pt2. Podemos obtener el vector así:
(setq vector
Siendo la coordenada X del vector: (- (car pt2) (car pt1)) la Y (- (cadr pt2) (cadr pt1)) y
la Z (- (caddr pt2) (caddr pt1))
La función que recibe mapcar es - y lo que hace aplicarla a los elementos de pt2 y
pt1, de modo que resta sus coordenadas X, las Y y las Z.
(mapcar '* vector vector) devolverá una lista con las coordenadas del vector al
cuadrado, ya que las estamos multiplicando por si mismas.
(setq pescalar (+
)
Pero resulta más sencillo utilizando MAPCAR y APPLY. Primero multiplicamos las
coordenadas de ambos vectores:
Tal vez el formato de la función LAMBDA recuerde algo a DEFUN. LAMBDA también
se utiliza para definir una función, pero a diferencia de DEFUN la función no se
almacena en ningún lugar, en este caso es temporal. Por tanto solo se puede
ejecutar donde se defina. Además la función creada no tiene nombre, por lo que
tampoco podriamos llamarla desde otra parte de nuestro código.
Utilizarla sola sin APPLY o MAPCAR no tiene sentido. Supongamos que queremos
obtener el punto medio de dos puntos...
Las coordenadas del punto medio serán las coordenadas de pt1 más las de pt2
divididas por 2.0. Para sumar sus coordenadas...
Bien... pero ahora, ¿Cómo las dividimos por 2.0? Podemos hacer lo siguiente:
(mapcar '/ (mapcar '+ pt1 pt2) (list 2.0 2.0 2.0))
Donde creamos una lista (list 2.0 2.0 2.0) y dividimos los elementos de (mapcar '+ pt1
pt2) entre los elementos de la lista anterior (2.0 2.0 2.0).
Recordemos... (mapcar '+ pt1 pt2) devuelve la suma de las coordenadas de pt1 y pt2.
Ahora necesitariamos dividila por 2.0, pues podemos crear una función que reciba
un número y lo divida por 2.0 devolviendo el resultado:
Y ahora pasarle a MAPCAR la nueva función 2/ para que divida cada elemento de
(mapcar '+ pt1 pt2) por 2.0
Por tanto, cuando queremos aplicar APPLY o MAPCAR a una o varias listas y
ejecutar una función que no existe en AutoLISP, podemos crearla previamente con
DEFUN o utilizar la función LAMBDA para definirla in situ.
(VER)
Esta función devuelve una cadena de texto con la versión de AutoLISP que se está
ejecutando. Por ejemplo: "Visual LISP 2000 (es)". Entre paréntesis indica la versión
idiomática, en este caso Español.
(TEXTSCR)
Se utiliza para pasar a pantalla de texto y siempre devuelve nil. Se suele emplear
cuando se quiere mostrar mucha información en pantalla de texto.
(GRAPHSCR)
Pasa a pantalla gráfica y también devuelve nil. Se utiliza para asegurarnos que el
usuario está viendo la pantalla gráfica, por ejemplo para indicar un punto.
Especialmente se utilizará si antes se ha pasado a pantalla de texto.
(TEXTPAGE)
Esta función es análoga a TEXTSCR. Pasa a pantalla de texto y también devuelve nil.
Tal vez estes preguntandote... ¿Cuántas funciones nos quedan aún? Pues entre
otras cosas, la siguiente función nos servirá para ver las funciones de AutoLISP que
hemos visto y las que nos quedan.
Esta función devuelve una lista con los símbolos que se han definido en el dibujo
actual. ¿Qué es un símbolo? Pues un nombre de variable de AutoLISP, el nombre de
una función de usuario que hallamos creado y también todos los nombres de las
funciones propias de AutoLISP.
• 0 para que devuelva una lista con los nombres de los símbolos.
• 1 para que devuelva una lista, pero siendo sus elementos cadenas de texto.
Veamos ahora algún ejemplo. Al escribir la línea siguiente sabras cuantas funciones
faltan...
(atoms-family 0) esto mostrará una lista con los nombres de todos los símbolos
definidos en el dibujo actual.
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.
(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:
(QUOTE expresión)
(quote +) devolverá +
Esto es lo mismo que haciamos en APPLY y MAPCAR:
PROGN en realidad no hace nada, simplemente nos servía para salvar la limitación
de IF de indicar más de 1 expresión, ya que evalúa las expresiones que contiene y
devuelve el resultado de la última expresión evaluada. Ahora si devolverá (SETQ A
"pepe" B 10.0). Pero no hemos asignado valores a las variables, ya que (SETQ A
"pepe" B 10.0) no se ha evaluado.
(quote (15.0 10.6 9.2)) devolverá (15.0 10.6 9.2) es decir, devuelve una lista. También
funcionaría con '(15.0 10.6 9.2)
podemos poner:
y también:
De modo que podemos usar QUOTE y ' para crear listas. Pero con excepciones... si
hacemos:
(setq a 2.0)
(apply '+ '(a 3.5 6.8)) indicará: ; error: tipo de argumento erróneo: numberp: A
(apply '+ (list a 3.5 6.8)) esto si funcionará porque (list a 3.5 6.8) devolverá (2.0 3.5
6.8), LIST se evalúa pero QUOTE no evalua la expresión que recibe.
Resumiendo: Podemos crear listas con QUOTE o con ' pero siempre que
conozcamos los elementos de dichas listas, y que estos sean valores concretos no
determinados a partir de expresiones o almacenados en variables.
(foreach p '((0.0 0.0 0.0) (10.0 10.0 0.0)) (command "_.circle" p 1.0))
(setq vector (mapcar '- '(10.0 10.0 0.0) '(5.0 0.0 0.0)))
(setq ptomed (mapcar '(lambda ( num ) (/ num 2.0)) (mapcar '+ '(10.0 10.0 0.0) '(5.0 0.0
0.0))))
(EVAL expresión)
(eval "Soy una cadena de texto") analogamente devolverá "Soy una cadena de texto"
(setq a 15.5)
(eval '(+ 10.0 5.5)) devolverá 15.5 ya que '(+ 10.0 5.5) devuelve (+ 10.0 5.5) y eval, lo
evalúa.
(READ texto)
(read "(15.2 9.3 15.5)") en este caso devolverá (15.2 9.3 15.5) porque al detectar el
paréntesis lo considera un mismo término. Es algo similar a escribir en la ventana
de comandos de AutoCAD, si no se pone un paréntesis delante no dejará escribir
espacios en blanco. Pues este es otro método para crear una lista, por tanto:
(apply 'max (read "(15.2 9.3 15.5)")) devolverá el máximo de la lista de números
indicados
¿Para qué sirve esto? Para solicitarle al usuario una expresión de AutoLISP y
evaluarla:
Si probamos:
(setq (read "num1") 5.0) nos da un error, ya que SETQ no acepta una expresión
como símbolo.
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é 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.
¿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:
Para ejecutarla, tan solo debemos añadir en dicho archivo la siguiente línea de
código:
(GAR)
(GAR 3.141592)
(C:CIRCPERI)
¿Sería mejor utilizar AUTOLOAD en lugar que LOAD en el ejemplo anterior? Pues
no. Utilizar AUTOLOAD no tendría mucho sentido, puesto que al ejecutar el
comando el archivo en el que esté definido se cargaría, por tanto podemos cargarlo
ya antes.
Existe otro método para ejecutar código directamente. Se trata de escribirlo
directamente, es decir sin meterlo dentro de ninguna función o comando. Por
ejemplo, si incluimos las siguientes líneas de código en el archivo ACADDOC.LSP
nos permitirá dibujar un circulo dado su centro y su perímetro:
Pero en este caso, el código anterior solo se ejecuta una vez. Si queremos dibujar
otro círculo dado su perímetro, no tendriamos la función CIRCPERI definida.
Además de mensajes de bienvenida podeis hacer otras cosas más útiles, como
asignar valores iniciales a algunas variables:
(setvar "osmode" 7)
S::STARTUP
Existe otro método para ejecutar código automáticamente. Hemos visto que
AutoCAD utiliza una función de tratamiento de errores por defecto que se llamaba
*error* y también hemos visto que podiamos redefinirla creando nuesta propia
función de tratamiento de errores. Bien, pues AutoCAD también tiene una función
interna que se ejecuta automáticamente se trata de S::STARTUP.
El que las funciones *error* y S::STARTUP tengan estos nombres tan raros es para
evitar que a alguien se le ocurra denominar así a alguna función de otro tipo.
Para definir la función S::STARTUP se utiliza DEFUN, al igual que para cualquier otra
función.
(defun S::STARTUP ( / )
)
La función S::STARTUP podemos utilizarla para los mismos ejemplos que se daban
antes.
Por último, para terminar el tema, un consejo... Al programar hay que tener bastante
cuidado para no cometer errores, ya que el omitir un simple paréntesis o unas
comillas, por ejemplo, modificaran totalmente nuestras funciones. Cuando además
estas funciones se van a ejecutar automáticamente, como en los ejemplos que se
han expuesto, la precaución al programar debe ser máxima.
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".
(while lst
(progn
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.
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:
(while lst
;; están cargadas
;; No se encuentra el archivo
(progn
;; Carga el archivo
;; Se encuentra el archivo
)
Esta función nos permitirá seleccionar un archivo, ya veremos más adelante como
seleccionar varios archivos. GETFILED muestra un letrero de gestión de archivos
tipico de Windows.
No pasa nada al pulsar en Abrir, ya que no se abre el archivo. GETFILED tan solo
nos deja seleccionarlo y devuelve su nombre.
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:
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.
(prin1 b)
Fijate que PRIN1 puede escribir tanto cadenas de texto como números. Sin embargo
la función PROMPT tan solo puede recibir como argumeto una cadena de texto.
(prin1)
Por eso se suele emplear como última expresión de los comandos, para que la
salida de nuestras rutinas sea limpia y no se vea el eco de mensajes de la última
expresión evaluada.
(progn (print a) (print a)) escribe 5.5 pasa a otra línea y escribe 5.5
Es decir, PRINT salta de línea antes de escribir la expresión indicada. PRINT escribe
la expresión en una línea nueva.
(print) devuelve una cadena nula, por eso también se emplea como última expresión
de los comandos
(print (strcat "\t" b)) devuelve "\tCurso de AutoLISP", es decir PRINT tampoco
entiende los caracteres de control.
(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.
Esta función permite escribir un caracter, recibe como argumento su código ASCII.
Si se indica el descriptor de archivo lo escribirá en el archivo y sino lo escribirá en la
ventana de comandos de AutoCAD.
Esta función escribe una línea de texto entera. El primer argumento debe ser una
cadena de texto no una expresión. Y si se indica un descriptor de archivo, escribirá
la línea de texto en el archivo, en caso contrario la escribe en la ventana de
comandos de AutoCAD.
(READ-CHAR [descriptor_archivo])
(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