Desventuras en Object ARX – Parte 2 – Creando comandos

ADVERTENCIA: Este artículo no es nada serio. Si usted busca información confiable y verídica al 100%, este no es el lugar. No lo leas.

En al artículo anterior empezamos configurando a Visual Studio, para poder empezar nuestro desarrollo con un simple programa inútil pero que servía para conocer el esquema de trabajo del ObjectARX.

Ese esquema implicaba que nuestra aplicación tenía que ser cargada primeramente y ahí permanecería paciente en espera de algún mensaje o hasta que se descargue, o hasta que nos tiremos abajo al AUTOCAD.

La interacción entonces, se logra con mensajes. Nuestra aplicación es como una oficina y AUTOCAD es un mensajero que se encarga de enviar mensajes a todas las aplicaciones ObjectARX existentes (y a otro tipo de aplicaciones). No vayamos a pensar egoístamente que AUTOCAD solo trabaja para nosotros.

AUTOCAD envía mensajes por diversos eventos, como cuando se abre un archivo, o cuando se inicia AUTOCAD.

Los tipos de mensajes que se manejan hasta la versión actual del 2018 son:

enum AppMsgCode {
  kNullMsg = 0,
  kInitAppMsg = 1,
  kUnloadAppMsg = 2,
  kLoadDwgMsg = 3,
  kUnloadDwgMsg = 4,
  kInvkSubrMsg = 5,
  kCfgMsg = 6,
  kEndMsg = 7,
  kQuitMsg = 8,
  kSaveMsg = 9,
  kDependencyMsg = 10,
  kNoDependencyMsg = 11,
  kOleUnloadAppMsg = 12,
  kPreQuitMsg = 13,
  kInitDialogMsg = 14,
  kEndDialogMsg = 15,
  kSuspendMsg = 16,
  kInitTabGroupMsg = 17,
  kEndTabGroupMsg = 18
};

El mecanismo de mensajes funciona bien, porque permite a nuestras aplicaciones ObjectARX, acceder a información relevante. Por ello el esquema de trabajo consiste en procesar solo los mensajes que nos pueden ser útiles y no estar como señoras chismosas escuchando de todo. De allí que nuestro programa tiene la estructura:

	switch (msg) {
	case AcRx::kInitAppMsg:
                ...
		break;
	case AcRx::kUnloadAppMsg:
                ...
		break;
	}

Pero los mensajes no son suficientes para una buena interacción entre mi programita ObjectARX  y el usuario de AUTOCAD, porque el usuario no sabe lo que son los mensajes, él solo sabe interactuar con AUTOCAD por medio del ratón o el teclado.

Entonces ¿Cómo hago para que el usuario pueda ejecutar acciones de mi programa?

La forma más sencilla, y tal vez la más básica, es mediante el uso de comandos, y es lo que vamos a ver en este artículo. Desde luego que existen otras formas de interacción con nuestro programa, pero de eso hablaré después, si es que me da el tiempo para seguir escribiendo estas notas.

Implementar comandos en ObjectARX se sencillo. Más sencillo que prepararse un café con leche. Lo que debe hacer nuestro aplicativo, es llamar al método addCommand() del objeto que devuelve acedRegCmds, que es en realidad una macro que sabe Dios a dónde apuntará.

Esta instrucción se ejecuta para registrar el comando (no para ejecutarlo), que luego podrá ser ejecutado por el usuario AUTOCAD, como si fuera un comando cualquiera.

Obviamente, el método acedRegCmds() deberá pedirnos el nombre de nuestro comando y la función que vamos a ejecutar cuando se invoque al comando (no tan obvio).

Por ejemplo si quisiéramos registrar el comando «HOLA», para implementar un saludo, sería algo así:

 acedRegCmds->addCommand(L"MI DICCIONARIO",
                         L"HOLA", 
                         L"HOLA", 
                         ACRX_CMD_USEPICKSET, 
                         comandoSaludar);

Sencillo, ¿verdad?

El primer parámetro es el nombre del grupo al cual pertenecerá nuestro comando, porque en ObjectARX, como en cualquier API ordenada, todos los comandos deben pertenecer a un grupo.

El segundo parámetro es lo que se llama el Nombre global, que es el nombre principal del comando. Como se le conocerá aquí, y en la China, y es fijo.

El tercer parámetro es el Nombre local. Esto es un nombre que puede estar escrito en el idioma del usuario, así que usted le puede poner un nombre en el idioma que le guste, pero no se pueden usar lenguajes extraterrestres.

Un secreto, pero no se lo cuenten a nadie: Los nombres de los comandos no pueden ser mayor de 30 caracteres.

El cuarto parámetro es muy útil para definir la forma en que se manejará el comando. Algunos valores son:

Name
Meaning
ACRX_CMD_USEPICKSET
When the pickfirst set is retrieved, it is cleared within AutoCAD.
Command is able to retrieve the pickfirst set via the functions ads_ssgetfirst() or ads_ssget(«I.»).
Command can set the pickfirst set via ads_sssetfirst(), but it only stays set until the command ends.
Command cannot retrieve nor set grips.
ACRX_CMD_REDRAW
When the pickfirst set or grip set are retrieved, they are not cleared within AutoCAD.
Command can retrieve the pickfirst set and the grip set by using the ads_ssgetfirst function.
Command can retrieve the pickfirst set via ads_ssget(«I.»).
ACRX_CMD_NOPERSPECTIVE
Command cannot be invoked while the current viewport is in perspective mode.
ACRX_CMD_NOTILEMODE
Command cannot be invoked when TILEMODE == 1.
ACRX_CMD_NOPAPERSPACE
Command cannot be invoked when in paperspace.
ACRX_CMD_UNDEFINED
Command can only be invoked via the cmdGroupName.cmdGlobalName syntax.
ACRX_CMD_DEFUN
This flag can only be set by the Visual LISP engine. Command can be invoked as a LISP function and can therefore use acedGetArgs() to receive arguments from LISP and can use the acedRetXxx() functions to return values to LISP.
ACRX_CMD_DOCREADLOCK
Document will be read locked when command is invoked. If this bit is not set, then the document will be write locked when the command is invoked.

Esta información no me la he sacado de debajo de la manga. Este es un fragmento de las ayudas que vienen en la documentación de ObjectARX.

Si se desea obtener información sobre ObjectARX, no es necesario tontear por la Web o preguntarle a ChatGPT. El primer lugar a preguntar debe ser la propia ayuda de ObjectARX que viene incluida en la carpeta donde se instalo. Más precisamente en la carpeta «/docs». Allí hay 100MB de bonitos archivos *.chm. El que agrupa a todos es «arxdoc.chm» así es ese el que debemos abrir si queremos acceder a todos los demás archivos que vienen en la carpeta.

Volviendo al cuarto parámetro de addCommand(), los valores que veremos aquí serán solo:

  • ACRX_CMD_MODAL
  • ACRX_CMD_USEPICKSET

El primero es como nuestra opción por defecto y el segundo es cuando queremos que el comando pueda aceptar selecciones de objetos realizados previamente.

Pero ya basta de palabreo. Vamos al código de una vez. Un simple programillo para responder a un comando sería algo como:

#include "stdafx.h"
#include "rxregsvc.h"
#include "acutads.h"
#include "adslib.h"
#include <aced.h>

void comandoSaludar() {
	acutPrintf(L"\n Hola hermano\n ");
}
extern "C" AcRx::AppRetCode acrxEntryPoint(AcRx::AppMsgCode msg, void* appId) {
	switch (msg) {
	case AcRx::kInitAppMsg:
		acrxUnlockApplication(appId);
		acrxRegisterAppMDIAware(appId);
		acutPrintf(L"\n== Ya me cargué ==\n");
		acedRegCmds->addCommand(L"MI DICCIONARIO",
			L"HOLA", L"HOLA", ACRX_CMD_USEPICKSET, comandoSaludar);
		acrxBuildClassHierarchy();
		break;
	case AcRx::kUnloadAppMsg:
		acutPrintf(L"\n== Adios ==\n");
		acedRegCmds->removeGroup(L"MI DICCIONARIO");
		break;
	}
	return AcRx::kRetOK;
}

Quizá alguien se haya preguntado ¿Qué significa esa «L» delante de las cadenas?

La respuesta sería: Esa es la forma en que le decimos a C++ que la cadena que vamos a escribir después, es de tipo WIDE CHAR o algo así. Es un tipo de carácter que puede soportar UNICODE y como eso está de moda por esto de la internacionalización (lo escribí bien a la primera), ObjectARX lo pide.

Sí. A mi también me parece una forma horrible de especificar tipos de datos, pero qué le vamos a hacer. Así es C++.

Notar que se han tenido que agregar algunos encabezados para poder compilar este programa.

También puede que necesite incluir algunas librerías más en la sección de configuración: «>Configuration Properties>Linker>Input» :

Estas librerías deberían ser: rxapi.lib;acdb22.lib; acad.lib;acge22.lib;ac1st22.lib; o sus equivalentes dependiendo de la versión de ObjectARX que maneje. Si no se incluyen, cuando se compile, se obtendrán los errores con mensajes más confusos que los del Oráculo de Poseidón: «… error LNK2019: unresolved external symbol «int __cdecl acedGetInt…» que en buen cristiano significa: «¡IDIOTA! Olvidaste incluir las librerías».

Por si acaso, incluir también: acgiapi.lib y también acapp.lib y por si acaso: accore.lib. Ya mejor inclúyalas todas y que no moleste el enlazador.

Ahora, si logramos compilar esta tontería de código, podremos cargar nuestro programa en AUTOCAD (eso ya lo explique en el artículo anterior) y llamar a nuestro comando escribiendo solo «HOLA» en el panel de comando de AUTOCAD.

Lo que veremos será que nuestro programa nos responde de forma amable.

Esto nos indica que hemos logrado crear nuestro primer comando con éxito.

¿Pero y si ahora queremos leer algún dato que debe introducir el usuario? Porque el usuario siempre anda introduciendo cosas a los programas. Esa es su razón de ser.

A lo que voy ¿Cómo podemos pedir (y leer) parámetros al usuario?

El método es bastante sencillo porque existen ya varias funciones predefinidas que nos ayudan en esta tarea. Por ejemplo, para leer un valor entero, existe: acedGetInt().

El siguiente código muestra de forma ya más ordenada, como implementar dos comandos, en donde el último tiene interacción con el usuario:

#include "stdafx.h"
#include "rxregsvc.h"
#include "acutads.h"
#include "adslib.h"
#include <aced.h>
#include <dbents.h>

void comandoSaludar() {
	acutPrintf(L"\n Hola hermano\n ");
}
void comandoAdivina() {
	int num;
	acutPrintf(L"\n Piensa (y escribe) un número: ");
	acedGetInt(L"", &num);
	acutPrintf(L"\n Te leo la mente. Pensaste: %d\n", num);
}
void initCommands() {
	acedRegCmds->addCommand(L"MI DICCIONARIO",
		L"HOLA", L"HOLA", ACRX_CMD_USEPICKSET, comandoSaludar);
	acedRegCmds->addCommand(L"MI DICCIONARIO",
		L"ADIVINA", L"ADIVINA", ACRX_CMD_MODAL, comandoAdivina);
	acrxBuildClassHierarchy(); 
}
extern "C" AcRx::AppRetCode acrxEntryPoint(AcRx::AppMsgCode msg, void* appId) {
	switch (msg) {
	case AcRx::kInitAppMsg:
		acrxUnlockApplication(appId);
		acrxRegisterAppMDIAware(appId);
		acutPrintf(L"\n== Ya me cargué ==\n");
		initCommands();
		break;
	case AcRx::kUnloadAppMsg:
		acutPrintf(L"\n== Adios ==\n");
		acedRegCmds->removeGroup(L"MI DICCIONARIO");
		break;
	}
	return AcRx::kRetOK;
}

Al cargar el programa y ejecutar el comando ADIVINA, veremos que nos pide ingresar un número:

Para más información sobre acedGetInt() ya saben donde buscar.

Así como existe acedGetInt(), también existen:

acedGetReal

Gets a real value

acedGetDist

Gets a distance

acedGetAngle

Gets an angle (oriented to 0 degrees specified by the ANGBASE)

acedGetOrient

Gets an angle (oriented to 0 degrees at the right)

acedGetPoint

Gets a point

acedGetCorner

Gets the corner of a rectangle

acedGetKword

Gets a keyword

acedGetString

Gets a string

Le dejo de tarea, al lector de este artículo, averiguar el funcionamiento de estos comandos y entender el programa también. Así al menos el lector hará algo, porque, finalmente, yo lo estoy haciendo todo.

SOBRE EL AUTOR

Tito Hinostroza es experto en conseguir comida barata y sobrevivir con poco dinero. Gusta de comer platos exóticos en mercados de baja categoría. Ha estudiado electricidad, electrónica, dibujo, mecánica,  pero nada que tenga que ver con informática. Odia a Arduino y a Java. Dice que es para niños.


Sé el primero en comentar

Dejar una contestacion

Tu dirección de correo electrónico no será publicada.


*