Разработка модулей нормоконтроля

Дата публикации: 17.07.2011
Состояние: завершена
Дата последних изменений: 17.07.2011

    Прежде всего, начать чтение рекомендую с того, что я когда-то опубликовал в своём блоге здесь и здесь. В качестве дополнения можно почитать это. В текущей статье я выкладываю слегка мною "причёсанный" (небольшой рефакторинг + перевод + свои комментарии) код Kean Walmsley, взятый отсюда. Автор кода написал модуль нормоконтроля, задачей которого является контроль цвета нарисованных в чертеже окружностей. Цвет назначается таким, какой имеет окружность из dws-файла с наиболее близким по модулю радиусом. 
    Я постарался подробно описать что происходит в процессе работы механизма AutoCAD, именуемого менеджером стандартов (экземпляр класса AcStManager) - именно этот объект вызывает на исполнение написанные нами модули нормоконтроля (реализующие, в свою очередь, интерфейс IAcStPlugin2). 
    Найденные мною ошибки автора кода я исправлять не стал, но указал где они находятся, когда возникают и как их можно исправить. В самом низу страницы выложены все исходники, оформленные в MS VS 2010 для AutoCAD 2009 x86. Я разбил код на три файла (указав класс CircleStandard как partial), чтобы насколько это возможно - облегчить понимание изложенного материала. Начнём с просмотра визуальной диаграммы классов:
    Как видно из схемы, основным классом является CircleStandard, реализующий интерфейс IAcStPlugin2. В теле этого класса определено два вспомогательных класса: CircleCach и ContextList. Первый представляет собой объект, в котором инкапсулирована единица проверяемой информации, а второй хранит в себе два контекста (массивы проверяемых и эталонных окружностей).
    Механизм работы менеджера стандартов я изучал посредством брэйкпоинтов, которые расставил на входе в каждый метод. Далее опубликовываю то, что мне самостоятельно удалось выяснить.     

Как всё это работает в AutoCAD?

Если открывать существующий чертёж, к которому не подключено ни одного dws-файла, то для этого чертежа механизм проверки на соответствие стандартам не запускается. Такое же поведение наблюдается, если создавать новый файл на основе шаблона, к которому не подключено ни одного dws-файла.

Если к чертежу подключен хотя бы один dws-файл, то в этом случае поведение AutoCAD зависит от настроек диалогового окна "CAD Standards Settings" (они влияют на все чертежи, к которым подключены dws-файлы), а так же от того, разрешено ли интересующему нас модулю нормоконтроля выполнять работу в AutoCAD (определяется установкой/снятием галочки напротив интересующего нас модуля на вкладке “Plag-ins” диалогового окна “Configure Standards”). Если обозначенная галочка снята – модуль не будет работать. Всё что изложено по тексту ниже, подразумевает, что для интересующего нас модуля эта галочка УСТАНОВЛЕНА.

Если в диалоговом окне "CAD Standards Settings" всё отключить, то не будет ничего происходить как в процессе открытия чертежа, так и в процессе его редактирования/сохранения/закрытия. Т.е. поведение будет таким, как будто к чертежу не подключено ни одного dws-файла. Однако при таких настройках всё же можно принудительно вызвать проверку чертежа на соответствие стандартам.

Если в диалоговом окне "CAD Standards Settings" отключена работа менеджера стандартов, но при этом в процессе работы над чертежом пользователь хотя бы один раз принудительно вызвал для этого файла проверку на соответствие стандартам, то при закрытии чертежа AutoCAD обязательно выполнит код метода Clear.

Всё то, что изложено далее по тексту, ориентировано на ситуацию, когда работа менеджера стандартов НЕ отключена в диалоговом окне "CAD Standards Settings".

Далее я расскажу, какие методы, когда и в какой последовательности запускает на исполнение AutoCAD...

Если запустить процесс проверки чертежа на предмет соответствия его содержимого правилам, обозначенным в подключенных к нему dws-файлах, то это приведёт к последовательному запуску методов:

  1. Initialize
  2. GetObjectFilter
  3. SetupForAudit
  4. SetContext
  5. Start
  6. Next (в коде автора был вызван из метода Start)
  7. Done
  8. GetError
  9. GetAllFixes
  10. GetRecommendedFix (в коде автора был вызван из метода GetAllFixes)
  11. GetPropertyDiffs (вызывается столько раз подряд, сколько окружностей получено из dws-файлов) 

После этого открывается диалоговое окно "Check Standards", в котором обозначена текущая ошибка, подлежащая исправлению и представлен список доступных вариантов исправления (если имеется рекомендуемый - он выделен галочкой). Если нажать кнопку Fix, согласившись с рекомендуемым вариантом, то происходит последовательный запуск следующих методов:

  1. FixError
  2. Done
  3. Next
  4. Done
  5. GetError
  6. GetAllFixes
  7. GetRecommendedFix (в коде автора был вызван из метода GetAllFixes)
  8. GetPropertyDiffs (вызывается столько раз подряд, сколько окружностей получено из dws-файлов) 

Эта последовательность вызовов будет при каждом нажатии кнопки Fix. Если вместо Fix нажать Next, то будет выполнена такая последовательность методов:

  1. Done
  2. Next
  3. Done
    В конце проверки появляется диалоговое окно "Check Standatds Check Complete" с отчётом о произведённых изменениях. Нажатие кнопки закрытия окна приводит к вызову метода SetContext (со вторым параметром равным true).  

При закрытии чертежа, к которому подключены dws-модули, в том случае если в диалоговом окне «CAD Standards Settings» не отключена работа менеджера стандартов, или же если  с этим чертежом работал модуль нормоконтроля (был принудительно запущен пользователем в процессе работы с документом) - AutoCAD обязательно запускает на исполнение код метода Clear.

Если в диалоговом окне "Check Standatds Check Complete" не отключен механизм проверки чертежей на соответствие стандартам, то при открытии файла, к которому подключен хотя бы один dws-файл, или же при создании нового чертежа на основе шаблона, к которому подключен хотя бы один dws-файл, AutoCAD последовательно выполняет следующие методы:

  1. Initialize
  2. GetObjectFilter
  3. SetupForAudit 

Если мы подключаем к чертежу, или отключаем от него очередной dws-файл, либо меняем приоритеты подключенных dws-файлов, перемещая их вверх/вниз по отношению друг к другу, то AutoCAD последовательно вызывает методы:

  1. Clear
  2. Initialize
  3. GetObjectFilter
  4. SetupForAudit 

Если мы отсоединяем от чертежа последний dws-файл, то AutoCAD выполняет только метод Clear.

В процессе работы AutoCAD ни разу не вызывал следующие методы интерфейса IAcStPlugin2:

  1. CheckSysvar
  2. StampDatabase
  3. UpdateStatus
  4. WritePluginInfo 

Об ошибках, найденных в коде оригинала:

     Код Kean Walmsley (причёсанный мною для удобства восприятия, но не изменённый по своей сути), изучаемый в этой статье (выложен ниже), содержит в себе две критичные ошибки (см. строки 113 и 195 кода и в файле CircleStandard.cs). Я не стал их исправлять, а вместо этого добавил комментарии, на основании которых не трудно понять, что следует сделать, дабы их исправить. Наличие обеих ошибок проверено мною на практике, при воссоздании обозначенных в комментариях условий их возникновения.

Важно!

    Т.о. стоит учитывать то, что если интересующий нас модуль нормоконтроля активирован в AutoCAD (путём установки соответствующей галочки на вкладке Plug-ins) и менеджер нормоконтроля работает, то код нашего модуля будет выполняться даже в тех файлах, к которым не подключены нужные для нашего модуля dws-файлы, но подключены какие-то другие. Если мы не будем этого учитывать - ошибки подобные тем, что я указал у Kean Walmsley нам гарантированы...

Далее я опубликовываю код Киана Вилмсли, слегка подправленный мною кое-где (в плане оптимизации синтаксиса и дополненного комментариями).

Код C# (файл _CircleCache.cs)

   1:  using AcStMgr;
   2:  using Autodesk.AutoCAD.Interop.Common;
   3:  using MSXML2;
   4:  using System;
   5:  using System.Collections.Generic;
   6:  using System.Runtime.InteropServices;
   7:   
   8:  namespace CircleStandard {
   9:   
  10:      public partial class CircleStandard : IAcStPlugin2 {
  11:          /// <summary>
  12:          /// Кэширование "стандартных" свойств (Цвет, Радиус) окружности  
  13:          /// полученных из .DWS файлов, а так же указание уровня исправлений
  14:          /// (поле AcStFix)
  15:          /// </summary>
  16:          private class CircleCache {
  17:              /// <summary>
  18:              /// Радиус окружности
  19:              /// </summary>
  20:              public double radius;
  21:              /// <summary>
  22:              /// Цвет окружности
  23:              /// </summary>
  24:              public ACAD_COLOR color;
  25:              /// <summary>
  26:              /// Имя файла стандарта (dws-файл)
  27:              /// </summary>
  28:              public string standardFileName;
  29:              /// <summary>
  30:              /// Описывает уровень исправлений
  31:              /// </summary>
  32:              public AcStFix pFix;
  33:          }
  34:      }
  35:  }

Код C# (файл _ContextList.cs)

   1:  using AcStMgr;
   2:  using Autodesk.AutoCAD.Interop.Common;
   3:  using MSXML2;
   4:  using System;
   5:  using System.Collections.Generic;
   6:  using System.Runtime.InteropServices;
   7:   
   8:  namespace CircleStandard {
   9:   
  10:      public partial class CircleStandard : IAcStPlugin2 {
  11:          /// <summary>
  12:          /// Класс, с помощью которого происходит управление тем, 
  13:          /// какие именно объекты необходимо проверить: все имеющиеся в базе данных, 
  14:          /// или же только недавно добавленные/изменённые
  15:          /// </summary>
  16:          private class ContextList {
  17:              //Список объектов, используемых вне контекста базы данных
  18:              List<long> m_altIdArray = new List<long>();
  19:   
  20:              //Список объектов, используемых в контексте базы данных
  21:              List<long> m_dbIdArray = new List<long>();
  22:   
  23:              // Поле, указывающая на то, следует ли производить 
  24:              //проверку всей базы данных
  25:              private bool m_useDb;
  26:   
  27:              /// <summary>
  28:              /// Получить индекс элемента из нужного контекстного списка
  29:              /// </summary>
  30:              /// <param name="index">индекс элемента</param>
  31:              /// <returns>Возвращается идентификатор объекта</returns>
  32:              public long this[int index] {
  33:                  get {
  34:                      if (m_useDb)
  35:                          return m_dbIdArray[index];
  36:                      else
  37:                          return m_altIdArray[index];
  38:                  }
  39:              }
  40:   
  41:              /// <summary>
  42:              /// Количество элементов в текущем списке
  43:              /// </summary>
  44:              public int Count {
  45:                  get {
  46:                      if (m_useDb)
  47:                          return m_dbIdArray.Count;
  48:                      else
  49:                          return m_altIdArray.Count;
  50:                  }
  51:              }
  52:   
  53:              /// <summary>
  54:              /// Добавить содержимое в контекст
  55:              /// </summary>
  56:              /// <param name="useDb">Флаг, определяющий из какого контекста следует вернуть элемент 
  57:              /// Выбрать для проверки всё содержимое базы данных или же только 
  58:              /// измененные элементы  (а также добавлять новые идентификаторы в базу данных массива)
  59:              /// </param>
  60:              /// <param name="objContextArray">массив добавляемых элементов</param>
  61:              public void SetContext(bool useDb, object objContextArray) {
  62:                  m_useDb = useDb;
  63:                  if (!m_useDb && objContextArray != null) {
  64:                      int[] idArray = (int[]) objContextArray;
  65:                      for (int i = 0; i < idArray.Length; i++) {
  66:                          long val = (long) idArray[i];
  67:                          m_altIdArray.Add(val);
  68:   
  69:                          //Следует сохранять список базы данных актуальным
  70:                          m_dbIdArray.Add(val);
  71:                      }
  72:                  }
  73:                  else {
  74:                      // Очистка
  75:                      m_altIdArray.Clear();
  76:                  }
  77:              }
  78:              /// <summary>
  79:              /// Добавить идентификатор в контекст
  80:              /// </summary>
  81:              /// <param name="id">добавляемый идентификатор</param>
  82:              /// <param name="useDb">в какой именно контекст следует добавить идентификатор: 
  83:              /// true - в контекст проверяемой базы данных, false - в контекст вне базы данных</param>
  84:              public void Add(long id, bool useDb) {
  85:                  if (useDb)
  86:                      m_dbIdArray.Add(id);
  87:                  else
  88:                      m_altIdArray.Add(id);
  89:              }
  90:   
  91:              /// <summary>
  92:              /// Очистить оба контекста
  93:              /// </summary>
  94:              public void Clear() {
  95:                  m_altIdArray.Clear();
  96:                  m_dbIdArray.Clear();
  97:              }
  98:          }
  99:      }
 100:  }

Код C# (файл CircleStandard.cs)

   1:  //   Пример разработки модуля нормоконтроля для AutoCAD
   2:  //
   3:  //   Этот пример добавляет пользовательский плагин в CAD Standards
   4:  // Drawing Checker.
   5:  //
   6:  //   Пример плагина, проверяющего цвета окружностей в текущем рисунке на соответствие 
   7:  // любым цветам окружностей, определённых в указанных стандартах (DWS-файлах). 
   8:  // Все цвета окружностей, определённых в dws-файле, рассматриваются как 
   9:  // кандидаты на исправление проверяемой окружности (чтобы быть взятыми за эталон). 
  10:  // Рекомендуемым к применению цветом будет цвет такой окружности из dws-файла, радиус 
  11:  // которой окажется наиболее близок к радиусу к проверяемой окружности (по значению).
  12:   
  13:  using AcStMgr;
  14:  using Autodesk.AutoCAD.Interop.Common;
  15:  using MSXML2;
  16:  using System;
  17:  using System.Collections.Generic;
  18:  using System.Runtime.InteropServices;
  19:   
  20:  namespace CircleStandard {
  21:   
  22:      [ProgId("CircleStandard.CircleStandard")]
  23:      public partial class CircleStandard : IAcStPlugin2 {
  24:          //ОБЪЯВЛЯЕМ ПЕРЕМЕННЫЕ:
  25:          private ContextList m_contexts = new ContextList();//контексты, с которыми предстоит работать
  26:          private AcStManager m_mgr;//менеджер управления стандартами
  27:          private CircleStandard m_plugin;//Объект, с помощью которого выполняется проверка
  28:          private AcadDatabase m_checkDb;//проверяемая база данных
  29:          private AcadDatabase m_dwsDb;//база данных dws-файла, взятого за эталон
  30:          private AcStError m_err;//сообщение об ошибке
  31:          private object m_fixArray;//исправленный массив
  32:          private CircleCache[] m_cirCacheArray;//Кэшированный массив "эталонных" окружностей (из dws-файлов)
  33:          
  34:          private int m_recFixIndex;
  35:          private int m_curIndex;//Индекс, использующийся для извлечения объектов из массива m_contexts
  36:          private int m_fixCnt;
  37:          private string m_propName;
  38:   
  39:          /// <summary>
  40:          /// Инициализация плагина. Это метод интерфейса, которому AutoCAD в качестве параметра
  41:          /// передаёт созданный им же экземпляр класса, реализующего интерфейс IAcStManager.
  42:          /// Посредством этого объекта и следует работать с базами данных.
  43:          /// </summary>
  44:          /// <param name="mgr">Менеджер управления стандартами</param>
  45:          public void Initialize(AcStManager mgr) {
  46:              // Сохраняем указатель на объект Manager
  47:              m_mgr = mgr;
  48:              m_plugin = this;
  49:          }
  50:   
  51:          /// <summary>
  52:          /// Плагин заполняет предоставленный массив именами классов, 
  53:          /// объекты которых он может проверять
  54:          /// </summary>
  55:          /// <returns>Возвращает строковый массив, содержащий имена классов, объекты которых
  56:          /// подлежат обработке плагином</returns>
  57:          public object GetObjectFilter() {
  58:              // В данном случае нас интересуют только окружности
  59:              return new string[] { "AcDbCircle" };
  60:          }
  61:   
  62:          /// <summary>
  63:          /// Назначение контекста плагину для проверки чертежа...
  64:          /// Этот метод определяет контекст, в котором плагин будет работать, 
  65:          /// (в частности - проверяемый чертёж) и DWS файлы, которые должны быть 
  66:          /// использованы для проверки чертежа.
  67:          /// Здесь мы кэшируем наши DWS-определения стандартов и в DWG формируем 
  68:          /// первоначальный кэш окружностей, который должен быть проверен.
  69:          /// 
  70:          /// ПРИМЕЧАНИЕ: 
  71:          /// НЕТ гарантии, что объекты AcadDatabase, содержащиеся в objDbArray 
  72:          /// будут действительными (валидными) после этого вызова.
  73:          /// 
  74:          /// ОБЪЕКТЫ AcadDatabase НЕ ДОЛЖНЫ КЭШИРОВАТЬСЯ!!!
  75:          /// </summary>
  76:          /// <param name="db">База данных проверяемого чертежа</param>
  77:          /// <param name="pathName">Полное имя проверяемого чертежа</param>
  78:          /// <param name="objNameArray">Массив строк, каждая из которых содержит имя DWS-файла, 
  79:          /// используемого при проверке чертежа</param>
  80:          /// <param name="objPathArray">Массив строк, каждая из которых содержит полный путь к DWS-файлу, 
  81:          /// используемому при проверке чертежа</param>
  82:          /// <param name="objDbArray">Массив объектов, реализующих интерфейс IAcadDatabase. Это массив баз 
  83:          /// данных DWS-файлов. Эти элементы можно приводить к типу AcadDatabase.
  84:          /// Каждый из элементов массива представляет собой базу данных DWS-файла и будет использоваться 
  85:          /// при проверке чертежа</param>
  86:          public void SetupForAudit(AcadDatabase db, string pathName, object objNameArray,
  87:            object objPathArray, object objDbArray) {
  88:              if (db != null) {
  89:                  // Сохраняем ссылку на проверяемую базу данных
  90:                  m_checkDb = db;
  91:                  // TODO: эта проверка лишняя, т.к. выше на null уже была проверка
  92:                  if (m_checkDb != null) {
  93:                      // Кэшируем список всех окружностей, имеющихся в проверяемом чертеже
  94:                      foreach (AcadObject obj in m_mgr.get_ModelSpaceProxy(m_checkDb)) {
  95:                          if (obj.ObjectName == "AcDbCircle") {
  96:                              m_contexts.Add(obj.ObjectID, true);
  97:                          }
  98:                      }
  99:                  }
 100:   
 101:                  object[] dbArray = (object[]) objDbArray;
 102:                  string[] nameArray = (string[]) objNameArray;
 103:                  string[] pathArray = (string[]) objPathArray;
 104:   
 105:                  int i = 0;
 106:   
 107:                  // Выполняем итерацию по всем подключенным DWS-файлам и кэшируем
 108:                  //интересующие нас свойства (цвет и радиус) стандартных окружностей
 109:                  for (int iDWS = 0; iDWS < dbArray.Length; iDWS++) {
 110:                      // Получаем базу данных DWS-файла
 111:                      m_dwsDb = (AcadDatabase) dbArray[iDWS];
 112:   
 113:                      // TODO: А если в пространстве Model DWS-файла будут не только окружности или будет др примитив? 
 114:                      //В этом случае мы однозначно получим ошибку! Следует сначала проверять, есть ли в 
 115:                      //модели примитивы вообще, а если есть, то являются ли они окружностями.
 116:                      foreach (AcadCircle stdCircle in m_mgr.get_ModelSpaceProxy(m_dwsDb)) {
 117:   
 118:                          // CircleCache является служебным объектом для хранения 
 119:                          //интересующих нас свойств
 120:                          CircleCache cirCache = new CircleCache();
 121:   
 122:                          //Кэшируем свойства (цвет и радиус) всех окружностей, имеющихся в
 123:                          // DWS-файле
 124:                          cirCache.color = stdCircle.color;
 125:                          cirCache.radius = stdCircle.Radius;
 126:                          cirCache.standardFileName = nameArray[iDWS];
 127:   
 128:                          //Переменная fix содержит в себе информацию об исправлении, которая позднее будет
 129:                          //обратно передана менеджеру
 130:                          AcStFix fix = new AcStFix();
 131:                          fix.Description = "Исправить цвет";
 132:                          fix.StandardFileName = cirCache.standardFileName;
 133:                          fix.FixObjectName = "Цвет: " + StripAcPrefix(stdCircle.color.ToString());
 134:   
 135:                          // TODO: не понял, для чего проверяется количество свойств?
 136:                          if (fix.PropertyCount == 0)
 137:                              fix.PropertyValuePut("Color", stdCircle.color);
 138:   
 139:                          cirCache.pFix = fix;
 140:                          Array.Resize<CircleCache>(ref m_cirCacheArray, i+1);
 141:                          m_cirCacheArray[i++] = cirCache;
 142:                      }
 143:                  }
 144:              }
 145:          }
 146:   
 147:          /// <summary>
 148:          /// Установить объекты для изучения при итерации по ошибкам. Если параметру useDb задано "true" 
 149:          /// (по умолчанию) или же параметр objIdArray равен null, тогда мы используем базу данных 
 150:          /// (получаем все идентификаторы ObjectId для проверяемого чертежа). В противном случае, мы 
 151:          /// устанавливаем предоставленный список objIdArrays как наш список.
 152:          /// </summary>
 153:          /// <param name="objIdArray">массив идентификаторов примитивов, подлежащих проверке</param>
 154:          /// <param name="useDb">использовать базу данных проверяемого чертежа: true - да, false - нет.</param>
 155:          public void SetContext(object objIdArray, bool useDb) {
 156:              m_contexts.SetContext(useDb, objIdArray);
 157:          }
 158:   
 159:          /// <summary>
 160:          /// Инициализация механизма итерации по ошибкам. Если pStartError указывает на объект ошибки, то 
 161:          /// мы должны начать проверку именно с этой ошибки, а не с самого начала. 
 162:          /// В основном мы только переходим по элементу Next в этом пункте...
 163:          /// </summary>
 164:          /// <param name="err">Объект ошибки, которую нужно исправить. Если параметру назначен null - это 
 165:          /// означает, что проверку следует выполнить с самого начала.</param>
 166:          public void Start(AcStError err) {
 167:              if (err != null) {
 168:                  long badId = err.BadObjectId;
 169:   
 170:                  // Найти индекс для BadObjectId в m_objIDArray
 171:                  for (m_curIndex = 0; m_curIndex < m_contexts.Count; m_curIndex++) {
 172:                      if (m_contexts[m_curIndex] == badId) {
 173:                          m_curIndex = (m_curIndex - 1);
 174:                          Next();
 175:                      }
 176:                  }
 177:              }
 178:              else {
 179:                  // Объект AcStError не был передан - значит запустить проверку с самого начала.
 180:                  m_curIndex = -1;
 181:                  Next();
 182:              }
 183:          }
 184:   
 185:          /// <summary>
 186:          /// Найти следующую ошибку в текущем контексте
 187:          /// </summary>
 188:          public void Next() {
 189:              m_err = null;
 190:   
 191:              // Если проверяемый чертёж содержит в себе окружности (объекты AcDbCircle):
 192:              if (m_contexts.Count > 0) {
 193:                  AcadCircle circle;
 194:                  bool foundErr;
 195:                  // TODO: Если открыть новый чертёж, к которому подключены dws-файлы, но нет нужных нам, то при попытке
 196:                  //вычертить в этом документе окружность мы получим исключение в следующей строке кода, т.к. в этот момент 
 197:                  //переменная m_cirCacheArray равна null. Соответственно сначала должна присутствовать проверка на null.
 198:                  if (m_cirCacheArray.Length > 0) {
 199:                      // Если мы не достигли конца списка - тогда увеличиваем текущий индекс 
 200:                      //анализируемого элемента в списке
 201:                      if (m_curIndex < m_contexts.Count - 1) {
 202:                          m_curIndex++;
 203:                          foundErr = false;
 204:                          while (m_curIndex < m_contexts.Count) {
 205:                              //Не выполняйте итерации вне границ списка объекта используя полученный от него ObjectId
 206:                              try {
 207:                                  circle = (AcadCircle) m_checkDb.ObjectIdToObject((int) m_contexts[m_curIndex]);
 208:   
 209:                                  //Пытаемся найти окружность с тем же самым цветом из кэшированного набора стандартных
 210:                                  //окружностей полученных из DWS-файлов.
 211:                                  for (int iCache = 0; iCache < m_cirCacheArray.Length; iCache++) {
 212:                                      if (circle.color.CompareTo(m_cirCacheArray[iCache].color) != 0)
 213:                                          //Если цвет не соответствует - мы нашли потенциальную ошибку
 214:                                          foundErr = true;
 215:                                      else {
 216:                                          // Если цвет соответствует любому стандарту - мы можем прекратить проверку
 217:                                          foundErr = false;
 218:                                          break;
 219:                                      }
 220:                                  }
 221:                                  // Проверка различий по цвету
 222:                                  if (foundErr) {
 223:                                      //Поскольку мы обнаружили ошибку - создаём локальный объект ошибки 
 224:                                      //(экземпляр AcStError)
 225:                                      AcStError err = new AcStError();
 226:                                      err.Description = "Цвет не является стандартным";
 227:                                      err.BadObjectId = circle.ObjectID;
 228:                                      err.BadObjectName = StripAcPrefix(circle.color.ToString());
 229:                                      err.Plugin = m_plugin;
 230:                                      err.ErrorTypeName = "Color ";
 231:                                      err.ResultStatus = AcStResultStatus.acStResFlagsNone;
 232:   
 233:                                      // TODO: не понял, для чего проверяется количество свойств?
 234:                                      if (err.PropertyCount == 0)
 235:                                          err.PropertyValuePut("Color", circle.color);
 236:                                      m_err = err;
 237:                                      foundErr = false;
 238:                                      break;
 239:                                  }
 240:                              }
 241:                              catch {
 242:                              }
 243:                              m_curIndex = (m_curIndex + 1);
 244:                          }
 245:                      }
 246:                  }
 247:              }
 248:          }
 249:   
 250:          /// <summary>
 251:          /// Проверить, все ли ошибки исправлены.
 252:          /// </summary>
 253:          /// <returns>Если возвращается true, значит все ошибки исправлены, а если false - не все.</returns>
 254:          public bool Done() {
 255:              return (m_err == null);
 256:          }
 257:   
 258:          /// <summary>
 259:          /// Получить текущую ошибку
 260:          /// </summary>
 261:          /// <returns>Возвращается текущая ошибка</returns>
 262:          public AcStError GetError() {
 263:              return m_err;
 264:          }
 265:   
 266:          /// <summary>
 267:          /// Получить все исправления. Возвращает массив объектов IAcStFix для заданной ошибки 
 268:          /// (ПРИМЕЧАНИЕ: вызывающий ответственен за освобождение объектов в этом массиве).
 269:          /// </summary>
 270:          /// <param name="err">Ошибка, для которой следует найти все возможные исправления</param>
 271:          /// <param name="fixArray">Через этот параметр из метода должен быть возвращён массив 
 272:          /// возможных исправлений</param>
 273:          /// <param name="recommendedFixIndex">индекс рекомендуемого исправления</param>
 274:          public void GetAllFixes(AcStError err, ref object fixArray, ref int recommendedFixIndex) {
 275:              if (err != null) {
 276:                  IAcStFix[] arr = new IAcStFix[m_cirCacheArray.Length];
 277:                  ACAD_COLOR vErrorVal;
 278:                  recommendedFixIndex = -1;
 279:                  m_fixCnt = 0;
 280:   
 281:                  // Если у нас есть кэш исправлений -используем его
 282:                  if (m_cirCacheArray.Length > 0) {
 283:                      for (int i = 0; i < m_cirCacheArray.Length; i++) {
 284:                          vErrorVal = (ACAD_COLOR) err.PropertyValueGet("Color");
 285:                          if (vErrorVal.CompareTo(m_cirCacheArray[i].color) != 0)
 286:                              //Если свойство цвета (color) исправленной ошибки совпадает с цветом 
 287:                              //одной из окружностей DWS-файла, то такую ошибку добавляем в список 
 288:                              //исправленных.
 289:                              arr[i] = m_cirCacheArray[i].pFix;
 290:                      }
 291:                      fixArray = arr;
 292:                      m_fixArray = fixArray;
 293:   
 294:                      //Найти индекс рекомендуемого исправления (recommendedFixIndex). Мы вызываем эту 
 295:                      //функцию, чтобы получить индекс (нам здесь не нужен возвращаемый объект исправления).
 296:                      GetRecommendedFix(err);
 297:                      recommendedFixIndex = m_recFixIndex;
 298:                  }
 299:   
 300:                  // удалось ли нам найти рекомендуемое исправление?
 301:   
 302:                  if (recommendedFixIndex == -1)
 303:                      //Не найдено рекомендуемых исправлений - устанавливаем надлежащий флаг в объекте ошибки
 304:                      err.ResultStatus = AcStResultStatus.acStResNoRecommendedFix;
 305:              }
 306:          }
 307:   
 308:          /// <summary>
 309:          /// Получить объект рекомендуемого исправления
 310:          /// </summary>
 311:          /// <param name="err">Объект ошибки, для которого должно быть получено рекомендуемое исправление</param>
 312:          /// <returns>Возвращается объект рекомендуемого исправления</returns>
 313:          public AcStFix GetRecommendedFix(AcStError err) {
 314:              AcStFix recFix = new AcStFix();
 315:   
 316:              if (m_cirCacheArray.Length == 0)
 317:                  err.ResultStatus = AcStResultStatus.acStResNoRecommendedFix;
 318:              else {
 319:                  // Получить objectId для этой ошибки
 320:                  long tmpObjID = err.BadObjectId;
 321:   
 322:                  // Найти объект исправления из DWG
 323:                  AcadCircle tmpCircle = (AcadCircle) m_checkDb.ObjectIdToObject((int) tmpObjID);
 324:                  double radiusToBeChecked = tmpCircle.Radius;
 325:   
 326:                  CircleCache cirCache = m_cirCacheArray[0];
 327:                  double diff = Math.Abs(radiusToBeChecked - cirCache.radius);
 328:                  m_recFixIndex = 0;
 329:   
 330:                  //Попытка получить исправление цвета из кэшируемого m_CircleCacheArray
 331:                  //
 332:                  //ПРАВИЛО: цвет стандартной окружности с радиусом, самым близким тому, цвет которого
 333:                  //сейчас подлежит исправлению
 334:                  for (int i = 0; i < m_cirCacheArray.Length; i++) {
 335:                      if (diff > Math.Abs(radiusToBeChecked - m_cirCacheArray[i].radius)) {
 336:                          cirCache = m_cirCacheArray[i];
 337:                          diff = Math.Abs(radiusToBeChecked - cirCache.radius);
 338:                          m_recFixIndex = i;
 339:                      }
 340:                  }
 341:   
 342:                  // Заполняем свойства объекта рекомендуемых изменений
 343:                  recFix.Description = "Color fix";
 344:                  recFix.StandardFileName = m_cirCacheArray[m_recFixIndex].standardFileName;
 345:                  recFix.FixObjectName = "Color";
 346:   
 347:                  // TODO: не понял, для чего проверяется количество свойств?
 348:                  if (recFix.PropertyCount == 0)
 349:                      recFix.PropertyValuePut("Color", m_cirCacheArray[m_recFixIndex].color);
 350:              }
 351:              return recFix;
 352:          }
 353:   
 354:          /// <summary>
 355:          /// Получить различия свойств. Заполняет предоставленные массивы с именами свойств, которые 
 356:          /// присутствуют в предоставленных объектах ошибок и исправлений. Этот метод используется для 
 357:          /// выполнения исправлений в диалоговом окне, в котором отображаются: "имя свойства, 
 358:          /// текущее значение, исправляемое значение".
 359:          /// </summary>
 360:          /// <param name="err">Анализируемая ошибка</param>
 361:          /// <param name="fix">Исправление</param>
 362:          /// <param name="objPropNames">Массив имён свойств анализируемой ошибки</param>
 363:          /// <param name="objErrorValues">Массив значений свойств анализируемой ошибки</param>
 364:          /// <param name="objFixValues">Массив исправленных значений, которыми можно 
 365:          /// исправить ошибку</param>
 366:          /// <param name="objFixableStatuses">Массив статусов исправлений</param>
 367:          public void GetPropertyDiffs(AcStError err, AcStFix fix, ref object objPropNames,
 368:            ref object objErrorValues, ref object objFixValues, ref object objFixableStatuses) {
 369:              if (err != null) {
 370:                  string[] propNames = new string[0];
 371:                  string propName = "";
 372:                  string[] errorValues = new string[0];
 373:                  object objErrorVal = new object();
 374:                  string[] fixValues = new string[0];
 375:                  object objFixVal = new object();
 376:                  bool[] fixableStatuses = new bool[0];
 377:   
 378:                  // Выполняем итерацию по свойствам объекта ошибки
 379:                  for (int i = 0; i < err.PropertyCount; i++) {
 380:                      err.PropertyGetAt(i, ref propName, ref objErrorVal);
 381:                      m_propName = propName;
 382:   
 383:                      // Получить соответствующее значение свойства объекта исправлений
 384:                      try {
 385:                          fix.PropertyValueGet(propName, ref objFixVal);
 386:   
 387:                          ACAD_COLOR errVal = (ACAD_COLOR) objErrorVal;
 388:                          ACAD_COLOR fixVal = (ACAD_COLOR) objFixVal;
 389:   
 390:                          // Проверяем, имеет ли объект исправления имеет такое же значение 
 391:                          //свойства, что и у объекта ошибки
 392:   
 393:                          if (errVal.CompareTo(fixVal) != 0) {
 394:                              //Сохраняем свойства ошибки и исправления в массив, готовый для возвращения
 395:                              //его обратно вызывающей стороне
 396:                              Array.Resize<string>(ref propNames, i+1);
 397:                              propNames[i] = propName;
 398:                              Array.Resize<string>(ref errorValues, i+1);
 399:                              errorValues[i] = StripAcPrefix(errVal.ToString());
 400:                              Array.Resize<string>(ref fixValues, i+1);
 401:                              fixValues[i] = StripAcPrefix(fixVal.ToString());
 402:                              Array.Resize<bool>(ref fixableStatuses, i+1);
 403:                              fixableStatuses[i] = true;
 404:                          }
 405:                      }
 406:                      catch {
 407:                      }
 408:                  }
 409:   
 410:                  // Инициализация массивов поставляемых вызывающей стороне
 411:                  objPropNames = propNames;
 412:                  objErrorValues = errorValues;
 413:                  objFixValues = fixValues;
 414:                  objFixableStatuses = fixableStatuses;
 415:                  m_fixCnt++;
 416:              }
 417:          }
 418:   
 419:          /// <summary>
 420:          /// Внутренний вспомогательный метод, с помощью которого наименование цвета делаем 
 421:          /// более "симпотичным"
 422:          /// </summary>
 423:          /// <param name="p">наименование цвета</param>
 424:          /// <returns>возвращается наименование цвета, из которого был изъят префикс "ac" 
 425:          /// (в случае его изначального наличия)</returns>
 426:          private string StripAcPrefix(string p) {
 427:              return p.StartsWith("ac") ? p.Substring(2) : p;
 428:          }
 429:   
 430:          /// <summary>
 431:          /// На основаниии объектов ошибки и исправления, полученных в качестве параметров, выполнить
 432:          /// попытку исправления ошибки
 433:          /// </summary>
 434:          /// <param name="err">Объект ошибки</param>
 435:          /// <param name="fix">Объект исправления</param>
 436:          /// <param name="failureReason">Строковое сообщение о причинах неудачи исправления 
 437:          /// (если исправить не удалось)</param>
 438:          public void FixError(AcStError err, AcStFix fix, out string failureReason) {
 439:              failureReason = "";
 440:              if (err != null) {
 441:                  long badObjID = err.BadObjectId;
 442:   
 443:                  // Получить объект из DWG для исправления
 444:                  AcadCircle badObj = (AcadCircle) m_checkDb.ObjectIdToObject((int) badObjID);
 445:                  if (fix == null) {
 446:                      // Если объект исправления является нулевым, тогда попытаться получить 
 447:                      // рекомендованное исправление
 448:                      AcStFix tmpFix = GetRecommendedFix(err);
 449:   
 450:                      if (tmpFix == null)
 451:                          // Устанавливаем результирующий статус ошибки безуспешным и
 452:                          // указываем, что нет рекомендуемых изменений
 453:                          err.ResultStatus = AcStResultStatus.acStResNoRecommendedFix;
 454:                      else
 455:                          fix = tmpFix;
 456:                  }
 457:   
 458:                  if (fix != null) {
 459:                      // Исправляем "плохую" окружность
 460:                      object sFixVal = new object();
 461:                      fix.PropertyValueGet(m_propName, ref sFixVal);
 462:                      ACAD_COLOR fixVal = (ACAD_COLOR) sFixVal;
 463:                      try {
 464:                          badObj.color = fixVal;
 465:                          err.ResultStatus = AcStResultStatus.acStResFixed;
 466:                      }
 467:                      catch {
 468:                          err.ResultStatus = AcStResultStatus.acStResFixFailed;
 469:                      }
 470:                  }
 471:              }
 472:          }
 473:   
 474:          /// <summary>
 475:          /// Очистить состояние плагина и освобождает все кэшированные объекты. Этот метод
 476:          /// вызывается перед тем, как плагин будет выпущен. Используйте этот метод, 
 477:          /// чтобы убирать за собой. Для всех имеющится (не равных null) объектов ошибок 
 478:          /// (AcStError) и исправлений (AcStFix) следует вызвать метод Reset.
 479:          /// </summary>
 480:          public void Clear() {
 481:              m_plugin = null;
 482:              m_curIndex = -1;
 483:              m_recFixIndex = -1;
 484:              m_fixCnt = 0;
 485:              m_propName = "";
 486:              m_mgr = null;
 487:              m_dwsDb = null;
 488:              m_checkDb = null;
 489:   
 490:              if (m_err != null) {
 491:                  m_err.Reset();
 492:                  m_err = null;
 493:              }
 494:              if (m_cirCacheArray != null) {
 495:                  for (int i = 0; i < m_cirCacheArray.Length; i++) {
 496:                      if (m_cirCacheArray[i].pFix != null) {
 497:                          m_cirCacheArray[i].pFix.Reset();
 498:                          m_cirCacheArray[i].pFix = null;
 499:                      }
 500:                  }
 501:              }
 502:              m_contexts.Clear();
 503:          }
 504:   
 505:          /// <summary>
 506:          /// Проверка системной переменной
 507:          /// </summary>
 508:          /// <param name="sysvarName">Имя переменной</param>
 509:          /// <param name="getAllFixes">true для того, чтобы получить все исправления в системную переменную; 
 510:          /// в противном случае - false</param>
 511:          /// <param name="passFail">true если системная переменная передаёт проверку; 
 512:          /// false в случае неудачи</param>
 513:          public void CheckSysvar(string sysvarName, bool getAllFixes, ref bool passFail) {
 514:          }
 515:          // Returns whether the plugin uses information
 516:          // from the database for checking
 517:   
 518:          /// <summary>
 519:          /// Метод, с помощью которого AutoCAD будет узнавать, стоит ли использовать информацию из DWS-файла
 520:          /// для проверки. 
 521:          /// Если DWS-файл содержит окружности, то переменной stampIt будет присвоено значение true, 
 522:          /// в противном же случае, ей будет назначено false.
 523:          /// </summary>
 524:          /// <param name="db">База данных DWS-файла, в которой сделует произвести поиск "эталонных" 
 525:          /// окружностей</param>
 526:          /// <param name="stampIt">Этому параметру должно присваиваться значение true, если DWS-файл содержит 
 527:          /// в себе окружности, или же false в противном случае</param>
 528:          public void StampDatabase(AcadDatabase db, ref bool stampIt) {
 529:              stampIt = false;
 530:              foreach (AcadObject obj in m_mgr.get_ModelSpaceProxy(db)) {
 531:                  if (obj.ObjectName == "AcDbCircle") {
 532:                      stampIt = true;
 533:                      break;
 534:                  }
 535:              }
 536:          }
 537:   
 538:          /// <summary>
 539:          /// Обновление статуса результата обозначенной ошибки
 540:          /// </summary>
 541:          /// <param name="err">Ошибка, статус которой следует обновить</param>
 542:          public void UpdateStatus(AcStError err) {
 543:          }
 544:   
 545:          /// <summary>
 546:          /// Принимает узел AcStPluginInfoSection и создает новый узел AcStPluginInfo под ним.
 547:          /// ПРИМЕЧАНИЕ: 
 548:          /// Этот метод используется в Batch Standards Checker (Пакетная Проверка Стандартов) 
 549:          /// для получения информации о плагине.
 550:          /// </summary>
 551:          /// <param name="objSectionNode">xml-объект, который следует наполнить информацией о 
 552:          /// данном модуле нормоконтроля</param>
 553:          public void WritePluginInfo(object objSectionNode) {
 554:              IXMLDOMNode section = (IXMLDOMNode) objSectionNode;
 555:              IXMLDOMElement xml = section.ownerDocument.createElement("AcStPluginInfo");
 556:              IXMLDOMElement info = (IXMLDOMElement) section.appendChild(xml);
 557:   
 558:              info.setAttribute("PluginName", Name);
 559:              info.setAttribute("Version", Version);
 560:              info.setAttribute("Description", Description);
 561:              info.setAttribute("Author", Author);
 562:              info.setAttribute("HRef", HRef);
 563:              info.setAttribute("DWSName", "");
 564:              info.setAttribute("Status", "1");
 565:          }
 566:   
 567:          /// <summary>
 568:          /// Автор плагина
 569:          /// </summary>
 570:          public string Author {
 571:              get { return "Kean Walmsley, Autodesk, Inc. (Бушман А.А. внёс некоторые изменения в код)"; }
 572:          }
 573:   
 574:          /// <summary>
 575:          /// Описание плагина
 576:          /// </summary>
 577:          public string Description {
 578:              get {
 579:                  return "Проверяет, что окружности на чертеже имеют цвет, соответствующий " +
 580:                      "окружности аналогичного (или ближайшего по значению) радиуса из " +
 581:                      "указанного к использованию файла стандартов.";
 582:              }
 583:          }
 584:   
 585:          /// <summary>
 586:          /// Сайт разработчика плагина
 587:          /// </summary>
 588:          public string HRef {
 589:              get {
 590:                  return
 591:                    "http://blogs.autodesk.com/through-the-interface";
 592:              }
 593:          }
 594:   
 595:          /// <summary>
 596:          /// Иконка (HICON)
 597:          /// </summary>
 598:          public int Icon {
 599:              get { return 1; }
 600:          }
 601:   
 602:          /// <summary>
 603:          /// Наименование плагина
 604:          /// </summary>
 605:          public string Name {
 606:              get { return "Проверка цвета круга"; }
 607:          }
 608:   
 609:          /// <summary>
 610:          /// Версия плагина
 611:          /// </summary>
 612:          public string Version {
 613:              get { return "2.0"; }
 614:          }
 615:      }
 616:  }

Напишем файл CircleStd.reg с таким содержимым:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Autodesk\Drawing Check\Plugins2\CircleStandard.CircleStandard]

@="AcStMgr"


Исходный код проекта в формате MS VS 2010 для AutoCAD 2009 x86 можно скачать отсюда.


Comments