Desventuras en Object ARX – Parte 3 – Manejando entidades

Preliminares

En el artículo anterior vimos como crear comandos nuevos para interactuar con nuestra aplicación. Algo elemental si queremos realizar tareas con nuestro aplicativo.

Pero hasta ahora lo único que podemos hacer es enviar unos patéticos mensajes por pantalla tipo «Hola mundo», «Adiós mundo».

Ahora vamos a crear verdaderos elementos gráficos en pantalla, e iremos subiendo nuestro nivel como desarrollador ObjectARX. Ya no seremos «bebé de kinder» sino «pequeño saltamontes».

Pero antes de ver la implementación, necesitamos conocer algunos conceptos aburridos.

Antes que nada, es útil saber que Autocad tiene un diseño OOP (qué novedad), por lo tanto también tiene una jerarquía de clases.  Pero también es útil saber que Autocad administra sus entidades y sus demás objetos mediante una base de datos especial en donde a cada objeto se le asigna un identificador único llamado ObjectId. La base de datos está asociada a un documento o proyecto de AUTOCAD.

Si bien Autocad tiene definidas muchas clases; las que nos importan en este momento son las que representan a:

  • Objetos (representados por la clase AcDbObject)
  • Entidades (derivan de AcDbObject).

Esta diferenciación no es excluyente. Las entidades (también llamadas objetos gráficos) son también objetos (en el sentido polimórfico), pero que tienen representación gráfica. Son descendientes de la clase AcDbObject (objetos) y por lo tanto heredan mucho de su comportamiento. Si no tiene idea de lo que estoy hablando, tal vez deba repasar sus conceptos de POO u OOP o como sea,

Aquí, en estos artículos, cuando hable de «entidades», también las podré llamar como «objetos» como una forma didáctica, pedagógica y estudiada  de poner a prueba sus conocimientos adquiridos, o simplemente porque «se me pasó».

La ayuda del SDK muestra un diagrama bastante clarificador sobre la organización de la base de datos de un documento:

La tabla de bloques (Block Table) es la que contiene a los objetos gráficos. En condiciones normales, esta tabla contendrá solo 3 registros: MODEL_SPACE, PAPER_SPACE, y PAPER_SPACE0. Las entidades sencillas, pertenecerán al registro  MODEL_SPACE (model space block table record).
El nombre «Block Table» es realmente significativo, por cuanto hace alusión al mismo concepto de Bloque que usa Autocad: Grupo de entidades que se comportan como una sola, y evitan duplicar información (No se me ocurrió una mejor definición).
Así, si asumimos que todo el contenido de un documento es un bloque, tiene sentido pensar que se almacene como un registro (MODEL_SPACE) de la tabla de bloques.
Para reforzar esta idea, resulta que los bloques que vayamos creando (comando BLOCK o INSERT), también se guardarán como un registro de la tabla de bloques del documento. Han sido muy listos los diseñadores de AUTOCAD.
Aterrizando un poco: Para acceder a un objeto gráfico (y tal vez modificarlo), se suele hacer a través de su ID, y se puede realizar mediante dos métodos:
  • Método 1: Abriendo y Cerrando el objeto.
  • Método 2: Usando Transacciones.

No voy a hablar del segundo método porque no lo he usado y porque no tengo tiempo de investigarlo en este momento.

Accediendo a Objetos

En cuanto al primer método,  se debe saber que es un poco inseguro por cuanto se puede olvidar el cerrar un objeto abierto y eso puede causar que AUTOCAD se comporte de forma extraña (como que no reconozca objetos) o hasta que colapse.

Para implementar el primer método debe usar la función acdbOpenObject() que tiene la siguiente declaración:

extern Acad::ErrorStatus acdbOpenObject(AcDbObject*& obj, AcDbObjectId objId,
    AcDb::OpenMode openMode, 
    Adesk::Boolean openErasedObject = Adesk::kFalse);

Es fácil deducir que el parámetro AcDbObjectId es el ID del objeto que queremos abrir y que AcDbObject es la dirección del objeto que quedará abierto.

El tercer parámetro es donde debemos declarar nuestras oscuras intenciones con respecto al objeto a abrir y puede ser:

AcDb::kForRead
AcDb::kForWrite
AcDb::kForNotify

Está claro que el primer valor se usa cuando se quiere abrir un objeto solo para lectura. El valor kForWrite permite leer y escribir en un objeto. No es recomendable abrir un objeto en este modo si no es estrictamente necesario, porque abrir un objeto para modificación, consume recursos y bloquea al objeto para otras operaciones de escritura. El valor kForNotify nos permite abrir un objeto para enviarle una notificación. ¿Qué es eso? pues lea la ayuda del SDK.

El último parámetro de acdbOpenObject() sirve para indicarle que puede acceder a objetos eliminados.

El siguiente código, basado en la ayuda del SDK (yo, sí la leo), muestra una ejemplo sencillo en el que se abre (y cierra) un objeto para cambiarle el color:

Acad::ErrorStatus
cambialeColor(AcDbObjectId entId, Adesk::UInt16 newColor)
{
    AcDbEntity *pEntity;
    acdbOpenObject(pEntity, entId, AcDb::kForWrite);
    pEntity->setColorIndex(newColor);
    pEntity->close();
    return Acad::eOk;
}

No hay nada mágico en este código, pero muestra claramente la secuencia de Abrir y Cerrar un objeto en la base de datos de AUTOCAD. Se supone que este método se aplicará únicamente a entidades, de otra forma no funcionará.  Si no le resulta clara la explicación, le recomiendo que revise sus conceptos de polimorfismo.

Tal vez habría que notar que se usa el método setColorIndex() para fijar el color de una entidad, usando un ordinal (número) que se basa en una tabla con colores fijos en los 7 primeros valores y en donde el 3 es el verde. Para mayor información leer la ayuda del SDK, en la sección «Entity Color».

Tener en cuenta que acdbOpenObject() devuelve un objeto de la clase AcDbObject, no importa si abrimos un objeto o una entidad. Si se conoce que el objeto es una entidad, se puede hacer luego un «casting» para obtener la clase original de la entidad.

Si el problema es conocer el ID de un objeto en particular, desde AUTOCAD no sé cómo se pueda hacer, pero desde ObjectARX,  se puede acceder a la selección o al objeto que el usuario elija. Algo de eso veremos en la siguiente parte de este artículo.

Creando objetos gráficos

Las entidades de AUTOCAD también pueden ser creadas desde nuestro código, es decir, se pueden crear «programáticamente», si vale el término, aunque mejor sería decir que se pueden crear de forma dinámica o en tiempo de ejecución.

La creación no difiere mucho de el acceso, excepto que no existe aún el objeto, porque lo vamos a crear.

El siguiente código está basado en los proyectos de ejemplo que vienen en el SDK de ObjectARX:

AcDbObjectId createLine(double x1, double y1, double x2, double y2){
	AcGePoint3d startPt(x1, y1, 0.0);
	AcGePoint3d endPt(x2, y2, 0.0);
	AcDbLine *pLine = new AcDbLine(startPt, endPt);
	AcDbBlockTable *pBlockTable;
	acdbHostApplicationServices()->workingDatabase()
		->getSymbolTable(pBlockTable, AcDb::kForRead);
	AcDbBlockTableRecord *pBlockTableRecord;
	pBlockTable->getAt(ACDB_MODEL_SPACE, pBlockTableRecord, AcDb::kForWrite);
	pBlockTable->close();
	AcDbObjectId lineId;
	pBlockTableRecord->appendAcDbEntity(lineId, pLine);
	pBlockTableRecord->close();
	pLine->close();
	return lineId;
}

La primera parte consiste en la creación de dos puntos de tipo ArGePoint3d, que usaremos para crear un objeto AcDbline. Hay otros constructores para AcDbline, pero solo este nos permite especificar coordenadas para sus puntos.

Una vez creada la instancia de nuestro objeto, debemos acceder a la base de datos del documento (acdbHostApplicationServices()->workingDatabase()) y luego a la tabla de bloques (getSymbolTable(pBlockTable, AcDb::kForRead);).

Luego se busca el registro donde se debe almacenar nuestra entidad (ACDB_MODEL_SPACE) y es allí en donde se debe insertar (método appendAcDbEntity).

El siguiente ejemplo muestra un programa completo que inserta una línea al momento de cargarse:

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

AcDbObjectId createLine(double x1, double y1, double x2, double y2)
{
	AcGePoint3d startPt(x1, y1, 0.0);
	AcGePoint3d endPt(x2, y2, 0.0);
	AcDbLine *pLine = new AcDbLine(startPt, endPt);
	AcDbBlockTable *pBlockTable;
	acdbHostApplicationServices()->workingDatabase()
		->getSymbolTable(pBlockTable, AcDb::kForRead);
	AcDbBlockTableRecord *pBlockTableRecord;
	pBlockTable->getAt(ACDB_MODEL_SPACE, pBlockTableRecord, AcDb::kForWrite);
	pBlockTable->close();
	AcDbObjectId lineId;
	pBlockTableRecord->appendAcDbEntity(lineId, pLine);
	pBlockTableRecord->close();
	pLine->close();
	return lineId;
}
extern "C" AcRx::AppRetCode acrxEntryPoint(AcRx::AppMsgCode msg, void* appId) {
	switch (msg) {
	case AcRx::kInitAppMsg:
		acrxUnlockApplication(appId);
		acrxRegisterAppMDIAware(appId);
		createLine(0,0,10,10);
		break;
	case AcRx::kUnloadAppMsg:
		break;
	}
	return AcRx::kRetOK;
}

Para insertar una polilínea, se puede hacer de forma similar:

void createPolyline(AcGePoint3dArray ptArr) {
/* Crea una polilinea abierta a partir de las coordenadas suministradas en 
ptArr. */
	AcDb2dPolyline *pNewPline = new AcDb2dPolyline(
		AcDb::k2dSimplePoly, ptArr, 0.0, Adesk::kFalse);
	pNewPline->setColorIndex(3);
	// Get a pointer to a Block Table object.
	AcDbBlockTable *pBlockTable;
	acdbHostApplicationServices()->workingDatabase()
		->getSymbolTable(pBlockTable, AcDb::kForRead);
	// Get a pointer to the MODEL_SPACE BlockTableRecord.
	AcDbBlockTableRecord *pBlockTableRecord;
	pBlockTable->getAt(ACDB_MODEL_SPACE, pBlockTableRecord, AcDb::kForWrite);
	pBlockTable->close();
	// Append the pline object to the database and obtain its Object ID.
	AcDbObjectId plineObjId;
	pBlockTableRecord->appendAcDbEntity(plineObjId,
		pNewPline);
	pBlockTableRecord->close();
	// Make the pline object reside on layer "0".
	pNewPline->setLayer(_T("0"));
	pNewPline->close();
}

Un excelente ejemplo para la creación de entidades, es el que se encuentra en los proyectos ejemplo del SDK,  en la ruta: «\samples\database\ents_dg».

Eliminar objetos

Así como se pueden crear objetos también se pueden eliminar.

El método para eliminar objetos gráficos es:

Acad::ErrorStatus AcDbObject::erase(Adesk::Boolean Erasing = true);

Hay que tener en cuenta que las entidades eliminadas de la base de datos, realmente no se eliminan, sino que simplemente se marcan como «eliminado», de forma similar a como trabajan algunas bases de datos. Seguramente esta previsión es usada para implementar la, siempre útil, operación  «Deshacer».

¿En qué momento se liberan las entidades eliminadas de la base de datos? Eso es un misterio para mi, pero pareciera que se quedan hasta que se cierre el documento.

Para «des-borrar» un objeto eliminado, se usa el mismo método erase() pero con el parámetro en FALSE (kfalse).

Es posible abrir objetos eliminados con acdbOpenObject(), teniendo el cuidado de poner el último parámetro en TRUE.

Para más información sobre erase(), no hay que ser muy hábil, solo basta buscar en la ayuda del ObjectARX. Una ayuda: busque el tema «Erasing Objects».

SOBRE EL AUTOR

Tito Hinostroza es creador de «Tito’s Terminal» y «TitoCAD», una burla del AUTOCAD. También es autor del «Poema al Pato» y el  «Poema cuántico Nº 1». Se ha desempeñado como analista, programador, documentador, hacker, técnico electrónico, electricista, fontanero, albañil, dibujante, y bodeguero.  En sus ratos libres se dedica a reparar cualquier cosa y a escribir estos artículos.


Sé el primero en comentar

Dejar una contestacion

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


*