четверг, 29 мая 2014 г.

Нарезка областей (regions) на прямоугольные куски заданного размера

Задача стояла следующая: необходимо для указанных областей (regions) создать их копию, нарезанную либо на прямоугольники заданного размера, либо на заданное количество строк и столбцов. При этом исходные области (regions), подлежащие разрезке могут быть абсолютно любой, произвольной формы: например, созданными на основании сплайнов и содержащими в себе отверстия.
AutoCAD вообще очень медленно работает с объектами Region, примерно так же медленно, как и с телами (объектами Solid3d). Причём AutoCAD 2014 и 2015 работают с областями и телами даже в два раза медленней, чем более старые версии. Это обусловлено тем, что в обозначенных версиях AutoCAD компанией Autodesk был переписан код по работе с Region и Solid3d. но при этом не было произведено надлежащего тестирования обновлённого функционала.

В данной заметке приведён исходный код разрезки областей различными способами, а так же предоставлен дополнительный программный функционал, который может быть полезен при работе с областями в ходе решения иных задач.

В процессе тестирования производилась разрезка различных областей, в т.ч. и созданных при помощи сплайнов (на скрине выделены коричневым цветом):



Код определяет ряд пользовательских команд:

1. Команда AcadBoundaryRegion создаёт жёлтую рамку вокруг регионов. Эта рамка показывает визуальные границы BoundaryBox для этих примитивов/ Габариты берутся именно те, которые AutoCAD API показывает программистам в свойствах примитивов:


Команду AcadBoundaryRegion я использую при написании программ, чтобы визуально увидеть границы, которые AutoCAD возвращает для запрошенного примитива, и тем самым понять, почему в той или иной ситуации мой код делает не совсем то, что я от него хотел. Обратите внимание на то, что области, выполненные из сплайнов, не чётко вписываются в эти границы. Однако в процессе программирования нам порой нужно точно знать габариты нашего примитива. Для этого была написана следующая команда.

2. Команда BoundaryRegion показывает корректные визуальные границы наших областей.


Как видим, созданные с помощью BoundaryRegion границы имеют зелёный цвет, для их визуального отличия от границ, созданных при помощи команды AcadBoundaryRegion.
Эту команду я использую для того, чтобы визуально убедиться в том, что вычисленные мною габариты верны.

3. Команда MeshRegionThroughCellSize нарезает копии наших исходных областей на ячейки, заданного размера:



Можно задавать произвольное значение высоты и ширины ячеек.

4. Команда MeshRegionThroughRowsAndColumnsCount режет копии наших областей на указанное количество строк и столбцов:



Используя команду MeshRegionThroughRowsAndColumnsCount можно нарезать области произвольным образом, например так:



Скорость работы команды MeshRegionThroughCellSize и MeshRegionThroughRowsAndColumnsCount зависит от количества областей, которые должны получиться на выходе, а так же от значения предельно допустимой абсолютной погрешности, указанной пользователем.

Исходный код программы разбит на несколько файлов. Далее приведено их полное содержимое.

Файл PluginEnvironment.cs

/* PluginEnvironment.cs
 * © Андрей Бушман, 2014 
 * Централизованное хранение общей информации, используемой в коде различных
 * методов. Т.о. при необходимости достаточно будет поменять её в одном месте
 * и все изменения автоматически подхватятся.
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace Bushman.CAD {
  public static class PluginEnvironment {
    /// <summary>
    /// Наименование группы команд AutoCAD, определённых в составе данной 
    /// сборки.
    /// </summary>
    public const String DefaultCommandGroup = "Bushman";
  }
}

Файл ExtensionApplication.cs

/* ExtensionApplication.cs
 * © Андрей Бушман, 2014 
 * Информация, отправляемая в консоль ActiveDocument при загрузке данной 
 * сборки. Если в текущий момент ActiveDocument равен null, то информация будет
 * выведена в консоль первого открытого в дальнейшем документа.
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;
 
#if AUTOCAD
using cad = Autodesk.AutoCAD.ApplicationServices.Application;
using Ap = Autodesk.AutoCAD.ApplicationServices;
using Db = Autodesk.AutoCAD.DatabaseServices;
using Ed = Autodesk.AutoCAD.EditorInput;
using Rt = Autodesk.AutoCAD.Runtime;
using Gm = Autodesk.AutoCAD.Geometry;
using Wn = Autodesk.AutoCAD.Windows;
using Hs = Autodesk.AutoCAD.DatabaseServices.HostApplicationServices;
using Us = Autodesk.AutoCAD.DatabaseServices.SymbolUtilityServices;
#endif
 
namespace Bushman.CAD {
 
  public sealed class ExtensionApplication : Rt.IExtensionApplication {
    #region IExtensionApplication Members
 
    public void Initialize() {
      Ap.Document doc = cad.DocumentManager.MdiActiveDocument;
      if(doc == null) {
        cad.DocumentManager.DocumentActivated += DocMng_DocActivated;
        return;
      }
      Db.Database db = doc.Database;
      Ed.Editor ed = doc.Editor;
      WriteAboutInfo(ed);
    }
 
    public void Terminate() {
    }
    #endregion
 
    internal static void WriteAboutInfo(Ed.Editor ed) {
      try {
        String title = GetAssemblyAttribute<AssemblyTitleAttribute>(
          a => a.Title);
        String copyright = GetAssemblyAttribute<AssemblyCopyrightAttribute>(
          a => a.Copyright);
        // String version = GetAssemblyAttribute<AssemblyVersionAttribute>(
        //  a => a.Version);
        String description = GetAssemblyAttribute<AssemblyDescriptionAttribute>(
          a => a.Description);
 
        ed.WriteMessage("\n{0}\n{1}. {2}\n{3}\n",
         Assembly.GetExecutingAssembly().Location, title, copyright,
         description);
      }
      catch(Exception ex) {
        ed.WriteMessage("{0}\n", ex.Message);
      }
    }
 
    void DocMng_DocActivated(object sender, Ap.DocumentCollectionEventArgs e) {
      cad.DocumentManager.DocumentActivated -= DocMng_DocActivated;
      WriteAboutInfo(e.Document.Editor);
    }
 
    internal static String GetAssemblyAttribute<T>(Func<T, String> value)
      where T : Attribute {
      T attribute = (T)Attribute.GetCustomAttribute(
        Assembly.GetExecutingAssembly(), typeof(T));
      return value.Invoke(attribute);
    }
  }
}

Файл About.cs

/* About.cs
 * © Андрей Бушман, 2014 
 * Команда, предоставляющая пользователю общую информацию о библиотеке.
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BUc = Bushman.CAD;
 
#if AUTOCAD
using cad = Autodesk.AutoCAD.ApplicationServices.Application;
using Ap = Autodesk.AutoCAD.ApplicationServices;
using Db = Autodesk.AutoCAD.DatabaseServices;
using Ed = Autodesk.AutoCAD.EditorInput;
using Rt = Autodesk.AutoCAD.Runtime;
using Gm = Autodesk.AutoCAD.Geometry;
using Wn = Autodesk.AutoCAD.Windows;
using Hs = Autodesk.AutoCAD.DatabaseServices.HostApplicationServices;
using Us = Autodesk.AutoCAD.DatabaseServices.SymbolUtilityServices;
#endif
 
[assembly: Rt.CommandClass(typeof(Bushman.CAD.Commands.About))]
 
namespace Bushman.CAD.Commands {
  public class About {
    [Rt.CommandMethod(BUc.PluginEnvironment.DefaultCommandGroup, "About",
      Rt.CommandFlags.Modal)]
    public void AboutCommand() {
      Ap.Document doc = cad.DocumentManager.MdiActiveDocument;
      if(doc == null || doc.IsDisposed)
        return;
      BUc.ExtensionApplication.WriteAboutInfo(doc.Editor);
    }
  }
}

Файл RegionTools.cs

/* RegionTools.cs
 * © Андрей Бушман, 2014 
 * Набор методов, предназначенных для создания разрезанной на части копии 
 * исходной области (region).
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
 
#if AUTOCAD
using cad = Autodesk.AutoCAD.ApplicationServices.Application;
using Ap = Autodesk.AutoCAD.ApplicationServices;
using Db = Autodesk.AutoCAD.DatabaseServices;
using Ed = Autodesk.AutoCAD.EditorInput;
using Rt = Autodesk.AutoCAD.Runtime;
using Gm = Autodesk.AutoCAD.Geometry;
using Wn = Autodesk.AutoCAD.Windows;
using Hs = Autodesk.AutoCAD.DatabaseServices.HostApplicationServices;
using Us = Autodesk.AutoCAD.DatabaseServices.SymbolUtilityServices;
using Br = Autodesk.AutoCAD.BoundaryRepresentation;
#endif
 
namespace Bushman.CAD.Extensions {
 
  /// <summary>
  /// Статический класс, предоставляющий дополнительный набор методов для 
  /// работы с объектами Region.
  /// </summary>
  public static class RegionTools {
 
    static Double dx = 10.0;
    static Double dy = 10.0;
 
    /// <summary>
    /// Предельно допустимое значение ширины ячейки. Свойство влияет на работу
    /// команды MeshRegionThroughCellSize.
    /// </summary>
    public static Double CellWidth {
      get { return dx; }
      set {
        if(dx <= 0)
          throw new ArgumentException(value.ToString());
        dx = value;
      }
    }
 
    /// <summary>
    /// Предельно допустимое значение высоты ячейки. Свойство влияет на работу
    /// команды MeshRegionThroughCellSize.
    /// </summary>
    public static Double CellHeight {
      get { return dy; }
      set {
        if(dy <= 0)
          throw new ArgumentException(value.ToString());
        dy = value;
      }
    }
 
    static Int32 rowsCount = 1;
    static Int32 columnsCount = 1;
 
    /// <summary>
    /// Количество строк в нарезаемой сетке. Свойство влияет на работу
    /// команды MeshRegionThroughRowsAndColumnsCount.
    /// </summary>
    public static Int32 RowsCount {
      get { return rowsCount; }
      set {
        if(rowsCount <= 0)
          throw new ArgumentException(value.ToString());
        rowsCount = value;
      }
    }
 
    /// <summary>
    /// Количество колонок в нарезаемой сетке. Свойство влияет на работу
    /// команды MeshRegionThroughRowsAndColumnsCount.
    /// </summary>
    public static Int32 ColumnsCount {
      get { return columnsCount; }
      set {
        if(columnsCount <= 0)
          throw new ArgumentException(value.ToString());
        columnsCount = value;
      }
    }
 
    // Предельная абсолютная погрешность точности измерения визуальных границ
    // примитива.
    static Double delta = 0.1;
 
    /// <summary>
    /// Предельная абсолютная погрешность точности измерения визуальных границ
    /// "GeometricExtents" региона. Чем меньше погрешность, тем точнее граница,
    /// но тем дольше она высчитывается. Свойство влияет на работу команды
    /// BoundaryRegion.
    /// </summary>
    public static Double Delta {
      get { return delta; }
      set {
        if(delta <= 0)
          throw new ArgumentException(value.ToString());
        delta = value;
      }
    }
 
    /// <summary>
    /// Настройка базы данных чертежа: установка нужных единиц измерения, 
    /// системы кооринат и т.п.
    /// </summary>
    /// <param name="db">Целевая база данных</param>
    internal static void SetDatabaseDefaultSettings(Db.Database db) {
      if(db == null)
        throw new ArgumentNullException("db");
      if(db.IsDisposed)
        throw new ArgumentException("db.IsDisposed == true");
 
      // Устанавливаем текущей метрическую систему измерения и миллиметры в
      // качестве используемых единиц измерения
      db.Measurement = Db.MeasurementValue.Metric;
      db.Insunits = Db.UnitsValue.Millimeters;
      // Устанавливаем текущей мировую систему координат
      db.WorldUcsBaseOrigin(Db.OrthographicView.TopView);
    }
 
    /// <summary>
    /// Создать прямоугольную область (Region), представляющую собой "ячейку"
    /// нарезаемой сетки.
    /// </summary>
    /// <param name="topLeftCorner">Координата верхнего левого угла "ячейки".
    /// </param>
    /// <param name="dx">Длина "ячейки".</param>
    /// <param name="dy">Высота "ячейки".</param>
    /// <returns>Возвращается новый объект Region. Программист должен либо 
    /// добавить полученный объект в Database, либо по завершению работы с ним
    /// не забыть его уничтожить, вызвав метод Dispose (дабы избежать утечки 
    /// памяти).</returns>
    public static Db.Region GetRegionCell(Gm.Point2d topLeftCorner, Double dx,
      Double dy) {
 
      #region Проверка входных данных
      if(dx <= 0)
        throw new ArgumentException("dx <= 0");
      if(dy <= 0)
        throw new ArgumentException("dy <= 0");
      #endregion
 
      Db.Region regCell = null;
      using(Db.Polyline pline = new Db.Polyline()) {
        pline.SetDatabaseDefaults();
        pline.AddVertexAt(0, new Gm.Point2d(topLeftCorner.X, topLeftCorner.Y),
          0, 0, 0);
        pline.AddVertexAt(1, new Gm.Point2d(topLeftCorner.X + dx,
          topLeftCorner.Y), 0, 0, 0);
        pline.AddVertexAt(2, new Gm.Point2d(topLeftCorner.X + dx,
          topLeftCorner.Y - dy), 0, 0, 0);
        pline.AddVertexAt(3, new Gm.Point2d(topLeftCorner.X, topLeftCorner.Y -
          dy), 0, 0, 0);
        pline.Closed = true;
 
        regCell = Db.Region.CreateFromCurves(new Db.DBObjectCollection() { 
          pline }).Cast<Db.Region>().First();
      }
      return regCell;
    }
 
    /// <summary>
    /// Получить массив областей (region) путём разреки копии указанной 
    /// области на отдельные прямоугольные (по возможности) части.
    /// </summary>
    /// <param name="region">Область, копия которой будет создана и затем 
    /// разрезана на части.</param>
    /// <param name="delta">Предельная абсолютная погрешность точности 
    /// измерения визуальных границ региона. Чем меньше погрешность, тем точнее
    /// граница, но тем дольше она высчитывается.</param>
    /// <param name="dx">Максимальная длина получаемых сегментов.</param>
    /// <param name="dy">Максимальная высота получаемых сегментов.</param>
    /// <returns>Возвращается массив объектов Region. Программист должен либо 
    /// добавить их в Database, либо по завершению своей работы с ними не 
    /// забыть уничтожить, вызвав для каждого элемента его метод Dispose (дабы
    /// избежать утечки памяти).</returns>
    public static Db.Region[] CloneAndMesh(this Db.Region region, Double delta,
      Double dx, Double dy) {
 
      #region Проверка входных данных
      if(region == null)
        throw new ArgumentNullException("region");
      if(region.IsDisposed)
        throw new ArgumentException("region.IsDisposed == true");
      if(dx < 0)
        throw new ArgumentException("dx < 0");
      if(dy < 0)
        throw new ArgumentException("dy < 0");
      #endregion
 
      List<Db.Region> result = new List<Db.Region>();
 
      Gm.Point2d minPoint = Gm.Point2d.Origin;
      Gm.Point2d maxPoint = Gm.Point2d.Origin;
 
      region.GetVisualBoundary(delta, ref minPoint, ref maxPoint);
 
      #region Если исходный регион не превышает ячейку, то резать нечего
      Double length = maxPoint.X - minPoint.X;
      Double height = maxPoint.Y - minPoint.Y;
 
      // Если исходный регион меньше ячейки нарезаемой сетки, то возвращаем
      // пустой массив, т.к. разрезать нечего
      if(dx >= length && dy >= height)
        return result.ToArray();
      #endregion
 
      for(Double offsetY = height; offsetY > -dy; offsetY -= dy) {
 
        Db.Region regionClone = region.Clone() as Db.Region;
        Gm.Point2d topLeftCorner = new Gm.Point2d(minPoint.X,
          minPoint.Y + offsetY);
 
        Db.Region regionRow = GetRegionCell(topLeftCorner, length, dy);
 
        regionClone.BooleanOperation(Db.BooleanOperationType.BoolIntersect,
          regionRow);
 
        // Если значения обоих свойств regionClone.IsNull и regionRow.IsNull
        // равны true, значит пересечение отсутствует
 
        // Если логическая операция выполнена успешно, то её результат 
        // сохраняем, т.к. его ещё предстоит резать вертикально
        if(!regionClone.IsNull && regionRow.IsNull) {
 
          // Теперь полученную горионтальную часть режем вертикально
          if(length > dx) {
            Db.Region row = regionClone;
            Double rowLength = maxPoint.X - minPoint.X;
 
            for(Double offsetX = rowLength - dx; offsetX > -dx;
              offsetX -= dx) {
              Db.Region rowClone = row.Clone() as Db.Region;
              topLeftCorner = new Gm.Point2d(minPoint.X + offsetX,
                minPoint.Y + offsetY);
 
              Db.Region regionCell = GetRegionCell(topLeftCorner, dx, dy);
              regionCell.LayerId = region.LayerId;
 
              rowClone.BooleanOperation(
                Db.BooleanOperationType.BoolIntersect, regionCell);
 
              // Если логическая операция выполнена успешно, то её результат 
              // сохраняем, т.к. его ещё предстоит резать вертикально
              if(!rowClone.IsNull && regionCell.IsNull) {
                result.AddRange(ExplodeIfUnitedRegions(rowClone));
 
                if(!regionCell.IsDisposed)
                  regionCell.Dispose();
              }
              // Предотвращаем утечку памяти
              else {
                if(!regionCell.IsDisposed)
                  regionCell.Dispose();
                if(!rowClone.IsDisposed)
                  rowClone.Dispose();
              }
            }
            row.Dispose();
          }
          else {
            result.AddRange(ExplodeIfUnitedRegions(regionClone));
          }
 
          if(!regionRow.IsDisposed)
            regionRow.Dispose();
        }
        // Предотвращаем утечку памяти
        else {
          if(!regionRow.IsDisposed)
            regionRow.Dispose();
          if(!regionClone.IsDisposed)
            regionClone.Dispose();
        }
      }
      return result.ToArray();
    }
 
    /// <summary>
    /// Метод получает на входе объект Region и проверяет, состоит ли он из
    /// нескольких объединённых областей. Если состоит, то эти области 
    /// извлекаются и записываются в массив объектов Region, после чего объект
    /// исходной области, переданной методу в качестве параметра, уничтожается.
    /// 
    /// Если исходная область не состоит из нескольких объединённых частей, то
    /// объект этой области добавляется в массив объектов Region.
    /// </summary>
    /// <param name="region">Исходная область, подлежащая обработке.</param>
    /// <returns>Возвращается массив областей. Как минимум, в массиве будет 
    /// присутствовать хотя бы один объект.</returns>
    public static Db.Region[] ExplodeIfUnitedRegions(Db.Region region) {
 
      if(region == null)
        throw new ArgumentNullException("region");
      if(region.IsDisposed)
        throw new ArgumentException("region.IsDisposed == true");
 
      List<Db.Region> result = new List<Db.Region>();
      using(Br.Brep brep = new Br.Brep(region)) {
        if(brep.Complexes != null && brep.Complexes.Count() > 1) {
          using(Db.DBObjectCollection explodeResult =
            new Db.DBObjectCollection()) {
            region.Explode(explodeResult);
            foreach(Db.DBObject item in explodeResult) {
              Db.Region n = (Db.Region)item;
              n.LayerId = region.LayerId;
              result.Add(n);
            }
          }
 
          if(!region.IsDisposed)
            region.Dispose();
        }
        else
          result.Add(region);
      }
      return result.ToArray();
    }
 
    // Код метода GetVisualBoundary был написан Александром Ривилисом здесь:
    // http://adn-cis.org/forum/index.php?topic=495.msg3043#msg3043
 
    /// <summary>
    /// Получить координаты границ левого нижнего и правого верхнего углов для
    /// визуального "GeometricExtents" региона.
    /// </summary>
    /// <param name="region">Регион, для которого следует получить координаты 
    /// границ визуального "GeometricExtents".</param>
    /// <param name="delta">Предельная арифметическая погрешность вычислений.
    /// </param>
    /// <param name="minPoint">Ссылка на переменную Point2d, в которой следует
    /// сохранить координаты левого нижнего угла визуального 
    /// "GeometricExtents".</param>
    /// <param name="maxPoint">Ссылка на переменную Point2d, в которой следует
    /// сохранить координаты правого верхнего угла визуального 
    /// "GeometricExtents".</param>
    public static void GetVisualBoundary(this Db.Region region, Double delta,
  ref Gm.Point2d minPoint, ref Gm.Point2d maxPoint) {
      using(Gm.BoundBlock3d boundBlk = new Gm.BoundBlock3d()) {
        using(Br.Brep brep = new Br.Brep(region)) {
          foreach(Br.Edge edge in brep.Edges) {
            using(Gm.Curve3d curve = edge.Curve) {
              Gm.ExternalCurve3d curve3d = curve as Gm.ExternalCurve3d;
              // Делать точный расчет нужно только если образующая - сплайн
              // в противном случае достаточно получить BoundBlock
              if(curve3d != null && curve3d.IsNurbCurve) {
                using(Gm.NurbCurve3d nurbCurve = curve3d.NativeCurve
                  as Gm.NurbCurve3d) {
                  Gm.Interval interval = nurbCurve.GetInterval();
                  for(double par = interval.LowerBound;
                    par <= interval.UpperBound; par += (delta * 2.0)) {
                    Gm.Point3d p = nurbCurve.EvaluatePoint(par);
                    if(!boundBlk.IsBox)
                      boundBlk.Set(p, p);
                    else
                      boundBlk.Extend(p);
                  }
                }
              }
              else {
                if(!boundBlk.IsBox) {
                  boundBlk.Set(edge.BoundBlock.GetMinimumPoint(),
                    edge.BoundBlock.GetMaximumPoint());
                }
                else {
                  boundBlk.Extend(edge.BoundBlock.GetMinimumPoint());
                  boundBlk.Extend(edge.BoundBlock.GetMaximumPoint());
                }
              }
            }
          }
        }
        // Возвращаем вычисленный результат
        minPoint = new Gm.Point2d(boundBlk.GetMinimumPoint().X,
          boundBlk.GetMinimumPoint().Y);
        maxPoint = new Gm.Point2d(boundBlk.GetMaximumPoint().X,
          boundBlk.GetMaximumPoint().Y);
      }
    }
  }
}

Файл RegionCommands.cs

/* RegionCommands.cs
 * © Андрей Бушман, 2014 
 * Дополнительные команды CAD для работы с объектами областей (region).
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
using BUc = Bushman.CAD;
using BUx = Bushman.CAD.Extensions;
// для возможности использования методов расширений
using Bushman.CAD.Extensions;
 
#if AUTOCAD
using cad = Autodesk.AutoCAD.ApplicationServices.Application;
using Ap = Autodesk.AutoCAD.ApplicationServices;
using Db = Autodesk.AutoCAD.DatabaseServices;
using Ed = Autodesk.AutoCAD.EditorInput;
using Rt = Autodesk.AutoCAD.Runtime;
using Gm = Autodesk.AutoCAD.Geometry;
using Wn = Autodesk.AutoCAD.Windows;
using Hs = Autodesk.AutoCAD.DatabaseServices.HostApplicationServices;
using Us = Autodesk.AutoCAD.DatabaseServices.SymbolUtilityServices;
using Br = Autodesk.AutoCAD.BoundaryRepresentation;
#endif
 
[assembly: Rt.CommandClass(typeof(Bushman.CAD.Commands.RegionCommands))]
 
namespace Bushman.CAD.Commands {
 
  public sealed class RegionCommands {
 
    /// <summary>
    /// Команда CAD, при помощи которой формируется "нарезка" копий указанных
    /// областей (Regions) на прямоугольные (по возможности) части, не 
    /// превышающие размеров dx и dy, заданных пользователем.
    /// </summary>
    [Rt.CommandMethod(Bushman.CAD.PluginEnvironment.DefaultCommandGroup,
      "MeshRegionThroughCellSize", Rt.CommandFlags.Modal)]
    public void MeshRegionThroughCellSize() {
      Ap.Document doc = cad.DocumentManager.MdiActiveDocument;
      if(doc == null)
        return;
 
      Db.Database db = doc.Database;
      BUx.RegionTools.SetDatabaseDefaultSettings(db);
      Ed.Editor ed = doc.Editor;
 
      Db.TypedValue[] tv = new Db.TypedValue[1];
      tv[0] = new Db.TypedValue((Int32)Db.DxfCode.Start, "REGION");
      Ed.SelectionFilter filter = new Ed.SelectionFilter(tv);
      Ed.PromptSelectionOptions pso = new Ed.PromptSelectionOptions();
      pso.MessageForAdding = "Выберите области (Regions) для добавления " +
        "их в набор";
      pso.MessageForRemoval =
        "Выберите области (Regions), подлежащие удалению из набора";
      pso.SingleOnly = false;
      pso.RejectObjectsFromNonCurrentSpace = true;
      pso.AllowDuplicates = false;
 
      Ed.PromptSelectionResult psr = ed.GetSelection(pso, filter);
 
      if(psr.Status != Ed.PromptStatus.OK) {
        ed.WriteMessage("Ничего не выбрано. Выполнение команды прервано\n");
        return;
      }
      else {
        ed.WriteMessage("\nВсего выбрано областей (Regions): {0}\n",
          psr.Value.Count);
      }
      Db.ObjectId[] ids = psr.Value.GetObjectIds();
      Ed.PromptDoubleOptions pdo = new Ed.PromptDoubleOptions("dx");
      pdo.AllowNegative = false;
      pdo.AllowZero = false;
      pdo.AllowNone = false;
      pdo.DefaultValue = RegionTools.CellWidth;
      pdo.UseDefaultValue = true;
 
      Ed.PromptDoubleResult pdr = ed.GetDouble(pdo);
      if(pdr.Status != Ed.PromptStatus.OK) {
        ed.WriteMessage("Выполнение команды прервано\n");
        return;
      }
      RegionTools.CellWidth = pdr.Value;
 
      ed.WriteMessage(Environment.NewLine);
 
      pdo.Message = "dy";
      pdo.DefaultValue = RegionTools.CellHeight;
      pdr = ed.GetDouble(pdo);
      if(pdr.Status != Ed.PromptStatus.OK) {
        ed.WriteMessage("Выполнение команды прервано\n");
        return;
      }
      RegionTools.CellHeight = pdr.Value;
 
      pdo = new Ed.PromptDoubleOptions(
        "Предельная абсолютная погрешность");
      pdo.AllowNegative = false;
      pdo.AllowZero = false;
      pdo.AllowNone = false;
      pdo.DefaultValue = RegionTools.Delta;
      pdo.UseDefaultValue = true;
 
      pdr = ed.GetDouble(pdo);
 
      if(pdr.Status != Ed.PromptStatus.OK) {
        ed.WriteMessage("Выполнение команды прервано\n");
        return;
      }
      RegionTools.Delta = pdr.Value;
 
      ed.WriteMessage(Environment.NewLine);
 
      using(Db.Transaction tr = db.TransactionManager.StartTransaction()) {
 
        Db.BlockTable bt = (Db.BlockTable)tr.GetObject(db.BlockTableId,
         Db.OpenMode.ForRead);
        Db.BlockTableRecord btr = (Db.BlockTableRecord)tr.GetObject(bt[
         Db.BlockTableRecord.ModelSpace], Db.OpenMode.ForWrite);
 
        DateTime start = DateTime.Now;
        Int32 count = 0;
        foreach(Db.ObjectId id in ids) {
          Db.Region region = tr.GetObject(id, Db.OpenMode.ForRead)
            as Db.Region;
          Db.Region[] regions = region.CloneAndMesh(RegionTools.Delta,
            RegionTools.CellWidth, RegionTools.CellHeight);
          count += regions.Length;
          foreach(Db.Region item in regions) {
            btr.AppendEntity(item);
            tr.AddNewlyCreatedDBObject(item, true);
          }
        }
        tr.Commit();
 
        DateTime end = DateTime.Now;
        ed.WriteMessage(
          "\nСоздано областей (region): {0}\nЗатраченное время: {1}\n",
         count, end - start);
      }
    }
 
    /// <summary>
    /// Команда CAD, при помощи которой формируется "нарезка" копий указанных
    /// областей (Regions) на прямоугольные (по возможности) части согласно 
    /// указанному количеству строк и столбцов.
    /// </summary>
    [Rt.CommandMethod(Bushman.CAD.PluginEnvironment.DefaultCommandGroup,
      "MeshRegionThroughRowsAndColumnsCount", Rt.CommandFlags.Modal)]
    public void MeshRegionThroughRowsAndColumnsCount() {
      Ap.Document doc = cad.DocumentManager.MdiActiveDocument;
      if(doc == null)
        return;
 
      Db.Database db = doc.Database;
      BUx.RegionTools.SetDatabaseDefaultSettings(db);
      Ed.Editor ed = doc.Editor;
 
      Db.TypedValue[] tv = new Db.TypedValue[1];
      tv[0] = new Db.TypedValue((Int32)Db.DxfCode.Start, "REGION");
      Ed.SelectionFilter filter = new Ed.SelectionFilter(tv);
      Ed.PromptSelectionOptions pso = new Ed.PromptSelectionOptions();
      pso.MessageForAdding = "Выберите области (Regions) для добавления " +
        "их в набор";
      pso.MessageForRemoval =
        "Выберите области (Regions), подлежащие удалению из набора";
      pso.SingleOnly = false;
      pso.RejectObjectsFromNonCurrentSpace = true;
      pso.AllowDuplicates = false;
 
      Ed.PromptSelectionResult psr = ed.GetSelection(pso, filter);
 
      if(psr.Status != Ed.PromptStatus.OK) {
        ed.WriteMessage("Ничего не выбрано. Выполнение команды прервано\n");
        return;
      }
      else {
        ed.WriteMessage("\nВсего выбрано областей (Regions): {0}\n",
          psr.Value.Count);
      }
      Db.ObjectId[] ids = psr.Value.GetObjectIds();
      Ed.PromptIntegerOptions pio = new Ed.PromptIntegerOptions(
        "Количество столбцов");
      pio.AllowNegative = false;
      pio.AllowZero = false;
      pio.AllowNone = false;
      pio.DefaultValue = RegionTools.ColumnsCount;
      pio.UseDefaultValue = true;
 
      Ed.PromptIntegerResult pir = ed.GetInteger(pio);
      if(pir.Status != Ed.PromptStatus.OK) {
        ed.WriteMessage("Выполнение команды прервано\n");
        return;
      }
      RegionTools.ColumnsCount = pir.Value;
 
      ed.WriteMessage(Environment.NewLine);
 
      pio.Message = "Количество строк";
      pio.DefaultValue = RegionTools.RowsCount;
      pir = ed.GetInteger(pio);
      if(pir.Status != Ed.PromptStatus.OK) {
        ed.WriteMessage("Выполнение команды прервано\n");
        return;
      }
      RegionTools.RowsCount = pir.Value;
 
      if(RegionTools.ColumnsCount != 1 || RegionTools.RowsCount != 1) {
        Ed.PromptDoubleOptions pdo = new Ed.PromptDoubleOptions(
  "Предельная абсолютная погрешность");
        pdo.AllowNegative = false;
        pdo.AllowZero = false;
        pdo.AllowNone = false;
        pdo.DefaultValue = RegionTools.Delta;
        pdo.UseDefaultValue = true;
 
        Ed.PromptDoubleResult pdr = ed.GetDouble(pdo);
 
        if(pdr.Status != Ed.PromptStatus.OK) {
          ed.WriteMessage("Выполнение команды прервано\n");
          return;
        }
        RegionTools.Delta = pdr.Value;
 
        ed.WriteMessage(Environment.NewLine);
 
        using(Db.Transaction tr = db.TransactionManager.StartTransaction()) {
 
          Db.BlockTable bt = (Db.BlockTable)tr.GetObject(db.BlockTableId,
           Db.OpenMode.ForRead);
          Db.BlockTableRecord btr = (Db.BlockTableRecord)tr.GetObject(bt[
           Db.BlockTableRecord.ModelSpace], Db.OpenMode.ForWrite);
 
          DateTime start = DateTime.Now;
          Int32 count = 0;
          foreach(Db.ObjectId id in ids) {
            Db.Region region = tr.GetObject(id, Db.OpenMode.ForRead)
              as Db.Region;
 
            Gm.Point2d minPoint = Gm.Point2d.Origin;
            Gm.Point2d maxPoint = Gm.Point2d.Origin;
 
            region.GetVisualBoundary(RegionTools.Delta, ref minPoint,
              ref maxPoint);
 
            Double width = (maxPoint.X - minPoint.X) / RegionTools.ColumnsCount;
            Double height = (maxPoint.Y - minPoint.Y) / RegionTools.RowsCount;
 
            Db.Region[] regions = region.CloneAndMesh(RegionTools.Delta, width,
              height);
            count += regions.Length;
            foreach(Db.Region item in regions) {
              btr.AppendEntity(item);
              tr.AddNewlyCreatedDBObject(item, true);
            }
          }
          tr.Commit();
 
          DateTime end = DateTime.Now;
          ed.WriteMessage(
            "\nСоздано областей (region): {0}\nЗатраченное время: {1}\n",
           count, end - start);
        }
      }
      else {
        ed.WriteMessage("\nЕсли количество строк и столбцов одновременно " +
          "равны 1, то создавать нечего.\n");
      }
    }
 
    /// <summary>
    /// Получение значения визуальных границ регионов (аналог реализации от
    /// AutoCAD по умолчанию). В случаях, когда контуры областей выполнены
    /// сплайнами, команда BoundaryRegion покажет более точные границы, чем 
    /// команда AcadBoundaryRegion. Назначение AcadBoundaryRegion в том, чтобы
    /// визуально показать программисту границы контейнера области. Это может
    /// помочь понять, почему в некоторых случаях программный код даёт не те 
    /// результаты, которые ожидались на выходе.
    /// </summary>
    [Rt.CommandMethod(Bushman.CAD.PluginEnvironment.DefaultCommandGroup,
      "AcadBoundaryRegion", Rt.CommandFlags.Modal)]
    public void AcadBoundaryRegion() {
      Ap.Document doc = cad.DocumentManager.MdiActiveDocument;
      if(doc == null)
        return;
 
      Db.Database db = doc.Database;
      BUx.RegionTools.SetDatabaseDefaultSettings(db);
      Ed.Editor ed = doc.Editor;
 
      Db.TypedValue[] tv = new Db.TypedValue[1];
      tv[0] = new Db.TypedValue((Int32)Db.DxfCode.Start, "REGION");
      Ed.SelectionFilter filter = new Ed.SelectionFilter(tv);
      Ed.PromptSelectionOptions pso = new Ed.PromptSelectionOptions();
      pso.MessageForAdding = "Выберите области (Regions) для добавления " +
        "их в набор";
      pso.MessageForRemoval =
        "Выберите области (Regions), подлежащие удалению из набора";
      pso.SingleOnly = false;
      pso.RejectObjectsFromNonCurrentSpace = true;
      pso.AllowDuplicates = false;
 
      Ed.PromptSelectionResult psr = ed.GetSelection(pso, filter);
 
      if(psr.Status != Ed.PromptStatus.OK) {
        ed.WriteMessage("Ничего не выбрано. Выполнение команды прервано\n");
        return;
      }
      else {
        ed.WriteMessage("\nВсего выбрано областей (Regions): {0}\n",
          psr.Value.Count);
      }
      Db.ObjectId[] ids = psr.Value.GetObjectIds();
 
      ed.WriteMessage(Environment.NewLine);
 
      using(Db.Transaction tr = db.TransactionManager.StartTransaction()) {
 
        Db.BlockTable bt = (Db.BlockTable)tr.GetObject(db.BlockTableId,
         Db.OpenMode.ForRead);
        Db.BlockTableRecord btr = (Db.BlockTableRecord)tr.GetObject(bt[
         Db.BlockTableRecord.ModelSpace], Db.OpenMode.ForWrite);
 
        foreach(Db.ObjectId id in ids) {
          Db.Region region = tr.GetObject(id, Db.OpenMode.ForRead)
            as Db.Region;
 
          using(Br.Brep brep = new Br.Brep(region)) {
            Gm.Point3d origin = new Gm.Point3d(0, 0, 0);
            Gm.Vector3d normal = new Gm.Vector3d(0, 0, 1);
            Gm.Plane plane = new Gm.Plane(origin, normal);
 
            Gm.BoundBlock3d bb = brep.BoundBlock;
 
            Gm.Point2d minPoint = Gm.Point2d.Origin;
            Gm.Point2d maxPoint = Gm.Point2d.Origin;
 
            Double minX = Double.MaxValue;
            Double minY = Double.MaxValue;
 
            Double maxX = Double.MinValue;
            Double maxY = Double.MinValue;
 
            if(brep.Edges != null) {
              foreach(Br.Edge edge in brep.Edges) {
 
                Gm.Point3d min = bb.GetMinimumPoint();
                Gm.Point3d max = bb.GetMaximumPoint();
 
                if(min.X < minX)
                  minX = min.X;
 
                if(min.Y < minY)
                  minY = min.Y;
 
                if(max.X > maxX)
                  maxX = max.X;
 
                if(max.Y > maxY)
                  maxY = max.Y;
              }
              minPoint = new Gm.Point2d(minX, minY);
              maxPoint = new Gm.Point2d(maxX, maxY);
            }
            else {
              minPoint = bb.GetMinimumPoint().Convert2d(plane);
              maxPoint = bb.GetMaximumPoint().Convert2d(plane);
            }
 
 
            Db.Polyline pline = new Db.Polyline(4);
            pline.SetDatabaseDefaults();
 
            Gm.Point2d[] points = new Gm.Point2d[]{
              new Gm.Point2d(minPoint.X,maxPoint.Y),
              new Gm.Point2d(maxPoint.X,maxPoint.Y),
              new Gm.Point2d(maxPoint.X,minPoint.Y),
              new Gm.Point2d(minPoint.X, minPoint.Y)};
 
            for(Int32 i = 0; i < points.Length; i++)
              pline.AddVertexAt(i, points[i], 0, 0, 0);
 
            pline.Closed = true;
            pline.ColorIndex = 50;
 
            btr.AppendEntity(pline);
            tr.AddNewlyCreatedDBObject(pline, true);
          }
        }
        tr.Commit();
      }
    }
 
    /// <summary>
    /// Получение значения визуальных границ регионов. В случаях, когда контуры
    /// областей выполнены сплайнами, данная команда покажет более точные 
    /// границы, чем команда AcadBoundaryRegion. Данная команда предназначена
    /// для демонстрирования работы статического метода 
    /// RegionTools.GetVisualBoundary(...).
    /// </summary>
    [Rt.CommandMethod(Bushman.CAD.PluginEnvironment.DefaultCommandGroup,
  "BoundaryRegion", Rt.CommandFlags.Modal)]
    public void BoundaryRegion() {
      Ap.Document doc = cad.DocumentManager.MdiActiveDocument;
      if(doc == null)
        return;
 
      Db.Database db = doc.Database;
      BUx.RegionTools.SetDatabaseDefaultSettings(db);
      Ed.Editor ed = doc.Editor;
 
      Db.TypedValue[] tv = new Db.TypedValue[1];
      tv[0] = new Db.TypedValue((Int32)Db.DxfCode.Start, "REGION");
      Ed.SelectionFilter filter = new Ed.SelectionFilter(tv);
      Ed.PromptSelectionOptions pso = new Ed.PromptSelectionOptions();
      pso.MessageForAdding = "Выберите области (Regions) для добавления " +
        "их в набор";
      pso.MessageForRemoval =
        "Выберите области (Regions), подлежащие удалению из набора";
      pso.SingleOnly = false;
      pso.RejectObjectsFromNonCurrentSpace = true;
      pso.AllowDuplicates = false;
 
      Ed.PromptSelectionResult psr = ed.GetSelection(pso, filter);
 
      if(psr.Status != Ed.PromptStatus.OK) {
        ed.WriteMessage("Ничего не выбрано. Выполнение команды прервано\n");
        return;
      }
      else {
        ed.WriteMessage("\nВсего выбрано областей (Regions): {0}\n",
          psr.Value.Count);
      }
      Db.ObjectId[] ids = psr.Value.GetObjectIds();
 
      ed.WriteMessage(Environment.NewLine);
 
      Ed.PromptDoubleOptions pdo = new Ed.PromptDoubleOptions(
        "Предельная абсолютная погрешность");
      pdo.AllowNegative = false;
      pdo.AllowZero = false;
      pdo.AllowNone = false;
      pdo.DefaultValue = RegionTools.Delta;
      pdo.UseDefaultValue = true;
 
      Ed.PromptDoubleResult pdr = ed.GetDouble(pdo);
      if(pdr.Status != Ed.PromptStatus.OK) {
        ed.WriteMessage("Выполнение команды прервано\n");
        return;
      }
      RegionTools.Delta = pdr.Value;
 
      using(Db.Transaction tr = db.TransactionManager.StartTransaction()) {
 
        Db.BlockTable bt = (Db.BlockTable)tr.GetObject(db.BlockTableId,
         Db.OpenMode.ForRead);
        Db.BlockTableRecord btr = (Db.BlockTableRecord)tr.GetObject(bt[
         Db.BlockTableRecord.ModelSpace], Db.OpenMode.ForWrite);
 
        Int32 count = 0;
        DateTime start = DateTime.Now;
 
        foreach(Db.ObjectId id in ids) {
          Db.Region region = tr.GetObject(id, Db.OpenMode.ForRead)
            as Db.Region;
          using(Br.Brep brep = new Br.Brep(region)) {
            Gm.Point3d origin = new Gm.Point3d(0, 0, 0);
            Gm.Vector3d normal = new Gm.Vector3d(0, 0, 1);
            Gm.Plane plane = new Gm.Plane(origin, normal);
 
            Gm.Point2d minPoint = Gm.Point2d.Origin;
            Gm.Point2d maxPoint = Gm.Point2d.Origin;
 
            region.GetVisualBoundary(RegionTools.Delta, ref minPoint,
              ref maxPoint);
 
            Db.Polyline pline = new Db.Polyline(4);
            pline.SetDatabaseDefaults();
 
            Gm.Point2d[] points = new Gm.Point2d[]{
              new Gm.Point2d(minPoint.X,maxPoint.Y),
              new Gm.Point2d(maxPoint.X,maxPoint.Y),
              new Gm.Point2d(maxPoint.X,minPoint.Y),
              new Gm.Point2d(minPoint.X, minPoint.Y)};
 
            for(Int32 i = 0; i < points.Length; i++)
              pline.AddVertexAt(i, points[i], 0, 0, 0);
 
            pline.Closed = true;
            pline.ColorIndex = 80;
 
            btr.AppendEntity(pline);
            tr.AddNewlyCreatedDBObject(pline, true);
            ++count;
          }
        }
        tr.Commit();
        DateTime end = DateTime.Now;
        ed.WriteMessage("\nСоздано границ: {0}\nЗатраченное время: {1}\n",
         count, end - start);
      }
    }
  }
}

Комментариев нет: