-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCommands.cs
More file actions
324 lines (283 loc) · 20 KB
/
Commands.cs
File metadata and controls
324 lines (283 loc) · 20 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
// #nullable disable — отключаем строгую проверку на null (пустые значения).
// В .NET 8 это включено по умолчанию, и без этой строки будет много желтых предупреждений,
// которые новичку только мешают.
#nullable disable
using System;
using System.IO; // Работа с файловой системой (Path, File, FileStream)
using System.Reflection; // "Рефлексия" - позволяет программе смотреть внутрь самой себя (нужно для ресурсов)
// --- ПРОСТРАНСТВА ИМЕН AUTOCAD ---
using Autodesk.AutoCAD.Runtime; // Атрибуты команд [CommandMethod]
using Autodesk.AutoCAD.ApplicationServices; // Доступ к приложению (Application, Document)
using Autodesk.AutoCAD.DatabaseServices; // Работа с базой DWG (Transaction, BlockTable, Entity)
using Autodesk.AutoCAD.EditorInput; // Взаимодействие с пользователем (Selection, Point)
using Autodesk.AutoCAD.Geometry; // Геометрия (Point3d, Scale3d)
using Autodesk.AutoCAD.Colors; // Цвета (Color)
namespace PoseEdit2026
{
// Этот класс не обязательно должен быть static, но методы команд должны быть public.
public class Commands
{
// ---------------------------------------------------------------------------------------
// КОНСТАНТЫ И НАСТРОЙКИ
// ---------------------------------------------------------------------------------------
// Полные имена ресурсов (файлов DWG), зашитых внутри нашей DLL.
// Формат строго такой: "ИмяПроекта.Папка.ИмяФайла.расширение"
// Если проект "PoseEdit2026", папка "Resources", файл "RL-POS.dwg":
private const string ResourceName1 = "PoseEdit2026.Resources.RL-POS.dwg";
private const string ResourceName2 = "PoseEdit2026.Resources.RL-POS2.dwg";
// Имя слоя, на который мы будем автоматически помещать наши блоки.
private const string TargetLayer = "ren.mtr.tb";
// ---------------------------------------------------------------------------------------
// ГЛАВНАЯ КОМАНДА "EEN"
// ---------------------------------------------------------------------------------------
// [CommandMethod] регистрирует команду в AutoCAD.
// Когда пользователь введет "EEN", вызовется этот метод.
[CommandMethod("EEN")]
public void EditPoseCommand()
{
// Получаем доступ к текущему открытому чертежу (Document)
Document doc = Application.DocumentManager.MdiActiveDocument;
if (doc == null) return; // Если нет открытых документов - выходим
Editor ed = doc.Editor; // "Редактор" - отвечает за общение (ввод/вывод)
Database db = doc.Database; // "База данных" - здесь хранятся все линии и блоки
// 1. СОХРАНЕНИЕ ПОЛЬЗОВАТЕЛЬСКИХ НАСТРОЕК
// Мы будем менять системные переменные, чтобы команда работала корректно.
// Хороший тон программиста — запомнить, как было, и вернуть всё назад в конце.
object oldClayer = Application.GetSystemVariable("CLAYER"); // Текущий слой
object oldDimzin = Application.GetSystemVariable("DIMZIN"); // Подавление нулей
object oldAttreq = Application.GetSystemVariable("ATTREQ"); // Запрос атрибутов при вставке
// Логические флаги
bool isNewBlock = false; // Мы создаем новый или редактируем старый?
ObjectId blockId = ObjectId.Null; // ID блока, с которым будем работать
Point3d insertPoint = Point3d.Origin; // Точка вставки (нужна для поворота)
try
{
// 2. НАСТРОЙКА СРЕДЫ ПЕРЕД РАБОТОЙ
// CMDECHO = 0: Отключаем "спам" в командной строке от выполняемых скриптов
Application.SetSystemVariable("CMDECHO", 0);
// DIMZIN = 1: Не подавлять нули (важно для корректного чтения чисел)
Application.SetSystemVariable("DIMZIN", 1);
// ATTREQ = 0: Самое важное! Отключаем запрос атрибутов.
// Иначе при вставке блока AutoCAD "зависнет", ожидая, что пользователь введет текст атрибутов.
Application.SetSystemVariable("ATTREQ", 0);
// Проверяем, существует ли слой "ren.mtr.tb". Если нет — создаем его.
EnsureLayerExists(db, TargetLayer);
// 3. ВЫБОР ОБЪЕКТА
// Создаем фильтр выбора. Разрешаем выбирать только:
// 1. Объекты типа INSERT (Блоки)
// 2. С именами "RL-POS" или "RL-POS2"
// C# 12: Используем новый синтаксис коллекций (collection expressions)
// Вместо new TypedValue[] { ... } теперь можно писать просто [ ... ]
TypedValue[] filterList = [
new TypedValue((int)DxfCode.Start, "INSERT"),
new TypedValue((int)DxfCode.BlockName, "RL-POS,RL-POS2")
];
SelectionFilter filter = new SelectionFilter(filterList);
// Настройки запроса
PromptSelectionOptions selOpts = new PromptSelectionOptions();
selOpts.MessageForAdding = "\nSelect position (RL-POS or RL-POS2) or press Enter for new: ";
selOpts.SingleOnly = true; // Разрешаем выбрать только один объект за раз
// Просим пользователя выбрать
PromptSelectionResult selRes = ed.GetSelection(selOpts, filter);
// 4. ЛОГИКА: РЕДАКТИРОВАНИЕ ИЛИ СОЗДАНИЕ?
if (selRes.Status == PromptStatus.OK)
{
// === ВЕТКА А: Пользователь выбрал блок ===
blockId = selRes.Value[0].ObjectId; // Запоминаем ID выбранного блока
isNewBlock = false; // Это не новый блок
}
else
{
// === ВЕТКА Б: Пользователь нажал Enter (ничего не выбрал) ===
isNewBlock = true;
// Спрашиваем точку вставки
PromptPointOptions ptOpts = new PromptPointOptions("\nInsertion point for new position: ");
PromptPointResult ptRes = ed.GetPoint(ptOpts);
// Если пользователь нажал Esc — прерываем команду
if (ptRes.Status != PromptStatus.OK) return;
insertPoint = ptRes.Value;
// Переключаем текущий слой на наш целевой
Application.SetSystemVariable("CLAYER", TargetLayer);
// --- ВСТАВКА ИЗ РЕСУРСОВ ---
// Вызываем наш метод ImportBlockFromResource.
// Он достанет файл из DLL и вставит его в чертеж.
blockId = ImportBlockFromResource(db, ResourceName1, insertPoint, 1.0);
// Если метод вернул Null, значит произошла ошибка (например, неправильное имя ресурса)
if (blockId == ObjectId.Null)
{
ed.WriteMessage("\nError: Failed to load block from DLL resources.");
return;
}
}
// 5. ЗАПУСК ОКНА WPF
// Создаем экземпляр нашего окна PoseEditWindow
PoseEditWindow win = new PoseEditWindow(blockId);
// ShowModalWindow — это специальный метод AutoCAD для запуска WPF окон.
// Он блокирует AutoCAD, пока окно открыто.
// Возвращает bool? (true, false или null).
// true = мы закрыли окно через this.DialogResult = true (кнопка Update).
bool? result = Application.ShowModalWindow(win);
// Если пользователь нажал "Update Pose"
if (result == true)
{
// Если блок был НОВЫМ, нужно дать пользователю повернуть его.
// В .NET сложно сделать интерактивный поворот (Jig), поэтому мы просто
// вызываем стандартную команду _.ROTATE.
if (isNewBlock)
{
ed.Command("_.ROTATE", blockId, "", insertPoint);
}
}
// Если пользователь нажал "Discard" (Отмена) или закрыл крестиком
else if (isNewBlock)
{
// Если блок был новым, его надо удалить, чтобы не оставлять мусор на чертеже.
using (Transaction tr = db.TransactionManager.StartTransaction())
{
// Проверяем, жив ли еще объект (вдруг пользователь успел его удалить)
if (!blockId.IsNull && !blockId.IsErased)
{
Entity ent = tr.GetObject(blockId, OpenMode.ForWrite) as Entity;
ent.Erase(); // Удаляем
}
tr.Commit();
}
}
}
catch (System.Exception ex)
{
// Ловим любые неожиданные ошибки и пишем их в командную строку
ed.WriteMessage("\nCritical error: " + ex.Message);
}
finally
{
// 6. БЛОК FINALLY (ВЫПОЛНЯЕТСЯ ВСЕГДА)
// Восстанавливаем системные переменные, даже если программа упала с ошибкой.
try
{
if (oldClayer != null) Application.SetSystemVariable("CLAYER", oldClayer);
if (oldDimzin != null) Application.SetSystemVariable("DIMZIN", oldDimzin);
if (oldAttreq != null) Application.SetSystemVariable("ATTREQ", oldAttreq);
Application.SetSystemVariable("CMDECHO", 1);
}
catch { /* Игнорируем ошибки при восстановлении */ }
}
}
// ---------------------------------------------------------------------------------------
// МЕТОД: ИМПОРТ БЛОКА ИЗ РЕСУРСОВ DLL
// ---------------------------------------------------------------------------------------
private ObjectId ImportBlockFromResource(Database db, string resourceId, Point3d position, double scale)
{
// Получаем ссылку на текущую сборку (наш файл .dll)
Assembly assembly = Assembly.GetExecutingAssembly();
// Вычисляем чистое имя блока (для AutoCAD) из имени ресурса.
// Из "PoseEdit2026.Resources.RL-POS.dwg" делаем "RL-POS".
string cleanBlockName = resourceId.Replace("PoseEdit2026.Resources.", "").Replace(".dwg", "");
// Открываем поток данных (Stream) из ресурса
using (Stream stream = assembly.GetManifestResourceStream(resourceId))
{
// Если stream == null, значит файл не найден (ошибка в имени ресурса)
if (stream == null)
{
string[] resources = assembly.GetManifestResourceNames();
Application.ShowAlertDialog("Resource not found: " + resourceId + "\n\nAvailable:\n" + string.Join("\n", resources));
return ObjectId.Null;
}
// AutoCAD не умеет вставлять блоки из потока памяти (Stream). Ему нужен файл на диске.
// Поэтому мы создаем временный файл.
string tempFile = Path.GetTempFileName();
try
{
// Копируем байты из DLL во временный файл
using (FileStream fileStream = File.OpenWrite(tempFile))
{
stream.CopyTo(fileStream);
}
ObjectId btrId = ObjectId.Null; // ID определения блока (Definition)
// Начинаем транзакцию
using (Transaction tr = db.TransactionManager.StartTransaction())
{
// Открываем таблицу блоков чертежа для записи
BlockTable bt = tr.GetObject(db.BlockTableId, OpenMode.ForWrite) as BlockTable;
// Проверяем: может, такой блок уже есть в чертеже?
if (bt.Has(cleanBlockName))
{
btrId = bt[cleanBlockName]; // Если есть, берем его ID
}
else
{
// Если нет, создаем "стороннюю базу данных" для временного файла
using (Database sourceDb = new Database(false, true))
{
// Читаем временный файл как DWG
sourceDb.ReadDwgFile(tempFile, FileOpenMode.OpenForReadAndAllShare, true, "");
// Вставляем (импортируем) блок в наш чертеж
btrId = db.Insert(cleanBlockName, sourceDb, true);
}
}
// Теперь создаем "Вхождение блока" (BlockReference) - то, что мы видим на экране
// btrId - это чертеж блока в памяти.
// blkRef - это ссылка на этот чертеж в конкретной точке.
BlockTableRecord btr = tr.GetObject(btrId, OpenMode.ForRead) as BlockTableRecord;
using (BlockReference blkRef = new BlockReference(position, btrId))
{
blkRef.ScaleFactors = new Scale3d(scale, scale, scale); // Масштаб
// Добавляем блок в текущее пространство (Модель или Лист)
BlockTableRecord curSpace = tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite) as BlockTableRecord;
curSpace.AppendEntity(blkRef);
tr.AddNewlyCreatedDBObject(blkRef, true); // Регистрируем новый объект
// --- КОПИРОВАНИЕ АТРИБУТОВ ---
// При программной вставке атрибуты НЕ создаются автоматически.
// Мы должны вручную пробежаться по определению блока и создать AttributeReference для каждого AttributeDefinition.
if (btr.HasAttributeDefinitions)
{
foreach (ObjectId id in btr)
{
// ПРОБЛЕМА МОЖЕТ БЫТЬ ТУТ: Если объект удален или некорректен
DBObject obj = tr.GetObject(id, OpenMode.ForRead);
if (obj is AttributeDefinition attDef) // Безопасная проверка типа
{
using (AttributeReference attRef = new AttributeReference())
{
attRef.SetAttributeFromBlock(attDef, blkRef.BlockTransform);
blkRef.AttributeCollection.AppendAttribute(attRef);
tr.AddNewlyCreatedDBObject(attRef, true);
}
}
}
}
tr.Commit(); // Применяем изменения
return blkRef.ObjectId; // Возвращаем ID созданного блока
}
}
}
finally
{
// Удаляем временный файл, чтобы не мусорить на диске
if (File.Exists(tempFile)) try { File.Delete(tempFile); } catch { }
}
}
}
// ---------------------------------------------------------------------------------------
// МЕТОД: СОЗДАНИЕ СЛОЯ
// ---------------------------------------------------------------------------------------
private void EnsureLayerExists(Database db, string layerName)
{
using (Transaction tr = db.TransactionManager.StartTransaction())
{
LayerTable lt = tr.GetObject(db.LayerTableId, OpenMode.ForRead) as LayerTable;
// Если слоя нет - создаем
if (!lt.Has(layerName))
{
lt.UpgradeOpen(); // Разрешаем запись в таблицу слоев
LayerTableRecord newLayer = new LayerTableRecord();
newLayer.Name = layerName;
newLayer.Color = Color.FromColorIndex(ColorMethod.ByAci, 7); // Цвет 7 (Белый/Черный)
lt.Add(newLayer);
tr.AddNewlyCreatedDBObject(newLayer, true);
}
tr.Commit();
}
}
}
}