4.3. Juntándolo todo... y una pizca de DCL

Los dos artículos anteriores examinaban la capacidad de Visual LISP para acceder a las propiedades y métodos de los objetos ActiveX. Ahora utilizaremos las funciones en ellos desarrolladas (CadXpress 61 y 62) para crear un programa Visual LISP dotado de una interfaz gráfica de usuario programada en DCL.


Antecedentes

En el número 61 describíamos la manera de acceder a una hoja de cálculo EXCEL desde AutoCAD y llenar los campos de una tabla con los datos de una lista de asociación LISP.

En el siguiente número examinamos una función capaz de extraer la información de los atributos y otros datos asociados a las referencias de bloque.

Contamos con que el lector ya dispondrá de las siguientes funciones organizadas en dos archivos:

Achivo fuente:

Funciones:

Propósito:

ListaExcel.LSP

apl-err

Gestión de errores.

IniciaExcel

Establece la comunicación.

TerminaExcel

Libera la aplicación.

DatoCelda

Inserta una valor en una celda Excel.

ProcesaFila

Escribe los campos de una fila.

ProcesaTabla

Escribe la fila de títulos y llena la tabla.

Lista->Excel

Recibe la lista e invoca las funciones anteriores

LeeAttribX.LSP
(CadXpress 62)

LeeAtribX

Extrae datos a partir de un objeto Referencia de Bloque

Procesa

Lee el identificador y el valor de un atributo

Los artículos mencionados incluyen además funciones diseñadas con el objeto de probar el funcionamiento de los programas. De una de esas funciones podemos ahora prescindir, la función TablaCirculos. Otra la utilizaremos con pequeñas modificaciones:

Artículo:

Funciones:

Propósito:

CadXpress 61

TablaCirculos

No se utilizará

C:TABLA

No se utilizará

CadXpress 62

SelBloque

Se modifica para que reciba el nombre de un bloque seleccionado de un Cuadro de Diálogo en lugar de teclearlo el usuario

A estos dos archivos añadiremos en esta entrega el archivo con la definición del Cuadro de Diálogo y otro con las funciones Visual LISP necesarias para gestionarlo y que explicaremos más abajo:

 

Achivo fuente:

Funciones:

Propósito:

ExtrATT.LSP


CargaDialogo

Inicia la operación del Cuadro de Diálogo.

CompruebaAtributos

Comprueba que el bloque seleccionado posea atributos.

ExtrATT

Lanza la función Lista->Excel.

ListaNombres

Devuelve una lista con los bloques de usuario encontrados en el dibujo.

TieneAtributos

Discrimina si una Definición de Bloque incluye atributos.

SelBloque

Selecciona las Referencias de Bloque que corresponden al nombre de bloque seleccionado por el usuario.

ExtrATT.DCL

 

Definición del Cuadro de Diálogo

El Cuadro de Diálogo

El cuadro de diálogo propuesto (ver Figura 1) es muy simple, aunque sirve perfectamente a los fines que nos proponemos.

 Figura 1. Cuadro de Diálogo de la Aplicación.

 El componente principal del Cuadro de diálogo lo es una casilla de lista (list_box) que presenta los nombres de los bloques presentes en el dibujo. Para cada nombre de bloque se indica si tiene atributos insertando el texto "ATRIB" en la fila correspondiente. El ordenamiento de este texto en forma de columna se logra introduciendo un carácter tabulador "\t" entre el nombre del bloque y el texto "ATRIB", y asignando al atributo tabs de la casilla de lista un valor de 33, siendo la anchura total (width) de  40. Esta casilla de lista sólo admite la selección de un nombre de bloque (multiple_select = false;).

Los otros componentes del diálogo son los botones de Aceptar y Cancelar que provienen del componente predefinido ok_cancel y una línea de texto para indicar errores también predefinida a partir del componente errtile. Para el código completo del archivo ExtrATT.DCL véase la Figura 2. Es importante que la ubicación de este archivo se encuentre en una carpeta que figure dentro de las rutas de búsqueda de archivos de soporte de AutoCAD. Estas rutas pueden definirse desde el cuadro de diálogo Opciones... del menú Herramientas. En caso de duda, puede simplemente ubicarse en la carpeta SUPPORT de AutoCAD.

Figura 2.  Código de la Definición del Cuadro de diálogo.

 Gestión del Cuadro de Diálogo

La operación de Cuadro de Diálogo se asegura a partir de la función CargaDialogo (ver Figura 3). Lo primero que comprueba esta función es la existencia de una variable global que llamamos *Posicion*. En caso que no exista se valora como '(-1 -1). Esta variable se suministra a la función new_dialog y sirve para determinar la posición del diálogo en la pantalla. Si el usuario desplazara el Cuadro a una nueva posición, los valores que a ella corresponden serán devueltos al cerrarse el diálogo mediante done_dialog y el programa almacenaría las nuevas coordenadas, de manera que al volverlo a invocar reaparecería en la última posición que ocupaba.

Una vez valorada *Posicion*, el programa comprobará que el archivo DCL está disponible y que la definición del Cuadro de Diálogo puede ser cargada, alertando de cualquier error encontrado. De aparecer uno de estos mensajes, debemos asegurarnos de haber situado el archivo DCL en la carpeta adecuada y de que no contiene errores sintácticos.

Llenar la Casilla de Lista

En caso de que todo marche bien, se procederá a llenar la casilla de lista con los nombres de los bloques encontrados en el dibujo. Para obtener estos nombres se llama a la función ListaNombres, que reviste cierta complejidad y que será explicada más abajo. Sólo diremos ahora que los nombres se guardan en una lista de asociación compuesta por pares punteados[1].

El procedimiento para llenar una lista DCL es el habitual:

·         Una llamada a la función start_list a la que se suministra la clave (key) asignada al componente:

(start_list "lista_bloques")

·         Una invocación de la función mapcar que aplica la función 'add_list a cada miembro de la lista.
Aquí tenemos una variante, otra llamada a mapcar anidada en la anterior, que tiene como objetivo crear una cadena que convertirá el par punteado

("NOMBREBLOQUE" . "ATRIB") en la cadena "NOMBREBLOQUE\tATRIB".

 

Figura 3. Función CargaDialogo

Esto se logra aplicando una función lambda, es decir una función anónima, definida en tiempo de ejecución y que desaparece una vez cumplido su objetivo:

(mapcar 'add_list

  (mapcar '(lambda (term) (strcat (car term) "\t" (cdr term)))

  *listaBloques*))

Si ListaNombres devolviera una lista vacía en lugar de llenar la casilla de lista se presentaría un mensaje de error en el componente errtile:

(set_tile "error" "No hay bloques en el Dibujo Actual")

·         El proceso de escritura en la lista concluye con la llamada a end_list.

Estableciendo las acciones de los componentes

Una vez llena la casilla de lista se procede a asignar la acción que desencadenará cada componente del Diálogo. La casilla de lista tendrá una acción propia asignada mediante action_tile:

(action_tile "lista_bloques" "(CompruebaAtributos $value)")

El argumento $value que se pasa a la función CompruebaAtributos (ver Figura 4) contendrá el índice de la línea sobre la que se haya pulsado el ratón, bien entendido que a la primera línea corresponderá el valor cero. La gestión de la casilla de lista exige conservar en memoria la lista original *listaBloques*. Para comprobar el bloque a que corresponde la línea pulsada se extraerá el miembro de la lista que ocupa la posición que corresponde al índice devuelto por $value. CompruebaAtributos lo hace aplicando la función nth:

(nth (atoi valor) *listaBloques*)

El valor devuelto que es de tipo String por lo que debe convertirse en Integer mediante la función atoi.

Se revisa entonces si el segundo término del par punteado es igual a "ATRIB". En caso contrario, se muestra un mensaje en errtile indicando que "El bloque seleccionado no posee atributos"

Figura 4. Función CompruebaAtributos.

Debemos señalar que además de esta acción que corresponde al evento de un clic del ratón, esta casilla tendrá también asignada la acción del botón Aceptar que se lanzará a partir del doble clic sobre una de sus líneas, tal como se define en el código DCL:  allow_accept = true;

La acción para la tecla Aceptar se define en la función ExtrATT a la que se pasa como argumento el índice de la línea resaltada, que se obtiene esta vez mediante la función get_tile:

(action_tile "accept" "(ExtrATT (get_tile \"lista_bloques\"))")

Figura 5. Función ExtrATT

La función ExtrATT (ver Figura 5) comprobará ante todo que exista *listaBloques*, previendo que el dibujo pueda no contener bloque alguno. Después comprueba que el segundo término del par punteado que identifica el bloque sea "ATRIB". Si ambas verificaciones tienen éxito, se lanza la función Lista->Excel (CadXpress 61) que recibe como argumento la función SelBloque (CadXpress 62), esta última modificada para eliminar la solicitud al usuario del nombre del bloque, ya que en este caso se le pasaría a partir de una selección en el cuadro de diálogo. (ver Figura 6).

El botón Cancelar no tiene otra acción asignada que registrar la posición del cuadro de diálogo al cerrarse.

Figura 6. Función SelBloque.

Creando la lista de Bloques

Como hemos visto, toda la funcionalidad de este Diálogo gira en torno a la lista de nombres de bloques *listaBloques*. Para acceder a esta información seguiremos la línea marcada en los artículos anteriores, empleando los métodos y propiedades de los objetos ActiveX. Para ello debemos conocer algo de la estructura de AutoCAD como aplicación.

AutoCAD en sí mismo constituye un objeto, el objeto Aplicación. Es a partir de este objeto que se podrá llegar a todos los componentes que integran el dibujo. Los objetos que se incluyen en la aplicación se cuentan por centenares. De ahí que sea necesario estructurarlos de alguna manera. Esa manera de estructurarlos es a partir de Colecciones. Todos los dibujos abiertos en una misma sesión de trabajo componen la Colección Documentos. El Documento Activo es uno de los miembros de esa Colección. A su vez el Documento Activo contiene una serie de Colecciones de Objetos.

Las Colecciones de Objetos

Una colección se define como un conjunto ordenado de elementos de información sobre los que podemos operar como una estructura de carácter unitario. Desde el punto de vista del programador AutoLISP, una colección es en gran medida como una lista. Aunque las colecciones no tienen que contener necesariamente el mismo tipo de dato para cada uno de sus miembros, las colecciones de AutoCAD tienden a componerse de referencias a objetos de tipo similar. Por ejemplo, la Colección  "Blocks" contiene sólo objetos Bloque y la Colección "TextStyles" contiene sólo objetos Estilo de Texto[2].

La vía para obtener la colección "BLOCKS" será la siguiente:

·         Obtener el objeto Aplicación AutoCAD

·         Obtener su objeto Documento Activo

·         Obtener del Documento Activo la Colección "BLOCKS"

·         Extraer los nombres de los objetos que integran la colección.

Los dos primeros pasos se resumen en la siguiente línea de código:

(setq *EsteDibujo* (vla-get-ActiveDocument (vlax-get-acad-object)))

Esta línea de código no forma parte de una de las funciones definidas. Aparecen de manera independiente al principio del archivo ExtrATT.LSP (ver Figura 7) junto con la llamada a (vl-load-com)[3], con lo que se valorará de inmediato en el momento de cargar el programa.

Figura 7. Funciones de Inicialización.

La referencia al objeto Documento Activo se guarda en la variable global *EsteDibujo*. La función ListaNombres utiliza esta referencia para acceder a la Colección "Blocks" (ver Figura 8). La colección "Blocks" es una propiedad del objeto Documento recuperable mediante

(vlax-get-property *EsteDibujo* "Blocks")

Visual LISP suministra vlax-for, una función que nos permite iterar a través de una colección con la misma facilidad que lo haríamos con una lista:

(vlax-for obj (vlax-get-property *EsteDibujo* "Blocks")...

De cada uno de los objetos que componen la colección y que recuperamos secuencialmente, podemos obtener la propiedad "Name", es decir su nombre:

(vlax-get-property obj "Name")

Figura 8. Función ListaNombres.

Pero no todos los objetos recuperados a partir de la colección "Blocks" interesan a los efectos de nuestro programa. Sólo deseamos obtener los nombres de bloques definidos por el usuario. Hay toda una serie de posibles objetos de tipo bloque que debemos excluir: los llamados bloques anónimos, generados y gestionados por AutoCAD que se distinguen por poseer como primer carácter un asterisco "*", los bloques de usuario que dependen de Referencias Externas y que se conocen por incluir un carácter pipe (|), y las Referencias Externas en sí mismas (detectados a partir de su propiedad "IsXRef"). De ahí la condicional en que se ubica el código para confeccionar la lista de bloques. Para las dos primeros casos se utiliza la función wcmatch con la cadena "`**,*|*" como patrón de comparación.

Otro aspecto que nos interesa comprobar ahora es si las Definiciones de Bloque restantes incluyen Definiciones de Atributos[4]. Para ello será necesario examinar, una a una, las Definiciones de Bloque encontradas. Una Definición de Bloque es también una Colección. En este caso integrada por entidades entre las cuales pueden encontrarse Definiciones de Atributo. La función TieneAtributos (ver figura 9) recorre mediante vlax-for cada Definición de Bloque y devuelve t (cierto) en caso de que la propiedad "ObjectName" de alguna entidad sea "AcDbAttributeDefinition".

Figura 9. Función TieneAtributos.

Implantar un nuevo Comando AutoCAD

Para terminar, sería conveniente crear un nombre para este programa que permitiera llamarlo desde la línea de comandos. En AutoLISP lo hacíamos definiendo una función cuyo nombre estuviera precedido por "C:". Ahora disponemos de otras maneras para hacerlo. Agregamos al final del archivo ExtrATT.LSP (ver figura 3, abajo) la siguiente línea:

(vlax-add-cmd "AtrEX" 'CargaDialogo)

Ahora bastará teclear ATREX en la línea de comandos para acceder al programa.

Recordemos que para probarlo debemos cargar todos los archivos que componen el proyecto: ListaExcel.LSP, LeeAttribX.LSP y ExtrATT.LSP.

Conclusiones

Con lo publicado en este número completamos lo que sería el código para una aplicación de utilidad evidente. Más adelante abordaremos los procedimientos para crear un proyecto que nos ayude a gestionar los diversos archivos, los aspectos a tener en cuenta para su compilación y el proceso mediante el cual podemos convertir todo esto en una aplicación ejecutable VLX.


[1] Listas de dos términos del tipo que se obtiene al aplicar la función cons sobre dos átomos: (cons "A" "B"), y que devuelve ("A" . "B"). Los pares punteados, también conocidos como CONSES se diferencian de las verdaderas listas en una serie de aspectos, entre ellos que la función cdr aplicada a un par punteado no devuelve una lista, sino un átomo. (cdr '("A" . "B")) devolvería "B", mientras que (cdr ("A" "B")) devolverá ("B").

[2] Gibb, J.W. y Kramer, B. AutoCAD VBA Programming: tools and techniques. Miller-Freeman, 1999. Pág. 83. Traducción libre de R. Togores.

[3] Sobre vl-load-com ver CadXpress 60.

[4] Recordemos la distinción entre Definición de Atributo y Referencia de Atributo que hacíamos en nuestro artículo anterior (CadXpress 62).