DOCE.9. UTILIDADES VARIAS (EL OBJETO Utility)

Como podemos comprobar al observar la tabla jerárquica de objetos y colecciones del principio del MÓDULO, existe un objeto de utilidad que es descendiente directo del documento actual activo. Este objeto llamado Utility, posee una serie de métodos que nos harán la vida más fácil a la hora de programar, ya que podremos acceder a ellos para solicitar puntos, convertir coordenadas, utilizar marcas de inicio y fin de DESHACER, etcétera.

La manera de llamar a este objeto y a sus métodos es idéntica a la ya utilizada para los demás objetos. Lo lógico será, si vamos a utilizarlo mucho, que creemos una variable para acceder a él de forma simple, como hemos venido haciendo. Por ejemplo, podemos declarar así una serie de variables de objeto:

Option Explicit
Dim AcadDoc As Object
Dim AcadModel As Object
Dim AcadUtil As Object

y luego inicializarlas así:

Set AcadDoc = GetObject(, "AutoCAD.Application").ActiveDocument
Set AcadModel = AcadDoc.ModelSpace
Set AcadUtil = AcadDoc.Utility

Como vemos, el objeto de utilidad desciende directamente del de documento activo, por lo que la forma de inicializarlo es similar a la de la colección de objetos de Espacio Modelo por ejemplo, como se ve (están en el mismo nivel).

Pasemos directamente a ver la lista de propiedades y métodos de este objeto, para después estudiarlos detenidamente.

Propiedades del objeto de utilidad:

Application

Métodos del objeto de utilidad:

AngleFromXAxis
AngleToReal
AngleToString
DistanceToReal
EndUndoMark
GetAngle
GetCorner
GetDistance
GetInput
GetInteger
GetKeyword
GetOrientation
GetPoint
GetReal
GetString
InitializeUserInput
PolarPoint
RealToString
StartUndoMark
TranslateCoordinates

Como propiedad tenemos, como siempre, Application, que es común a todos los objetos VBA. Los diversos métodos serán los que se expliquen a continuación, como venimos haciendo hasta ahora.

· AngleFromXAxis. Este método obtiene el ángulo comprendido entre la línea o vector determinado por los dos puntos que se suministran como argumentos, y el eje X actual del dibujo, siempre en sentido trigonométrico o antihorario y en radianes. La sintaxis para AngleFromXAxis es:

DblÁnguloDesdeX = ObjUtilidad.AngleFromXAxis(DblPtoInic, DblPtoFinal)

Tanto DblPtoInic como DblPtoFinal son los dos puntos de una línea —o de un vector que no tiene por qué estar dibujado—, por lo que serán matrices o arrays de tres elementos (coordenada X, coordenada Y y coordenada Z) de tipo de dato Double.

No es indiferente el orden en que se introducen los puntos, ya que no es igual el ángulo desde el eje X a una línea que va desde un punto 1 hasta un punto 2, que el ángulo desde el eje X a una línea que va desde 2 hasta 1, evidentemente.

NOTA: La variable que recoja este valor (DblÁnguloDesdeX) habrá sido declarada como Double.

· AngleToReal. Este método obtiene la conversión en radianes de un ángulo en formato de texto a un número real de doble precisión. Veamos la sintaxis de este método:

DblÁnguloReal = ObjUtilidad.AngleToReal(StrÁngulo, IntUnidades)

Como vemos, el ángulo ha de ser en formato de cadena de texto —normalmente introducido por el usuario—. IntUnidades, por su lado, es un valor Integer que determina en qué unidades aparece el ángulo del argumento anterior, procediendo a su conversión en número real según convenga. Este segundo argumento admite también las siguientes constantes:

acDegrees
acDegreeMinuteSeconds
acGrads
acRadians
acSurveyorUnits

Éstas se corresponden con los diferentes tipos de ángulos que maneja AutoCAD (grados sexagesiamales decimales, grados/minutos/segundos, grados centesimales, radianes, unidades geodésicas).

Así pues, una operación como la que sigue (siguiendo con la notación especificada al principio de esta sección):

Dim Resultado As Double
Resultado = AcadUtil.AngleToReal("180", acDegrees)
MsgBox Resultado

devolverá 3.14159265358979, esto es, el resultado de pasar a radianes la cantidad obtenida de convertir el texto "180" considerado como grados en notación decimal. El resultado (PI) sería el mismo al hacer lo siguiente:

Dim Resultado As Double
Resultado = AcadUtil.AngleToReal("100", acGrads)
MsgBox Resultado

· AngleToString. AngleToString convierte el ángulo proporcionado, el cual se considera siempre en radianes, a cadena de texto de acuerdo con las unidades y los decimales de precisión indicados:

StrÁnguloCadena = ObjUtilidad.AngleToString(DblÁngulo, IntUnidades, IntPrecis)

DblÁngulo es un valor Double que, como decimos, estará en radianes, ya que VBA así siempre lo interpreta. IntUnidades es un valor Integer que admite las mismas constantes explicadas en el método anterior y que representa las unidades a las que se convertirán los radianes especificados. IntPrecis especifica la precisión del ángulo, que se convertirá a cadena, en número de decimales. Este último argumento admite un valor entre 0 y 8 (siempre entero).

Veamos una rutina de ejemplo:

Dim Resultado As String
Const PI = 3.14159265358979
Resultado = AcadUtil.AngleToString(PI / 2, acDegrees, 3)
MsgBox Resultado

El resultado sería la cadena "90". Como vemos se ha transformado en grados sexagesimales en formato decimal (acDegrees), en cadena (AngleToString) y, aunque se indicó un precisión de tres decimales (3), no se ha tenido en cuenta al resultar un valor exacto.

· DistanceToReal. Convierte un distancia en formato de texto —normalmente indicada por el usuario— a un valor real, de acuerdo con el tipo de unidades especificado:

DblDistReal = ObjUtilidad.DistanceToReal(StrDistancia, IntUnidades)

StrDistancia es un valor tipo String que, como decimos, será una cadena. IntUnidades es un valor entero (Integer) que también admite las siguientes constantes:

acScientific
acDecimal
acEngineering
acArchitectural
acFractional

Este último argumento indica las unidades de conversión, cuyas constantes se corresponden con las unidades manejadas por AutoCAD (científicas, decimales, pies y pulgadas I, pies y pulgadas II y fraccionarias).

NOTA: El valor de la distancia obtenido es siempre en unidades de dibujo.

· EndUndoMark. Este método coloca una señal de fin de DESHACER en el lugar del programa que se utilice. Su sintaxis es:

ObjUtilidad.EndUndoMark

Si recordamos, las marcas de inicio y fin del comando de AutoCAD 14 DESHACER las utilizábamos mucho en los programas de AutoLISP, por lo que no hay motivo para no hacerlo en VBA también.

Cuando realizamos un programa o una macro que dibuja varias entidades, por ejemplo, si al término de su ejecución no estamos contentos con el resultado y utilizamos el comando H para deshacer el dibujo, únicamente se deshará la última entidad dibujada. Si antes de comenzar el dibujo del conjunto introducimos una marca de inicio de DESHACER y, tras terminar el dibujo completo, otra de fin de DESHACER, al introducir H al final de la ejecución del programa o de la macro, el conjunto de entidades se deshará por completo como un todo, cosa que nos interesa por estética y por funcionalidad.

Esta característica se corresponde con la opción Fin (End en inglés) del comando DESHACER (UNDO en inglés) de AutoCAD.

NOTA: Más adelante se explicará el método StartUndoMark que coloca marcas de inicio de DESHACER.

· GetAngle. Acepta el valor de un ángulo indicado por el usuario. Sintaxis:

DblÁngulo = ObjUtilidad.GetAngle(DblPtoBase, StrMensaje)

Este valor se puede introducir directamente desde el teclado (en el formato actual de unidades en AutoCAD o con los sufijos permitidos para radianes, centesimales, etc.), o señalando puntos en pantalla. Si se especifica un punto de base (es opcional) Double, se muestra un cursor elástico "enganchado" a dicho punto y el ángulo es el formado por la línea desde ese punto hasta el señalado por el usuario. Si no se especifica un punto de base, el usuario puede señalar dos puntos en pantalla para indicar el ángulo. El ángulo se mide siempre en dos dimensiones, ignorándose las coordenadas Z de los puntos.

En cualquiera de los supuestos, el ángulo se mide a partir del origen actualmente establecido en AutoCAD (variable de ANGBASE), siempre en sentido antihorario. El valor devuelto es siempre en radianes. Si se introduce el valor por teclado, se considerará como radianes. A diferencia del método GetOrientation —explicado más adelante—, GetAngle se emplea sobre todo para medir ángulos relativos.

El mensaje también es opcional (tipo String), y especifica el texto que aparecerá como solicitud del ángulo en la línea de comandos.

Comentemos la macro siguiente:

Option Explicit

Dim AcadDoc As Object
Dim AcadModel As Object
Dim AcadUtil As Object

Sub Macro() Set AcadDoc = GetObject(, "AutoCAD.Application").ActiveDocument Set AcadModel = AcadDoc.ModelSpace Set AcadUtil = AcadDoc.Utility Dim Resultado As Double Dim PtoBase(1 To 3) As Double PtoBase(1) = 10: PtoBase(2) = 18 Resultado = AcadUtil.GetAngle(PtoBase, "Ángulo: ") MsgBox Resultado End Sub

Veamos que aquí se solicita un ángulo al usuario y luego se muestra con un MsgBox. Percatémonos también de que la coordenada Z del punto base no es necesario que tenga valor, ya que como hemos dicho no se toma en cuenta. Sin embargo, a la hora de declarar la variable habremos de hacerlo como un array de tres valores (aunque el último luego lo dejemos vacío), ya que de otro modo no funcionará el método.

· GetCorner. Este método acepta el valor de un punto indicado por el usuario, mientras existe otro punto "enganchado", lo que forma un rectángulo. Sintaxis:

VarPuntoEsquina = ObjUtilidad.GetCorner(DblPtoBase, StrMensaje)

En este caso DblPtoBase es obligatorio (Double). StrMensaje sigue siendo opcional, al igual que con el método anterior, y es una cadena alfanumérica (String). El resultado de este método será un punto (matriz de tres elementos Double), por lo que habrá de recogerse en una variable tipo Variant para luego, y si se quieren utilizar las coordenadas de dicho punto, extraer los valores mediante índices y hacer un trasvase.

· GetDistance. Este método acepta el valor de una distancia indicada por el usuario. Este valor (Double) podrá ser introducido mediante el teclado o directamente en pantalla marcando dos puntos. En este caso da lo mismo el orden de introducción de puntos.

La sintaxis para GetDistance es la siguiente:

DblDistancia = ObjUtilidad.GetDistance(DblPtoBase, StrMensaje)

Si se indica un punto base, que habrá de ser Double, el cursor se "enganchará" a él y la distancia será medida desde ahí hasta el siguiente punto indicado por el usuario. StrMensaje (String), como viene siendo habitual, es un valor opcional.

NOTA: En principio la distancia medida con GetDistance es un distancia 3D. Aprenderemos al ver el método InitializeUserInput que esto puede modificarse.

· GetInput. Este método Se utiliza inmediatamente después de alguno de los otros métodos de solicitud (Get...), para detectar si el usuario ha dado alguna respuesta textual desde el teclado. Devuelve el texto introducido desde el teclado. Su sintaxis es:


StrCadenaDevuelta
= ObjUtilidad.GetInput

NOTA: Veremos más adelante algún ejemplo de este método (después de InitializeUserInput) que nos lo aclarará más.

· GetInteger. Solicita del usuario que indique un número entero. Si no es así, rechaza el dato introducido y vuelve a solicitar un número entero. Se permiten valores enteros negativos y el valor 0, a no ser que se especifique lo contrario mediante el método InitializeUserInput.

La sintaxis de GetInteger es:

IntValorEntero = ObjUtilidad.GetInteger(StrMensaje)

StrMensaje es opcional y funciona como en los métodos explicados anteriormente.

NOTA: Más aclaraciones sobre este y otros métodos tras InitializeUserInput.

· GetKeyword. Solicita del usuario que indique la palabra clave que le interesa. El mensaje de solicitud ofrecerá, lógicamente, las opciones posibles con las abreviaturas en mayúsculas para que el usuario sepa a qué atenerse. Previamente, se habrá utilizado el método InitializeUserInput para establecer las palabras clave permitidas.

Su sintaxis:

StrPalabraClave = ObjUtilidad.GetKeyword(StrMensaje)

StrMensaje es opcional y funciona como en los métodos explicados anteriormente.

· GetOrientation. Funciona de manera muy similar a GetAngle, con la única diferencia de que los ángulos se miden siempre desde el origen por defecto (posición de las 3 en el reloj o punto cardinal Este), independientemente del establecido en AutoCAD (variable de ANGBASE). Este método se emplea sobre todo para mediar ángulos absolutos y su sintaxis es:

DblOrientación = ObjUtilidad.GetOrientation(DblPtoBase, StrMensaje)

Los argumentos funcionan igual que en GetAngle

· GetPoint. GetPoint solicita al usuario un punto que podrá ser marcado en pantalla o introducido por teclado:

VarPunto = ObjUtilidad.GetPoint(DblPtoBase, StrMensaje)

Si se indica un punto Double de base (opcional), el cursor aparece "enganchado" mediante una línea elástica a dicho punto. StrMensaje funciona igual que en métodos anteriores y también es opcional.

La siguiente macro de ejemplo se utiliza para dibujar rectángulos mediante polilíneas con sólo marcar dos puntos en pantalla: el primero controlado por un GetPoint y el segundo con un GetCorner, para poder ver el rectángulo final en tiempo real antes de ser dibujado:

Option Explicit

Dim AcadDoc As Object
Dim AcadModel As Object
Dim AcadUtil As Object


Sub Macro()
  Set AcadDoc = GetObject(, "AutoCAD.Application").ActiveDocument
  Set AcadModel = AcadDoc.ModelSpace
  Set AcadUtil = AcadDoc.Utility

  Dim Punto1, Punto2
  Dim PuntoTras(2) As Double
  Dim PuntosPol(9) As Double
  Punto1 = AcadUtil.GetPoint(, "Primera esquina: ")
  PuntoTras(0) = Punto1(0): PuntoTras(1) = Punto1(1): PuntoTras(2) = Punto1(2)
  Punto2 = AcadUtil.GetCorner(PuntoTras, "Esquina opuesta: ")

  PuntosPol(0) = Punto1(0): PuntosPol(1) = Punto1(1)
  PuntosPol(2) = Punto2(0): PuntosPol(3) = Punto1(1)
  PuntosPol(4) = Punto2(0): PuntosPol(5) = Punto2(1)
  PuntosPol(6) = Punto1(0): PuntosPol(7) = Punto2(1)
  PuntosPol(8) = Punto1(0): PuntosPol(9) = Punto1(1)

  Call AcadDoc.ModelSpace.AddLightWeightPolyline(PuntosPol)
End Sub

Lo primero que se hace, tras declarar las variables, es solicitar el primer punto del rectángulo. Se realiza ahora un trasvase de coordenadas (de Variant a Double) para podérselas suministrar al método GetCorner como punto base. Se pide el segundo punto (el cursor permanecerá enganchado al primero mediante un rectángulo elástico) y se calculan los puntos para la polilínea que dibujará el rectángulo.

Una nota importante que debemos reseñar de este ejemplo es la manera de dibujar la polilínea. Nótese que por primera vez en estas páginas, en lugar de utilizar el método como explicamos en su momento, lo hemos usado con Call. Es momento ahora de decir que esto es perfectamente factible con todos los métodos de dibujo de entidades. Lo que ocurre, es que normalmente se utiliza la otra manera (guardando el objeto resultante en una variable de objeto) para después tener acceso absoluto a la entidad dibujada: cambiar su color, su tipo de línea, etcétera o utilizar cualquiera de las propiedades o métodos de ella.

Ahora bien, en momentos en los que no nos interese de una entidad más que su puro dibujo, se puede utilizar esta técnica.

NOTA: Percatémonos que declarar un matriz con (2) elementos es igual que hacerlo con (1 To 3) elementos. En este segundo caso los índices variarían de 1 a 3, y en el primero de 0 a 2; lo que da un total de tres elementos en ambos casos.

· GetReal. Solicita del usuario que indique un número real. Si no es así, rechaza el dato introducido y vuelve a solicitar un número real. Si se indica un número entero, es aceptado como real.

La sintaxis de GetReal es:

DblValorReal = ObjUtilidad.GetReal (StrMensaje)

StrMensaje es opcional y funciona como en los métodos explicados anteriormente.

· GetString. Acepta un cadena de texto introducida por el usuario. Si contiene más de 132 caracteres, sólo devuelve los primeros 132 caracteres. Sintaxis:

StrCadena = ObjUtilidad.GetString (BooModo, StrMensaje)

El modo es un valor Boolean (True o False) que indica si la cadena de texto puede contener espacios en blanco. Si es verdadero se admiten espacios y el texto introducido por teclado debe terminarse con INTRO. Si es falso, el primer espacio se considerará como un INTRO y terminará el texto. Si no se introduce ningún texto y se pulsa directamente INTRO, se devuelve una cadena vacía.

· InitializeUserInput. Este método establece limitaciones para aceptar los datos introducidos por el usuario, y también permite especificar palabras clave para ser aceptadas como nombres de opción. Veamos la sintaxis de utilización:

Call ObjUtilidad.InitializeUserInput (IntModo, StrPalabrasClave)

La mayoría de los métodos Get... que hemos visto se parecen enormemente (hasta algunos en los nombres) a las funciones GET... de AutoLISP que realizaban los mismos cometidos. Evidentemente necesitaremos pues un método como InitializeUserInput que haya opción de añadir justo antes de cualquier Get... para filtrar sus resultados; es lo que hacíamos en AutoLISP con INITGET.

IntModo es un valor entero (Integer) con código de bits que determina las limitaciones impuestas al usuario. Los modos posibles coinciden con los de la función INITGET de AutoLISP y se encuentran en la siguiente tabla:

Valor de bit		Modo

1 No admite valores nulos, es decir, INTRO como respuesta. 2 No admite el valor cero (0). 4 No admite valores negativos. 8 No verifica límites, aunque estén activados. 16 (No se utiliza). 32 Dibuja la línea o el rectángulo elásticos con línea de trazos en lugar de continua. 64 Hace que la función GETDIST devuelva distancias 2D. 128 Permite introducir datos arbitrarios por teclado. Tiene prioridad sobre el valor 1.


Al indicar un modo se pueden sumar varios de los bits. Por ejemplo, para impedir que el usuario indique un valor cero, nulo (es decir
INTRO) y/o negativo, el modo que se especificará será 7 (1 + 2 + 4).

Este método debe invocarse justo antes del método Get... que limita. Los modos que tienen sentido para cada método Get... también coinciden con los correspondientes de AutoLISP y se encuentran en la siguiente tabla:

Método		Valores de bits de modo con sentido para el método

GetInteger 1 2 4 128 GetReal 1 2 4 128 GetDistance 1 2 4 32 64 128 GetAngle 1 2 32 128 GetOrientation 1 2 32 128 GetPoint 1 8 32 128 GetCorner 1 8 32 128 GetString GetKeyword 1 128


NOTA
:
El modo no es opcional, y si no se desea ninguno hay que especificar un valor
0.

El segundo parámetro (String) es una cadena que define las palabras clave válidas como nombres de opciones. Estas se indican entre comillas, separadas por un espacio en blanco, y con la abreviatura en mayúsculas. La abreviatura es el mínimo número de caracteres en que debe coincidir la respuesta del usuario con una de las palabras clave válidas. El método siempre devuelve la palabra tal y como está escrita en InitializeUserInput. La solicitud de palabra clave se realiza mediante el método GetKeyword. Por ejemplo:

Call AcadUtil.InitializeUserInput (7, "Alta Baja Normal")
Op = AcadUtil.GetKeyword ("Precisión Alta/Baja/Normal: ")

En el ejemplo se supone que las variables AcadUtil y Op ya han sido definidas. El método GetKeyword solicita del usuario una opción. Si éste desea la opción Alta, puede indicar a, al, alt o alta, y en todos los casos la variable Op almacena el valor Alta.

Aceptación de valores por defecto


Estudiemos ahora un mecanismo para aceptar valores por defecto, tan típicos en línea de comandos.

Cuando desde un programa en VBA se introducen valores no esperados por teclado, se produce un error de VBA. En estos casos, VBA ejecuta automáticamente una sentencia especial llamada On Error, si se ha incluido en el programa. Si no es así, detiene automáticamente el programa informando del error. Si se indica la sentencia On Error Resume Next, el programa se reanuda en la línea siguiente a la que ha producido el error, sin detenerse.

Además, existe un objeto específico denominado Err, que tiene una propiedad Number que almacena un número de error. Cuando no hay errores, ese número o índice de error es 0. Cuando VBA recibe un tipo de dato inesperado (es lo que ocurre al pulsar INTRO en la solicitud de número entero —por ejemplo—, o al cancelar con ESC), el número de error es diferente de 0. Una vez que se detecta que ha habido error, para averiguar el tipo de dato inesperado causante del mismo, se puede examinar la propiedad Description del mismo objeto Err. Si el usuario ha introducido una letra o un texto por teclado ante la solicitud de un entero, real o punto, la descripción de error es "La entrada de usuario es una palabra clave". Si el usuario cancela, mediante ESC por ejemplo, la descripción de error será diferente.

El método GetInput estudiado, devuelve el texto introducido por teclado que ha producido el error. Cuando el usuario pulsa INTRO para aceptar una opción por defecto, y VBA espera un número o un punto, lo considera un texto vacío y por lo tanto produce un error con la misma descripción expuesta más arriba. En este caso, GetInput devuelve una cadena vacía "".

Para que VBA considere INTRO como un error, podría pensarse en establecer un modo 1 en InitializeUserInput. Pero en este caso, simplemente se impediría el INTRO mostrándose un mensaje y solicitando de nuevo el dato. Si no se establece un modo 1, el INTRO es aceptado, pero entonces no se produce error. El resultado es que cada tipo de solicitud acepta un valor diferente. Así, GetInteger podría considerar INTRO como 0 (depende del diseño del programa y de la definición de variable asignada), GetReal como un valor muy pequeño prácticamente 0 y GetPoint tomaría la posición del cursor en pantalla en el momento de pulsar INTRO o el punto de base si se ha especificado.

La solución a este problema es utilizar el modo 128 en InitializeUserInput. Este modo acepta datos arbitrarios por teclado y tiene prioridad sobre el modo 1. Por lo tanto, si se indica el modo 129 (1 + 128), se está impidiendo el INTRO a causa del modo 1 pero el modo 128 fuerza a aceptarlo. VBA lo considera entonces un error, y lo acepta como palabra clave no esperada, con valor de cadena vacía.

En resumen, un mecanismo general para detectar el INTRO pulsado por el usuario, comprende los siguientes pasos:

— Establecer la sentencia On Error Resume Next para que el programa no se detenga al producirse un error.

— Establecer un modo en InitializeUserInput con 129 como sumando.

— Detectar si ha habido error, examinando si Err.Number es diferente de 0.

— Detectar si el error se debe a texto del teclado, examinando si Err.Description = "La entrada de usuario es una palabra clave"

— Recuperar el texto introducido por teclado, mediante GetInput.

— Examinar si ese texto es una cadena vacía "".

El siguiente ejemplo muestra cómo aceptar una opción por defecto desde GetInteger.

Option Explicit

Dim AcadDoc As Object
Dim AcadModel As Object
Dim AcadUtil As Object


Sub Macro()
  Set AcadDoc = GetObject(, "AutoCAD.Application").ActiveDocument 
  Set AcadModel = AcadDoc.ModelSpace
  Set AcadUtil = AcadDoc.Utility

  Dim Prec As Integer
  Dim HayClave As Boolean
  Dim ValorClave As String
  On Error Resume Next

  Call AcadUtil.InitializeUserInput(135)
  Prec = AcadUtil.GetInteger("Valor de precisión <50>: ")
  If Err.Description = "La entrada de usuario es una palabra clave" Then
    HayClave = True
  Else
    HayClave = False
  End If
  ValorClave = AcadUtil.GetInput
  If Err.Number <> 0 Then
    If HayClave And ValorClave = "" Then Prec = 50 Else GoTo Errores
  End If
  Err.Clear

... resto del código ...

  End

Errores:
  MsgBox "*NO VALE*"
End Sub

El modo empleado en InitializeUserInput es 135 (1 + 2 + 4 + 128). Esto impide valores negativos ó 0, pero no INTRO, debido a que el modo 128 prevalece sobre el 1.

Mediante GetInteger se solicita un número entero. La variable Prec se define como Integer y acepta la respuesta del usuario. Si éste indica un valor negativo o cero, se rechaza y se vuelve a solicitar. Si indica un valor entero positivo éste se almacena en Prec, el valor de Err.Number es 0 por lo que no se ejecuta la sentencia dentro del If, y Prec mantiene su valor para el resto del programa.

Si el usuario cancela mediante ESC, se produce un error. La variable HayClave, definida como Boolean, almacena el resultado verdadero o falso de comparar Err.Description con el texto de descripción, en este caso False. Por eso dentro de la sentencia de If no se cumplirá la condición y el programa saltará a la subrutina Errores, que por simplificar consiste simplemente en mostrar un cuadro de diálogo de aviso con el mensaje *NO VALE*.

Si el usuario pulsa INTRO, también se produce un error al haberse sumado 1 al modo de InitializeUserInput. La descripción del error sí coincide con el texto indicado en el código del programa. La variable ValorClave, definida como String, almacena el valor devuelto por GetInput que en este caso es una cadena vacía. Por lo tanto, se cumple la sentencia dentro del If y la variable Prec se iguala al valor por defecto 50.

Si el usuario pulsa cualquier otra combinación (un valor numérico con decimales o un texto), se produce un error con la descripción de palabra clave. Pero GetInput devuelve el texto introducido en vez de cadena vacía, por lo que la sentencia dentro del If no cumplirá su condición, y se producirá un salto a la subrutina de Errores.

En todos los casos, la sentencia Err.Clear elimina la descripción de error para que no se quede almacenada y pueda originar mal funcionamiento en la siguiente ejecución del programa.

Con métodos de aceptación de cadenas de texto (GetKeyword y GetString)

Los métodos de solicitud de cadenas de texto como GetKeyword y GetString no producen error al pulsar el usuario INTRO y lo entienden como cadena vacía "". El mecanismo para aceptar opciones por defecto difiere del explicado anteriormente. Si se utiliza el modo 128 de InitializeUserInput todos los textos introducidos por teclado se aceptan como palabras clave, sin producir error. Esto obliga a examinar esos textos para ver si coinciden con las palabras claves permitidas y se desvirtúa la finalidad de GetKeyword. Por lo tanto, un mecanismo sencillo para aceptar opciones por defecto podría ser:

Dim Op As String

On Error Resume Next
Call AcadUtil.InitializeUserInput(0, "Alta Baja Normal")
Op = AcadUtil.GetKeyword("Precisión Alta/Baja/<Normal>: ")
If Err.Number = 0 Then 
  If Op = "" Then Op = "Normal" Else GoTo Errores
End If

El modo indicado en InitializeUserInput es 0, y se incluyen tres palabras clave como nombres de opciones permitidas. El propio método GetKeyword impide indicar valores numéricos o textos que no correspondan con las tres palabras clave. Si se produce cualquier error inesperado, la sentencia Else dentro del If hace que el programa salte a la subrutina de control de errores. Si el usuario introduce una de las opciones por teclado, no se produce error y la variable Op almacena la palabra clave de la opción. Por lo tanto, la sentencia dentro del If no se cumple y la variable Op sigue con su valor. Si el usuario pulsa INTRO, no se produce tampoco error, pero la variable Op almacena una cadena vacía y eso hace que la sentencia dentro del If se cumpla. El resultado es asignar a la variable Op el valor correspondiente a la opción por defecto, en este caso Normal.

Con el método de aceptación de números enteros (GetInteger)

Para aceptar un valor por defecto se puede utilizar el mecanismo ya explicado. Para aceptar palabras clave además de valores numéricos, el mecanismo se explica un poco más adelante. Estos mecanismos tienen la ventaja de que se pueden aplicar con mínimas modificaciones a todos los métodos que solicitan valores numéricos y puntos. No obstante, es posible utilizar otros mecanismos específicos dependiendo del diseño del programa y conociendo los tipos de errores producidos.

Por ejemplo, cuando se define una variable como Integer y se le asigna el valor devuelto por GetInteger, si el usuario pulsa INTRO, VBA lo considera un tipo de dato inesperado y origina un error de desbordamiento con un valor de Err.Number igual a 6. Cualquier otro error, por ejemplo al cancelar mediante ESC, produce otro número diferente. Por lo tanto, un mecanismo sencillo para aceptar opciones por defecto es:

Dim N As Integer

Call AcadUtil.InitializeUserInput(6)
On Error Resume Next
N = AcadUtil.GetInteger("Precisión <3>: ")
If Err.Number <> 0 Then 
  If Err.Number = 6 Then N = 3 Else GoTo Errores
End If

Mediante el modo 6, el método InitializeUserInput impide valores negativos y 0. La sentencia On Error Resume Next, hace que el programa no se detenga al producirse un error y continúe normalmente. El método GetInteger solicita un número entero. Si el usuario indica uno que no sea negativo ni 0, se acepta, el valor de Err.Number es 0, la condicional If no se cumple y el programa continúa sin problemas. Si el usuario indica INTRO, se produce un error aunque el programa continúa sin detenerse, el valor de Err.Number es 6, y entonces en la variable N se almacena el valor por defecto 3.

Si se origina cualquier otro error (por ejemplo el usuario cancela mediante ESC), el valor de Err.Number es diferente de 0 y 6, y entonces la sentencia GoTo salta a una subrutina Errores, donde habrá especificadas una serie de actuaciones y después se abortará el programa.

Con el método de aceptación de números reales (GetReal)

Para la aceptación de valores por defecto se puede utilizar el mismo mecanismo, sustituyendo simplemente la declaración de variable como Double en vez de Integer, y empleando lógicamente el método GetReal en lugar de GetInteger. Para aceptar palabras clave además de valores numéricos, el mecanismo se explica un poco más adelante —en esta misma página—.

Si se desea un mecanismo específico para números reales, cuando se define una variable como Double y se le asigna el valor devuelto por GetReal, si el usuario pulsa INTRO, VBA lo considera un valor residual muy pequeño próximo a 0. Como ese valor es un número real, GetReal no produce error. Por lo tanto, el mecanismo de aceptación no va a ser detectar un número de error, sino un valor devuelto muy pequeño.

Dim Prec As Double

On Error GoTo Errores
Call AcadUtil.InitializeUserInput(6)
Prec = AcadUtil.GetReal("Precisión <2.5>: ")
If Prec < 0.00000001 Then Prec = 2.5

En este caso, la sentencia On Error envía el programa directamente a la subrutina Errores, porque el INTRO como respuesta no va a ser considerado un error sino como un valor muy pequeño próximo a 0. Mediante el modo 6, el método InitializeUserInput impide valores negativos y 0. El propio método GetReal impide valores no numéricos. Mediante If se analiza el valor introducido por el usuario. Si es más pequeño que cualquiera que hubiera podido indicar por teclado, entonces es que ha pulsado INTRO y se asigna a la variable el valor por defecto. En caso contrario, en If no se realiza ninguna acción y el programa continúa normalmente.

Otros métodos

Crear una rutina de aceptación de valores por defecto para los demás métodos resulta sencillo, ya que sólo hay que reflejarse en los ejemplos vistos hasta aquí y adecuar el más preciso.

Combinación de solicitudes numéricas con opciones textuales y GetInput

Si se ha indicado un modo 128 en InitializeUserInput, el método de solicitud empleado a continuación aceptará la entrada de teclado como palabra clave y GetInput devolverá dicha entrada como un texto. Si la entrada ha sido INTRO, lo devolverá como cadena vacía. Esto permite establecer un mecanismo de aceptación de valores por defecto cuando se emplean funciones de solicitud de datos numéricos o puntos, tal como se ha explicado anteriormente.

Pero si la entrada de teclado no es INTRO se puede emplear GetInput para combinar solicitudes numéricas con opciones de texto. Por ejemplo:

Dim Prec As Integer
Dim HayClave As Boolean
Dim ValorClave As String

On Error Resume Next
Call AcadUtil.InitializeUserInput(135, "Alta Baja Normal")
Prec = AcadUtil.GetInteger("Valor de precisión o Alta/Baja/Normal: ")
If Err.Description = "La entrada de usuario es una palabra clave" Then
  HayClave = True
Else
  HayClave = False
End If
ValorClave = AcadUtil.GetInput
If Err.Number <> 0 Then
  If HayClave And ValorClave <> "" Then GoSub Precision Else GoTo Errores
End If
Err.Clear

... resto del código ...

End

Precision:
  If ValorClave = "Normal" Then Prec = 10: Return
  If ValorClave = "Alta" Then Prec = 100: Return
  If ValorClave = "Baja" Then Prec = 1: Return
  GoTo Errores

El mecanismo es similar al explicado ya. Si se indica un valor negativo o cero se rechaza. Si se indica un número entero positivo se acepta porque no produce error. Si se indica un valor no esperado por teclado, se produce un error y el modo 128 como sumando en InitializeUserInput acepta la entrada como palabra clave. GetInput devuelve esa entrada como texto y lo almacena en ValorClave. La sentencia dentro del If examina si HayClave es verdadera y si ValorClave no es una cadena vacía. En caso de ser así, llama a la subrutina Precision, donde se analiza el texto aceptado como palabra clave y se asigna a la variable Prec el valor entero correspondiente a cada precisión, continuando la ejecución del programa mediante Return. Si la palabra clave no es ninguna de las tres admitidas, se salta a la subrutina de errores.

Si se produce un error inesperado, su descripción no corresponderá a la de palabra clave, y la sentencia dentro del If no se cumplirá por lo que Else saltará a la subrutina Errores. Si se pulsa INTRO (en el ejemplo no se admite una opción por defecto), ValorClave será una cadena vacía, la sentencia dentro del If tampoco se cumplirá y se saltará a la subrutina de Errores.

Sigamos pues ahora con la explicación de los métodos que faltan del objeto Utility de utilidad.

· PolarPoint. Este método obtiene el punto (matriz de tres elementos Double) a partir de otro punto dado (matriz de tres elementos Double), según un ángulo en radianes (Double) y una distancia en las unidades actuales (Double). Es decir, obtiene un punto por coordenadas polares a partir de otro dado.

La sintaxis de este método es:

DblPunto2 = ObjUtilidad.PolarPoint (DblPunto1, DblÁngulo, DblDistancia)

En el siguiente ejemplo, se dibuja una línea perpendicular desde el punto medio entre otros dos puntos, con una longitud especificada:

Option Explicit

Dim AcadDoc As Object
Dim AcadModel As Object
Dim AcadUtil As Object


Sub Macro()
  Set AcadDoc = GetObject(, "AutoCAD.Application").ActiveDocument
  Set AcadModel = AcadDoc.ModelSpace
  Set AcadUtil = AcadDoc.Utility

  Dim VPunto1, VPunto2, VPuntoFinal
  Dim Punto1(2) As Double, Punto2(2) As Double
  Dim PuntoMedio(2) As Double, PuntoFinal(2) As Double
  Dim Ángulo As Double, Distancia As Double
  Const PI = 3.1415926

  Call AcadUtil.InitializeUserInput(1)  
  VPunto1 = AcadUtil.GetPoint(, "Primer punto: ")
  Punto1(0) = VPunto1(0): Punto1(1) = VPunto1(1): Punto1(2) = VPunto1(2)
  Call AcadUtil.InitializeUserInput(1)
  VPunto2 = AcadUtil.GetPoint(Punto1, "Segundo punto: ")
  Punto2(0) = VPunto2(0): Punto2(1) = VPunto2(1): Punto2(2) = VPunto2(2)
  PuntoMedio(0) = ((Punto1(0) + Punto2(0)) / 2)
  PuntoMedio(1) = ((Punto1(1) + Punto2(1)) / 2): PuntoMedio(2) = Punto1(2)
  Ángulo = AcadUtil.AngleFromXAxis(Punto1, Punto2)
  Distancia = AcadUtil.GetDistance(PuntoMedio, "Distancia en perpendicular: ")
  VPuntoFinal = AcadUtil.PolarPoint(PuntoMedio, Ángulo + PI / 2, Distancia)
  PuntoFinal(0) = VPuntoFinal(0)
  PuntoFinal(1) = VPuntoFinal(1)
  PuntoFinal(2) = VPuntoFinal(2)
  Call AcadDoc.ModelSpace.AddLine(PuntoMedio, PuntoFinal)
End Sub

Los puntos Punto1 y Punto2 (VPunto1 y VPunto2 en su definición Variant) son solicitados por el programa, sin permitir INTRO como respuesta nula. Pueden ser los extremos de una línea ya dibujada o dos puntos cualesquiera. A continuación, el programa calcula el punto medio PuntoMedio haciendo medias aritméticas con las coordenadas X e Y. Mediante el método AngleFromXAxis calcula el ángulo absoluto entre los puntos 1 y 2. Después solicita la distancia en perpendicular y calcula el punto PuntoFinal a partir del punto medio, llevando la distancia a un ángulo que resulta de sumar PI / 2 al ángulo absoluto entre 1 y 2. La última operación es dibujar una línea entre los dos últimos puntos.

· RealToString. RealToString convierte el valor proporcionado, el cual será real (Double), a cadena de texto de acuerdo con las unidades y los decimales de precisión indicados:

StrRealCadena = ObjUtilidad.RealToString(DblValorReal, IntUnidades, IntPrecis)

DblValorReal es, como hemos dicho, un valor Double. IntUnidades es un valor Integer que admite las mismas constantes explicadas en el método DistanceToReal y que representa las unidades a las que se convertirá el valor real. IntPrecis especifica la precisión en decimales del número real, el cual se convertirá a cadena. Este último argumento admite un valor entre 0 y 8 (siempre entero).

En la siguiente rutina:

Dim TxValor As String
TxValor = AcadUtil.RealToString(326.7539, acFractional, 2)
MsgBox TxValor

el valor devuelto será la cadena "326 3/4".

· StartUndoMark. Este método coloca una señal de inicio del comando DESHACER en el lugar del programa que se utilice. Su sintaxis es:

ObjUtilidad.StartUndoMark

Esta característica se corresponde con la opción Inicio (BEgin en inglés) del comando DESHACER (UNDO en inglés) de AutoCAD.

NOTA: Véase en esta misma sección el método EndUndoMark que coloca marcas de fin de DESHACER.

· TranslateCoordinates. Convierte un punto o vector de desplazamiento de un sistema de coordenadas de origen a otro de destino:

VarPtoConvertido = ObjUtilidad.TranlateCoordinates(DblPtoOriginal, IntSisOrigen,
IntSisDestino
, BooDesplazamiento)

BooDesplazamiento es un valor Boolean que indica si la matriz de tres valores Double que es DblPtoOriginal se considera un vector de desplazamiento. Si es verdadero, se considera un vector de desplazamiento. Si es falso, se considera un punto.

Los sistemas de coordenadas de origen y destino se indican mediante un código de número entero (Integer). Para mayor facilidad y comodidad existen cuatro constantes que también se pueden especificar:

acWorld
acUCS
acDisplayDCS
acPaperSpaceDCS

Estas constantes se corresponden con los distintos sistemas de coordenadas que se utilizan en la interfaz gráfica de AutoCAD 14 (Sistema de Coordenadas Universal o SCU, Sistema de Coordenadas Personal o SCP, Sistema de Coordenadas de Visualización o SCV y Sistema de Coordenadas de Espacio Papel o SCEP).

En esta rutina (utilizando las convenciones que arrastramos desde el inicio de esta sección):

Dim VarPtoOr, VarPtoDest
VarPtoOr = AcadUtil.GetPoint(, "Punto que convertir: ")
VarPtoDest = AcadUtil.TranslateCoordinates(VarPtoOr, acUCS, acWorld, False)

el método GetPoint solicita indicar un punto, éste se acepta en la variable (definida como Variant) VarPtoOr y después se convierte desde el SCP actual al SCU, indicando un valor False de desplazamiento para que no se considere un vector.

NOTA: Recuérdese que no declarar una variable con un tipo concreto es lo mismo que declararla como Variant.

A continuación estudiaremos un programa que resulta muy jugoso como repaso de todo lo que hemos estudiado hasta ahora. Este programa maneja un cuadro de diálogo (formulario) que es el que se observa en la página siguiente.

Como vemos, resulta ser un programa para dibujar agujeros para tornillos en alzado. Se indica primero el tipo de agujero (con cajera recta, con cajera avellanada o sin cajera). Después hemos de introducir en las diferentes casillas los distintos valores necesarios para dibujar el agujero.

Al pulsar el botón Aceptar se nos solicitará el punto y el ángulo de inserción, tras lo cual se dibujará el agujero con la línea de ejes en una capa llamada EJES, con tipo de línea TRAZO_Y_PUNTO y color rojo.

El botón Cancelar termina el programa.

Veamos, tras el diseño del letrero, el código VBA de este programa —ya un poco complejo—.

 


Option Explicit Dim AcadDoc As Object, AcadUtil As Object Dim AcadObj As Object, AcadEje As Object, AcadCapa As Object Dim VPt0, VPt1, VPt2, VPt3, VPt4, VPt5 Dim VPtgj, VPt1Eje, VPt2Eje Dim DiamAgujero0, ProfAgujero0, DiamCajera0, ProfCajera0 Dim Pt0(2) As Double Dim Pt1(2) As Double, Pt2(2) As Double, Pt3(2) As Double Dim Pt4(2) As Double, Pt5(2) As Double Dim Ptgj(2) As Double, Pt1Eje(2) As Double, Pt2Eje(2) As Double Dim Refent0 As Integer, Angulo As Variant, PI As Double


Private Sub CajeraAvellanada_Click()
  DiamCajera.Enabled = True
  ProfCajera.Enabled = True
  Label3.Enabled = True
  Label4.Enabled = True
  Imagen.Picture = LoadPicture("cajera_avellanada.bmp")
End Sub


Private Sub CajeraRecta_Click()
  DiamCajera.Enabled = True
  ProfCajera.Enabled = True
  Label3.Enabled = True
  Label4.Enabled = True
  Imagen.Picture = LoadPicture("cajera_recta.bmp")
End Sub


Private Sub Cancelar_Click()
  End
End Sub


Private Sub Dibujar_Click()
  On Error GoTo Error
  Chequear
  If Errores.Caption = "" Then Else Exit Sub
  formAgujeros.Hide

  Call AcadUtil.InitializeUserInput(1)
  VPt0 = AcadUtil.GetPoint(, "Punto: ")
  Pt0(0) = VPt0(0): Pt0(1) = VPt0(1): Pt0(2) = VPt0(2)
  Call AcadDoc.SetVariable("osmode", 512)
  Call AcadUtil.InitializeUserInput(1)
  Angulo = AcadDoc.Utility.GetAngle(VPt0, "Angulo (Cerca de): ")
  PI = 3.14159265359
  If SinCajera.Value = True Then
    VPtgj = VPt0
    DibAgujero
  Else
    DibCajera
    DibAgujero
  End If
  
  VPt1Eje = AcadDoc.Utility.PolarPoint(Pt0, Angulo + PI, 5)
  Pt1Eje(0) = VPt1Eje(0): Pt1Eje(1) = VPt1Eje(1): Pt1Eje(2) = VPt1Eje(2):
  VPt2Eje = AcadDoc.Utility.PolarPoint(Pt3, Angulo, 5)
  Pt2Eje(0) = VPt2Eje(0): Pt2Eje(1) = VPt2Eje(1): Pt2Eje(2) = VPt2Eje(2):
  Set AcadEje = AcadObj.AddLine(Pt1Eje, Pt2Eje)

  On Error Resume Next
  If IsEmpty(AcadDoc.Linetypes.Item("trazo_y_punto")) Then
    Call AcadDoc.Linetypes.Load("trazo_y_punto", "acadiso.lin")
  End If
  If IsEmpty(AcadDoc.Layers.Item("ejes")) Then
    Set AcadCapa = AcadDoc.Layers.Add("ejes")
    AcadCapa.Linetype = "trazo_y_punto"
    AcadCapa.Color = 1
  End If
  AcadEje.Layer = "ejes"

  Call AcadDoc.SetVariable("osmode", Refent0)
  Open "agujeros.$vr" For Output As #1
  Write #1, DiamAgujero, ProfAgujero, DiamCajera, ProfCajera
  Close #1

Error:
  MsgBox "¡NO VALE!", , "Mensaje de error"
End Sub


Private Sub DibCajera()
  VPt1 = AcadUtil.PolarPoint(Pt0, Angulo - (PI / 2), Val(DiamCajera) / 2)
  Pt1(0) = VPt1(0): Pt1(1) = VPt1(1): Pt1(2) = VPt1(2)
  VPt2 = AcadUtil.PolarPoint(Pt1, Angulo, Val(ProfCajera))
  Pt2(0) = VPt2(0): Pt2(1) = VPt2(1): Pt2(2) = VPt2(2)
  VPt3 = AcadUtil.PolarPoint(Pt2, Angulo + (PI / 2), Val(DiamCajera))
  Pt3(0) = VPt3(0): Pt3(1) = VPt3(1): Pt3(2) = VPt3(2)
  VPt4 = AcadUtil.PolarPoint(Pt3, Angulo + PI, Val(ProfCajera))
  Pt4(0) = VPt4(0): Pt4(1) = VPt4(1): Pt4(2) = VPt4(2)
  If CajeraAvellanada.Value = True Then
    VPt1 = AcadUtil.PolarPoint(Pt1, Angulo - (PI / 2), Val(ProfCajera) / 2): _
    Pt1(0) = VPt1(0): Pt1(1) = VPt1(1): Pt1(2) = VPt1(2)
  End If
  If CajeraAvellanada.Value = True Then
    VPt4 = AcadUtil.PolarPoint(Pt4, Angulo + (PI / 2), Val(ProfCajera) / 2): _
    Pt4(0) = VPt4(0): Pt4(1) = VPt4(1): Pt4(2) = VPt4(2)
  End If

  Call AcadObj.AddLine(Pt1, Pt2)
  Call AcadObj.AddLine(Pt2, Pt3)
  Call AcadObj.AddLine(Pt3, Pt4)
  VPtgj = AcadUtil.PolarPoint(Pt0, Angulo, Val(ProfCajera))
End Sub


Private Sub DibAgujero()
  Ptgj(0) = VPtgj(0): Ptgj(1) = VPtgj(1): Ptgj(2) = VPtgj(2)
  VPt1 = AcadUtil.PolarPoint(Ptgj, Angulo - (PI / 2), Val(DiamAgujero) / 2)
  Pt1(0) = VPt1(0): Pt1(1) = VPt1(1): Pt1(2) = VPt1(2)
  VPt2 = AcadUtil.PolarPoint(Pt1, Angulo, Val(ProfAgujero)) 
  Pt2(0) = VPt2(0): Pt2(1) = VPt2(1): Pt2(2) = VPt2(2)
  VPt3 = AcadUtil.PolarPoint(Pt2, Angulo + (PI / 2), Val(DiamAgujero) / 2)
  Pt3(0) = VPt3(0): Pt3(1) = VPt3(1): Pt3(2) = VPt3(2)
  VPt3 = AcadUtil.PolarPoint(Pt3, Angulo, Val(DiamAgujero) / 4)
  Pt3(0) = VPt3(0): Pt3(1) = VPt3(1): Pt3(2) = VPt3(2)
  VPt4 = AcadUtil.PolarPoint(Pt2, Angulo + (PI / 2), Val(DiamAgujero))
  Pt4(0) = VPt4(0): Pt4(1) = VPt4(1): Pt4(2) = VPt4(2)
  VPt5 = AcadUtil.PolarPoint(Pt4, Angulo + PI, Val(ProfAgujero))
  Pt5(0) = VPt5(0): Pt5(1) = VPt5(1): Pt5(2) = VPt5(2)

  Call AcadObj.AddLine(Pt1, Pt2)
  Call AcadObj.AddLine(Pt2, Pt4)
  Call AcadObj.AddLine(Pt4, Pt5)
  Call AcadObj.AddLine(Pt2, Pt3)
  Call AcadObj.AddLine(Pt3, Pt4)
End Sub


Private Sub Chequear()
  If Val(DiamAgujero) <= 0 Then
    Errores.Caption = "Diámetro de agujero debe ser mayor o igual que 0"
    DiamAgujero.SelStart = 0: DiamAgujero.SelLength = Len(DiamAgujero)
    DiamAgujero.SetFocus: Exit Sub
  End If

  If Val(ProfAgujero) <= 0 Then 
    Errores.Caption = "Profundidad de agujero debe ser mayor o igual que 0"
    ProfAgujero.SelStart = 0: ProfAgujero.SelLength = Len(ProfAgujero)
    ProfAgujero.SetFocus: Exit Sub 
  End If

  If Cajera.Enabled = True Then
    If Val(DiamCajera) <= Val(DiamAgujero) Then
      Errores.Caption = "Diámetro de cajera debe ser mayor que el de agujero"
      DiamCajera.SelStart = 0: DiamCajera.SelLength = Len(DiamCajera)
      DiamCajera.SetFocus: Exit Sub
    End If
  End If

  If Cajera.Enabled = True Then
    If Val(ProfCajera) <= 0 Then
      Errores.Caption = "Profundidad de cajera debe ser mayor o igual que 0"
      ProfCajera.SelStart = 0: ProfCajera.SelLength = Len(ProfCajera)
      ProfCajera.SetFocus: Exit Sub
    End If
  End If

  Errores.Caption = ""
End Sub


Private Sub UserForm_Initialize()
  Set AcadDoc = GetObject(, "Autocad.Application").ActiveDocument
  Set AcadUtil = AcadDoc.Utility
  Set AcadObj = AcadDoc.ModelSpace

  On Error Resume Next
  Refent0 = AcadDoc.GetVariable("osmode")
  Open "agujeros.$vr" For Input As #1
    If Err.Description = "No se encontró el archivo" Then GoSub Defecto
    Input #1, DiamAgujero0, ProfAgujero0, DiamCajera0, ProfCajera0

    DiamAgujero = DiamAgujero0 
    ProfAgujero = ProfAgujero0
    DiamCajera = DiamCajera0
    ProfCajera = ProfCajera0
  Close #1

  Exit Sub
Defecto:
  Open "agujeros.$vr" For Append As #1
    Write #1, "10", "10", "20", "5"
  Close #1
  Open "agujeros.$vr" For Input As #1
  Return
End Sub


Private Sub SinCajera_Click()
  DiamCajera.Enabled = False
  ProfCajera.Enabled = False
  Label3.Enabled = False
  Label4.Enabled = False
  Imagen.Picture = LoadPicture("sin_cajera.bmp")
End Sub

Iremos comentando cada procedimiento Sub por separado y no en el orden en que están en el listado, sino en uno quizá más lógico.

— (General)_(Declaraciones)

Aquí como siempre se declaran todas las variables que luego utilizaremos, tanto las de objeto (Object), como Variant y Double. Recordar la necesidad de tener un doble juego de variables, unas Variant y otras Double, para hacer trasvase al obtener un punto y luego querer utilizarlo.

— UserForm_Initialize()

Este es el procedimiento que se ejecuta nada más correr el programa, al inicializarse el formulario. Lo primero es lo de siempre, esto es, asignar a cada objeto de AutoCAD que vamos a necesitar su valor correspondiente. Después se utiliza la sentencia On Error Resume Next para controlar la apertura del archivo que vamos a explicar ahora. Luego se guarda en Refent0 el valor de la variable OSMODE de AutoCAD; ya veremos para qué.

Así como AutoLISP guarda los valores de las variables globales utilizadas hasta cerrar AutoCAD, con VBA no disponemos de esa ventaja. Es por ello que, dada la característica de los programas para AutoCAD que poseen la capacidad de almacenar los últimos valores utilizados como valores por defecto, nos vamos a inventar un método para que esto suceda también en nuestros programas VBA.

El sencillo método consiste simplemente en crear un archivo de texto donde se almacenarán, en cada ejecución del programa, los últimos valores utilizados. De este modo, al correr de nuevo el programa, se leerán dichos valores y se introducirán en el cuadro para ofrecerlos por defecto.

De esta manera intentamos leer el archivo que almacenará los valores (AGUJEROS.$VR) en el directorio actual. Si no existiera se produciría un error, por lo que la ejecución sigue en la siguiente línea (recordemos el On Error Resume Next). En esta línea se compara el texto del error con el que significa que el archivo no se ha encontrado y, si fueran iguales, la ejecución se dirige a la subrutina Defecto donde se crea y se le añaden unos valores por defecto.

Tanto si existiera como si estuviera recién creado, se continúa la ejecución leyendo los valores del archivo e introduciéndolos en el cuadro.

NOTA: Es más lógico utilizar los valores numéricos de Err en lugar de sus descripciones, ya que podría utilizarse así en cualquier plataforma idiomática VBA. Para hallar el número de un error (si no disponemos de una lista), sólo hemos de provocarlo y extraer el valor con Err.Number.

— SinCajera_Click()

Este procedimiento y los dos siguientes dicen referencia a la hora de hacer clic en alguno de los tres botones excluyentes para elegir el tipo de agujero. Este concretamente responde al evento de hacer clic en el botón excluyente Sin cajera. Al hacerlo, tanto la casillas de profundidad de cajera como la de diámetro de la cajera, así como sus etiquetas, deben inhabilitarse. También se refleja en el cuadro de imagen el archivo .BMP correspondiente.

— CajeraRecta_Click()

Al igual que en el anterior procedimiento explicado, en CajeraRecta_Click() se muestra la imagen correspondiente en el cuadro de imagen y se establecen como habilitadas las casillas y etiquetas de profundidad y diámetro de cajera por si al hacer clic en Cajera recta se proviniera de Sin cajera, la cual las desactiva como sabemos.

— CajeraAvellanada_Click()

Así también, en este procedimiento Sub se muestra la imagen correspondiente con una cajera avellanada en el cuadro de imagen, y también se establecen como habilitadas las casillas y etiquetas de profundidad y diámetro de cajera por si al hacer clic en Cajera avellanada se proviniera de Sin cajera.

— Dibujar_Click()

Este es el procedimiento que arranca al ser pulsado el botón Aceptar. Lo primero que hace es definir una rutina de errores que controlará salidas no deseadas, por ejemplo (como al pulsar ESC), u otros errores no deseados. Después se llama al procedimiento de chequeo de casillas, el cual se comentará seguido de éste.

Tras ocultar el formulario (letrero de diálogo) se pregunta por el punto de inserción del agujero (sin admitir INTRO como respuesta) y se guardan sus coordenadas en VPt0. Seguidamente se hace el trasvase de variables con Pto0, que será la que se utilice para el dibujo.

Se establece el valor de OSMODE a 512 (Cercano) y se pide el ángulo de inserción. Y tras establecer el valor de PI se comprueba si el agujero tiene cajera o no. Si no tuviera se llama únicamente a la rutina de dibujo del agujero y, si tuviera cajera (recta o avellanada), se llama primero a la rutina que dibuja la cajera y luego a la del agujero. Además, el hecho de no tener cajera hace que el punto de inserción sea igual al primer punto de dibujo del agujero

Las sentencias siguientes se corresponden con los cálculos de los puntos del eje de simetría. Además se carga el tipo de línea —si no está cargado— y se crea la capa —si no existe— que pertenecerán al eje. A esta última se le asigna el tipo de línea cargado y el color rojo.

Por último se reasigna a la variable OSMODE su valor original (por eso lo guardamos en Refent0) y se guardan los valores utilizados en el archivo de valores por defecto.

NOTA: Recuérdese que antes de finalizar este procedimiento ha habido que pasar por otros dos o tres: el de chequeo, el de dibujo de cajera y el de dibujo de agujero. Estos se estudian ahora por ese orden.

— Chequear()

Aquí se comprueban los valores de cada casilla y, si alguno estuviera errado, se muestra un mensaje en una línea de errores inferior (que es una etiqueta), se selecciona el texto de la casilla y se sale de procedimiento.

Al haber error el texto de la línea inferior es diferente que una cadena vacía. Si no hay error este texto es igual a la cadena vacía (""). Repásese el código del procedimiento anterior para ver cómo se controla después esto.

— DibCajera()

Para dibujar la cajera se calculan todos los puntos necesarios y se dibuja. También se tiene en cuenta si es recta o avellanada.

— DibAgujero()

Para dibujar el agujero se calculan todos los puntos y se dibuja.

NOTA: Recuérdese que tras este procedimiento Sub se sigue en Dibujar_Click().

— Cancelar_Click()

Este Sub únicamente dice que al pulsar el botón Cancelar se acabe el programa, sin más.

NOTA: Evidentemente, para que este programa funcione, habrán de estar los archivos .BMP en el directorio actual de trabajo.

 

4ª fase intermedia de ejercicios

· Escribir una programa que maneje el cuadro de diálogo que se puede ver en la página siguiente.

El programa dibuja tuercas hexagonales en vista de perfil. Los datos de la distancia entre aristas y la altura de la tuerca se introducen en las casillas correspondientes. El botón Punto de inserción< sale del letrero para marcar un punto en pantalla. Al hacerlo vuelve al letrero y escribe las coordenadas en sus casillas correspondientes.

Una vez introducidos todos los datos necesarios, el botón Dibujar realiza el dibujo y acaba el programa. El botón Cancelar simplemente termina la aplicación.

Introdúzcase algún control de entrada de datos del usuario, así como unos valores por defecto con los que arranque el cuadro (se puede hacer en tiempo de diseño).

  

 

Autor: Jonathan Préstamo Rodríguez.
Para:
La Web del Programador.