Не удаляются примитивы внутри блока после команды UNDO

Автор Тема: Не удаляются примитивы внутри блока после команды UNDO  (Прочитано 9136 раз)

0 Пользователей и 6 Гостей просматривают эту тему.

Оффлайн Александр Пекшев aka ModisАвтор темы

  • ADN Club
  • *****
  • Сообщений: 1658
  • Карма: 366
  • Отец modplus.org
    • ModPlus
Всем привет. Вопрос в продолжение моих многочисленных тем по поводу динамического редактирования графики внутри анонимного блока. Мы уже много чего обсуждали и частично подобную тему тоже.
У меня работает все практически как надо, но вот столкнулся с неприятным моментом при обработке команды UNDO.
Все действия происходят в ObjectOverrule:
Код - C# [Выбрать]
  1. public override void Close(DBObject dbObject)
  2. {
  3.     if (IsApplicable(dbObject))
  4.     {
  5.         if (!OverrulesInteraction.IsGripOverruleWork)
  6.         {
  7.             if (dbObject != null &&
  8.                 dbObject.IsModified &
  9.                 !dbObject.IsErased &
  10.                 !dbObject.IsEraseStatusToggled &
  11.                 !dbObject.IsModifiedXData &
  12.                 !dbObject.IsNewObject &
  13.                 !dbObject.IsCancelling
  14.                 )
  15.             {
  16.                 var breakLine = BreakLineXDataHelper.GetBreakLineFromEntity((Autodesk.AutoCAD.DatabaseServices.Entity)dbObject);
  17.                 if (breakLine != null)
  18.                 {
  19.                     breakLine.UpdateEntities();
  20.                     breakLine.BlockRecord.UpdateAnonymousBlocks();
  21.  
  22.                     using (ResultBuffer resBuf = breakLine.GetParametersForXData())
  23.                     {
  24.                         dbObject.XData = resBuf;
  25.                     }
  26.                 }
  27.             }
  28.             else if (dbObject != null && dbObject.IsUndoing & dbObject.IsModifiedXData)
  29.             {
  30.                 var breakLine = BreakLineXDataHelper.GetBreakLineFromEntity((Autodesk.AutoCAD.DatabaseServices.Entity)dbObject);
  31.                 if (breakLine != null)
  32.                 {
  33.                     breakLine.UpdateEntities();
  34.                     breakLine.BlockRecord.UpdateAnonymousBlocks();
  35.                 }
  36.             }
  37.         }
  38.     }
  39.     base.Close(dbObject);
  40. }
Я специально вынес в отдельное условие else if (dbObject != null && dbObject.IsUndoing & dbObject.IsModifiedXData) чтобы было удобней работать.
Сначала я получаю экземпляр класса, описывающий графику внутри блока методом GetBreakLineFromEntity():
Код - C# [Выбрать]
  1. public static BreakLine GetBreakLineFromEntity(Autodesk.AutoCAD.DatabaseServices.Entity ent)
  2. {
  3.     using (ResultBuffer resBuf = ent.GetXDataForApplication(BreakLineFunction.MPCOEntName))
  4.     {
  5.  
  6.         BreakLine breakLine = new BreakLine(ent.ObjectId);
  7.         // Получаем параметры из самого блока
  8.         // ОБЯЗАТЕЛЬНО СНАЧАЛА ИЗ БЛОКА!!!!!!
  9.         breakLine.GetParametersFromEntity(ent);
  10.         // Получаем параметры из XData
  11.         breakLine.GetParametersFromResBuf(resBuf);
  12.  
  13.         return breakLine;
  14.     }
  15. }
В этом методе в первую очередь я получаю данные из самого блока, а потом уже из расширенных данных блока.
Затем вызываю метод UpdateEntities(), который эту графику (примитивы внутри блока) перерисовывает по новым данным (точкам) и затем получаю BlockTableRecord:
Код - C# [Выбрать]
  1. private BlockTableRecord _blockRecord;
  2. public BlockTableRecord BlockRecord
  3. {
  4.     get
  5.     {
  6.         try
  7.         {
  8.             if (!BlockId.IsNull)
  9.             {
  10.                 using (AcadHelpers.Document.LockDocument())
  11.                 {
  12.                     using (var tr = AcadHelpers.Database.TransactionManager.StartTransaction())
  13.                     {
  14.                         var blkRef = (BlockReference)tr.GetObject(BlockId, OpenMode.ForWrite);
  15.                         _blockRecord = (BlockTableRecord)tr.GetObject(blkRef.BlockTableRecord, OpenMode.ForWrite);
  16.                         if (_blockRecord.GetBlockReferenceIds(true, true).Count <= 1)
  17.                         {
  18.                             foreach (var objectId in _blockRecord)
  19.                             {
  20.                                 tr.GetObject(objectId, OpenMode.ForWrite).Erase();
  21.                             }
  22.                         }
  23.                         else
  24.                         {
  25.                             _blockRecord = new BlockTableRecord { Name = "*U" , BlockScaling = BlockScaling.Uniform };
  26.                             using (var blockTable = AcadHelpers.Database.BlockTableId.Write<BlockTable>())
  27.                             {
  28.                                 if (Annotative)
  29.                                 {
  30.                                     _blockRecord.Annotative = AnnotativeStates.True;
  31.                                 }
  32.                                 blockTable.Add(_blockRecord);
  33.                                 tr.AddNewlyCreatedDBObject(_blockRecord, true);
  34.                             }
  35.                             blkRef.BlockTableRecord = _blockRecord.Id;
  36.                         }
  37.                         tr.Commit();
  38.                     }
  39.                     using (var tr = AcadHelpers.Database.TransactionManager.StartTransaction())
  40.                     {
  41.                         var blkRef = (BlockReference)tr.GetObject(BlockId, OpenMode.ForWrite);
  42.                         _blockRecord = (BlockTableRecord)tr.GetObject(blkRef.BlockTableRecord, OpenMode.ForWrite);
  43.                         _blockRecord.BlockScaling = BlockScaling.Uniform;
  44.                         var matrix3D = Matrix3d.Displacement(-InsertionPoint.TransformBy(BlockTransform.Inverse()).GetAsVector());
  45.                         foreach (var entity in Entities)
  46.                         {
  47.                             var transformedCopy = entity.GetTransformedCopy(matrix3D);
  48.                             _blockRecord.AppendEntity(transformedCopy);
  49.                             AcadHelpers.Document.TransactionManager.AddNewlyCreatedDBObject(transformedCopy, true);
  50.                         }
  51.                         tr.Commit();
  52.                     }
  53.                     try
  54.                     {
  55.                         AcadHelpers.Document.TransactionManager.FlushGraphics();
  56.                     }
  57.                     catch (Exception exception)
  58.                     {
  59.                         MpExWin.Show(exception);
  60.                     }
  61.                 }
  62.             }
  63.             else if (!IsValueCreated)
  64.             {
  65.                 var matrix3D = Matrix3d.Displacement(-InsertionPoint.TransformBy(BlockTransform.Inverse()).GetAsVector());
  66.                 foreach (var ent in Entities)
  67.                 {
  68.                     var transformedCopy = ent.GetTransformedCopy(matrix3D);
  69.                     _blockRecord.AppendEntity(transformedCopy);
  70.                 }
  71.                 IsValueCreated = true;
  72.             }
  73.         }
  74.         catch (Exception exception)
  75.         {
  76.             MpExWin.Show(exception);
  77.         }
  78.         return _blockRecord;
  79.     }
  80.     set => _blockRecord = value;
  81. }
Для которого вызываю метод UpdateAnonymousBlocks().
Как мы видим, при получении BlockTableRecord сначала удаляются все примитивы, а затем добавляются новые. Я перепроверил – после команды UNDO все данные – я имею ввиду XData – корректно обновляются в примитиве так как нужно и до того, как происходит метод ObjectOverrule.Close(). Т.е. все данные, нужные чтобы вернуть графику в нужный вид, есть.

И вот самое интересное – после вызова команды UNDO у меня графика внутри блока дублируется! Т.е. остается та, которая была перед вызовом команды UNDO и та, которая должна стать как надо. Причем если вызвать какие-то события, которые снова приведут к «обновлению» блока, то вся графика станет верной – т.е. ненужная копия исчезнет.
Использование регенерации, TransactionManager.FlushGraphics() и ((BlockReference)dbObject).RecordGraphicsModified(true) не дает эффекта.

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13882
  • Карма: 1787
  • Рыцарь ObjectARX
  • Skype: rivilis
Как вариант возможно всё портят транзакции. Попробуй их все заменить на OpenCloseTransaction
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Александр Пекшев aka ModisАвтор темы

  • ADN Club
  • *****
  • Сообщений: 1658
  • Карма: 366
  • Отец modplus.org
    • ModPlus
Как вариант возможно всё портят транзакции. Попробуй их все заменить на OpenCloseTransaction
Это я уже пробовал, к сожалению, неоднократно. В этом случае вообще все перестает работать, то с ошибкой eWasOpenForRead, то с eLockViolation, то просто с фаталом. В общем - там только StartTransaction() и работает.
Я уже много вариантов испробовал и логичных и нелогичных. Даже уже склоняюсь к тому, чтобы из метода ObjectOverrule.Close() при втором условии (отмена) каким-то образом снова заставить сработать этот метод
Завтра еще попробую сделать проект под другую версию автокада и протестировать. Если такого не будет - значит глюк 2010 автокада. Хотя, от этого легче не станет

Оффлайн Александр Пекшев aka ModisАвтор темы

  • ADN Club
  • *****
  • Сообщений: 1658
  • Карма: 366
  • Отец modplus.org
    • ModPlus
Завтра еще попробую сделать проект под другую версию автокада и протестировать. Если такого не будет - значит глюк 2010 автокада. Хотя, от этого легче не станет
Не дождался до завтра и проверил сегодня - то же самое. Значит это не глюк 2010 автокада

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13882
  • Карма: 1787
  • Рыцарь ObjectARX
  • Skype: rivilis
В этом случае вообще все перестает работать, то с ошибкой eWasOpenForRead, то с eLockViolation, то просто с фаталом. В общем - там только StartTransaction() и работает.
Не верю. Это говорит лишь о том, что ты что-то делаешь неправильно.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Александр Пекшев aka ModisАвтор темы

  • ADN Club
  • *****
  • Сообщений: 1658
  • Карма: 366
  • Отец modplus.org
    • ModPlus
В этом случае вообще все перестает работать, то с ошибкой eWasOpenForRead, то с eLockViolation, то просто с фаталом. В общем - там только StartTransaction() и работает.
Не верю. Это говорит лишь о том, что ты что-то делаешь неправильно.
С этими транзакциями прям беда. Одна из главных проблем OpenCloseTransaction в том, что нельзя открывать в них вложенные транзакции. Но и я не могу использовать OpenCloseTransaction для создания, а не редактирования блока. Получается, что при получении BlockTableRecord (код, который я приложил) нужно учитывать три варианта работы - использовать OpenCloseTransaction , использовать обычную транзакцию или использовать TopTransaction.
Ну или опять перелапатить вообще ВСЕ и придумать какие-то другие решения, уменьшая тем самым количество открытий транзакций

Оффлайн Александр Пекшев aka ModisАвтор темы

  • ADN Club
  • *****
  • Сообщений: 1658
  • Карма: 366
  • Отец modplus.org
    • ModPlus
Как вариант возможно всё портят транзакции. Попробуй их все заменить на OpenCloseTransaction
Не верю. Это говорит лишь о том, что ты что-то делаешь неправильно.
В общем - не помогает. Сделал отдельный метод:
Код - C# [Выбрать]
  1. public BlockTableRecord GetBlockTableRecordOpenCloseTransaction(BlockReference blkRef)
  2. {
  3.     BlockTableRecord blockTableRecord;
  4.     using (AcadHelpers.Document.LockDocument())
  5.     {
  6.         using (var tr = AcadHelpers.Database.TransactionManager.StartTransaction())
  7.         {
  8.             blockTableRecord = (BlockTableRecord)tr.GetObject(blkRef.BlockTableRecord, OpenMode.ForWrite);
  9.             if (blockTableRecord.GetBlockReferenceIds(true, true).Count <= 1)
  10.             {
  11.                 foreach (var objectId in blockTableRecord)
  12.                 {
  13.                     tr.GetObject(objectId, OpenMode.ForWrite).Erase();
  14.                 }
  15.             }
  16.             else
  17.             {
  18.                 blockTableRecord = new BlockTableRecord { Name = "*U", BlockScaling = BlockScaling.Uniform };
  19.                 using (var blockTable = AcadHelpers.Database.BlockTableId.Write<BlockTable>())
  20.                 {
  21.                     if (Annotative)
  22.                     {
  23.                         blockTableRecord.Annotative = AnnotativeStates.True;
  24.                     }
  25.                     blockTable.Add(blockTableRecord);
  26.                     tr.AddNewlyCreatedDBObject(blockTableRecord, true);
  27.                 }
  28.                 blkRef.BlockTableRecord = blockTableRecord.Id;
  29.             }
  30.             tr.Commit();
  31.         }
  32.         using (var tr = AcadHelpers.Database.TransactionManager.StartOpenCloseTransaction())
  33.         {
  34.             blockTableRecord = (BlockTableRecord)tr.GetObject(blkRef.BlockTableRecord, OpenMode.ForWrite);
  35.             blockTableRecord.BlockScaling = BlockScaling.Uniform;
  36.             var matrix3D = Matrix3d.Displacement(-InsertionPoint.TransformBy(BlockTransform.Inverse())
  37.                 .GetAsVector());
  38.             foreach (var entity in Entities)
  39.             {
  40.                 var transformedCopy = entity.GetTransformedCopy(matrix3D);
  41.                 blockTableRecord.AppendEntity(transformedCopy);
  42.                 tr.AddNewlyCreatedDBObject(transformedCopy, true);
  43.             }
  44.             tr.Commit();
  45.         }
  46.         try
  47.         {
  48.             AcadHelpers.Document.TransactionManager.FlushGraphics();
  49.         }
  50.         catch (Exception exception)
  51.         {
  52.             MpExWin.Show(exception);
  53.         }
  54.     }
  55.     return blockTableRecord;
  56. }
Изменил работу в методе Close:
Код - C# [Выбрать]
  1. else if (dbObject != null && dbObject.IsUndoing & dbObject.IsModifiedXData)
  2. {
  3.     var breakLine = BreakLineXDataHelper.GetBreakLineFromEntity((Autodesk.AutoCAD.DatabaseServices.Entity)dbObject);
  4.     if (breakLine != null)
  5.     {
  6.         breakLine.UpdateEntities();
  7.         breakLine.GetBlockTableRecordOpenCloseTransaction((BlockReference)dbObject).UpdateAnonymousBlocks();
  8.         //breakLine.BlockRecord.UpdateAnonymousBlocks();
  9.  
  10.     }
  11. }
И все осталось по прежнему - задваиваются примитивы

Оффлайн Дмитрий Загорулькин

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 737
С этими транзакциями прям беда. Одна из главных проблем OpenCloseTransaction в том, что нельзя открывать в них вложенные транзакции.
Разве? А что мешает?
Не верю. Это говорит лишь о том, что ты что-то делаешь неправильно.
Вот тут я согласен с Александром Наумовичем. Тут вот в чем дело. Транзакции хорошо использовать в простых сценариях, например: выбрал объекты на чертеже, открыл транзакцией, модифицировал, закрыл. Когда производишь обработку в методах Overrule или в обработчиках событий, работаешь с неактивным документом и пр. продвинутые сценарии - то транзакции могут сбоить. Можно рискнуть и попробовать их использовать, но проще сразу писать код без них.
Простая замена Transaction на OpenCloseTransaction - это не выход. Нужно тщательно следить за всеми открытыми объектами. Закрывать их сразу, как только они перестают быть нужными. Следить, чтобы открытый на запись объект случайно не открывался повторно и т.д. и т.п. Сам на этом регулярно спотыкаюсь. Часто вместо OpenCloseTransaction лучше напрямую открывать объект через ObjectId.Open с использованием блока using. Потому что открытый в OpenCloseTransaction объект остается открытым до тех пор, пока выполнение не дойдет до Commit или Abort.
P.S. Как-то Александр Наумович говорил, что транзакции менее требовательны к качеству кода и прощают многие ошибки разработчика. Если запустить такой код без транзакций, то все это выплывает наружу.
Цитировать
eWasOpenForRead
Выдается при попытке модифицировать объект, который открыт на чтение.
Цитировать
eLockViolation
Попытка внести изменения в незаблокированный документ.
Обычно, при возникновении этих исключений, в режиме отладки студия показывает место в коде, где исключение произошло. Так что, с поиском проблемы не должно быть больших сложностей.

Оффлайн Александр Пекшев aka ModisАвтор темы

  • ADN Club
  • *****
  • Сообщений: 1658
  • Карма: 366
  • Отец modplus.org
    • ModPlus
Разве? А что мешает?
Вот тут с примером описано про это
Вот тут я согласен с Александром Наумовичем. Тут вот в чем дело. Транзакции хорошо использовать в простых сценариях, например: выбрал объекты на чертеже, открыл транзакцией, модифицировал, закрыл. Когда производишь обработку в методах Overrule или в обработчиках событий, работаешь с неактивным документом и пр. продвинутые сценарии - то транзакции могут сбоить. Можно рискнуть и попробовать их использовать, но проще сразу писать код без них.
Простая замена Transaction на OpenCloseTransaction - это не выход. Нужно тщательно следить за всеми открытыми объектами. Закрывать их сразу, как только они перестают быть нужными. Следить, чтобы открытый на запись объект случайно не открывался повторно и т.д. и т.п. Сам на этом регулярно спотыкаюсь. Часто вместо OpenCloseTransaction лучше напрямую открывать объект через ObjectId.Open с использованием блока using. Потому что открытый в OpenCloseTransaction объект остается открытым до тех пор, пока выполнение не дойдет до Commit или Abort.
P.S. Как-то Александр Наумович говорил, что транзакции менее требовательны к качеству кода и прощают многие ошибки разработчика. Если запустить такой код без транзакций, то все это выплывает наружу
Да я во многом согласен. Вот этот код, который в топике, где меняются примитивы в BlockTableRecord хотелось бы сделать универсальным, но видимо не получится. При создании объекта и работе Jig все нормально. При работе с ручками получается и вариант с транзакцией и вариант с эмуляцией транзакции. Но вот к работе с ObjectOverrule он не подходит, т.к. BlockReference уже открыт. А еще там могут создаваться копии объекта

Оффлайн Дмитрий Загорулькин

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 737
Вот тут с примером описано про это
Ну так надо делать правильные выводы. Вывод из этого примера: нельзя дважды открыть объект на запись при использовании OpenCloseTransaction, в отличие от Transaction. Заменить в первой транзакции OpenMode.ForWrite на OpenMode.ForRead (ну и убрать модификацию объекта в первой транзакции) - и все нормально отработает.
« Последнее редактирование: 07-06-2017, 15:04:18 от Дмитрий Загорулькин »

Оффлайн Александр Пекшев aka ModisАвтор темы

  • ADN Club
  • *****
  • Сообщений: 1658
  • Карма: 366
  • Отец modplus.org
    • ModPlus
Ну так надо делать правильные выводы. Вывод из этого примера: нельзя дважды открыть объект на запись при использовании OpenCloseTransaction, в отличие от Transaction. Замени в первой транзакции OpenMode.ForWrite на OpenMode.ForRead (ну и убрать модификацию объекта в первой транзакции) - и все нормально отработает.
Я про это уже выше писал - про универсальность - в какой-то момент это так и срабатывает, а в какой-то - ровно наоборот (т.е. передается объект открытый на чтение и нужно открыть на запись). Придется делать различные методы - для вызова из Jig, для вызова из GripsOverrule и для вызова из ObjectOverrule. Причем два последних еще вдобавок и коррелируют между собой, и создают копии

Оффлайн Дмитрий Загорулькин

  • ADN
  • *
  • Сообщений: 2531
  • Карма: 737
Ну тут, как говорится: Взялся за гуж - не говори, что не дюж.
Если уж так глубоко погрузились в API, что используете Overrules, то надо уметь работать с объектами без транзакций. Тут уж много тонкостей и особенностей, но такова специфика.

Оффлайн Александр Пекшев aka ModisАвтор темы

  • ADN Club
  • *****
  • Сообщений: 1658
  • Карма: 366
  • Отец modplus.org
    • ModPlus
В общем я нашел "костыль". Вот этот метод, который я выше писал для тестов GetBlockTableRecordOpenCloseTransaction (название конечно неверное) я оставил для использования в случае работы команды отмены, т.е. при условии if (dbObject != null && dbObject.IsUndoing & dbObject.IsModifiedXData) и изменил таким образом, что убрал из него стирание объектов и оставил создание нового BlockTableRecord с последующим заполнением объектами. Таким образом при каждой команде UNDO у меня получается новый анонимный блок.
Без транзакций это не обходится - как и в коде выше сначала нужна обычная транзакция для создания BlockTableRecord, а потом можно и эмуляцию для ее правки

Оффлайн Александр Пекшев aka ModisАвтор темы

  • ADN Club
  • *****
  • Сообщений: 1658
  • Карма: 366
  • Отец modplus.org
    • ModPlus
Дабы не создавать новых тем, спрошу тут:
Если я изменяю свойства примитива (блока) через палитру свойств (и только!), то в случае что был выбран один блок - все нормально. В случае, если выбрано больше, то ловится ошибка AccessViolationException на строчке base.Close(dbObject). При изменении блоков другими способами (копирование, масштабирование и пр) - все нормально.
Это тоже может быть связанно с транзакциями? Срабатывание метода Close() происходит одновременно или по очереди для всех объектов?

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13882
  • Карма: 1787
  • Рыцарь ObjectARX
  • Skype: rivilis
Это тоже может быть связанно с транзакциями? Срабатывание метода Close() происходит одновременно или по очереди для всех объектов?
Одновременно не может быть - это очевидно.
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение