Marco Cobeña Morián
Software Development Engineer
Aviso: PointNet ha sido renombrado recientemente Typhoon; en cambio, algunas de las siguientes imágenes todavía muestran el anterior, puesto que fueron tomadas antes del renombrado.
Typhoon es una herramienta desarrollada por Plain Concepts para la visualización, medición y edición de nubes de puntos, Gaussian Splattings y modelos BIM, hecha con Evergine. Después de iterarla durante los últimos años, nos enfrentamos a una necesidad: cómo permitir que terceros creen nuevas funcionalidades encima de Typhoon. En lugar de tener nuestro equipo individual trabajando en la base de código, ¿cómo podríamos permitir que otros, de una manera asíncrona y paralela, extendieran su núcleo?
En este artículo mostramos la nueva arquitectura basada en plug-ins y, cómo esta ha ayudado, en un caso real, a simplificar el trabajo de varios equipos sobre una misma base de código.
Una de sus múltiples funcionalidades es la generación automática de archivos BIM desde nubes de puntos, también conocidos como Digital Twins (gemelos digitales). Estos gemelos empoderan a las empresas aflorando la información en sus edificios de una manera digital: imaginen un edificio industrial que muestra alertas digitales si los componentes reales alcanzan una temperatura límite, por ejemplo. Soporta los tipos de archivo más comunes: E57, LAS/Z y PCD. También trabaja con PLY y SPLAT para Gaussian Splatting e IFC, el estándar de facto en entornos CAD.
–
Antes de todo eso, estábamos trabajando con una base de código escrita en Python que, compilada en archivos EXE de Windows, se llamaban directamente desde Typhoon, a través de CLI. No teníamos una aproximación mejor para mezclar los mundos .NET y Python por aquel entonces.
Distribuir Python en los ejecutables tiene pros y contras. Uno de los peores contras fue su tamaño: solíamos trabajar con archivos EXE de 2,5 GB. Cuando haces doble clic en él, se tarda 1 min en levantar todo el entorno de Python y luego, comienza a ejecutar nuestro código. Era un dolor para lidiar a diario. (Un tiempo después solucionamos este problema gracias a la integración de CSnakes.)
Un enfoque que apenas consideramos es mover Python a la nube, teniendo una ligera API web en medio. Sin embargo, dada la naturaleza del procesamiento de nubes de puntos, delegar la computación en la nube no es barato al utilizar máquinas con GPUs muy potentes; y, en ocasiones, cuando cuentas con una GPU suficientemente potente en tu máquina, puede ser mucho más interesante poder ejecutar dichos procesos en local.
Finalmente, se nos ocurrieron los plug-ins: tienen el beneficio de seguir estresando nuestras GPU locales, pero incluso esto se puede mover a la nube y aún estar ocultas para el usuario final. ¿Por dónde empezamos?
Cuando definimos el nuevo diagrama de arquitectura, pensamos en un punto de entrada compartido por cada plug-in: una interfaz simple que autoexplica lo que un plug-in puede hacer (y no puede). Lo llamamos IPlugIn y lo distribuimos en su propio paquete: Typhoon.PlugIns. Al final, es un ensamblado de .NET DLL ligero que es la única dependencia que un plug-in tiene contra Typhoon.
Hoy en día, IPlugIn permite:
Por ejemplo, un plug-in no puede añadir un nuevo panel en el lado derecho; en cambio, se ve obligado a poblar su propia ventana que Typhoon controla. Hemos intentado, en tiempo de construcción, hacer cumplir algunos de los requisitos que tiene un plug-in. ¿Cómo, entonces, Typhoon realmente detecta plug-ins?
Typhoon ya se distribuye internamente con un montón de plug-ins, todos ellos enfocados en disminuir el número de puntos en una nube de puntos (con técnicas de muestreo, por ejemplo).
Cada plug-in vive bajo la carpeta Plug-ins, justo al mismo nivel que Typhoon.Windows.exe (punto de entrada de Typhoon). En el interior, cada plug-in tiene su propia subcarpeta, donde se almacenan todas sus dependencias: ver aquellas de .NET, o archivos (imágenes, JSONs, etc.), por ejemplo.
¿Qué pasa si un plug-in depende de una referencia de paquete ya presente en Typhoon, pero con una versión diferente? No podemos cargar el mismo ensamblado con dos versiones diferentes al mismo tiempo, al menos por defecto. Al comienzo de .NET, esto se logró a través de App Domains; hoy en día, esto ha sido reemplazado por Assembly Load Context. Es un mecanismo integrado para cargar un ensamblado (DLL aquí) de disco y decidir si sus dependencias se cargan de los archivos acompañantes o desde el host. De esta manera, como veremos en la siguiente sección, los plug-ins pueden llamar a ImGui
(la tecnología usada para hacer toda la interfaz de usuario en Typhoon) y, tal es compartido entre Typhoon y ellos. Pagamos la carta de potencialmente tener exactamente los mismos ensamblados repetidos en disco (es decir, dos plug-ins comparten dependencias); sin embargo, consideramos que esto no es un problema en 2025 dados los tamaños pequeños y el precio del hardware.
Los plug-ins también pueden requerir instancias de dependencias a través de la Inyección de Dependencia. Si un plug-in necesita trabajar con la nube de puntos actual, puede simplemente añadir IIOService a su constructor público, y Typhoon hace el resto. Si un plug-in quiere alertar al usuario en cualquier momento, puede hacer lo mismo con IAlertService. Además, esto culmina el camino a los tests unitarios, que es una práctica común en nuestro equipo, aprovechando una arquitectura simple y desacoplada.
Gracias a nuestra integración incorporada con ImGui (y a nuestro propio envoltorio también: ImGui.Net), toda la interfaz de usuario se ha construido utilizando solamente esto. Tiene los beneficios del prototipado rápido, más el estilo que nos permite darle un aspecto fresco.
Durante las diferentes iteraciones, hemos jugado con diferentes enfoques para dividir el código ImGui en diferentes componentes que se comunican entre ellos. Gracias al modelo basado en componentes de Evergine, ambos han encajado perfectamente, ayudándonos a lograr un software más mantenible. Cuando decidimos cómo cada plug-in podía definir su propia interfaz de usuario, era natural seguir con ImGui. Ya teníamos experiencia con componentes de interfaz de usuario disociados, e imaginamos los plug-ins de la misma manera.
Typhoon se encarga de preguntar a cada plug-in qué elemento del menú desea mostrar en Plug-ins (en la barra de menú superior). Una vez que el usuario hace clic en uno de ellos, Typhoon llama directamente IPlugIn.DrawWindow() y, lo que sucede dentro, es la responsabilidad del plug-in.
Some plug-ins may have a list of inputs and a button, others a tab view with a more complex set-up. Developers have the entire ImGui’s catalog at their hands. This insanely simple approach allows to convey plug-ins’ UX from Typhoon. For example, every plug-in has a window with its name in the tittle bar, and it can be closed with the typical ‘x’ button at the right side. This way, teams can asynchronously take their own decisions on their plug-ins while all of them convey to an homogeneous solution.
As you can appreciate, plug-ins started having a certain complexity and we needed an approach to build on solid.
Tan pronto como pusimos los plug-ins en práctica, los bugs comenzaron a aparecer aquí y allá. Uno de ellos era tan simple como cerrar Typhoon porque un plug-in tenía un ‘.’ (punto) en su nombre (es un patrón común nombrar cosas con puntos en .NET). ¿Cómo pudimos reproducirlo sin tener que crear manualmente un nuevo plug-in? ¿Sin depurar manualmente Typhoon hasta el cierre? ¿Evitanto las regresiones de ahora en adelante? Gracias a Evergine.Mocks, pensamos, podríamos levantar plug-ins en las pruebas unitarias, y funcionó. Con el fin de reproducir el ejemplo de arriba, simplemente
añadimos un nuevo plug-in mínimo (implementación predeterminada de IPlugIn, recuerda) y lo nombramos con un punto en el medio.
Es fácil imaginar que esto nos abrió un mundo de pruebas para las anteriormente mencionadas dependencias, el informe de estado, etc.
Tan pronto como los requisitos comenzaron a crecer, tomamos la decisión de abrir una puerta para interacciones más ricas.
Un requisito particular que tuvimos al principio fue resaltar un área en la nube de puntos. Esto significa que un plug-in puede interactuar con la escena actual de alguna manera. El “resaltar un área” se tradujo en dibujar un cubo de alambre a través del LineBatch3D de Evergine y, esto dentro de un componente Drawable3D.
Nuestro primer enfoque fue enriquecer IPlugIn con la capacidad de inyectar un componente del que Typhoon se encargaría. Añadiríamos tal a una entidad que controlamos, por lo que el plug-in podría simplemente jugar con su componente (desactivarlo o activarlo, por ejemplo). Aunque nos gustó, podríamos caer en tener IPlugIn llena de pequeñas necesidades, o casos de uso, por lo que nos fuimos a una opción más amplia.
¿Por qué no permitimos que los plug-ins inyecten entidades? Una entidad puede ser tan simple como tener un componente, y tan compleja como uno podría imaginar. En el futuro, un plug-in puede añadir un nuevo formato de nube de puntos, por ejemplo. Por otro lado, una entidad tiene el poder potencial para atravesar las otras entidades y modificar sus formas (es decir, eliminando componentes, por ejemplo), o accediendo a la cámara principal, etc. Sin embargo, decidimos confiar y ajustar nuestra implementación en base a los resultados.
Gracias a este enfoque los equipos pueden aprovechar sus conocimientos sobre Evergine y su arquitectura basada en componentes, haciendo de los plug-ins una pegamento transparente entre Typhoon y ellos.
La idea de extender Typhoon a través de plug-ins ha terminado siendo una solución práctica. Hemos estado trabajando simultáneamente hasta tres equipos diferentes (incluso con empresas externas) creando múltiples plug-ins. La sincronización entre todos ellos se redujo significativamente gracias a compartir un contrato común con especificaciones claras: la interfaz IPlugIn, cómo se instalan plug-ins y aparecen en la interfaz de usuario y cómo pueden interactuar con la escena a través de sus propias entidades.
Hoy en día, la mayoría de los plug-ins se desarrollan internamente; sin embargo, nos gustaría que otros crearan otros también, tan pronto como pongamos Typhoon disponible públicamente.
Mientras tanto, seguimos evolucionando, y tenemos en mente características como las siguientes:
Estamos encantados de imaginar lo que tú puedas construir encima de Typhoon, y qué desafíos podamos encontrar en el camino.
Marco Cobeña Morián
Software Development Engineer