Время от времени, в AutoCAD возникает необходимость преобразовать (не заменить, а именно преобразовать) один примитив в другой. Под преобразованием я подразумеваю, что результат будет иметь те же самые значения ObjectId и Handle, какие имел до операции преобразования, а так же сохранит уже имеющуюся подписку на события. В качестве примера преобразования, которое может потребоваться на практике - преобразовать окружность в дугу.
Для того, чтобы решить обозначенную проблему, следует прибегать к помощи метода DBObject.HandOverTo().
Метод DBObject.HandOverTo() имеет следующую сигнатуру:
Исходный код примера
Теперь запустим команду CircleToArc. Результат будет выглядеть так:
Как видим, окружность была преобразована в дугу. Изменив радиус дуги, мы видим, что у нас автоматически изменяется и содержимое текстовых объектов. Т.о. все привязки, созданные нами для окружности, были сохранены и для дуги:
Внимание! Если вы не особо знакомы с тем, как изменять объекты базы данных чертежа при помощи транзакции и без неё (в т. ч. с эмуляцией транзакции) - рекомендую предварительно прочесть это.
Иногда вместо преобразования можно воспользоваться обычной заменой, т.е. удалить существующий объект и на его месте создать новый. Но проблема в том, что новый объект получит иное значение для ObjectId и для Handle. Как следствие - все поля (Fields), ранее ссылавшиеся на некоторые свойства удалённого нами объекта, потеряют связь с источником и в качестве значения будут отображать ######. Кроме того, Если ранее была осуществлена подписка на некоторые события (Events) удаляемого нами объекта, то в случае обычной замены, мы уже не получим ожидаемой реакции.
Для того, чтобы решить обозначенную проблему, следует прибегать к помощи метода DBObject.HandOverTo().
Метод DBObject.HandOverTo() имеет следующую сигнатуру:
1: public void HandOverTo(
2: [CallerMustClose] DBObject newPointer,
3: [MarshalAs(UnmanagedType.U1)] bool keepXData,
4: [MarshalAs(UnmanagedType.U1)] bool keepExtensionDictionary
5: );
Метод DBObject.HandOverTo() предоставляет возможность подменять имеющиеся в базе данных объекты (DBRO - database-resident object), иными объектами, не входящими в состав этой базы данных (NDBRO - non-database-resident object), при этом подменяющему объекту будут назначены ObjectId, Handle и список зарегистрированных событий подменяемого объекта. Пример такой ситуации - когда используется команда _.BREAK для того, чтобы разрезав Circle, преобразовать его в Arc. Arc - это абсолютно другой класс, отличный от Circle, таким образом, в базе данных чертежа, новым объектом Arc нужно заменить уже существующий объект Circle, при этом Arc должны быть назначены Handle и ObjectId, которые до этого имел Circle.
Для того, чтобы использовать метод DBObject.HandOverTo(), тот объект, который будет заменен (Circle в примере выше), должен быть открыт в режиме OpenMode.ForWrite. Метод DBObject.HandOverTo() объекта вызывается с параметром newPointer, указывающим на новый, "не резидентный объект базы данных", который его заменяет (Arc в примере выше).
Параметр keepXData следует назначать true, если xdata должен быть сохранен, или же false - если xdata должен быть выброшен.
Параметр keepExtensionDictionary следует назначать true, если расширенный словарь данных и его содержание должны быть сохранены, или же false - если словарь расширенных данных и его содержание должны быть выброшены.
Внимание! Если в коде не используется OpenCloseTransaction (ниже в коде я покажу оба варианта), то после того, как замещяемый объект (Circle в примере выше) открыт с опцией OpenMode.ForWrite - для него позднее должен быть обязательно вызван метод Dispose(), а для замещающего - метод Close() для того, чтобы зафиксировать изменение и закрыть запись в файле "UNDO.ac$". Обратите внимание на то, что удалять старый объект (Circle в примере выше), вызывая экземплярный метод Erase(), в данном случае не нужно, так как эта операция удаления выполняется в коде метода DBObject.HandOverTo(). Однако очистка памяти от замещаемого объекта (Circle в примере выше) путём вызова его метода Dispose() нужна, т. к. в коде метода HandOverTo() эта очистка не выполняется.
Примечание
Файл UNDO.ac$ находится в каталоге %Temp% и используется в AutoCAD для того, чтобы можно было отменить уже выполненные изменения.
Файл REDO.ac$ находится в каталоге %Temp% и используется в AutoCAD для того, чтобы можно было вернуть обратно уже отменённые изменения.
Внимание!
Метод DBObject.HandOverTo()не разрешён к использованию на объектах, которые являются резидентными объектами транзакции. Если объект, на котором вызывают метод, является резидентным объектом транзакции, то метод DBObject.HandOverTo() не выполнит ожидаемой от него работы. В данном случае, под "резидентным" подразумеваются не только созданные в рамках транзакции объекты, но и все объекты, открытые в этой транзакции.
Пример использования
В качестве примера использования DBObject.HandOverTo() выполним следующие действия (программно):
- Создадим окружность
- Создадим однострочный текст с полем, связанным со значеним радиуса окружности, созданной в п.1.
- Создадим однострочный текст без поля.
- Зарегистрируемся на событие Modified окружности, созданной в п.1, считывая значение её радиуса и обновляя им содержимое текста, созданного в п.3.
- Заменим окружность дугой и проверим, продолжают ли текстовые объекты по прежнему считывать информацию, но уже с дуги, вместо окружности.
После выполнения указанной в п.5 замены, все текстовые объекты, созданные в п.2 и п.3 должны по прежнему корректно обновляться в случае изменения свойств дуги.
1: // © Andrey Bushman, 2013
2: // Загрузив сборку в AutoCAD, сначала выполните команду AddCircleAndText,
3: // создав тем самым окружность и пару текстовых объектов, один из которых содержит поле.
4: // Затем запустите команду CircleToArc и смотрите результат преобразования.
5:
6: using System;
7: using cad = Autodesk.AutoCAD.ApplicationServices.Application;
8: using AppSrv = Autodesk.AutoCAD.ApplicationServices;
9: using DbSrv = Autodesk.AutoCAD.DatabaseServices;
10: using EdInp = Autodesk.AutoCAD.EditorInput;
11: using Geom = Autodesk.AutoCAD.Geometry;
12: using Rtm = Autodesk.AutoCAD.Runtime;
13:
14: [assembly: Rtm.CommandClass(typeof(AndreyBushman.Samples.TestClass))]
15:
16: namespace AndreyBushman.Samples {
17:
18: public sealed class TestClass {
19:
20: static DbSrv.ObjectId idText = DbSrv.ObjectId.Null;
21: static DbSrv.ObjectId idGeom = DbSrv.ObjectId.Null;
22:
23:
24: [Rtm.CommandMethod("AddCircleAndText")]
25: public void AddCircleAndText() {
26:
27: AppSrv.Document doc = cad.DocumentManager.MdiActiveDocument;
28: DbSrv.Database db = doc.Database;
29: EdInp.Editor ed = doc.Editor;
30:
31: // Используем транзакцию
32: using (DbSrv.Transaction tr = db.TransactionManager.StartTransaction()) {
33:
34: // Добавляем окружность
35: DbSrv.Circle circle = new DbSrv.Circle();
36: circle.SetDatabaseDefaults();
37: circle.Center = new Geom.Point3d(0.0, 0.0, 0.0);
38: circle.Radius = 30.0;
39:
40: DbSrv.BlockTable bt = tr.GetObject(db.BlockTableId, DbSrv.OpenMode.ForRead)
41: as DbSrv.BlockTable;
42:
43: DbSrv.BlockTableRecord ms = tr.GetObject(bt[DbSrv.BlockTableRecord.ModelSpace],
44: DbSrv.OpenMode.ForWrite) as DbSrv.BlockTableRecord;
45:
46: ms.AppendEntity(circle);
47: tr.AddNewlyCreatedDBObject(circle, true);
48:
49: // Добавляем текст с полем
50: DbSrv.DBText txtFild = new DbSrv.DBText();
51: txtFild.SetDatabaseDefaults();
52: txtFild.Position = new Geom.Point3d(0.0, 40.0, 0.0);
53: txtFild.TextString = @"%<\AcObjProp.16.2 Object(%<\_ObjId " + circle.ObjectId.OldIdPtr
54: + @">%).Radius \f ""%lu2%pr2%ps[, mm]%zs8"">%";
55:
56: ms.AppendEntity(txtFild);
57: tr.AddNewlyCreatedDBObject(txtFild, true);
58:
59: // Добавляем текст без поля
60: DbSrv.DBText txt2 = new DbSrv.DBText();
61: txt2.SetDatabaseDefaults();
62: txt2.Position = new Geom.Point3d(0.0, 50.0, 0.0);
63: txt2.TextString = circle.Radius.ToString() + " mm";
64:
65: ms.AppendEntity(txt2);
66: tr.AddNewlyCreatedDBObject(txt2, true);
67:
68: idText = txt2.ObjectId;
69: idGeom = circle.ObjectId;
70:
71: circle.Modified += circle_Modified;
72:
73: tr.Commit();
74: }
75: }
76:
77: void circle_Modified(object sender, EventArgs e) {
78:
79: if (idText == DbSrv.ObjectId.Null || idText.IsErased || !idText.IsValid)
80: return;
81:
82: AppSrv.Document doc = cad.DocumentManager.MdiActiveDocument;
83: DbSrv.Database db = doc.Database;
84: EdInp.Editor ed = doc.Editor;
85:
86: using (DbSrv.Transaction tr = db.TransactionManager.StartTransaction()) {
87: DbSrv.DBText txt2 = tr.GetObject(idText, DbSrv.OpenMode.ForWrite) as DbSrv.DBText;
88:
89: if (sender is DbSrv.Circle) {
90: DbSrv.Circle circle = sender as DbSrv.Circle;
91: txt2.TextString = Math.Round(circle.Radius, 2).ToString() + " mm";
92: }
93: else if (sender is DbSrv.Arc) {
94: DbSrv.Arc arc = sender as DbSrv.Arc;
95: txt2.TextString = Math.Round(arc.Radius, 2).ToString() + " mm";
96: }
97: else {
98: txt2.TextString = "Объект не является окружностью или дугой!";
99: }
100: tr.Commit();
101: }
102: }
103:
104: /// <summary>
105: /// В этом варианте метода CircleToArc операцию преобразования выполняем с эмуляцией транзакции
106: /// </summary>
107: [Rtm.CommandMethod("CircleToArc")]
108: public void CircleToArc() {
109:
110: AppSrv.Document doc = cad.DocumentManager.MdiActiveDocument;
111: DbSrv.Database db = doc.Database;
112: EdInp.Editor ed = doc.Editor;
113:
114: using (DbSrv.OpenCloseTransaction tr = db.TransactionManager.StartOpenCloseTransaction()) {
115:
116: if (idGeom == DbSrv.ObjectId.Null || idGeom.IsErased || !idGeom.IsValid)
117: return;
118:
119: DbSrv.Circle circle = tr.GetObject(idGeom, DbSrv.OpenMode.ForRead) as DbSrv.Circle;
120:
121: // Создаём дугу
122: DbSrv.Arc arc = new DbSrv.Arc(new Geom.Point3d(0.0, 0.0, 0.0), circle.Radius, 1.117, 3.5605);
123: arc.SetDatabaseDefaults();
124:
125: circle.UpgradeOpen();
126: circle.HandOverTo(arc, true, true);
127: Boolean x = circle.IsDisposed; // Здесь получаю false, значит очистить память нужно самому:
128: circle.Dispose();
129: tr.AddNewlyCreatedDBObject(arc, true);
130: tr.Commit();
131: }
132: }
133: }
134: }
Обратите внимание на реализацию метода CircleToArc - в нём модификация объектов выполняется в контексте эмуляции транзакции. Этот вариант более предпочтителен, чем редактировать объекты без такой эмуляции, что выглядело бы так (альтернативный вариант реализации метода CircleToArc):
1: /// <summary>
2: /// В этом варианте показан вызов метода HandOverTo без использования эмуляции транзакции,
3: /// т. е. без работы в контексте объекта OpenCloseTransaction.
4: /// </summary>
5: [Rtm.CommandMethod("CircleToArc")]
6: public void CircleToArc() {
7:
8: AppSrv.Document doc = cad.DocumentManager.MdiActiveDocument;
9: DbSrv.Database db = doc.Database;
10: EdInp.Editor ed = doc.Editor;
11:
12: // ВНИМАНИЕ! В данном случае следует использовать именно метод Open, а не GetObject.
13: // Метод GetObject следует использовать лишь в рамках эмуляции транзакции (OpenCloseTransaction).
14: DbSrv.Circle circle = idGeom.Open(DbSrv.OpenMode.ForRead) as DbSrv.Circle;
15: DbSrv.Arc arc = new DbSrv.Arc(new Geom.Point3d(0.0, 0.0, 0.0), circle.Radius, 1.117, 3.5605);
16: arc.SetDatabaseDefaults();
17: circle.UpgradeOpen();
18: circle.HandOverTo(arc, true, true);
19: arc.Close();
20: Boolean x = circle.IsDisposed; // Здесь получаю false, значит очистить память нужно самому:
21: circle.Dispose();
22:
23: // ВНИМАНИЕ! Не следует вызывать метод circle.Erase(), т. к. удаление этого объекта выполняется
24: // в коде метода HandOverTo.
25: }
Загрузив в AutoCAD данный код и запустив на исполнение команду AddCircleAndText, мы получим такой результат:
При изменении радиуса данной окружности, верхний текст изменяется автоматически, а для обновления нижнего необходимо выполнять регенерацию (поскольку это поле):
Теперь запустим команду CircleToArc. Результат будет выглядеть так:
Как видим, окружность была преобразована в дугу. Изменив радиус дуги, мы видим, что у нас автоматически изменяется и содержимое текстовых объектов. Т.о. все привязки, созданные нами для окружности, были сохранены и для дуги:
Комментариев нет:
Отправить комментарий