Una extensión es una colección de clases y de recursos que proporcionan funcionalidad adicional a OrbisCAD. Desde la perspectiva del usuario es tan fácil como dejar caer un jar en el directorio de extensiones.
Vamos a ver el proceso de desarrollo de una extensión que instala un plugin que cierra la aplicación. El primer paso es escribir el plugin. Un plugin es una clase que añade un menú y/o un botón a la barra de herramientas y responde cuando el usuario los selecciona. Aquí se puede ver el código de un plugin sencillo que deshace la última operación de edición realizada. Una vez creado el plugin hay que crear la extensión que lo carga.
Un plugin es un objeto que realiza una acción simple en respuesta a la selección de un menú o a la pulsación de un botón por parte del usuario. En el código de OrbisCAD hay múltiples ejemplos de plugins ya que todos los menúesy botones que se ven en la aplicación son plugins. Los plugins se caracterizan por implementar la interfaz org.estouro.ui.extensionSystem.Plugin, la cual tiene cinco métodos: initialize, execute, run, isVisible e isEnable. El método initialize es invocado al iniciarse el sistema y es el encargado de instalar los menúes y botones. El método execute y run son invocados en ese orden cuando el usuario selecciona el menu o presiona el botón del plugin. Los métodos isEnabled e isVisible son invocados de forma regular para gestionar la activación del plugin.
Todos estos métodos reciben una instancia de org.estouro.ui.extensionSystem.PluginContext la cual da acceso al entorno de edición (tema activo, herramienta seleccionada, ...), ventana de la aplicación, etc, ... Para más información consultar el javadoc de dicha clase.
Desde el punto de vista del usuario, las herramientas en OrbisCAD son botones en la barra de herramientas seleccionadas para operar con los temas en edición. Vienen definidas por un autómata de estados finitos. En cada estado del autómata se ejecuta un código de dibujado y en los estados finales se realiza la acción de edición concreta de la herramienta.
En la wikipedia se puede encontrar una definición bastante útil de lo que es un autómata. El autómata de la herramienta define en cualquier estado qué operaciones pueden realizarse y cuáles no. El siguiente ejemplo es bastante explicativo. En él se construye una herramienta que inserta linestrings en el tema en edición.

Lo primero que hay que hacer es definir el comportamiento de la herramienta con un autómata especificado mediante XML. Dicho XML será procesado por el generador de herramientas de OrbisCAD y de él se obtendrá una clase que implementa el autómata. La herramienta será una segunda clase que heredará de la clase generada y deberá implementar el código a ejecutar en cada estado del autómata. Veamos un ejemplo.
<automaton initial-status="Standby"
package="org.estouro.tools.generated"
name="Line"
icon="line.png"
tooltip="line_tooltip"
command="line">
<node name="Standby" text="line_standby">
<transition to="Point" code="point"/>
</node>
<node name="Point" text="line_point">
<transition to="Point" code="point"/>
<transition to="Done" code="t" label="line_point_to_done"
on-exit="true"/>
</node>
<node name="Done">
<transition to="Standby" code="init"/>
</node>
<node name="Cancel"/>
<transition to="Cancel" code="esc" label="cancel"/>
</automaton>
Automaton
initial-status: El estado en el que se inicia el automata
package: El paquete en el que se generará la clase automáticamente
name: Nombre de la clase generada
icon: Nombre del icono para el botón de la barra de tareas
cursor: Nombre del icono para el ratón cuando la herramienta está seleccionada
tooltip: Texto con el tooltip del botón
command: Nombre del comando que escrito en la consola seleccionará la herramienta
node: Estado del autómata
name: Nombre del estado
text: Texto a mostrar en la consola cuando se llega a éste estado
transition: Arco de salida desde éste nodo
to: Nombre del nodo al que llega el arco
code: Código de la transición. El código de la transición se corresponde con el texto introducido por el usuario en la consola, aunque lo habitual son las transiciones espaciales “point”, “number” y “esc”, que se producen cuando el usuario pincha en el mapa (o introduce un punto por la consola), cuando introduce un número en la consola y cuando cancela respectivamente.
label: Texto que aparecerá en el menú contextual de forma que cuando el usuario seleccione el botón derecho del ratón pueda seleccionar esta transición
on-exit: Si la transición se lanzará cuando la herramienta finalice por la selección de otra herramienta. Sólo una transición en cada nodo puede tener este atributo a true
transition: Arco de salida desde todos los nodos al nodo especificado. Puede contener los mismos atributos que una transición de nodo a nodo. Su uso habitual es para la opción de cancelar
Para obtener la clase que implementa el automata se dispone de un generador en la clase org.estouro.tools.AG que toma como parámetros un directorio donde se encuentran los XML de los autómatas, el directorio base de los fuentes y el paquete dentro de dicho directorio:
java org.estouro.tools.AG ./automata/ ./src/ org.estouro.tools.generated
El nombre de los ficheros con los automatas deben terminar en “fsa.xml”. Una vez generada la clase hay que crear otra clase que herede de aquella. Dicha clase deberá implementar para cada estado del autómata un par de métodos con el código a ejecutar al llegar a dicho estado y con el código de dibujado en dicho estado. Además de estos métodos se deberán implementar los métodos isEnabled e isVisible que gestionan la visibilidad y activación de la herramienta. A continuacion se presenta la herramienta para crear lineas aunque se dispone de muchos más ejemplos en el código fuente de OrbisCAD en el paquete org.estouro.tools.
public class LineTool extends Line {
private ArrayList<Coordinate> points = new ArrayList<Coordinate>();
public void transitionTo_Standby(PluginContext ctx)
throws FinishedAutomatonException, TransitionException {
points.clear();
}
public void transitionTo_Point(PluginContext ctx)
throws FinishedAutomatonException, TransitionException {
EditionContext ec = ctx.getEditionContext();
points.add(new Coordinate(ec.getValues()[0], ec.getValues()[1]));
}
public void transitionTo_Done(PluginContext ctx)
throws FinishedAutomatonException, TransitionException {
if (points.size() < 2)
throw new TransitionException(“La linea ha de tener al menos dos puntos”);
try {
EditionContext ec = ctx.getEditionContext();
Theme t = ec.getActiveTheme();
DriverMetadata metadata = t.getDriverMetadata();
Value[] row = new Value[metadata.getFieldCount()];
for (int i = 0; i < row.length; i++) {
row[i] = ValueFactory.createNullValue();
}
LineString ls = new GeometryFactory().createLineString(points
.toArray(new Coordinate[0]));
com.vividsolutions.jts.geom.Geometry g = ls;
if (ctx.getEditionContext().getActiveTheme().getGeometryType() == SpatialDataSource.MULTILINE) {
g = new GeometryFactory()
.createMultiLineString(new LineString[] { ls });
}
if (!g.isValid()) {
throw new TransitionException(“No es una línea válida”);
}
row[t.getSpatialFieldIndex()] = FMapUtilities.getGeometry(g);
t.insertFilledRow(row);
} catch (DriverException e) {
throw new TransitionException(e);
}
transition(ctx, "init");
}
public void transitionTo_Cancel(PluginContext ctx)
throws FinishedAutomatonException, TransitionException {
}
public void drawIn_Standby(PluginContext ctx, Graphics g)
throws DrawingException {
}
public void drawIn_Point(PluginContext ctx, Graphics g)
throws DrawingException {
EditionContext ec = ctx.getEditionContext();
Point2D current = ec.getLastRealMousePosition();
ArrayList<Coordinate> tempPoints = (ArrayList<Coordinate>) points
.clone();
tempPoints.add(new Coordinate(current.getX(), current.getY()));
LineString ls = new GeometryFactory().createLineString(tempPoints
.toArray(new Coordinate[0]));
Geometry geom = FMapUtilities.getGeometry(ls);
try {
SpatialDataSource sds = ec.getDrawingDataSource();
sds.beginTrans();
sds.insertFilledRow(new Value[] { geom });
sds.commitTrans();
} catch (DriverException e) {
} catch (FreeingResourcesException e) {
}
if (!ls.isValid()) {
throw new DrawingException(“No es una línea válida”);
}
}
public void drawIn_Done(PluginContext ctx, Graphics g)
throws DrawingException {
}
public void drawIn_Cancel(PluginContext ctx, Graphics g)
throws DrawingException {
}
public boolean isEnabled(PluginContext ctx) {
EnableUtils eu = ctx.getEnableUtils();
return (eu.isGeometryTypeEqualTo(SpatialDataSource.LINE) || eu
.isGeometryTypeEqualTo(SpatialDataSource.MULTILINE))
&& eu.isThemeWritable();
}
public boolean isVisible(PluginContext ctx) {
return true;
}
}
Para extender OrbisCAD mediante scripts se dispone del ScriptManager, en el cual se pueden definir scripts junto con su ubicación en el menú principal