ADN Open CIS
Сообщество программистов Autodesk в СНГ

13/02/2015

Создание DWF-подложки в AutoCAD лиспом

Понадобилось мне тут вставить несколько dwf (или dwfx, не столь суть важно) в текущий документ dwg. Естественно, что захотелось мне задачку решить лиспом, да без применения команд типа _.dwfattach. Вот о своих мытарствах и рассказываю чуть ниже :)

Несложные ковыряния в dwg-файле выявили, что записи о DWF(x) описаниях хранятся в словаре ACAD_DWFDEFINITIONS. Ок, проверить наличие словаря и при необходимости создать его - не вопрос. А вот дальше началось уже не очень понятное и очевидное.

Во-первых, мне никак не удавалось корректно создать запись в этом словаре. Естественно, что dwf существует, естественно, его ACAD находит. Но и все! Решения, которые мне удалось найти, рано или поздно сводятся к коду, показанному на ADN DevBlog. Код приводить не буду, покажу лучше, как я над ним измывался :)

Во-первых, я из команды сделал LISP-функцию, и, во-вторых, убрал вставку DWF в пространство модели. Ну захотелось мне такое провернуть ;)

Код - C#: [Выделить]
  1. [LispFunction("kpblc_dwfinsert")]
  2.         public static object lisp_dwfinsert(ResultBuffer Args)
  3.         {
  4.             ObjectId res = ObjectId.Null;
  5.             Array arguments = Args.AsArray();
  6.             string sFileName = Convert.ToString(((TypedValue)(arguments.GetValue(0))).Value);
  7.  
  8.             if (System.IO.File.Exists(sFileName))
  9.             {
  10.  
  11.                 Document doc = Application.DocumentManager.MdiActiveDocument;
  12.                 Database db = doc.Database;
  13.                 Editor ed = doc.Editor;
  14.  
  15.                 using (Transaction tr = db.TransactionManager.StartTransaction())
  16.                 {
  17.                     //first check for the required Layout
  18.                     UnderlayFile layouts = UnderlayHost.DwfHost.GetFile(sFileName, "");
  19.                     UnderlayItem selectedItem = null;
  20.  
  21.                     Regex rxEng = new Regex("model", RegexOptions.IgnoreCase);
  22.                     Regex rxRus = new Regex("модел", RegexOptions.IgnoreCase);
  23.  
  24.                     if (layouts.Items.Count > 1)
  25.                     {
  26.                         foreach (UnderlayItem item in layouts.Items)
  27.                         {
  28.                             Match strMatchEng = rxEng.Match(item.Name);
  29.                             Match strMatchRus = rxRus.Match(item.Name);
  30.                             if ((String.Compare(item.Name.ToUpper(), "MODEL", false) == 0) || strMatchEng.Success || strMatchRus.Success)
  31.                             { selectedItem = item; break; }
  32.                         }
  33.                     }
  34.                     else
  35.                     { selectedItem = layouts.Items[0]; }
  36.  
  37.                     if (selectedItem == null)
  38.                     { ed.WriteMessage("error, No page with name Model"); return null; }
  39.  
  40.                     DBDictionary nod = (DBDictionary)tr.GetObject(db.NamedObjectsDictionaryId, OpenMode.ForRead);
  41.                     string defDictKey = UnderlayDefinition.GetDictionaryKey(typeof(DwfDefinition));
  42.                     ObjectId defId = ObjectId.Null;
  43.                     if (!nod.Contains(defDictKey))
  44.                     {
  45.                         using (DBDictionary dict = new DBDictionary())
  46.                         {
  47.                             nod.UpgradeOpen();
  48.                             defId = nod.SetAt(defDictKey, dict);
  49.                             tr.AddNewlyCreatedDBObject(dict, true);
  50.                         }
  51.                     }
  52.                     else
  53.                     { defId = nod.GetAt(defDictKey); }
  54.  
  55.                     ObjectId idDef = ObjectId.Null;
  56.                     DBDictionary dwfDict = (DBDictionary)tr.GetObject(defId, OpenMode.ForRead);
  57.  
  58.                     string sDwfName = Path.GetFileNameWithoutExtension(sFileName);
  59.  
  60.                     if (dwfDict.Contains(sDwfName))
  61.                     { idDef = dwfDict.GetAt(sDwfName); }
  62.                     else
  63.                     {
  64.                         using (DwfDefinition dwfDef = new DwfDefinition())
  65.                         {
  66.                             dwfDict.UpgradeOpen();
  67.                             dwfDef.SourceFileName = sFileName;
  68.                             dwfDef.SetUnderlayItem(sFileName, sFileName, selectedItem);
  69.                             idDef = dwfDict.SetAt(sDwfName, dwfDef);
  70.                             tr.AddNewlyCreatedDBObject(dwfDef, true);
  71.                         }
  72.                     }
  73.                     res = idDef;
  74.                     tr.Commit();
  75.                 }
  76.             }
  77.  
  78.             return res;
  79.         }

Да, я понимаю: код ужасен. Но я и на .NET не пишу :)

Тем не менее свою задачу код выполняет: при условии вызова (kpblc_dwfinsert "c:\\test01.dwf") возвращается ename-указатель на созданную запись в словаре. Если вызвать окно диспетчера внешних ссылок, то мы увидим вполне логичную картину:

Код - Auto/Visual LISP: [Выделить]
  1. $ (kpblc_dwfinsert "c:\\test01.dwf")
  2.  

А вот теперь попробуем создать вхождение dwf в текущий документ. ActiveX-методов для этого не существует, поэтому попробуем использовать обычный entmakex:

Код - Auto/Visual LISP: [Выделить]
  1. (entmakex
  2.   (append '((0 . "DWFUNDERLAY")
  3.             (100 . "AcDbEntity")
  4.             (67 . 0)
  5.             (410 . "Model")
  6.             (8 . "0")
  7.             (100 . "AcDbUnderlayReference")
  8.             )
  9.           (list (cons 340 (kpblc_dwfinsert "c:\\test01.dwf")))
  10.           '((10 0.0 0.0 0.0)
  11.             (41 . 1.0)
  12.             (42 . 1.0)
  13.             (43 . 1.0)
  14.             (50 . 0.0)
  15.             (210 0.0 0.0 1.0)
  16.             (280 . 11)
  17.             (281 . 75)
  18.             (282 . 25)
  19.             )
  20.           ) ;_ end of append
  21.   ) ;_ end of entmakex

Так, код сработал: возвращает указатель на созданный примитив (<Entity name: 7ffffb06140>); в пространстве модели появилось вхождение dwf:

А вот в диспетчере ссылок творится что-то странное:

А если повести себя как обезьяна с гранатой и выполнить "Detach" (отключить) ссылку?

Попытки выделить вставленный dwf, описание которого отсутствует в словаре, ACAD2015 не рушит (ACAD2009 падал моментально при попытке простого выделения DWF-ссылки на чертеже), но как-то лично мне неуютно от того, что в файле dwg начинается такой бардак. Кроме того, можно в ACAD20015 скрыть отображение этой "неправильной" dwf-ссылки и потом попробовать снова ее отобразить: вместо более-менее нормального вида будет показываться черт-те что (но при этом имеющее некоторые свойства DWF-подложки).

Я обратился в ADN DevHelp, ответ был примерно следующим: "Создание DWF-подложки, используя механизм COM, на данный момент невозможно. Создание соответствующих элементов через DXF является очень громоздким и неустойчивым"

Подводя итоги, можно сказать следующее: если надо вставить DWF-подложку в документ, то либо использовать командные методы, либо полностью разрабатывать на .NET: лисп с этой задачей не справится.

Автор: Алексей Кулик, на основании материалов ADN DevBlog, статьи Как можно вставить dwf(x) в текущий документ dwg?, при содействии Александра Ривилиса.

Обсуждение: http://adn-cis.org/forum/index.php?topic=1860

Опубликовано 13.02.2015