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

ADN Club => AutoCAD .NET API => Тема начата: Вильдар от 16-05-2016, 10:58:38

Название: Обновление полей в атрибутах блока
Отправлено: Вильдар от 16-05-2016, 10:58:38
Всем добрый день,

Какие есть варианты обновления полей в атрибутах вхождения блока?

Можно регенерировать весь чертеж. Но, лучше если можно обновлять только нужные объекты.
Что-то найти ничего похожего не удалось  :(
Название: Re: Обновление полей в атрибутах блока
Отправлено: Александр Пекшев aka Modis от 16-05-2016, 11:05:00
А я не понял - а в какой момент это нужно? При создании блока? При вставке?
Название: Re: Обновление полей в атрибутах блока
Отправлено: Вильдар от 16-05-2016, 11:06:10
Перед созданием спецификации по выбранным вхождениям блоков.
Название: Re: Обновление полей в атрибутах блока
Отправлено: Александр Пекшев aka Modis от 16-05-2016, 11:10:49
Может тут (http://through-the-interface.typepad.com/through_the_interface/2007/07/updating-a-spec.html) найдете что-то подходящее
Название: Re: Обновление полей в атрибутах блока
Отправлено: Александр Ривилис от 16-05-2016, 12:22:07
1. Autodesk.AutoCAD.DatabaseServices.Database.EvaluateFields - в AutoCAD .NET API. Обновляет все поля в базе.
2. acdbEvaluateFields - в ObjectARX можно указать какие поля будут обновляться.
Название: Re: Обновление полей в атрибутах блока
Отправлено: Вильдар от 16-05-2016, 13:15:16
Спасибо, Database.EvaluateFields сойдет. Не видел его.

Пробовал так, но не работает.
Поле определяется, пересчитывается, но текст атрибута не обновляется после SetField().
Код - C# [Выбрать]
  1.         [CommandMethod("Test", "Test", CommandFlags.Modal)]
  2.         public void Test()
  3.         {
  4.             // Обновление полей в блоке
  5.             Document doc = Application.DocumentManager.MdiActiveDocument;
  6.             Database db = doc.Database;
  7.             Editor ed = doc.Editor;            
  8.  
  9.             var sel = ed.GetEntity("Выбери блок для обновления полей в атрибутах");
  10.             if (sel.Status != PromptStatus.OK) return;
  11.  
  12.             using (var t = db.TransactionManager.StartTransaction())
  13.             {
  14.                 var blRef = sel.ObjectId.GetObject(OpenMode.ForRead) as BlockReference;
  15.  
  16.                 foreach (ObjectId idAtr in blRef.AttributeCollection)
  17.                 {
  18.                     var atrRef = idAtr.GetObject(OpenMode.ForRead) as AttributeReference;                    
  19.                     if (atrRef.HasFields)
  20.                     {
  21.                         var fieldId = atrRef.GetField();
  22.                         var field = fieldId.GetObject(OpenMode.ForWrite) as Field;
  23.                         field.Evaluate();
  24.                        
  25.                         atrRef.UpgradeOpen();
  26.                         atrRef.SetField("TextString", field);
  27.                     }
  28.                 }                
  29.                 t.Commit();
  30.             }
  31.         }
Название: Re: Обновление полей в атрибутах блока
Отправлено: Александр Пекшев aka Modis от 16-05-2016, 13:19:15
А если так:
Код - C# [Выбрать]
  1. atrRef.UpgradeOpen();
  2. atrRef.TextString = "";
  3. atrRef.SetField("TextString", field);
  4. atrRef.DowngradeOpen();
Название: Re: Обновление полей в атрибутах блока
Отправлено: Александр Ривилис от 16-05-2016, 13:27:03
А если так:
Код - C# [Выбрать]
  1. atrRef.UpgradeOpen();
  2. atrRef.TextString = "";
  3. atrRef.SetField("TextString", field);
  4. atrRef.DowngradeOpen();
SetField устанавливает поле, но не обновляет его отображение.
Название: Re: Обновление полей в атрибутах блока
Отправлено: Вильдар от 16-05-2016, 13:39:21
Пока непонятное поведение SetField(). при первом вызове обновляет, дальше - нет. DowngradeOpen() не влияет.

SetField устанавливает поле, но не обновляет его отображение.
А как обновить? Field.Evaluate() не то?
Название: Re: Обновление полей в атрибутах блока
Отправлено: Александр Ривилис от 16-05-2016, 15:49:04
Field.Evaluate() не то?
Нет. Это просто вычисляет поле, но не обновляет видимость его в чертеже.
Можешь попробовать для atrRef вызвать atrRef.RecordGraphicsModified(true); после изменения поля и в конце TransactionManager.QueueForGraphicsFlush(); хотя мне кажется, что в этой ситуации не сработает. Не зря специальный метод acdbEvaluateFields в ObjectARX присутствует. 
Название: Re: Обновление полей в атрибутах блока
Отправлено: Привалов Дмитрий от 16-05-2016, 15:57:56
Пока непонятное поведение SetField(). при первом вызове обновляет, дальше - нет. DowngradeOpen() не влияет.
возможно тебе поможет, после изменения в блоке, пометить принудительно, что графика выбранного блока была изменена.

...
blRef.RecordGraphicsModified(true);
t.Commit();

Блок будет помечен как измененный, но не обязательно перерисуется сам.
тогда попробуй вызвать регенерацию после закрытия транзакции

using (var t = db.TransactionManager.StartTransaction())
{
...
}
ed.Regen();

..если это не поможет попробуй пометить еще и атрибуты внутри блока как измененные(как написал Александр)
var atrRef = idAtr.GetObject(OpenMode.ForRead) as DBText;                   
if (atrRef.HasFields)
{
}
atrRef.RecordGraphicsModified(true);


Название: Re: Обновление полей в атрибутах блока
Отправлено: Привалов Дмитрий от 16-05-2016, 15:58:55
...как удалять свои сообщения?  :P
Название: Re: Обновление полей в атрибутах блока
Отправлено: Александр Ривилис от 16-05-2016, 16:46:34
...как удалять свои сообщения?  :P
Никак. Их можно только редактировать - это политика нашего форума. Чтобы не возникло ситуации, когда на вопрос отвечают, тратят на это время и силы, а вопроса уже нет...
Название: Re: Обновление полей в атрибутах блока
Отправлено: Вильдар от 16-05-2016, 20:32:15
Пока непонятное поведение SetField(). при первом вызове обновляет, дальше - нет. DowngradeOpen() не влияет.
возможно тебе поможет, после изменения в блоке, пометить принудительно, что графика выбранного блока была изменена.
...
blRef.RecordGraphicsModified(true);
t.Commit();
Не подходит. И QueueForGraphicsFlush тоже.

При повторном получении поля из атрибута у него в статусе добавляется флаг Modified. Что делать в зависимости от этого флага или как его изменить непонятно.
Т.е. в блоке изменяются параметры влияющие на поле в атрибуте, и при первом запуске команды Test поле в атрибуте обновляется. Потом опять редактируется параметр блока и опять запускается команда Test. В результате атрибут получает пустое значение.
Название: Re: Обновление полей в атрибутах блока
Отправлено: Александр Ривилис от 16-05-2016, 20:42:14
При повторном получении поля из атрибута, у него в статусе добавляется флаг Modified. что делать в зависимости от этого флага или как его изменить непонятно.
Переведи. А заодно приложи код и файл с блоком, на котором тестируешь. Кроме того я не понял если уже нашлось решение Database.EvaluateFields, то зачем всё остальное?
Название: Re: Обновление полей в атрибутах блока
Отправлено: Вильдар от 17-05-2016, 08:16:38
Код такой:
Код - C# [Выбрать]
  1.         [CommandMethod("Test", "Test", CommandFlags.Modal)]
  2.         public void Test()
  3.         {
  4.             // Обновление полей в блоке
  5.             Document doc = Application.DocumentManager.MdiActiveDocument;
  6.             Database db = doc.Database;
  7.             Editor ed = doc.Editor;            
  8.  
  9.             var sel = ed.GetEntity("Выбери блок для обновления полей в атрибутах");
  10.             if (sel.Status != PromptStatus.OK) return;
  11.  
  12.             using (var t = db.TransactionManager.StartTransaction())
  13.             {
  14.                 var blRef = sel.ObjectId.GetObject(OpenMode.ForRead) as BlockReference;                                
  15.                 foreach (ObjectId idAtr in blRef.AttributeCollection)
  16.                 {
  17.                     var atrRef = idAtr.GetObject(OpenMode.ForRead) as AttributeReference;
  18.                     if (atrRef.HasFields)
  19.                     {
  20.                         var fieldId = atrRef.GetField();                        
  21.                         var field = fieldId.GetObject(OpenMode.ForWrite) as Field;                        
  22.                         field.Evaluate();
  23.                         atrRef.UpgradeOpen();
  24.                         atrRef.SetField("TextString", field);
  25.                         atrRef.RecordGraphicsModified(true);                        
  26.                     }
  27.                 }
  28.                 db.TransactionManager.QueueForGraphicsFlush();
  29.                 t.Commit();                
  30.             }
  31.         }

EvaluateFields хорошо, но если получится обновлять только нужные поля - еще лучше.

Название: Re: Обновление полей в атрибутах блока
Отправлено: Александр Ривилис от 17-05-2016, 11:57:42
Я задал вопрос в ADN DevHelp есть ли в AutoCAD .NET API альтернатива для acdbEvaluateFields. Подождём ответа.
Название: Re: Обновление полей в атрибутах блока
Отправлено: Привалов Дмитрий от 17-05-2016, 17:00:05
EvaluateFields хорошо, но если получится обновлять только нужные поля - еще лучше.
Для пересчета полей и обновления блока ты можешь использовать код:
1.
Document doc = Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
db.EvaluateFields();

2. Работает так же хорошо(на тесте разумеется)
Document doc = Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
ed.Regen();

Когда ты пытаешься обновить точечно.
field.Evaluate(); срабатывает 1й раз, на 2й раз перестает пересчитывать!

можешь сам проверить.
ed.WriteMessage("/r/n" + field.GetStringValue());
field.Evaluate();
ed.WriteMessage("/r/n"+field.GetStringValue());

atrRef.SetField("TextString", field); первый раз прокатывает, второй раз действует нехорошо(удаляет кое-что)
вдаваться в подробности не буду, возможно зависит от работы первого метода field.Evaluate();
Возможно "TextString" не тот параметр, который следует в него передавать. Но в любом случае нужно разбираться вначале почему не срабатывает field.Evaluate();

отсюда скорее всего следует вывод, что field.Evaluate() недостаточен. После него нужно как-то обновлять блок.
Можно пытаться добавлять RecordGraphicsModified(true); и вызывать к примеру "_regen3"
Но на данном тесте смысла делать этого нет, т.к. цель была, если я правильно понял сэкономить на вызове "_regen".
нужен большой рабочий файл и смотреть что сработает быстрее.
Либо искать метод обновления вида одного примитива типа Entity.Regen() если такой вообще существует.
Название: Re: Обновление полей в атрибутах блока
Отправлено: Александр Ривилис от 17-05-2016, 17:11:22
Самый правильный и быстрый способ P/Invoke для acdbEvaluateFields, когда локально обновляется только атрибут, переданный через ObjectId. Но сигнатура метода достаточно нетривиальная:
Код - C++ [Выбрать]
  1. Acad::ErrorStatus acdbEvaluateFields(
  2.     const AcDbObjectId& objId,
  3.     int nContext,
  4.     const ACHAR* pszPropName = NULL,
  5.     AcDbDatabase* pDb = NULL,
  6.     AcFd::EvalFields nEvalFlag = AcFd::kEvalRecursive,
  7.     int* pNumFound = NULL,
  8.     int* pNumEvaluated = NULL
  9. );

И потребуется несколько сигнатур (в зависимости от версии AutoCAD и его разрядности).
Название: Re: Обновление полей в атрибутах блока
Отправлено: Александр Ривилис от 17-05-2016, 17:14:33
Либо искать метод обновления вида одного примитива типа Entity.Regen() если такой вообще существует.
Если бы ты запустил пример с приложенным dwg-файлом, то убедился бы, что _REGEN в данном случае не помогает. Точнее обновление происходит не моментально. Помогает сразу только _UPDATEFIELDS.
Название: Re: Обновление полей в атрибутах блока
Отправлено: Привалов Дмитрий от 17-05-2016, 18:45:42
Если бы ты запустил пример с приложенным dwg-файлом...
только на нем и пробовал

Точнее обновление происходит не моментально.
скорее всего, я только хотел указать что тоже может помочь.

Помогает сразу только _UPDATEFIELDS.
предполагаю что регенерация вызывает вначале группу методов, таких как db.EvaluateFields(); а затем обновляет кэш изображений.
и не настаиваю, что вызов регенерации - правильное и оптимальное решение. )))
Название: Re: Обновление полей в атрибутах блока
Отправлено: Александр Ривилис от 17-05-2016, 22:46:23
Пока через P/Invoke acdbEvaluateFields вот так. Кстати, не нужно указывать конкретный атрибут. Достаточно указать ObjectId вставки блока.

Код - C# [Выбрать]
  1. #region PInvoke acdbEvaluateFields
  2. // Acad::ErrorStatus acdbEvaluateFields (
  3. //   const AcDbObjectId& objId,
  4. //   int nContext,
  5. //   const ACHAR* pszPropName = NULL,
  6. //   AcDbDatabase* pDb        = NULL,
  7. //   AcFd::EvalFields nEvalFlag = AcFd::kEvalRecursive,
  8. //   int* pNumFound           = NULL,
  9. //   int* pNumEvaluated       = NULL
  10. // );
  11. // AutoCAD 2013 и 2014 x64
  12. [DllImport("acdb19.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode, ExactSpelling = true,
  13.   EntryPoint = "?acdbEvaluateFields@@YA?AW4ErrorStatus@Acad@@AEBVAcDbObjectId@@HPEB_WPEAVAcDbDatabase@@W4EvalFields@AcFd@@PEAH4@Z")]
  14. private static extern Int32 acdbEvaluateFields19x64(ref ObjectId id, Int32 context, IntPtr pszPropName, IntPtr db, Int32 eval, IntPtr i1, IntPtr i2);
  15. // AutoCAD 2013 и 2014 x86
  16. [DllImport("acdb19.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode, ExactSpelling = true,
  17. EntryPoint = "?acdbEvaluateFields@@YA?AW4ErrorStatus@Acad@@ABVAcDbObjectId@@HPB_WPAVAcDbDatabase@@W4EvalFields@AcFd@@PAH4@Z")]
  18. private static extern Int32 acdbEvaluateFields19x32(ref ObjectId id, Int32 context, IntPtr pszPropName, IntPtr db, Int32 eval, IntPtr i1, IntPtr i2);
  19. // AutoCAD 2015 и 2016 x64
  20. [DllImport("acdb20.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode, ExactSpelling = true,
  21.   EntryPoint = "?acdbEvaluateFields@@YA?AW4ErrorStatus@Acad@@AEBVAcDbObjectId@@HPEB_WPEAVAcDbDatabase@@W4EvalFields@AcFd@@PEAH4@Z")]
  22. private static extern Int32 acdbEvaluateFields20x64(ref ObjectId id, Int32 context, IntPtr pszPropName, IntPtr db, Int32 eval, IntPtr i1, IntPtr i2);
  23. // AutoCAD 2015 и 2016 x86
  24. [DllImport("acdb20.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode, ExactSpelling = true,
  25. EntryPoint = "?acdbEvaluateFields@@YA?AW4ErrorStatus@Acad@@ABVAcDbObjectId@@HPB_WPAVAcDbDatabase@@W4EvalFields@AcFd@@PAH4@Z")]
  26. private static extern Int32 acdbEvaluateFields20x32(ref ObjectId id, Int32 context, IntPtr pszPropName, IntPtr db, Int32 eval, IntPtr i1, IntPtr i2);
  27. // AutoCAD 2017 x64
  28. [DllImport("acdb21.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode, ExactSpelling = true,
  29.   EntryPoint = "?acdbEvaluateFields@@YA?AW4ErrorStatus@Acad@@AEBVAcDbObjectId@@HPEB_WPEAVAcDbDatabase@@W4EvalFields@AcFd@@PEAH4@Z")]
  30. private static extern Int32 acdbEvaluateFields21x64(ref ObjectId id, Int32 context, IntPtr pszPropName, IntPtr db, Int32 eval, IntPtr i1, IntPtr i2);
  31. // AutoCAD 2017x86
  32. [DllImport("acdb21.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode, ExactSpelling = true,
  33. EntryPoint = "?acdbEvaluateFields@@YA?AW4ErrorStatus@Acad@@ABVAcDbObjectId@@HPB_WPAVAcDbDatabase@@W4EvalFields@AcFd@@PAH4@Z")]
  34. private static extern Int32 acdbEvaluateFields21x32(ref ObjectId id, Int32 context, IntPtr pszPropName, IntPtr db, Int32 eval, IntPtr i1, IntPtr i2);
  35. #endregion
  36. private Int32 acdbEvaluateFields(ref ObjectId id, Int32 context)
  37. {
  38.   switch (Application.Version.Major)
  39.   {
  40.     case 21:
  41.       if (IntPtr.Size == 8)  
  42.         return acdbEvaluateFields21x64(ref id, 16, IntPtr.Zero, IntPtr.Zero, 1, IntPtr.Zero, IntPtr.Zero);
  43.       else
  44.         return acdbEvaluateFields21x32(ref id, 16, IntPtr.Zero, IntPtr.Zero, 1, IntPtr.Zero, IntPtr.Zero);
  45.     case 20:
  46.       if (IntPtr.Size == 8)
  47.         return acdbEvaluateFields20x64(ref id, 16, IntPtr.Zero, IntPtr.Zero, 1, IntPtr.Zero, IntPtr.Zero);
  48.       else
  49.         return acdbEvaluateFields20x32(ref id, 16, IntPtr.Zero, IntPtr.Zero, 1, IntPtr.Zero, IntPtr.Zero);
  50.     case 19:
  51.       if (IntPtr.Size == 8)
  52.         return acdbEvaluateFields19x64(ref id, 16, IntPtr.Zero, IntPtr.Zero, 1, IntPtr.Zero, IntPtr.Zero);
  53.       else
  54.         return acdbEvaluateFields19x32(ref id, 16, IntPtr.Zero, IntPtr.Zero, 1, IntPtr.Zero, IntPtr.Zero);
  55.     default:
  56.       break;
  57.   }
  58.   return 0;
  59. }
  60.  
  61. [CommandMethod("Test", "Test", CommandFlags.Modal)]
  62. public void Test()
  63. {
  64.   // Обновление полей в блоке
  65.   Document doc = Application.DocumentManager.MdiActiveDocument;
  66.   Database db = doc.Database;
  67.   Editor ed = doc.Editor;
  68.  
  69.   var sel = ed.GetEntity("Выбери блок для обновления полей в атрибутах: ");
  70.   if (sel.Status == PromptStatus.OK)
  71.   {
  72.     ObjectId id = sel.ObjectId;
  73.     acdbEvaluateFields(ref id, 16);
  74.   }
  75. }

Так это выглядит:



Я проверял только в AutoCAD 2015 и 2016 x64.
Название: Re: Обновление полей в атрибутах блока
Отправлено: Вильдар от 18-05-2016, 08:18:09
Спасибо большое!
Название: Re: Обновление полей в атрибутах блока
Отправлено: Александр Ривилис от 19-05-2016, 14:14:23
В ADN DevHelp мне подтвердили, что предложенное мной решение - единственное, и направили в Dev Team запрос на добавление функционала в .NET API. Пока вот так.
Название: Re: Обновление полей в атрибутах блока
Отправлено: Geniy_dzydo от 16-09-2016, 11:41:47
Здравствуйте гуру автокада. Подскажите пожалуйста может ли ваш код помочь в моем случае. Делаю блок с тремя атрибутами, в два атрибута вношу значения, в третьем атрибуте вычисляю разницу этих значений. Делаю полями в формуле; в пространстве редактора блоков обновление вычисления происходит. А когда потом уже после закрытия блока делаю изменения атрибутов уже ничего не вычисляется. Так понимаю эта проблема известна и простыми способами не решается. Можете что то подсказать, очень прошу!

И еще вопрос: как этот кода использовать в автокад, сохранять как лисп?
Название: Re: Обновление полей в атрибутах блока
Отправлено: Вильдар от 16-09-2016, 12:35:41
Привет.
Прикрепи файл с блоком, посмотреть.
Код приведен для примера. Использовать на твое усмотрение в своем проекте. Это тестовый черновик.
Название: Re: Обновление полей в атрибутах блока
Отправлено: Geniy_dzydo от 16-09-2016, 12:47:34
Прикладываю файл.
ту же задачу решил полями в обычном тексте и в таблице, но блок был бы лучшим вариантом. странно думал что в блоке будет так же отрабатывать, ан нет((((
Название: Re: Обновление полей в атрибутах блока
Отправлено: Вильдар от 16-09-2016, 12:52:54
Какая то особенность вычисления полей в формулах.
Немного поменял вычисляемый атрибут. Так работает.
Название: Re: Обновление полей в атрибутах блока
Отправлено: Geniy_dzydo от 16-09-2016, 14:13:43
ммм....ничего не понимаю, почему не работало. А еще такой вопрос, в вычисляемом поле можно поставить в качестве разделителя и точку и запятую, в тех атрибутах куда мы вносим значения можно ставить только точку. Можно ли что то изменить чтобы формула понимала и запятую тоже???
Название: Re: Обновление полей в атрибутах блока
Отправлено: Вильдар от 16-09-2016, 15:16:00
Думаю нет. Или делать свое программное решение.