Archivos Mensuales: junio 2011

Detectando líneas sin continuidad

Aquí el vídeo relacionado con esta entrada

En ocasiones nos interesa realizar análisis de continuidad geométrica.

Quizás queramos detectar como error líneas que no tienen continuidad, es decir, que en las coordenadas de alguno de sus extremos no nace otra línea, o quizás justo lo contrario, una línea que continúa con otra con un código que las hace incompatibles, como curva de nivel fina con curva de nivel maestra, o dos curvas de nivel finas pero con distinta coordenada Z, …

Si queremos hacer un control de bordes, quizás nos interese detectar como errores líneas que en la práctica no tienen la obligación de continuar con otra línea, pero que finalizan en un marco de hoja y no continúan en otro archivo.

Todos estos análisis los podemos realizar mediante el tipo Digi21.DigiNG.Topology.NodeDetector que implementa una serie de métodos de extensión que se ejecutan sobre una secuencia de líneas y que nos devolverá una secuencia de IGrouping<Point2D, VertexPointer>, es decir, una secuencia de entidades agrupadas por el punto en el que coinciden esas entidades.

El tipo Digi21.DigiNG.Entities.VertexPointer es muy parecido al tipo Digi21.DigiNG.Entities.SegmentPointer, dispone de dos propiedades: una para indicar la línea que llega al nodo y otra para indicar el vértice de esa línea que llega al nodo.

A continuación la definición de este tipo:

namespace Digi21.DigiNG.Entities
{
    public struct VertexPointer
    {
        public VertexPointer(ReadOnlyLine line, int vertex);

        public ReadOnlyLine Line { get; }
        public int Vertex { get; }
    }
}

Al igual que en el caso del detector de intersecciones, dispondemos de varias sobrecargas del método de extensión que nos permitirán o detectar todos los nodos, o especificar si permitimos analizar una determinada entidad o si nos interesa un nodo en unas determinadas coordenadas o una combinación de estas opciones.

Veamos un ejemplo muy sencillo: vamos a crear una orden que añade tantas tareas en la ventana de tareas como entidades que no tienen continuidad.
Lo que vamos a hacer es detectar todos los nodos y crear una consulta Linq que se quede únicamente con la secuencia de nodos que estén formados únicamente por una entidad.

Si tenemos dos líneas, A y B, ambas formadas por dos puntos, y con las siguientes coordenadas: A: (100, 100) – (200, 200) y B: (200, 200) – (300, 100) tendremos tres nodos, uno en las coordenadas (100, 100) al que llega únicamente una entidad, la entidad A, un segundo nodo (200, 200) al que llegan dos entidades, la A y la B y por último el tercer nodo (300, 100) al que llega únicamente la entidad B.

Está claro que los nodos A y B son nodos en los que no hay continuidad pues únicamente llega a ellos una línea.

Aquí el código de la orden:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Digi21.DigiNG.Plugin.Command;
using Digi21.DigiNG.Topology;
using Digi21.DigiNG;
using Digi21.DigiNG.Entities;
using Digi21.Digi3D;
using Digi21.Math;

namespace Acme
{
    [Command(Name="detectar_líneas_sin_continuidad")]
    public class DetectarLíneasSinContinuidad : Command
    {
        public DetectarLíneasSinContinuidad()
        {
            this.Initialize += new EventHandler(DetectarLíneasSinContinuidad_Initialize);
        }

        void DetectarLíneasSinContinuidad_Initialize(object sender, EventArgs e)
        {
            try
            {
                var todosLosNodos = DigiNG.DrawingFile.OfType<ReadOnlyLine>().DetectNodes();

                var nodosConUnaÚnicaLínea = from nodo in todosLosNodos
                                            where nodo.Count() == 1
                                            select nodo;

                foreach (var nodo in nodosConUnaÚnicaLínea)
                    Digi3D.Tasks.Add(new TaskEntityGotoPoint(
                        (Point3D)nodo.Key,
                        nodo.ElementAt(0).Line,
                        2,
                        "Extremo de línea sin continuidad",
                        TaskSeverity.Error,
                        DigiNG.DrawingFile.Path,
                        "detectar_líneas_sin_continuidad"));
                DigiNG.RenderScene();
            }
            finally
            {
                Dispose();
            }
        }
    }
}

Muy sencillo, ¿no?. Con esto hemos simulado la opción de Bintram que marca como errores entidades sin continuidad.

Vamos a añadir una tabla de códigos a nuestra orden. Ahora la orden va tener en cuenta únicamente las líneas que tengan alguno de los códigos pasados por parámetros:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Digi21.DigiNG.Plugin.Command;
using Digi21.DigiNG.Topology;
using Digi21.DigiNG;
using Digi21.DigiNG.Entities;
using Digi21.Digi3D;
using Digi21.Math;
using UtilidadesDigi;

namespace Acme
{
    [Command(Name="detectar_líneas_sin_continuidad")]
    public class DetectarLíneasSinContinuidad : Command
    {
        public DetectarLíneasSinContinuidad()
        {
            this.Initialize += new EventHandler(DetectarLíneasSinContinuidad_Initialize);
        }

        void DetectarLíneasSinContinuidad_Initialize(object sender, EventArgs e)
        {
            try
            {
                if (this.Args.Length == 0)
                {
                    Digi3D.Music(MusicType.Error);
                    Digi3D.ShowBallon(
                        "detectar_líneas_sin_continuidad",
                        "No has indicado los códigos de las entidades a analizar",
                        2,
                        BallonIcon.Error);
                    return;
                }

//                var todosLosNodos = DigiNG.DrawingFile.QueTenganAlgúnCódigo(this.Args).SoloLíneas().DetectNodes();
                var todosLosNodos = DigiNG.DrawingFile.
                    SoloLíneasSinEliminar().DetectNodes(
                    entidad => entidad.TieneAlgúnCódigo(this.Args));


                var nodosConUnaÚnicaLínea = from nodo in todosLosNodos
                                            where nodo.Count() == 1
                                            select nodo;

                foreach (var nodo in nodosConUnaÚnicaLínea)
                    Digi3D.Tasks.Add(new TaskEntityGotoPoint(
                        (Point3D)nodo.Key,
                        nodo.ElementAt(0).Line,
                        2,
                        "Extremo de línea sin continuidad",
                        TaskSeverity.Error,
                        DigiNG.DrawingFile.Path,
                        "detectar_líneas_sin_continuidad"));
                DigiNG.RenderScene();
            }
            finally
            {
                Dispose();
            }
        }
    }
}

Evitando "código duro" en nuestra orden contabilizadora de entidades

Aquí el vídeo relacionado con este post

En el post anterior desarrollamos una orden que contabilizaba todos los tipos de entidad de todos los archivos cargados.

Esta orden tenía programado mediante código duro los distintos casos a contabilizar, como líneas, puntos, textos, …

¿Que pasaría si nuestra orden en un futuro se ejecuta en una versión más moderna de Digi3D.NET que admite nuevos tipos de entidades, como por ejemplo esferas?

Pues sencillamente que no sería capaz de contabilizar el número de esferas porque no contempla ese caso.

Siempre que desarrollemos este tipo de órdenes, deberíamos intentar evitar el código duro y hacer que la orden esté preparada para un futuro.

Linq nos permite agrupar (mediante la cláusula group) una secuencia de objetos mediante una clave, y gracias a que mediante Reflexión podemos obtener en tiempo de ejecución el tipo de un determinado objeto (llamando al método Object.GetType(), podríamos modificar nuestra orden y convertirla en algo como lo siguiente:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Digi21.DigiNG.Plugin.Command;
using Digi21.DigiNG;
using Digi21.Digi3D;
using UtilidadesDigi;
using Digi21.DigiNG.Entities;

namespace Acme
{
    [Command(Name = "suma_todas_las_entidades")]
    public class SumaTodasLasEntidades
        : Command
    {
        public SumaTodasLasEntidades()
        {
            this.Initialize += new EventHandler(SumaTodasLasEntidades_Initialize);
        }

        void SumaTodasLasEntidades_Initialize(object sender, EventArgs e)
        {
            try
            {
                var agrupación = from entidad in UtilidadesDigiNG.EnumeraTodasLasEntidades()
                                 group entidad by entidad.GetType().Name;

                foreach(var tiposDeEntidad in agrupación)
                    Digi3D.OutputWindow.WriteLine(
                        "{0}: {1}",
                        tiposDeEntidad.Key,
                        tiposDeEntidad.Count());
            }
        }
    }
}

De esta manera hemos desvinculado completamente nuestra orden de unos tipos de entidad fijos y está preparada para el futuro.

Secuencias de entidades de un tipo y enumerando entidades de todos los archivos cargados

Aquí el vídeo relacionado con este post

En el post anterior creamos un método de extensión denominado EnumeraEntidades que extendía una secuencia de archivos de dibujo de solo lectura y nos devolvía una secuencia de todas las entidades de todos los archivos de referencia cargados dando la impresión que únicamente había un único archivo.

Esta secuencia devolvía todas las entidades de los archivos de referencia cargados, independientemente de su tipo.

En este post vamos a crear una sobrecarga de este método de extensión para hacerlo genérico y poder indicar mediante su parámetro genérico el tipo de entidad en el que estamos interesados:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Digi21.DigiNG.Entities;
using Digi21.DigiNG.IO;

namespace UtilidadesDigi
{
    public static class UtilidadesSecuenciaReadOnlyDrawingFile
    {
        public static IEnumerable<T> EnumeraEntidades<T>(this IEnumerable<ReadOnlyDrawingFile> archivos)
            where T:class
        {
            var entidadesADevolver = from archivo in archivos
                                     from entidad in archivo
                                     where entidad is T
                                     select entidad as T;

            return entidadesADevolver;
        }

    }
}

Si te fijas, he cambiado completamente el algoritmo. Ahora ya no utilizamos yield return, y toda la lógica está expresada como una consulta Linq. Podríamos haberlo hecho así en el post anterior, pero quería enseñarte el funcionamiento de yield return.

Ahora tenemos que cambiar el código de la orden, que ya no va a llamar a CuantasEntidadesDeTipo<T>:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Digi21.DigiNG.Entities;
using Digi21.DigiNG.IO;
using Digi21.DigiNG.Plugin.Command;
using Digi21.DigiNG;
using Digi21.Digi3D;
using UtilidadesDigi;

namespace Acme
{
    [Command(Name="suma_entidades_archivos_referencia")]
    public class SumaEntidadesArchivosReferencia
        : Command
    {
        public SumaEntidadesArchivosReferencia()
        {
            this.Initialize += new EventHandler(SumaEntidadesArchivosReferencia_Initialize);
        }

        void SumaEntidadesArchivosReferencia_Initialize(object sender, EventArgs e)
        {
            try
            {
                if (DigiNG.ReferenceFiles.Length == 0)
                {
                    Digi3D.Music(MusicType.Error);
                    Digi3D.ShowBallon(
                        "suma_entidades_archivos_referencia",
                        "No hay ningún archivo de referencia cargado",
                        2,
                        BallonIcon.Error);
                    return;
                }

                Digi3D.OutputWindow.WriteLine(
                    "Suma de todos los tipos de entidades de los archivos de referencia cargados:");


                Digi3D.OutputWindow.WriteLine(
                    "Número de líneas: {0}",
                    DigiNG.ReferenceFiles.EnumeraEntidades<ReadOnlyLine>().Count());

                Digi3D.OutputWindow.WriteLine(
                    "Número de puntos: {0}",
                    DigiNG.ReferenceFiles.EnumeraEntidades<ReadOnlyPoint>().Count());

                Digi3D.OutputWindow.WriteLine(
                    "Número de textos: {0}",
                    DigiNG.ReferenceFiles.EnumeraEntidades<ReadOnlyText>().Count());

                Digi3D.OutputWindow.WriteLine(
                    "Número de polígonos: {0}",
                    DigiNG.ReferenceFiles.EnumeraEntidades<ReadOnlyPolygon>().Count());

                Digi3D.OutputWindow.WriteLine(
                    "Número de complejos: {0}",
                    DigiNG.ReferenceFiles.EnumeraEntidades<ReadOnlyComplex>().Count());
            }
            finally
            {
                Dispose();
            }
        }
    }
}

Ahora vamos a hacer un método más que nos va a enumerar todas las entidades de todos los archivos cargados, tanto del archivo de dibujo como de los archivos de referencia.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Digi21.DigiNG.Entities;
using Digi21.DigiNG;

namespace UtilidadesDigi
{
    public static class UtilidadesDigiNG
    {
        public static IEnumerable<Entity> EnumeraTodasLasEntidades()
        {
            foreach (var entidad in DigiNG.DrawingFile)
                yield return entidad;

            foreach (var archivoReferencia in DigiNG.ReferenceFiles)
                foreach (var entidad in archivoReferencia)
                    yield return entidad;
        }

        public static IEnumerable<T> EnumeraTodasLasEntidades<T>()
            where T:class
        {
            foreach (var entidad in DigiNG.DrawingFile)
            {
                if( entidad is T)
                    yield return entidad as T;
            }

            foreach (var archivoReferencia in DigiNG.ReferenceFiles)
            {
                foreach (var entidad in archivoReferencia)
                {
                    if( entidad is T )
                        yield return entidad as T;
                }
            }
        }
    }
}

y para probar su funcionamiento, vamos a crear una orden nueva que va a contabilizar las entidades de cada tipo de todos los archivos cargados:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Digi21.DigiNG.Plugin.Command;
using Digi21.DigiNG;
using Digi21.Digi3D;
using UtilidadesDigi;
using Digi21.DigiNG.Entities;

namespace Acme
{
    [Command(Name = "suma_todas_las_entidades")]
    public class SumaTodasLasEntidades
        : Command
    {
        public SumaTodasLasEntidades()
        {
            this.Initialize += new EventHandler(SumaTodasLasEntidades_Initialize);
        }

        void SumaTodasLasEntidades_Initialize(object sender, EventArgs e)
        {
            try
            {
                Digi3D.OutputWindow.WriteLine(
                    "Suma de todos los tipos de entidades de todos los archivos cargados:");

                Digi3D.OutputWindow.WriteLine(
                    "Número de líneas: {0}",
                    UtilidadesDigiNG.
                    EnumeraTodasLasEntidades<ReadOnlyLine>().Count());

                Digi3D.OutputWindow.WriteLine(
                    "Número de puntos: {0}",
                    UtilidadesDigiNG.
                    EnumeraTodasLasEntidades<ReadOnlyPoint>().Count());

                Digi3D.OutputWindow.WriteLine(
                    "Número de textos: {0}",
                    UtilidadesDigiNG.
                    EnumeraTodasLasEntidades<ReadOnlyText>().Count());

                Digi3D.OutputWindow.WriteLine(
                    "Número de polígonos: {0}",
                    UtilidadesDigiNG.
                    EnumeraTodasLasEntidades<ReadOnlyPolygon>().Count());

                Digi3D.OutputWindow.WriteLine(
                    "Número de complejos: {0}",
                    UtilidadesDigiNG.
                    EnumeraTodasLasEntidades<ReadOnlyComplex>().Count());
            }
            finally
            {
                Dispose();
            }
        }
    }
}

Secuencias infinitas con Yield return y creación de una única secuencia de entidades de todos los archivos de referencia

Aquí el vídeo relacionado con este post

En ocasiones nos interesará crear un enumerador que enumere las entidades de todos los archivos de referencia como un todo, es decir, como si todos los archivos de referencia fueran un único archivo, por ejemplo cuando realicemos un control de bordes: nos interesará analizar la continuidad de las entidades del archivo de dibujo activo con respecto a todos los archivos de dibujo de referencia cargados.

Para ello, vamos a aprovecharnos de una de las maravillas de C# que es la palabra clave yield. Esta palabra clave básicamente nos permite devolver un valor al llamador, pero almacenando el estado en el que hemos devuelvo el control para continuar por el mismo sitio y con los mismos valores si se vuelve a llamar al método que utiliza el yield return.

Por ejemplo, (te voy a explicar el ejemplo más común, pero es que viene de perlas) si queremos hacer un método que devuelva una secuencia de 10 números aleatorios, podríamos implementarlo de la siguiente manera:

       public IEnumerable<int> SecuenciaNúmerosAleatorios()
        {
            Random r = new Random();

            int[] array = new int[10];
            for (int i = 0; i < 10; i++)
                array[i] = r.Next();

            return array;
        }

Como un array de int es siempre un IEnumerable<int> pues tan solo tenemos que crear un array de 10 posiciones, rellenarlo y devolverlo, y estaremos devolviendo una secuencia de 10 números aleatorios. Perfecto.

Ahora, ¿que pasa si te digo que quiero que esa secuencia sea infinita?

No podemos hacer un array infinito porque nuestro ordenador no tiene una cantidad infinita de memoria.

Aquí es donde viene a ayudarnos la palabra clave yield. Mira cómo podríamos hacer ese método como un método que devuelve un número infinito de valores:

        public IEnumerable<int> SecuenciaNúmerosAleatorios()
        {
            Random r = new Random();

            while(true)
                yield return r.Next();
        }

¿no te parece precioso?

Yield lo que hace es implementar una secuencia perezosa, únicamente se generará un nuevo número aleatorio en el momento en el que ordenemos al iterador que se mueva a la siguiente posición, de modo que no hay consumo de memoria ni tiempo de espera para que se rellene el ¿array? ¿que array? de valores.

Bien, pues ahora que conoces la palabra clave yield, vamos a crear un método de extensión que se ejecute sobre una secuencia de ReadOnlyDrawingFile y que nos devuelva una secuencia única de entidades que devuelva una a una todas las entidades de todos los archivos de referencia cargados.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Digi21.DigiNG.Entities;
using Digi21.DigiNG.IO;

namespace UtilidadesDigi
{
    public static class UtilidadesSecuenciaReadOnlyDrawingFile
    {
        public static IEnumerable<Entity> EnumeraEntidades(this IEnumerable<ReadOnlyDrawingFile> archivos)
        {
            foreach (var archivo in archivos)
                foreach (var entidad in archivo)
                    yield return entidad;
        }

    }
}

No se a tí, pero a mí me encanta C#, por cosas como esta.

A continuación vamos a hacer una orden que nos muestre en la ventana de resultados la suma de entidades de cada tipo (líneas, puntos, …) de todos los archivos de referencia cargados utilizando el método de extensión que acabamos de desarrollar:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Digi21.DigiNG.Entities;
using Digi21.DigiNG.IO;
using Digi21.DigiNG.Plugin.Command;
using Digi21.DigiNG;
using Digi21.Digi3D;
using UtilidadesDigi;

namespace Acme
{
    [Command(Name="suma_entidades_archivos_referencia")]
    public class SumaEntidadesArchivosReferencia
        : Command
    {
        public SumaEntidadesArchivosReferencia()
        {
            this.Initialize += new EventHandler(SumaEntidadesArchivosReferencia_Initialize);
        }

        void SumaEntidadesArchivosReferencia_Initialize(object sender, EventArgs e)
        {
            try
            {
                if (DigiNG.ReferenceFiles.Length == 0)
                {
                    Digi3D.Music(MusicType.Error);
                    Digi3D.ShowBallon(
                        "suma_entidades_archivos_referencia",
                        "No hay ningún archivo de referencia cargado",
                        2,
                        BallonIcon.Error);
                    return;
                }

                Digi3D.OutputWindow.WriteLine(
                    "Suma de todos los tipos de entidades de los archivos de referencia cargados:");


                Digi3D.OutputWindow.WriteLine(
                    "Número de líneas: {0}",
                    DigiNG.ReferenceFiles.EnumeraEntidades().
                    CuantasEntidadesDeTipo<ReadOnlyLine>());

                Digi3D.OutputWindow.WriteLine(
                    "Número de puntos: {0}",
                    DigiNG.ReferenceFiles.EnumeraEntidades().
                    CuantasEntidadesDeTipo<ReadOnlyPoint>());

                Digi3D.OutputWindow.WriteLine(
                    "Número de textos: {0}",
                    DigiNG.ReferenceFiles.EnumeraEntidades().
                    CuantasEntidadesDeTipo<ReadOnlyText>());

                Digi3D.OutputWindow.WriteLine(
                    "Número de polígonos: {0}",
                    DigiNG.ReferenceFiles.EnumeraEntidades().
                    CuantasEntidadesDeTipo<ReadOnlyPolygon>());

                Digi3D.OutputWindow.WriteLine(
                    "Número de complejos: {0}",
                    DigiNG.ReferenceFiles.EnumeraEntidades().
                    CuantasEntidadesDeTipo<ReadOnlyComplex>());
            }
            finally
            {
                Dispose();
            }
        }

    }
}

Archivos de referencia

Aquí el vídeo relacionado con este post

Digi3D 2011 expone las entidades de los archivos de referencia (aquellos archivos que no son el archivo de dibujo activo) a través de la propiedad DigiNG.ReferenceFiles que devuelve un array de tipo ReadOnlyDrawingFile.

El array devuelto tendrá tantos valores como archivos de referencia cargados, y tal y como su nombre indica, no podemos realizar ninguna modificación sobre estos archivos, pues nos devuelve enumeradores de solo lectura.

En el siguiente ejemplo, vamos a desarrollar una orden que nos muestra en la ventana de resultados el número de entidades de cada tipo que tiene cada uno de los archivos de referencia cargados.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Digi21.DigiNG.Plugin.Command;
using Digi21.DigiNG.IO;
using Digi21.DigiNG;
using Digi21.Digi3D;
using Digi21.DigiNG.Entities;

namespace Pruebas
{
    public static class UtilidadesSecuenciaEntity
    {
        public static int CuantasEntidadesDeTipo<T>(this IEnumerable<Entity> entidades)
        {
            var secuenciaDeT = from entidad in entidades
                               where entidad is T
                               select entidad;

            return secuenciaDeT.Count();
        }
    }


    [Command(Name="cuenta_entidades_archivos_referencia")]
    public class CuentaEntidadesDeArchivosReferencia : Command
    {
        public CuentaEntidadesDeArchivosReferencia()
        {
            this.Initialize +=
                new EventHandler(CuentaEntidadesDeArchivosReferencia_Initialize);
        }


        void CuentaEntidadesDeArchivosReferencia_Initialize(object sender, EventArgs e)
        {
            try
            {
                var archivosReferencia = DigiNG.ReferenceFiles;

                if (archivosReferencia.Length == 0)
                {
                    Digi3D.Music(MusicType.Error);
                    Digi3D.ShowBallon(
                        "cuenta_entidades_archivos_referencia",
                        "No hay ningún archivo de referencia cargado",
                        2,
                        BallonIcon.Error);
                    return;
                }

                foreach (var archivo in DigiNG.ReferenceFiles)
                {
                    Digi3D.OutputWindow.WriteLine(archivo.Path);

                    Digi3D.OutputWindow.WriteLine(
                        "{0} líneas",
                        archivo.CuantasEntidadesDeTipo<ReadOnlyLine>());

                    Digi3D.OutputWindow.WriteLine(
                        "{0} puntos",
                        archivo.CuantasEntidadesDeTipo<ReadOnlyPoint>());

                    Digi3D.OutputWindow.WriteLine(
                        "{0} textos",
                        archivo.CuantasEntidadesDeTipo<ReadOnlyText>());

                    Digi3D.OutputWindow.WriteLine(
                        "{0} polígonos",
                        archivo.CuantasEntidadesDeTipo<ReadOnlyPolygon>());

                    Digi3D.OutputWindow.WriteLine(
                        "{0} complejos",
                        archivo.CuantasEntidadesDeTipo<ReadOnlyComplex>());
                }
            }
            finally
            {
                Dispose();
            }
        }
    }
}

Detectando las intersecciones de una determinada línea

En el post anterior nos centramos en los métodos de extensión implementados en el tipo Digi21.DigiNG.Topology.IntersectionDetector que extendían secuencias de líneas.

En este post vamos a centrarnos en las sobrecargas que actúan únicamente sobre una determinada línea.

Aquí tienes el vídeo relacionado con este bloque de código

Si queremos averiguar únicamente las intersecciones que tiene una línea con el resto de líneas del modelo, podemos utilizar cualquiera de los métodos del post anterior y luego centrarnos únicamente en las intersecciones entre las que esté involucrada la línea que nos interesa.

Veámoslo con un ejemplo: Vamos a hacer una orden que solicita al usuario que se seleccione una línea y luego añadirá tantas tareas como intersecciones tenga esa línea con el resto de líneas del archivo de dibujo.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Digi21.DigiNG.Plugin.Command;
using Digi21.DigiNG.Plugin.Shell;
using Digi21.Digi3D;
using Digi21.DigiNG;
using Digi21.DigiNG.Entities;
using UtilidadesDigi;
using Digi21.DigiNG.Topology;
using Digi21.Math;

namespace Pruebas
{
    [CommandInMenu(
        "Mostrar las intersecciones de la línea seleccionada",
        MenuItemGroup.GeometricAnalysisGroup1)]
    [Command(Name="intersecciones_de_línea")]
    public class InterseccionesDeLínea : Command
    {
        public InterseccionesDeLínea()
        {
            this.Initialize += new EventHandler(InterseccionesDeLínea_SetFocus);
            this.SetFocus += new EventHandler(InterseccionesDeLínea_SetFocus);
            this.DataUp += new EventHandler<Digi21.Math.Point3DEventArgs>(InterseccionesDeLínea_DataUp);
            this.EntitySelected += new EventHandler<EntitySelectedEventArgs>(InterseccionesDeLínea_EntitySelected);
        }

        void InterseccionesDeLínea_SetFocus(object sender, EventArgs e)
        {
            Digi3D.StatusBar.Text = "Selecciona la línea para mostrar sus intersecciones";
        }

        void InterseccionesDeLínea_DataUp(object sender, Digi21.Math.Point3DEventArgs e)
        {
            DigiNG.SelectEntity(
                e.Coordinates,
                entidad => entidad is ReadOnlyLine);
        }

        void InterseccionesDeLínea_EntitySelected(object sender, EntitySelectedEventArgs e)
        {
            ReadOnlyLine líneaSeleccionada = (ReadOnlyLine)e.Entity;

            var todasLasIntersecciones = DigiNG.DrawingFile.SoloLíneas().DetectIntersections();
            foreach (var intersección in todasLasIntersecciones)
            {
                foreach (var entidadImplicadaEnLaIntersección in intersección)
                {
                    if (líneaSeleccionada == entidadImplicadaEnLaIntersección.Line)
                    {
                        Digi3D.Tasks.Add(new TaskGotoPoint(
                            (Point3D)intersección.Key,
                            "Intersección",
                            TaskSeverity.Error,
                            DigiNG.DrawingFile.Path,
                            "intersecciones_de_línea"));
                        DigiNG.RenderScene();
                        break;
                    }
                }
            }

            Dispose();
        }
    }
}

El problema que tiene este sistema es que estamos analizando las intersecciones existentes en todo el archivo de dibujo para luego centrarnos únicamente en un subconjunto en teoría muy pequeño de todas estas intersecciones de modo que estamos desaprovechando recursos (tiempo y memoria).

Afortunadamente, el tipo Digi21.DigiNG.Topology.IntersectionDetector expone métodos de extensión que afectan a una única línea. Estos métodos de extensión son idénticos a aquellos que se aplican a una secuencia de líneas, pero centrándose únicamente en las intersecciones de una determinada línea, de modo que es mucho más rápido, consume menos memoria y además ya no tenemos que buscar los nodos a los que llega nuestra línea, pues todos ellos cumplirán esta función.

La sobrecarga más sencilla es aquella que recibe como único parámetro el conjunto de líneas con las que queremos comprobar sus intersecciones.

Veamos entonces como queda nuestra orden optimizada con el método de extensión aplicado a líneas:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Digi21.DigiNG.Plugin.Command;
using Digi21.DigiNG.Plugin.Shell;
using Digi21.Digi3D;
using Digi21.DigiNG;
using Digi21.DigiNG.Entities;
using UtilidadesDigi;
using Digi21.DigiNG.Topology;
using Digi21.Math;

namespace Pruebas
{
    [CommandInMenu(
        "Mostrar las intersecciones de la línea seleccionada",
        MenuItemGroup.GeometricAnalysisGroup1)]
    [Command(Name="intersecciones_de_línea")]
    public class InterseccionesDeLínea : Command
    {
        public InterseccionesDeLínea()
        {
            this.Initialize += new EventHandler(InterseccionesDeLínea_SetFocus);
            this.SetFocus += new EventHandler(InterseccionesDeLínea_SetFocus);
            this.DataUp += new EventHandler<Digi21.Math.Point3DEventArgs>(InterseccionesDeLínea_DataUp);
            this.EntitySelected += new EventHandler<EntitySelectedEventArgs>(InterseccionesDeLínea_EntitySelected);
        }

        void InterseccionesDeLínea_SetFocus(object sender, EventArgs e)
        {
            Digi3D.StatusBar.Text = "Selecciona la línea para mostrar sus intersecciones";
        }

        void InterseccionesDeLínea_DataUp(object sender, Digi21.Math.Point3DEventArgs e)
        {
            DigiNG.SelectEntity(
                e.Coordinates,
                entidad => entidad is ReadOnlyLine);
        }

        void InterseccionesDeLínea_EntitySelected(object sender, EntitySelectedEventArgs e)
        {
            ReadOnlyLine líneaSeleccionada = (ReadOnlyLine)e.Entity;

            var todasLasIntersecciones =
                líneaSeleccionada.DetectIntersections(DigiNG.DrawingFile.SoloLíneas());

            foreach (var intersección in todasLasIntersecciones)
            {
                foreach (var entidadImplicadaEnLaIntersección in intersección)
                {
                    if (líneaSeleccionada == entidadImplicadaEnLaIntersección.Line)
                    {
                        Digi3D.Tasks.Add(new TaskGotoPoint(
                            (Point3D)intersección.Key,
                            "Intersección",
                            TaskSeverity.Error,
                            DigiNG.DrawingFile.Path,
                            "intersecciones_de_línea"));
                        DigiNG.RenderScene();
                        break;
                    }
                }
            }

            Dispose();
        }
    }
}

Y como este método es tan rápido, podemos permitirnos el lujo de ejecutarlo en tiempo real.

Vamos a desarrollar una orden que se ejecutará cada vez que el usuario digitaliza una línea. Si esta línea es una curva de nivel, se comprobará mediante este método si esta ha interseccionado con otra curva de nivel, en cuyo caso se mostrará al usuario una tarea, un globo y un sonido de error, con “sorpresa final”, de modo que en el mismo instante en el que el usuario digitaliza la línea, el programa ya le informa de que ha cometido un error.

Si te fijas en el código, esta orden no hace una llamada al método Dispose, por lo tanto, nunca se auto destruye. Al ejecutarla se queda residente, a la escucha, esperando que se digitalice una línea. En nomenclatura UNIX esta orden sería un demonio, en nomenclatura Windows sería una especia de proceso.
La única manera de destruir la orden es al finalizar Digi3D, el propio CLR de Windows se encargará de destruirla.

El truco para convertir esta orden en un proceso consiste en la llamada al método DigiNG.Commands.Pop(), que elimina la orden de la pila de órdenes de DigiNG, de modo que aunque el usuario pulse la tecla Escape, no puede destruir la orden, pues la tecla Escape destruye las órdenes que están en la pila de órdenes.

Y ¿cuándo entra en acción la orden?

Cada vez que DigiNG almacena una entidad en el archivo de dibujo, lanza el evento DigiNG.EntityAddes, evento al cual se puede conectar cualquier orden.

Veamos el código de la orden:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Digi21.DigiNG.Plugin.Command;
using Digi21.DigiNG;
using Digi21.Digi3D;
using Digi21.DigiNG.Entities;
using UtilidadesDigi;
using Digi21.DigiNG.Topology;
using Digi21.Math;

namespace Acme
{
    [Command(Name="servicio_curvas_nivel")]
    public class ServicioCurvasNivel : Command
    {
        public ServicioCurvasNivel()
        {
            this.Initialize += new EventHandler(ServicioCurvasNivel_Initialize);
            DigiNG.EntityAdded += new EventHandler<EntityAddedEventArgs>(DigiNG_EntityAdded);
        }

        void DigiNG_EntityAdded(object sender, EntityAddedEventArgs e)
        {
            if (e.Entity is ReadOnlyLine &&
                e.Entity.TieneAlgúnCódigo(this.Args))
                ControlCalidadCurvasNivel(e.Entity as ReadOnlyLine);
        }

        private void ControlCalidadCurvasNivel(ReadOnlyLine línea)
        {
            var curvasDeNivelExistentes = from entidad in DigiNG.DrawingFile
                                          where entidad != línea
                                          where entidad.TieneAlgúnCódigo(this.Args)
                                          select entidad as ReadOnlyLine;

            var intersecciones = línea.DetectIntersections(curvasDeNivelExistentes);

            List<ITask> tareasAAñadir = new List<ITask>();
            foreach (var intersección in intersecciones)
            {
                tareasAAñadir.Add(new TaskEntityGotoPoint(
                    (Point3D)intersección.Key,
                    línea,
                    2,
                    "Curva de nivel que cruza con otras curvas de nivel",
                    TaskSeverity.Error,
                    DigiNG.DrawingFile.Path,
                    "servicio_curvas_nivel"));
            }

            foreach (var tarea in tareasAAñadir)
                Digi3D.Tasks.Add(tarea);

            if (tareasAAñadir.Count != 0)
            {
                Digi3D.Music(MusicType.Error);
                DigiNG.RenderScene();
            }
        }


        void ServicioCurvasNivel_Initialize(object sender, EventArgs e)
        {
            if (this.Args.Length == 0)
            {
                Digi3D.Music(MusicType.Error);
                Digi3D.ShowBallon(
                    "servicio_curvas_nivel",
                    "No se han indicado los códigos de las curvas de nivel",
                    2,
                    BallonIcon.Error);
                Dispose();
                return;
            }

            DigiNG.Commands.Pop();
        }
    }
}

Y ahora la “sorpresa final”, ¿Por qué no hacer que en vez de sonar el sonido de error de Digi, una voz sintética hable indicándole al usuario en perfecto castellano que la curva de nivel que acaba de digitalizar se cruza X veces con otra curva de nivel?

Si hacemos que nuestro proyecto referencie el ensamblado System.Speech.dll, en el espacio de nombres System.Speech.Systhesis tenemos el tipo SpeechSynthesizer.

Lo único que tenemos que hacer es crear una instancia de este tipo y llamar a su método SpeakAsync pasando como parámetro la cadena que queremos que sea sintetizada por el motor de texto a voz.

Los sistemas operativos Windows vienen de serie con un motor de texto a voz para frases en inglés. Si quieres que el sintetizador funcione con frases en castellano tendrás que comprarte una voz en castellano. Envíame un correo si quieres que te diga dónde comprar voces en castellano.

A continuación la orden pero esta vez con voz sintética.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Digi21.DigiNG.Plugin.Command;
using Digi21.DigiNG;
using Digi21.Digi3D;
using Digi21.DigiNG.Entities;
using UtilidadesDigi;
using Digi21.DigiNG.Topology;
using Digi21.Math;
using System.Speech.Synthesis;

namespace Acme
{
    [Command(Name="servicio_curvas_nivel")]
    public class ServicioCurvasNivel : Command
    {
        public ServicioCurvasNivel()
        {
            this.Initialize += new EventHandler(ServicioCurvasNivel_Initialize);
            DigiNG.EntityAdded += new EventHandler<EntityAddedEventArgs>(DigiNG_EntityAdded);
        }

        void DigiNG_EntityAdded(object sender, EntityAddedEventArgs e)
        {
            if (e.Entity is ReadOnlyLine &&
                e.Entity.TieneAlgúnCódigo(this.Args))
                ControlCalidadCurvasNivel(e.Entity as ReadOnlyLine);
        }

        private void ControlCalidadCurvasNivel(ReadOnlyLine línea)
        {
            var curvasDeNivelExistentes = from entidad in DigiNG.DrawingFile
                                          where entidad != línea
                                          where entidad.TieneAlgúnCódigo(this.Args)
                                          select entidad as ReadOnlyLine;

            var intersecciones = línea.DetectIntersections(curvasDeNivelExistentes);

            List<ITask> tareasAAñadir = new List<ITask>();
            foreach (var intersección in intersecciones)
            {
                tareasAAñadir.Add(new TaskEntityGotoPoint(
                    (Point3D)intersección.Key,
                    línea,
                    2,
                    "Curva de nivel que cruza con otras curvas de nivel",
                    TaskSeverity.Error,
                    DigiNG.DrawingFile.Path,
                    "servicio_curvas_nivel"));
            }

            foreach (var tarea in tareasAAñadir)
                Digi3D.Tasks.Add(tarea);

            if (tareasAAñadir.Count != 0)
            {
                string mensaje = string.Format(
                    "La curva de nivel que acaba de registrar se cruza {0} veces con otra curva de nivel",
                    tareasAAñadir.Count);

                SpeechSynthesizer motorVoz = new SpeechSynthesizer();
                motorVoz.SpeakAsync(mensaje);
                DigiNG.RenderScene();
            }
        }


        void ServicioCurvasNivel_Initialize(object sender, EventArgs e)
        {
            if (this.Args.Length == 0)
            {
                Digi3D.Music(MusicType.Error);
                Digi3D.ShowBallon(
                    "servicio_curvas_nivel",
                    "No se han indicado los códigos de las curvas de nivel",
                    2,
                    BallonIcon.Error);
                Dispose();
                return;
            }

            DigiNG.Commands.Pop();
        }
    }
}

Aquí tienes el vídeo de esta sección de código

y para terminar, vamos a hacer que la orden no detecte como erróneas intersecciones en las que una curva de nivel continúa a otra curva de nivel (es decir, si el nodo de intersección coincide con el comienzo o final de ambas curvar) y además vamos a solucionar un problema gramatical para evitar que la voz sintética diga “… se ha cruzado una veces con…”

Para comprobar si una intersección se ha realizado al comienzo o al final de una determinada línea, vamos a crear un método de extensión para el tipo SegmentPointer que nos devolverá verdadero si se cumple esta condición, facilitando mucho la lectura del código principal.

Aquí tienes el código definitivo:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Digi21.DigiNG.Plugin.Command;
using Digi21.DigiNG;
using Digi21.Digi3D;
using Digi21.DigiNG.Entities;
using UtilidadesDigi;
using Digi21.DigiNG.Topology;
using Digi21.Math;
using System.Speech.Synthesis;

namespace Acme
{
    public static class UtilidadesSegmentPointer
    {
        public static bool IntersecciónAlComienzoOFinalDeLínea(this SegmentPointer entidad)
        {
            if (entidad.FirstVertex == entidad.SecondVertex &&
                (entidad.FirstVertex == 0 ||
                entidad.FirstVertex == entidad.Line.Points.Count - 1))
                return true;

            return false;
        }
    }

    [Command(Name="servicio_curvas_nivel")]
    public class ServicioCurvasNivel : Command
    {
        public ServicioCurvasNivel()
        {
            this.Initialize += new EventHandler(ServicioCurvasNivel_Initialize);
            DigiNG.EntityAdded += new EventHandler<EntityAddedEventArgs>(DigiNG_EntityAdded);
        }

        void DigiNG_EntityAdded(object sender, EntityAddedEventArgs e)
        {
            if (e.Entity is ReadOnlyLine &&
                e.Entity.TieneAlgúnCódigo(this.Args))
                ControlCalidadCurvasNivel(e.Entity as ReadOnlyLine);
        }

        private void ControlCalidadCurvasNivel(ReadOnlyLine línea)
        {
            var curvasDeNivelExistentes = from entidad in DigiNG.DrawingFile
                                          where entidad != línea
                                          where entidad.TieneAlgúnCódigo(this.Args)
                                          select entidad as ReadOnlyLine;

            var intersecciones = línea.DetectIntersections(curvasDeNivelExistentes).ToArray();

            if (intersecciones.Length == 1)
            {
                if (intersecciones[0].Count() == 2)
                {
                    SegmentPointer líneaA = intersecciones[0].ElementAt(0);
                    SegmentPointer líneaB = intersecciones[0].ElementAt(1);

                    if (líneaA.IntersecciónAlComienzoOFinalDeLínea() &&
                        líneaB.IntersecciónAlComienzoOFinalDeLínea())
                        return;
                }
            }

            List<ITask> tareasAAñadir = new List<ITask>();
            foreach (var intersección in intersecciones)
            {
                tareasAAñadir.Add(new TaskEntityGotoPoint(
                    (Point3D)intersección.Key,
                    línea,
                    2,
                    "Curva de nivel que cruza con otras curvas de nivel",
                    TaskSeverity.Error,
                    DigiNG.DrawingFile.Path,
                    "servicio_curvas_nivel"));
            }

            foreach (var tarea in tareasAAñadir)
                Digi3D.Tasks.Add(tarea);

            if (tareasAAñadir.Count != 0)
            {
                string mensaje;

                if( tareasAAñadir.Count == 1 )
                    mensaje = "La curva de nivel que acaba de registrar se cruza una vez con otra curva de nivel";
                else
                    mensaje = string.Format(
                        "La curva de nivel que acaba de registrar se cruza {0} veces con otra curva de nivel",
                        tareasAAñadir.Count);

                SpeechSynthesizer motorVoz = new SpeechSynthesizer();
                motorVoz.SpeakAsync(mensaje);
                DigiNG.RenderScene();
            }
        }


        void ServicioCurvasNivel_Initialize(object sender, EventArgs e)
        {
            if (this.Args.Length == 0)
            {
                Digi3D.Music(MusicType.Error);
                Digi3D.ShowBallon(
                    "servicio_curvas_nivel",
                    "No se han indicado los códigos de las curvas de nivel",
                    2,
                    BallonIcon.Error);
                Dispose();
                return;
            }

            DigiNG.Commands.Pop();
        }
    }
}

Detectando intersecciones entre múltiples líneas

Podemos detectar intersecciones entre líneas mediante el tipo Digi21.DigiNG.Topology.IntersectionDetector, que es una clase abstracta que define métodos de extensión sobre el tipo Digi21.DigiNG.ReadOnlyLine o sobre una secuencia de líneas IEnumerable<Digi21.DigiNG.ReadOnlyLine>.

Estos métodos de extensión disponen de varias sobrecargas que nos van a permitir filtrar el tipo de intersecciones que queremos detectar, de manera que podemos llegar a realizar consultas con un altísimo nivel de detalle, por ejemplo, podríamos realizar búsquedas tan extrañas como intersecciones entre dos líneas, la primera forzosamente con el código A y un número par de vértices y la segunda con código B y con un número par de vértices.
Las posibilidades son infinitas.

Todas las sobrecargas devuelven una secuencia objetos de Digi21.DigiNG.Entities.SegmentPointer como por ejemplo el siguiente recorte en el que te muestro la primera sobrecarga:

        public static IEnumerable<IGrouping<Point2D, SegmentPointer>> DetectIntersections(
            this IEnumerable<ReadOnlyLine> entities);

Y por su parte, el tipo SegmentPointer tiene el siguiente aspecto:

        public class SegmentPointer
        {
            public SegmentPointer(
                ReadOnlyLine line,
                int firstVertex,
                int secondVertex);

            public int FirstVertex { get; }
            public ReadOnlyLine Line { get; }
            public int SecondVertex { get; }
        }

que si te fijas tiene tres propiedades: Una que devuelve la línea implicada en la intersección, y otras dos que nos indican entre que vértices de esa línea se ha detectado la intersección. Si el valor almacenado en estas dos últimas propiedades coincide, significa que la intersección se detectó en ese vértice, y si no coinciden, las coordenadas de la intersección estarán en el segmento que une los dos vértices.

Utilizar estos métodos es trivial, tan solo tenemos que añadir una referencia al ensamblado Digi21.DigiNG.Topology, hacer visible en nuestro código el espacio de nombres Digi21.DigiNG.Topology y llamar al método de extensión que nos interese o sobre una línea o sobre una secuencia de líneas.

En este post nos vamos a centrar únicamente en los métodos de extensión que se aplican a secuencias de entidades. En el próximo post nos centraremos en las sobrecargas que se aplican a una línea en concreto.

Aquí el vídeo relacionado con este código

El siguiente código detecta las intersecciones de todas las entidades del archivo de dibujo activo, añadiendo una tarea en la ventana de tareas de la aplicación por cada intersección, de modo que cuando el usuario hace doble clic sobre la tarea se desplaza la ventana de dibujo a las coordenadas de la intersección:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Digi21.DigiNG.Plugin.Command;
using Digi21.DigiNG;
using Digi21.DigiNG.Topology;
using Digi21.Digi3D;
using Digi21.DigiNG.Entities;
using Digi21.Math;

namespace Pruebas
{
    [Command(Name = "detectar_intersecciones")]
    public class DetectarIntersecciones : Command
    {
        public DetectarIntersecciones()
        {
            this.Initialize += new EventHandler(DetectarIntersecciones_Initialize);
        }

        void DetectarIntersecciones_Initialize(object sender, EventArgs e)
        {
            var líneas = from entidad in DigiNG.DrawingFile
                         where entidad is ReadOnlyLine
                         select entidad as ReadOnlyLine;

            foreach (var intersección in líneas.DetectIntersections())
            {
                Digi3D.Tasks.Add(new TaskGotoPoint(
                    (Point3D)intersección.Key,
                    "Detectada una intersección",
                    TaskSeverity.Error,
                    DigiNG.DrawingFile.Path,
                    "detectar_intersecciones"));
            }

            Dispose();
        }
    }
}

Pero podemos hacer las cosas mejor, ya que el método lo que nos ha devuelto es una secuencia de grupos (cuya clave principal es la coordenada de la intersección), así que podríamos hacer que en la barra de tareas apareciera únicamente una tarea por cada punto en el que se ha detectado una intersección y que dicha tarea tenga como nodos hijos una subtarea por cada entidad. Al hacer doble clic sobre la tarea se desplazará la cámara a las coordenadas de la intersección, y al hacer doble clic sobre cada subtarea, se animará durante dos segundos la línea en cuestión:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Digi21.DigiNG.Plugin.Command;
using Digi21.DigiNG;
using Digi21.DigiNG.Topology;
using Digi21.Digi3D;
using Digi21.DigiNG.Entities;
using Digi21.Math;

namespace Pruebas
{
    [Command(Name = "detectar_intersecciones")]
    public class DetectarIntersecciones : Command
    {
        public DetectarIntersecciones()
        {
            this.Initialize += new EventHandler(DetectarIntersecciones_Initialize);
        }

        void DetectarIntersecciones_Initialize(object sender, EventArgs e)
        {
            var líneas = from entidad in DigiNG.DrawingFile
                         where entidad is ReadOnlyLine
                         select entidad as ReadOnlyLine;

            foreach (var intersección in líneas.DetectIntersections())
            {
                List<ITask> subtareas = new List<ITask>();
                foreach (var entidad in intersección)
                {
                    string títuloSubtarea = string.Format(
                        "Intersección entre los vértices {0} y {1}",
                        entidad.FirstVertex,
                        entidad.SecondVertex);

                    subtareas.Add(new TaskAnimateEntity(
                        entidad.Line,
                        2,
                        títuloSubtarea,
                        TaskSeverity.Error));
                }

                string títuloTarea = string.Format(
                    "Detectada una intersección en las coordenadas {0}",
                    intersección.Key);

                Digi3D.Tasks.Add(new TaskGotoPoint(
                    (Point3D)intersección.Key,
                    títuloTarea,
                    TaskSeverity.Error,
                    DigiNG.DrawingFile.Path,
                    "detectar_intersecciones",
                    subtareas.ToArray()));
            }

            Dispose();
        }
    }
}

Especializaciones

Los métodos de extensión DetectIntersections disponen de varias sobrecargas que nos van a permitir realizar búsquedas especializadas.

Aquí el vídeo relacionado con este código

La primera de ellas nos permite que pasemos un predicado de tipo Func<Point2D, bool> para indicar si queremos que se añada una determinada intersección al resultado.

Para ello vamos a modificar nuestra orden para hacer que únicamente detecte intersecciones dentro de un rango de coordenadas máximas y mínimas pasadas por parámetro.
Si el usuario no pasa parámetros a la orden, se mostrarán todas las intersecciones, y si pasa cuatro parámetros (xmin, ymin, xmax, ymax) se mostrarán únicamente las intersecciones dentro de este rango de coordenadas.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Digi21.DigiNG.Plugin.Command;
using Digi21.DigiNG;
using Digi21.DigiNG.Topology;
using Digi21.Digi3D;
using Digi21.DigiNG.Entities;
using Digi21.Math;

namespace Pruebas
{
    [Command(Name = "detectar_intersecciones")]
    public class DetectarIntersecciones : Command
    {
        private Window2D maxmin = Window2D.WholeWorld;

        public DetectarIntersecciones()
        {
            this.Initialize += new EventHandler(DetectarIntersecciones_Initialize);
        }

        void DetectarIntersecciones_Initialize(object sender, EventArgs e)
        {
            if (this.Args.Length == 4)
            {
                maxmin.Xmin = double.Parse(this.Args[0]);
                maxmin.Ymin = double.Parse(this.Args[1]);
                maxmin.Xmax = double.Parse(this.Args[2]);
                maxmin.Ymax = double.Parse(this.Args[3]);
            }

            var líneas = from entidad in DigiNG.DrawingFile
                         where entidad is ReadOnlyLine
                         select entidad as ReadOnlyLine;

            var intersecciones = líneas.DetectIntersections(
                (Point2D coordenada) => maxmin.Contains(coordenada));

            foreach (var intersección in intersecciones)
            {
                List<ITask> subtareas = new List<ITask>();
                foreach (var entidad in intersección)
                {
                    string títuloSubtarea = string.Format(
                        "Intersección entre los vértices {0} y {1}",
                        entidad.FirstVertex,
                        entidad.SecondVertex);

                    subtareas.Add(new TaskAnimateEntity(
                        entidad.Line,
                        2,
                        títuloSubtarea,
                        TaskSeverity.Error));
                }

                string títuloTarea = string.Format(
                    "Detectada una intersección en las coordenadas {0}",
                    intersección.Key);

                Digi3D.Tasks.Add(new TaskGotoPoint(
                    (Point3D)intersección.Key,
                    títuloTarea,
                    TaskSeverity.Error,
                    DigiNG.DrawingFile.Path,
                    "detectar_intersecciones",
                    subtareas.ToArray()));
            }

            Dispose();
        }
    }
}

Pero hay más sobrecargas: La siguiente nos permite indicar si queremos o no incluir una línea (mediante un predicado de tipo Func<Entity, bool>) en el proceso.

Podemos utilizar esta sobrecarga para filtrar por códigos por ejemplo.

Aquí el vídeo relacionado con este código
Veámoslo creando una nueva orden que detecta intersecciones entre las líneas con los códigos pasados por parámetro.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Digi21.DigiNG.Plugin.Command;
using Digi21.DigiNG.Topology;
using Digi21.DigiNG;
using Digi21.DigiNG.Entities;
using UtilidadesDigi;
using Digi21.Digi3D;
using Digi21.Math;

namespace Acme
{
    [Command(Name = "detectar_intersecciones_cod")]
    public class DetectarInterseccionesCod : Command
    {
        public DetectarInterseccionesCod()
        {
            this.Initialize += new EventHandler(DetectarInterseccionesCod_Initialize);
        }

        //bool MeInteresaEstaLínea(ReadOnlyLine línea)
        //{
        //    return línea.TieneAlgúnCódigo(this.Args);
        //}

        void DetectarInterseccionesCod_Initialize(object sender, EventArgs e)
        {
            try
            {
                if (this.Args.Length == 0)
                {
                    Digi3D.Music(MusicType.Error);
                    Digi3D.ShowBallon(
                        "detectar_intersecciones_cod",
                        "No has indicado los códigos de las entidades para la detección de intersecciones",
                        2,
                        BallonIcon.Error);
                    return;
                }

                var intersecciones = DigiNG.DrawingFile.SoloLíneas().DetectIntersections(
                    línea => línea.TieneAlgúnCódigo(this.Args));

                foreach (var intersección in intersecciones)
                {
                    List<ITask> subtareas = new List<ITask>();
                    foreach (var entidad in intersección)
                    {
                        string títuloSubtarea = string.Format(
                            "Intersección entre los vértices {0} y {1}",
                            entidad.FirstVertex,
                            entidad.SecondVertex);

                        subtareas.Add(new TaskAnimateEntity(
                            entidad.Line,
                            2,
                            títuloSubtarea,
                            TaskSeverity.Error));
                    }

                    Digi3D.Tasks.Add(new TaskGotoPoint(
                        (Point3D)intersección.Key,
                        "Detectada una intersección",
                        TaskSeverity.Error,
                        DigiNG.DrawingFile.Path,
                        "detectar_intersecciones",
                        subtareas.ToArray()));
                }
                DigiNG.RenderScene();

            }
            finally
            {
                Dispose();
            }
        }
    }
}

La siguiente sobrecarga nos permite especificar si permitimos analizar las intersecciones entre dos líneas. Cada vez que el algoritmo detecta que dos líneas son candidatas a tener intersecciones, el algoritmo llama a nuestro predicado para que le indiquemos si le damos permiso para analizar las intersecciones entre esas dos líneas.

Supón que quieres que únicamente se analicen las intersecciones entre línas de tipo A con líneas de tipo B, pero nunca intersecciones de líneas de tipo A con líneas de tipo A y de tipo B con tipo B

Aquí el vídeo relacionado con este código

Vamos a crear una orden nueva que esperae dos códigos y analice únicamente las intersecciones entre líneas que tengan una el primer código y otra el segundo o viceversa, pero nunca intersecciones de dos líneas con el mismo código.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Digi21.DigiNG.Plugin.Command;
using Digi21.DigiNG.Topology;
using Digi21.DigiNG;
using Digi21.DigiNG.Entities;
using UtilidadesDigi;
using Digi21.Digi3D;
using Digi21.Math;

namespace Acme
{
    [Command(Name = "detectar_intersecciones_códigos_distintos")]
    public class DetectarInterseccionesCódigosDistintos : Command
    {
        public DetectarInterseccionesCódigosDistintos()
        {
            this.Initialize += new EventHandler(DetectarInterseccionesCódigosDistintos_Initialize);
        }

        //private bool PermitoCalcularInterseccionesEntreEstasDosLíneas(ReadOnlyLine líneaA, ReadOnlyLine líneaB)
        //{
        //    return líneaA.TieneElCódigo(this.Args[0]) && líneaB.TieneElCódigo(this.Args[1]) ||
        //        líneaA.TieneElCódigo(this.Args[1]) && líneaB.TieneElCódigo(this.Args[0]);
        //}

        void DetectarInterseccionesCódigosDistintos_Initialize(object sender, EventArgs e)
        {
            try
            {
                if (this.Args.Length != 2)
                {
                    Digi3D.Music(MusicType.Error);
                    Digi3D.ShowBallon(
                        "detectar_intersecciones_códigos_distintos",
                        "No has indicado los dos códigos con los que trabajar",
                        2,
                        BallonIcon.Error);
                    return;
                }

                var intersecciones = DigiNG.DrawingFile.SoloLíneas().DetectIntersections(
                    (líneaA, líneaB) =>
                        líneaA.TieneElCódigo(this.Args[0]) && líneaB.TieneElCódigo(this.Args[1]) ||
                        líneaA.TieneElCódigo(this.Args[1]) && líneaB.TieneElCódigo(this.Args[0]));

                foreach (var intersección in intersecciones)
                {
                    List<ITask> subtareas = new List<ITask>();
                    foreach (var entidad in intersección)
                    {
                        string títuloSubtarea = string.Format(
                            "Intersección entre los vértices {0} y {1}",
                            entidad.FirstVertex,
                            entidad.SecondVertex);

                        subtareas.Add(new TaskAnimateEntity(
                            entidad.Line,
                            2,
                            títuloSubtarea,
                            TaskSeverity.Error));
                    }

                    Digi3D.Tasks.Add(new TaskGotoPoint(
                        (Point3D)intersección.Key,
                        "Detectada una intersección",
                        TaskSeverity.Error,
                        DigiNG.DrawingFile.Path,
                        "detectar_intersecciones",
                        subtareas.ToArray()));
                }
                DigiNG.RenderScene();

            }
            finally
            {
                Dispose();
            }
        }
    }
}

Pero podríamos haber optimizado el proceso anterior, ya que el algoritmo va a llamar a nuestro predicado por todas las entidades que tengan posibilidad de cruzarse, independientemente de sus códigos.

El resto de sobrecargas son combinaciones las sobrecargas anteriores, de modo que existe una que nos permite pasarle dos predicados: uno para indicar si analizar las intersecciones de una línea y otro para indicar si permitimos analizar las intersecciones entre un par de líneas, de modo que la siguiente modificación mejora sustancialmente la velocidad de proceso del algoritmo con idéntico resultado:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Digi21.DigiNG.Plugin.Command;
using Digi21.DigiNG.Topology;
using Digi21.DigiNG;
using Digi21.DigiNG.Entities;
using UtilidadesDigi;
using Digi21.Digi3D;
using Digi21.Math;

namespace Acme
{
    [Command(Name = "detectar_intersecciones_códigos_distintos")]
    public class DetectarInterseccionesCódigosDistintos : Command
    {
        public DetectarInterseccionesCódigosDistintos()
        {
            this.Initialize += new EventHandler(DetectarInterseccionesCódigosDistintos_Initialize);
        }

        //private bool PermitoCalcularInterseccionesEntreEstasDosLíneas(ReadOnlyLine líneaA, ReadOnlyLine líneaB)
        //{
        //    return líneaA.TieneElCódigo(this.Args[0]) && líneaB.TieneElCódigo(this.Args[1]) ||
        //        líneaA.TieneElCódigo(this.Args[1]) && líneaB.TieneElCódigo(this.Args[0]);
        //}

        void DetectarInterseccionesCódigosDistintos_Initialize(object sender, EventArgs e)
        {
            try
            {
                if (this.Args.Length != 2)
                {
                    Digi3D.Music(MusicType.Error);
                    Digi3D.ShowBallon(
                        "detectar_intersecciones_códigos_distintos",
                        "No has indicado los dos códigos con los que trabajar",
                        2,
                        BallonIcon.Error);
                    return;
                }

                var intersecciones = DigiNG.DrawingFile.SoloLíneas().DetectIntersections(
                    línea => línea.TieneAlgúnCódigo(this.Args),
                    (líneaA, líneaB) =>
                        líneaA.TieneElCódigo(this.Args[0]) && líneaB.TieneElCódigo(this.Args[1]) ||
                        líneaA.TieneElCódigo(this.Args[1]) && líneaB.TieneElCódigo(this.Args[0]));

                foreach (var intersección in intersecciones)
                {
                    List<ITask> subtareas = new List<ITask>();
                    foreach (var entidad in intersección)
                    {
                        string títuloSubtarea = string.Format(
                            "Intersección entre los vértices {0} y {1}",
                            entidad.FirstVertex,
                            entidad.SecondVertex);

                        subtareas.Add(new TaskAnimateEntity(
                            entidad.Line,
                            2,
                            títuloSubtarea,
                            TaskSeverity.Error));
                    }

                    Digi3D.Tasks.Add(new TaskGotoPoint(
                        (Point3D)intersección.Key,
                        "Detectada una intersección",
                        TaskSeverity.Error,
                        DigiNG.DrawingFile.Path,
                        "detectar_intersecciones",
                        subtareas.ToArray()));
                }
                DigiNG.RenderScene();

            }
            finally
            {
                Dispose();
            }
        }
    }
}