В последующих шагах содержится инструкция по реализации метода CustomPlotSettings().
Шаг 3. Получение доступа к документу nanoCAD и его настройкам.
// Получение ссылки на активный документ, его базу данных и редактор
HostMgd.ApplicationServices.Document doc =
HostMgd.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
nanoCAD.Document comDoc = doc.AcadDocument as nanoCAD.Document;
Teigha.DatabaseServices.Database db = doc.Database;
HostMgd.EditorInput.Editor editor = doc.Editor;
Шаг 4. Получение ссылки на активный лист и расширенные настройки печати.
// Получение ссылки на активный лист документа
OdaX.IAcadLayout activeLayout = comDoc.ActiveLayout;
// Получение ссылки на свойство Plot активного документа
nanoCAD.Plot plot = (nanoCAD.Plot)comDoc.Plot;
// Получение ссылки на расширенные настройки печати
nanoCAD.InanoCADPlotCustomParams customPlotSettings =
plot.CustomPlotSettings[activeLayout];
// Назначение активному листу встроенного pdf-принтера
activeLayout.ConfigName = "Встроенный pdf-принтер";
Также сразу назначаем встроенный pdf-принтер активному листу.
Шаг 5. Изменяем расширенные настройки через переменную customPlotSettings и передаем измененные настройки в документ.
// Определяем, как выровнять область печати на листе
customPlotSettings.PaperOutMargins = 1;
customPlotSettings.Alignment = 3;
// Определяем толщину рамки вокруг области печати
customPlotSettings.InflateWidth = 10;
// Определяем, в какую папку сохранить напечатанный файл
customPlotSettings.UseDWGPath = false;
customPlotSettings.FileFolder = "C:\\Work";
// Определяем, с каким именем будет сохранен напечатанный файл
customPlotSettings.UsePredefinedNames = true;
customPlotSettings.FileMask = "<DN>_<LN>";
// Определяем разрешение печати
customPlotSettings.PrinterDPI = 1000;
// Определяем действия при наличии в указанной нами папке для сохранения
// файла с таким же именем
customPlotSettings.IfFileExist = 1;
// Определяем, открывать документ сразу после печати или нет
customPlotSettings.RunPDFApp = true;
// Определяем признак создания комментариев SHX
using (Teigha.DatabaseServices.Transaction transaction =
db.TransactionManager.StartTransaction())
{
HostMgd.ApplicationServices.Application.SetSystemVariable("PDFSHX", false);
transaction.Commit();
}
// Добавляем несколько областей печати «Рамка» в активный лист
for(int i = 0; i <= 2; i++)
{
// Очистка коллекции PlotAreas перед добавлением первой новой «Рамки»
if (i==0) customPlotSettings.PlotAreas.Clear();
// Запрос точки левого нижнего угла области печати
var point1 = editor.GetPoint("Укажите левую нижнюю точку области печати:");
if (point1.Status == HostMgd.EditorInput.PromptStatus.OK)
{
// Запрос точки правого верхнего угла области печати
var point2 = editor.GetCorner("Укажите правую верхнюю точку области печати:", point1.Value);
if (point2.Status == HostMgd.EditorInput.PromptStatus.OK && point2.Value != point1.Value)
{
// Добавление области печати «Рамка» в коллекцию PlotAreas
customPlotSettings.PlotAreas.Add(new double[] { point1.Value.X, point1.Value.Y },
new double[] { point2.Value.X, point2.Value.Y });
}
else { editor.WriteMessage("Введены неверные данные"); return; }
}
else { editor.WriteMessage("Введены неверные данные"); return; }
}
// Проверка, установлен ли активному листу тип области печати «Рамка»
if (activeLayout.PlotType != OdaX.AcPlotType.acWindow)
activeLayout.PlotType = OdaX.AcPlotType.acWindow;
// Передаем измененные настройки в активный лист
plot.CustomPlotSettings[activeLayout] = customPlotSettings;
Измененные настройки обязательно следует передать в документ, без этого действия расширенные настройки не изменятся!
Рассмотрим каждую расширенную настройку печати в том порядке, в котором они расположены в переменной customPlotSettings.
Может принимать значения, указанные на рис. 3: каждой стороне листа соответствует определенное значение типа int. Свойство применяется совместно со свойством PaperOutMargins.
Используется при значении свойства UseDWGPath = false, которое означает, что напечатанный документ будет сохранен не в папку расположения файла чертежа, а в папку, указанную в свойстве FileFolder. При значении UseDWGPath = true свойство FileFolder игнорируется.
Используется при значении свойства UsePredefinedNames = true и может содержать в себе набор символов, которые будут распознаны в nanoCAD как автозаполняемые поля. Существует несколько вариаций этих полей, которые можно комбинировать друг с другом, используя между ними разделители, допустимые в названиях файла:
Например, комбинация <DN>_<LN>_<C2> сформирует имя файла типа «Имя документа_Имя листа_01».
Возможны следующие значения:
Имеет два возможных значения: true и false.
Автоматически изменяется на true при добавлении в лист нескольких областей печати «Рамка». Через интерфейс доступно для изменения только в листах; предназначено для размещения чертежа на нескольких листах меньшего формата, чем чертеж, если принтер не поддерживает формат чертежа.
Возможны два значения (рис. 4):
По умолчанию равно 0. Применяется совместно со свойством Alignment.
Тип данных nanoCAD.PlotAreas включает в себя следующие методы и свойства:
Элементы коллекции имеют тип данных nanoCAD.PlotArea. Доступ к элементам коллекции - по индексу: customPlotSettings.PlotAreas[0]. У каждого элемента есть свойства, через которые можно изменять параметры конкретной области печати «Рамка» из коллекции.
Возможны два значения: true - печатать в файл, false - не печатать в файл. Во встроенных в nanoCAD принтерах это значение по умолчанию равно true, для остальных принтеров возможны оба варианта.
Возможные значения DPI для встроенных принтеров: 75, 150, 200, 300, 400, 600, 1000, 1200.
Возможны два значения: true - сохранить в директорию расположения документа nanoCAD; false - использовать директорию, отличную от директории расположения документа, который будет отправлен на печать.
Директорию можно указать в свойстве FileFolder либо установить свойству UsePredefinedNames значение false, чтобы перед печатью появилось диалоговое окно сохранения файла.
Возможны два значения: true - использовать шаблон имени файла, false - показать диалоговое окно для ввода имени файла.
Если свойство имеет значение true, имя файла должно быть заранее определено в свойстве FileMask.
Принимает два значения: true (или 1) - создавать комментарии SHX в pdf-файле, false (или 0) - не создавать комментарии SHX.
Шаг 5.1. Добавим вывод значений расширенных настроек в консоль до их изменения и после в виде вызова метода DisplayCustomPlotSettings().
private void DisplayCustomPlotSettings(nanoCAD.InanoCADPlotCustomParams customPlotSettings, HostMgd.EditorInput.Editor ed)
{
ed.WriteMessage("PaperOutMargins is {0}, Alignment is {1}", customPlotSettings.PaperOutMargins,
customPlotSettings.Alignment);
ed.WriteMessage("InflateWidth is {0}", customPlotSettings.InflateWidth);
ed.WriteMessage("UseDWGPath is {0}, FileFolder is {1}", customPlotSettings.UseDWGPath, customPlotSettings.FileFolder);
ed.WriteMessage("UsePredefinedNames is {0}, FileMask is {1}", customPlotSettings.UsePredefinedNames,
customPlotSettings.FileMask);
ed.WriteMessage("Printer DPI is {0}", customPlotSettings.PrinterDPI);
ed.WriteMessage("If file exist is {0}", customPlotSettings.IfFileExist);
ed.WriteMessage("RunPDFApp is {0}", customPlotSettings.RunPDFApp);
ed.WriteMessage("SHX comments are {0}", HostMgd.ApplicationServices.Application.GetSystemVariable("PDFSHX"));
ed.WriteMessage("{0} plot areas 'Window'", customPlotSettings.PlotAreas.Count);
}
Шаг 6. Компилируем наше приложение, загружаем его в nanoCAD и запускаем.
При успешном завершении работы команды CustomPlotSettings в консоли появится сообщение пользователю, показанное на рис. 5.
Итак, мы разработали .NET (C#)-приложение, которое работает только с расширенными настройками печати nanoCAD. В процессе его создания были приведены примеры работы с этими настройками печати, в том числе с выравниванием области печати на листе или добавлением нескольких областей печати «Рамка» на один лист через API nanoCAD.
На этом мы завершаем цикл материалов, посвященных API печати nanoCAD. Подводя итоги, кратко перечислим то, что вы можете узнать, изучив все четыре статьи:
В последующих шагах содержится инструкция по реализации метода SetPlotAreaToActiveLayout().
Шаг 3. Получаем доступ к документу nanoCAD и его настройкам:
// Получение ссылки на активный документ
HostMgd.ApplicationServices.Document doc =
HostMgd.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
nanoCAD.Document comDoc = doc.AcadDocument as nanoCAD.Document;
// Получение ссылки на редактор активного документа
HostMgd.EditorInput.Editor ed = doc.Editor;
// Получение ссылки на активный лист документа
OdaX.AcadLayout activeLayout = comDoc.ActiveLayout;
Помимо ссылок на .NET- и COM-документы nanoCAD, нам будут нужны:
Через редактор документа будем вести диалог с пользователем: запрашивать у него данные и получать его ответы. Через ссылку на активный лист будем получать его текущие настройки и изменять их.
Шаг 4. Формируем приглашающее сообщение пользователю для выбора области печати.
При формировании запроса лучше добавить к нему ключевые слова, чтобы у пользователя была подсказка, дающая понять, какой формат ответа будет принят и обработан программой. В нашем случае ключевыми словами запроса служат названия областей печати:
// Приглашающее сообщение для выбора области печати
HostMgd.EditorInput.PromptStringOptions promptOptions =
new HostMgd.EditorInput.PromptStringOptions("Выберите область печати");
// Запись в ключевые слова приглашающего сообщения наименований доступных типов
// областей печати, которые возможны и в пространстве модели, и в листах
promptOptions.Keywords.Add("Экран");
promptOptions.Keywords.Add("Границы");
promptOptions.Keywords.Add("Рамка");
// Для области печати "Вид" создаем коллекцию,
// в которую будем записывать названия именованных видов активного листа
System.Collections.Generic.List<string> namedViews =
new System.Collections.Generic.List<string>();
// Тип области печати "Вид" доступен только
// если у активного листа есть сохраненные именованные виды
if (comDoc.Views.Count != 0)
{
// Если коллекция Views не пустая, ищем в ней именованные виды
// активного листа
foreach (OdaX.AcadView namedView in comDoc.Views)
{
// Если найден именованный вид для активного листа,
// добавляем название этого вида в коллекцию namedViews
if (namedView.LayoutId == activeLayout.ObjectID)
namedViews.Add(namedView.Name);
}
// Если в итоге коллекция namedViews не пустая,
// то добавляем к запросу ключевое слово "Вид"
if (namedViews.Count != 0) promptOptions.Keywords.Add("Вид");
}
// Добавление к запросу ключевых слов в зависимости от типа активного листа
if (activeLayout.ModelType) promptOptions.Keywords.Add("Лимиты");
else promptOptions.Keywords.Add("Лист");
// Запрос пользователю с предложением выбрать область печати
HostMgd.EditorInput.PromptResult result = ed.GetString(promptOptions);
Сначала определяем основной текст приглашающего сообщения, потом добавляем к сообщению ключевые слова:
Сформированное нами сообщение пользователю в новом документе без сохраненных именованных видов в листе будет выглядеть так, как показано на рис. 2.
В пространстве модели вместо ключевого слова «Лист» будет другое - «Лимиты».
Шаг 5. Обработка ответа пользователя.
Теперь пользователь должен выбрать одно из ключевых слов, а программе нужно обработать его ответ.
Для начала следует проверить, совпадает ли введенный пользователем ответ с одним из ключевых слов запроса. Если да, то продолжаем обработку, если нет - завершаем работу программы, оставив настройки печати активного листа без изменений:
// Обработка полученного результата запроса
if (result.Status == HostMgd.EditorInput.PromptStatus.Keyword)
{
switch (result.StringResult)
{
}
// Составление словаря для сопоставления названий
// типов областей печати в перечислении OdaX.AcPlotType и в интерфейсе
System.Collections.Generic.Dictionary<string, OdaX.AcPlotType> dictionary =
new System.Collections.Generic.Dictionary<string, OdaX.AcPlotType>();
dictionary.Add("Экран", OdaX.AcPlotType.acDisplay);
dictionary.Add("Границы", OdaX.AcPlotType.acExtents);
dictionary.Add("Лист", OdaX.AcPlotType.acLayout);
dictionary.Add("Лимиты", OdaX.AcPlotType.acLimits);
dictionary.Add("Вид", OdaX.AcPlotType.acView);
dictionary.Add("Рамка", OdaX.AcPlotType.acWindow);
// Поиск в словаре выбранного типа области печати
// и запись найденного значения в переменную ptResult
OdaX.AcPlotType ptResult;
dictionary.TryGetValue(result.StringResult, out ptResult);
// Назначение выбранной области печати активному листу
activeLayout.PlotType = ptResult;
ed.WriteMessage("Листу '{0}' назначена область печати '{1}'",
activeLayout.Name, result.StringResult);
}
else ed.WriteMessage("Область печати не изменена");
Чтобы изменить область печати активного листа, необходимо сопоставить название области печати из запроса пользователю со значением из перечисления OdaX.AcPlotType. Для этого создаем словарь, в каждой записи которого будет пара значений: ключевое слово из запроса и соответствующее ему значение перечисления OdaX.AcPlotType. Далее, используя этот словарь, мы сможем легко находить нужное нам значение OdaX.AcPlotType по ответу пользователя.
При обработке ответа пользователя также стоит учесть, что есть два типа области печати, для которых требуются дополнительные данные, - это acView («Вид») и acWindow («Рамка»). Для области печати «Вид» нужно указать название именованного вида, а для области печати «Рамка» - указать две точки области печати. То есть, если пользователь выбрал один из этих двух типов области печати, программа должна сделать больше чем просто изменить значение свойства PlotType.
В фрагменте кода, приведенном в этом шаге, есть пустой оператор switch, который был добавлен именно для этих двух областей печати:
switch (result.StringResult)
{
}
Такую обработку необходимо выполнить именно до изменения значения свойства PlotType, иначе наша программа не сработает как надо.
Шаг 5.1. Обработка ключевого слова «Вид».
case "Вид":
{
// Приглашающее сообщение для выбора именованного вида
HostMgd.EditorInput.PromptStringOptions opts =
new HostMgd.EditorInput.PromptStringOptions("Выберите именованный вид");
// Запись названий именованных видов активного листа
// в ключевые слова запроса
namedViews.ForEach(nv => opts.Keywords.Add(nv));
// Запрос пользователю с предложением выбрать именованный вид
HostMgd.EditorInput.PromptResult res = ed.GetString(opts);
// Обработка полученного результата запроса
if (res.Status == HostMgd.EditorInput.PromptStatus.Keyword)
activeLayout.ViewToPlot = res.StringResult;
break;
}
Если пользователь выбрал тип области печати «Вид», необходимо сформировать еще один запрос, в результате которого пользователь выберет, какой именно именованный вид будет использован.
Для того чтобы пользователь выбрал именованный вид, добавляем в ключевые слова второго запроса названия именованных видов активного листа, которые хранятся в коллекции namedViews. Далее нужно обработать ответ пользователя. Выбранный им именованный вид назначаем свойству ViewToPlot активного листа.
И только после этого меняем значение свойства PlotType на acView.
Шаг 5.2. Обработка ключевого слова «Рамка».
case "Рамка":
{
// Запрос точки левого нижнего угла области печати
var point1 = ed.GetPoint("Укажите левую нижнюю точку области печати:");
if (point1.Status == HostMgd.EditorInput.PromptStatus.OK)
{
// Вывод в консоль координат выбранной точки
ed.WriteMessage("({0};{1})", point1.Value.X, point1.Value.Y);
// Запрос точки правого верхнего угла области печати
var point2 = ed.GetCorner("Укажите правую верхнюю точку области печати:", point1.Value);
if(point2.Status== HostMgd.EditorInput.PromptStatus.OK)
{
// Вывод в консоль координат выбранной точки
ed.WriteMessage("({0};{1})", point2.Value.X, point2.Value.Y);
// Очистка коллекции PlotAreas
nanoCAD.InanoCADPlot plot = (nanoCAD.InanoCADPlot)comDoc.Plot;
nanoCAD.InanoCADPlotCustomParams param = plot.CustomPlotSettings[activeLayout];
if (param.PlotAreas.Count != 0)
{
// Добавление области печати "Рамка" методом SetWindowToPlot()
// происходит верно только если коллекция PlotAreas пустая
param.PlotAreas.Clear();
plot.CustomPlotSettings[activeLayout] = param;
}
// Получение текущего вида
Teigha.DatabaseServices.ViewTableRecord curView = ed.GetCurrentView();
// Получение матрицы преобразования из WCS в DCS
Teigha.Geometry.Matrix3d matrix;
matrix = Teigha.Geometry.Matrix3d.PlaneToWorld(curView.ViewDirection);
matrix = Teigha.Geometry.Matrix3d.Displacement(
curView.Target - Teigha.Geometry.Point3d.Origin)* matrix;
matrix = Teigha.Geometry.Matrix3d.Rotation(-curView.ViewTwist, curView.ViewDirection, curView.Target) * matrix;
matrix = matrix.Inverse();
// Преобразование координат полученных точек в DCS
double[] ptMin = new double[] { point1.Value.TransformBy(matrix).X, point1.Value.TransformBy(matrix).Y };
double[] ptMax = new double[] { point2.Value.TransformBy(matrix).X, point2.Value.TransformBy(matrix).Y };
// Добавление новой области печати "Рамка" методом SetWindowToPlot()
activeLayout.SetWindowToPlot(ptMin, ptMax);
}
}
else ed.WriteMessage("Cancel");
break;
}
Если пользователь выбрал область печати «Рамка», необходимо запросить у него две точки этой прямоугольной области печати: левый нижний и правый верхний углы.
В нашу программу координаты точек, указанных пользователем, будут переданы в WCS (World Coordinate System). Для того чтобы область печати правильно отобразилась на экране, координаты этих точек должны быть переведены в DCS (Display Coordinate System). Далее передаем преобразованные координаты точек в метод SetWindowToPlot(), который преобразует координаты двух точек в прямоугольную область печати.
После всех этих действий меняем значение свойства PlotType на acWindow.
Шаг 6. Компилируем наше приложение, загружаем в nanoCAD и запускаем.
Для наглядности приведем текст нашей программы целиком:
public partial class Commands
{
[Teigha.Runtime.CommandMethod("SetPlotArea")]
public void SetPlotAreaToActiveLayout()
{
// Получение ссылки на активный документ
HostMgd.ApplicationServices.Document doc =
HostMgd.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
nanoCAD.Document comDoc = doc.AcadDocument as nanoCAD.Document;
// Получение ссылки на редактор активного документа
HostMgd.EditorInput.Editor ed = doc.Editor;
// Получение ссылки на активный лист документа
OdaX.AcadLayout activeLayout = comDoc.ActiveLayout;
// Приглашающее сообщение для выбора области печати
HostMgd.EditorInput.PromptStringOptions promptOptions =
new HostMgd.EditorInput.PromptStringOptions("Выберите область печати");
// Запись в ключевые слова приглашающего сообщения наименований доступных
// типов областей печати, которые возможны и в пространстве модели, и в листах
promptOptions.Keywords.Add("Экран");
promptOptions.Keywords.Add("Границы");
promptOptions.Keywords.Add("Рамка");
// Для области печати "Вид" создаем коллекцию string,
// в которую будем записывать названия именованных видов
System.Collections.Generic.List<string> namedViews = new System.Collections.Generic.List<string>();
// Тип области печати "Вид" доступен только
// если у активного листа есть сохраненные именованные виды
if (comDoc.Views.Count != 0)
{
// Если коллекция Views не пустая, ищем в ней именованные виды активного листа
foreach (OdaX.AcadView namedView in comDoc.Views)
{
// Если найден именованный вид для активного листа,
// добавляем название этого вида в коллекцию namedViews
if (namedView.LayoutId == activeLayout.ObjectID) namedViews.Add(namedView.Name);
}
// Если в итоге коллекция namedViews не пустая,
// добавляем к запросу ключевое слово "Вид"
if (namedViews.Count != 0) promptOptions.Keywords.Add("Вид");
}
// Добавление к запросу ключевых слов в зависимости от типа активного листа
if (activeLayout.ModelType) promptOptions.Keywords.Add("Лимиты");
else promptOptions.Keywords.Add("Лист");
// Запрос пользователю с предложением выбрать область печати
HostMgd.EditorInput.PromptResult result = ed.GetString(promptOptions);
// Обработка полученного результата запроса
if (result.Status == HostMgd.EditorInput.PromptStatus.Keyword)
{
// Составление словаря для сопоставления названий типов областей печати в перечислении
OdaX.AcPlotType и в интерфейсе
System.Collections.Generic.Dictionary<string, OdaX.AcPlotType> dictionary =
new System.Collections.Generic.Dictionary<string, OdaX.AcPlotType>();
dictionary.Add("Экран", OdaX.AcPlotType.acDisplay);
dictionary.Add("Границы", OdaX.AcPlotType.acExtents);
dictionary.Add("Лист", OdaX.AcPlotType.acLayout);
dictionary.Add("Лимиты", OdaX.AcPlotType.acLimits);
dictionary.Add("Вид", OdaX.AcPlotType.acView);
dictionary.Add("Рамка", OdaX.AcPlotType.acWindow);
switch (result.StringResult)
{
case "Вид":
{
// Приглашающее сообщение для выбора именованного вида
HostMgd.EditorInput.PromptStringOptions opts =
new HostMgd.EditorInput.PromptStringOptions("Выберите именованный вид");
// Запись названий именованных видов активного листа
// в ключевые слова запроса
namedViews.ForEach(nv => opts.Keywords.Add(nv));
// Запрос пользователю с предложением выбрать именованный вид
HostMgd.EditorInput.PromptResult res = ed.GetString(opts);
// Обработка полученного результата запроса
if (res.Status == HostMgd.EditorInput.PromptStatus.Keyword)
activeLayout.ViewToPlot = res.StringResult;
break;
}
case "Рамка":
{
// Запрос точки левого нижнего угла области печати
var point1 = ed.GetPoint("Укажите левую нижнюю точку области печати:");
if (point1.Status == HostMgd.EditorInput.PromptStatus.OK)
{
//Вывод в консоль координат выбранной точки
ed.WriteMessage("({0};{1})", point1.Value.X, point1.Value.Y);
// Запрос точки правого верхнего угла области печати
var point2 = ed.GetCorner("Укажите правую верхнюю точку области печати:", point1.Value);
// Обработка результата запроса
if(point2.Status== HostMgd.EditorInput.PromptStatus.OK)
{
// Вывод в консоль координат выбранной точки
ed.WriteMessage("({0};{1})", point2.Value.X, point2.Value.Y);
// Очистка коллекции PlotAreas
nanoCAD.InanoCADPlot plot = (nanoCAD.InanoCADPlot)comDoc.Plot;
nanoCAD.InanoCADPlotCustomParams param = plot.CustomPlotSettings[activeLayout];
if (param.PlotAreas.Count != 0)
{
// Добавление области печати "Рамка" методом SetWindowToPlot()
// происходит верно только если коллекция PlotAreas пустая
param.PlotAreas.Clear();
plot.CustomPlotSettings[activeLayout] = param;
}
// Получение текущего вида
Teigha.DatabaseServices.ViewTableRecord curView = ed.GetCurrentView();
// Получение матрицы преобразования из WCS в DCS
Teigha.Geometry.Matrix3d matrix;
matrix = Teigha.Geometry.Matrix3d.PlaneToWorld(curView.ViewDirection);
matrix = Teigha.Geometry.Matrix3d.Displacement
(curView.Target - Teigha.Geometry.Point3d.Origin) * matrix;
matrix = Teigha.Geometry.Matrix3d.Rotation(-curView.ViewTwist, curView.ViewDirection,
curView.Target) * matrix;
matrix = matrix.Inverse();
// Преобразование координат полученных точек в DCS
double[] ptMin = new double[] { point1.Value.TransformBy(matrix).X, point1.Value.TransformBy(matrix).Y };
double[] ptMax = new double[] { point2.Value.TransformBy(matrix).X, point2.Value.TransformBy(matrix).Y };
// Добавление новой области печати "Рамка" методом SetWindowToPlot()
activeLayout.SetWindowToPlot(ptMin, ptMax);
}
}
else ed.WriteMessage("Cancel");
break;
}
}
// Поиск в словаре выбранного типа области печати и запись
// найденного значения в переменную ptResult
OdaX.AcPlotType ptResult;
dictionary.TryGetValue(result.StringResult, out ptResult);
// Назначение выбранной области печати активному листу
activeLayout.PlotType = ptResult;
ed.WriteMessage("Листу '{0}' назначена область печати '{1}'", activeLayout.Name, result.StringResult);
}
else ed.WriteMessage("Область печати не изменена");
}
}
После запуска команды SetPlotArea пользователь увидит запрос о выборе типа области печати, дальше все зависит от выбора пользователя.
Если выбрана «Рамка», необходимо указать левый нижний и правый верхний углы прямоугольной области печати. После того как пользователь указал две точки, создается область печати - методом SetWindowToPlot(), а в консоль nanoCAD выводится сообщение об успешном выполнении команды (рис. 3).
Через интерфейс nanoCAD назначенную область печати можно увидеть, вызвав окно Параметры листа. Область будет обозначена в пространстве чертежа красной пунктирной линией (рис. 4).
При повторном запуске команды SetPlotArea и выборе типа области печати «Рамка» назначенная в первый раз область печати будет заменена новой.
Если у активного листа есть именованные виды, пользователь может выбрать тип области печати «Вид». После этого команда запросит, какой именно именованный вид назначить (рис. 5).
Итак, в этой статье мы подробно разобрали шаги, необходимые для создания .NET-приложения, работающего с областями печати документа nanoCAD.
В приложении был создан фильтр доступных для выбора областей печати, который учитывает тип активного листа и его текущие настройки. Также был подробно представлен алгоритм работы с областями печати «Рамка» и «Вид», более сложными с точки зрения использования через API nanoCAD, чем остальные области печати. В следующей статье подробнее рассмотрим изменение расширенных настроек печати через API nanoCAD.
`:notags:trunk=`500|450| ...`:hupher:format=`Полный пример кода содержится в SDK:
\SDK\samples\Mgd\HelloHost\HelloHost.cs
Перейдите в настройки пользовательского интерфейса (НПИ): Сервис -> Интерфейс -> Настройка пользовательского интерфейса либо используйте команду INTERFACE в командной строке nanoCAD (рис. 3).
Для кастомизации меню используется отдельный файл, его нужно создать (рис. 4).
Переключитесь на созданный конфигурационный файл, выбрав его из выпадающего списка (рис. 5).
Создайте меню верхнего уровня (правая кнопка мыши (ПКМ) -> вкладка Главное меню) в созданном конфигурационном файле (рис. 6).
Результат должен выглядеть так, как показано на рис. 7.
Создайте описание команд HelloHost_Example1 и HelloHost_Example2 (ПКМ -> Создать команду). Команды будут вызываться в пунктах меню (рис. 8).
Поле Внутреннее имя должно содержать точное название команды, как в DLL-приложении (рис. 9, 10).
Команда HelloHost_Example1 |
Команда HelloHost_Example2 |
Добавьте пункты меню, вызывающие команды HelloHost_Example1 и HelloHost_Example2 (рис. 11).
Сохраните и подключите конфигурационный файл.
При подключении Платформа попросит выбрать файл - нужно указать созданный частичный файл меню: MyMenuConfig.cfg (рис. 12).
Для применения новых настроек перезагрузите Платформу nanoCAD полностью.
Результат должен быть таким, как показано на рис. 13.
Примечание. Так как созданное меню использует команды из DLL-библиотеки, ее загрузку следует автоматизировать, то есть добавить DLL в список автоматически загружаемых приложений.
Перейдите в настройки пользовательского интерфейса (НПИ): Сервис -> Интерфейс -> Настройка пользовательского интерфейса либо используйте команду INTERFACE в командной строке nanoCAD. Вкладка Панели инструментов.
В примере используется ранее созданный частичный конфигурационный файл.
Добавьте панель инструментов (щелчок ПКМ в окне вкладки Панели инструментов) и укажите нужные параметры в окне создания панели инструментов (рис. 14).
Для удобства первой настройки параметру Размещение можно назначить значение Сверху (рис. 15).
Добавьте кнопки на панель инструментов - они будут вызывать команды из DLL-приложения (рис. 16).
Полученный результат представлен на рис. 17.
Сохраните и подключите конфигурационный файл. Если конфигурационный файл уже был подключен ранее, пункт 2 (см. рис. 18) следует пропустить.
Для применения новых настроек перезагрузите Платформу nanoCAD полностью.
После создания и подключения панель инструментов отображается с системными иконками (рис. 19).
Созданный частичный конфигурационный файл располагается по следующему адресу:
C:\Users\nanoUser\AppData\Roaming\Nanosoft\nanoCAD x64 XX.x\Config\MyMenuConfig.cfg
При подключении конфигурационного файла в диалоге НПИ название файла будет добавлено в основной конфигурационный файл:
C:\Users\nanoUser\AppData\Roaming\Nanosoft\nanoCAD x64 XX.x\Config\nanoCAD.cfg
Листинг nanoCAD.cfg:
...
[\ribbon]
...
#include "MyMenuConfig.cfg"
...
Лента приложения описывается в файле CUIX, который представляет собой ZIP-архив, где расположены XML-файлы, поименованные определенным образом.
Частичные (пользовательские) файлы ленты поддерживаются Платформой, но не поддерживаются диалогом НПИ.
Диалог НПИ умеет редактировать лишь основную ленту приложения, поэтому его имеет смысл применять только в том случае, когда используется подход «настроить и распространить по всем компьютерам».
Для создания частичной (пользовательской) ленты требуется выполнить следующие действия:
Листинг [Content_Types].xml:
<?xml version="1.0" encoding="utf-8"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="cui" ContentType="text/xml" />
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml" />
<Default Extension="xml" ContentType="text/xml" />
</Types>
Листинг Menu_Package_Info.xml:
<?xml version="1.0" encoding="utf-8"?>
<MenuPackageParts>
<PartData PartData_Name="/RibbonRoot.cui" PartData_Modified="2022-01-31T12:42:04.9848238+03:00" />
<PartData PartData_Name="/Menu_Package_Info.xml" PartData_Modified="2022-01-31T12:42:05.0058294+03:00" />
</MenuPackageParts>
Листинг RibbonRoot.cui:
<?xml version="1.0"?>
<RibbonRoot>
<RibbonPanelSourceCollection xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<RibbonPanelSource UID="RBNU_HelloHost_GroupA" Text="Команды A" HiddenInEditor="false">
<RibbonRow UID="RBNUR_Sample_Ribbon_Row_Main_GroupA">
<RibbonRowPanel UID="RBNURP_SDK_Samples_nano" ResizeStyle="None" ResizePriority="100" TopJustify="True">
<RibbonRow UID="RBNURR_Samples_GroupA">
<RibbonCommandButton UID="ID_RBN_GR_A_HELLOHOSTEX1" Id="AcRibbonCommandButton" Text="Команда 1" ButtonStyle="LargeWithText" MenuMacroID="HelloHost_Example1" KeyTip="">
<TooltipTitle xlate="true" UID="XLS_HelloHost1">Выводит в командную строку сообщение</TooltipTitle>
</RibbonCommandButton>
<RibbonCommandButton UID="ID_RBN_GR_A_HELLOHOSTEX2" Id="AcRibbonCommandButton" Text="Команда 2" ButtonStyle="LargeWithText" MenuMacroID="HelloHost_Example2" KeyTip="">
<TooltipTitle xlate="true" UID="XLS_HelloHost2">Выводит в командную строку информацию о первых 10 слоях</TooltipTitle>
</RibbonCommandButton>
</RibbonRow>
</RibbonRowPanel>
</RibbonRow>
<RibbonPanelBreak Id="RibbonPanelBreak"/>
<RibbonRow UID="RBNUR_Sample_Ribbon_Row_Additional_1" ResizeStyle="None" ResizePriority="100" TopJustify="True">
<RibbonCommandButton UID="ID_RBN_GR_A_HELLOHOSTEX3" Id="RibbonCommandButton" Text="Команда 3" ButtonStyle="SmallWithText" MenuMacroID="HelloHost_Example3" resolved="1" KeyTip=""/>
</RibbonRow>
<RibbonRow UID="RBNUR_Sample_Ribbon_Row_Additional_2" ResizeStyle="None" ResizePriority="100" TopJustify="True">
<RibbonCommandButton UID="ID_RBN_GR_A_HELLOHOSTEX4" Id="RibbonCommandButton" Text="Команда 4" ButtonStyle="SmallWithText" MenuMacroID="HelloHost_Example4" resolved="1" KeyTip=""/>
</RibbonRow>
<RibbonRow UID="RBNUR_Sample_Ribbon_Row_Additional_3" ResizeStyle="None" ResizePriority="100" TopJustify="True">
<RibbonCommandButton UID="ID_RBN_GR_A_HELLOHOSTEX5" Id="RibbonCommandButton" Text="Команда 5" ButtonStyle="SmallWithText" MenuMacroID="HelloHost_Example5" resolved="1" KeyTip=""/>
</RibbonRow>
</RibbonPanelSource>
<RibbonPanelSource UID="RBNU_HelloHost_GroupB" Text="Команды B" HiddenInEditor="false">
<RibbonRowPanel UID="RBNUR_Sample_Ribbon_Row_Main_GroupB" ResizeStyle="None" ResizePriority="100" TopJustify="True">
<RibbonRow UID="RBNUR_Sample_Ribbon_Row_1_Main_GroupB">
<RibbonCommandButton UID="RBNU_HelloHost_Example1" Id="AcRibbonCommandButton" Text="Команда 1" ButtonStyle="SmallWithText" MenuMacroID="HelloHost_Example1" KeyTip="">
<TooltipTitle xlate="true" UID="XLS_HelloHost_Example1">Выводит в командную строку сообщение</TooltipTitle>
</RibbonCommandButton>
</RibbonRow>
<RibbonRow UID="RBNUR_Sample_Ribbon_Row_2_Main_GroupB">
<RibbonCommandButton UID="RBNU_HelloHost_Example2" Id="AcRibbon CommandButton" Text="Команда 2" ButtonStyle="SmallWithText" MenuMacroID="HelloHost_Example2" KeyTip="">
<TooltipTitle xlate="true" UID="XLS_HelloHost_Example2">Выводит в командную строку информацию о первых 10 слоях</TooltipTitle>
</RibbonCommandButton>
</RibbonRow>
<RibbonRow UID="RBNUR_Sample_Ribbon_Row_3_Main_GroupB">
<RibbonCommandButton UID="RBNU_HelloHost_Example3" Id="AcRibbonCommandButton" Text="Команда 3" ButtonStyle="SmallWithText" MenuMacroID="HelloHost_Example3" KeyTip="">
<TooltipTitle xlate="true" UID="XLS_ListCrCr">Третья команда</TooltipTitle>
</RibbonCommandButton>
</RibbonRow>
</RibbonRowPanel>
<RibbonPanelBreak Id="RibbonPanelBreak"/>
<RibbonRowPanel UID="" ResizeStyle="None" ResizePriority="100" TopJustify="True">
<RibbonRow UID="RBNURR_SDK_MAPI">
<RibbonSplitButton UID="ID_RBN_SPLITBTN_ADDCOMMANDS" Id="RibbonSplitButton" Text="Доп.команды" KeyTip="" Behavior="SplitFollowStaticText" ListStyle="IconText" ButtonStyle="LargeWithText" Grouping="false">
<RibbonCommandButton UID="RBNU_AddCommand6" Id="AcRibbon CommandButton" Text="Команда 6" ButtonStyle="SmallWithText" MenuMacroID="TextInBox" KeyTip="" />
</RibbonSplitButton>
<RibbonSplitButton UID="ID_RBN_SPLITBTN_SOMECOMMANDS" Id="RibbonSplitButton" Text="Еще доп.команды" KeyTip="" Behavior="SplitFollowStaticText" ListStyle="IconText" ButtonStyle="LargeWithText" Grouping="false">
<RibbonCommandButton UID="ID_RBN__AddCommand7" Id="RibbonCommandButton" Text="Команда 7" ButtonStyle="SmallWithText" MenuMacroID="testTable" resolved="1" KeyTip=""/>
<RibbonCommandButton UID="ID_RBN__AddCommand8" Id="RibbonCommandButton" Text="Команда 8" ButtonStyle="SmallWithText" MenuMacroID="testSymbols" resolved="1" KeyTip=""/>
</RibbonSplitButton>
</RibbonRow>
</RibbonRowPanel>
</RibbonPanelSource>
</RibbonPanelSourceCollection>
<RibbonTabSourceCollection>
<RibbonTabSource Text="Моя лента" UID="ID_MY_RIBBON">
<RibbonPanelSourceReference UID="RBNU_LargeWithText_GroupA_Ref" PanelId="RBNU_HelloHost_GroupA" ResizeStyle="Default" />
<RibbonPanelSourceReference UID="RBNU_SmallWithText_GroupC_Ref" PanelId="RBNU_HelloHost_GroupB" ResizeStyle="Default" />
</RibbonTabSource>
</RibbonTabSourceCollection>
</RibbonRoot>
Переименуйте файл MyRibbon.zip в MyRibbon.cuix. Например, в командной строке Windows:
C:\Users\nanoUser\AppData\Roaming\Nanosoft\Config\>rename MyRibbon.zip MyRibbon.cuix
Листинг MyMenuConfig.cfg:
[\Menu]
[\Menu\MainMenu_2]
Name=sMyMenu3">
[\Menu\MainMenu_2\MenuItem_1]
Intername=sHelloHost_Example1
Name=sКоманда 1
[\Menu\MainMenu_2\MenuItem_2]
Intername=sHelloHost_Example2
Name=sКоманда 2
[\ConfigMan]
[\ConfigMan\Commands]
[\ConfigMan\Commands\Command_1]
cmdtype=i1
weight=i0
Intername=sHelloHost_Example1
LocalName=sHelloHostEx1
DispName=sHelloHost (Пример 1)
IsUserCommand=f1
[\ConfigMan\Commands\Command_2]
cmdtype=i1
weight=i0
DispName=sHelloHost (Пример 2)
Intername=sHelloHost_Example2
LocalName=sHelloHostEx2
IsUserCommand=f1
[\ribbon\MyRibbon]
CUIX=s%CFG_PATH%\MyRibbon.cuix
visible=f1
Примечание. Вносить изменения в файл CUIX можно без перезапуска всей Платформы, достаточно воспользоваться командой RELOADRIBBON.
Результат показан на рис. 21.
Примечание. Дополнительные (частные) ленты поддерживаются только в формате CUIX.
Иконка команды - это свойство описания команды. Если она не указана, будет отображаться иконка по умолчанию.
Настроенная иконка выводится и в меню, и на панель инструментов, и в ленту.
Задать иконку можно в поле Ресурсная DLL (рис. 22). При этом принимаются форматы ICO, BMP и иконки в составе ресурсной DLL (рис. 23).
Примеры иконок входят в состав SDK: \SDK\samples\Menu\MenuRes\res.
Результат добавления иконок показан на рис. 24.
Иконки для светлой и темной схемы разные, предусмотрен механизм автоматического переключения. Если в описании команд прописана иконка myicon.ico, то при переключении в темную схему будет использована иконка myicon_dark.ico (при ее наличии).
В случае использования изображений формата BMP файлы подбираются по следующим правилам:
myBitmap.bmp - светлая схема, 16x16;
myBitmap_large.bmp - светлая схема, 32x32;
myBitmap_dark.bmp - темная схема, 16x16;
myBitmap_dark_large.bmp - темная схема, 32x32.
Пример проекта Visual Studio, с помощью которого можно сформировать ресурсную DLL с иконками, входит в состав SDK:
\SDK\samples\Menu\MenuRes\MenuRes.vcxprojИтоговый листинг MyMenuConfig.cfg:
[\Menu]
[\Menu\MainMenu_2]
Name=sМоеМеню
[\Menu\MainMenu_2\MenuItem_1]
Intername=sHelloHost_Example1
Name=sКоманда 1
[\Menu\MainMenu_2\MenuItem_2]
Intername=sHelloHost_Example2
Name=sКоманда 2
[\ConfigMan]
[\ConfigMan\Commands]
[\ConfigMan\Commands\Command_1]
cmdtype=i1
weight=i0
Intername=sHelloHost_Example1
LocalName=sHelloHostEx1
DispName=sКоманда 1
IsUserCommand=f1
BitmapDll=sG:\SDK\samples\Menu\MenuRes\res\a.ico
[\ConfigMan\Commands\Command_2]
cmdtype=i1
weight=i0
Intername=sHelloHost_Example2
LocalName=sHelloHostEx1
DispName=sКоманда 2
IsUserCommand=f1
BitmapDll=sG:\SDK\samples\Menu\MenuRes\res\n.ico
[\Toolbars]
[\Toolbars\Toolbar_1]
Name=sМояПанельИнстр
Intername=sMyToolbar
[\Toolbars\Toolbar_1\ToolbarButton_1]
Intername=sHelloHost_Example1
Name=sКоманда 1
[\Toolbars\Toolbar_1\ToolbarButton_2]
Intername=sHelloHost_Example2
Name=sКоманда 2
[\ToolbarsPos]
[\ToolbarsPos\Toolbar_1]
InitialVisible=i1
Row=i5">
Pos=i1
DockPosition=sTop
[\ribbon]
[\ribbon\MyRibbon]
CUIX=s%CFG_PATH%\MyRibbon.cuix
visible=f1
Для отключения ранее подключенного меню или панели инструментов нужно:
Для отключения ленты достаточно убрать ссылку на файл CUIX из частичного конфигурационного файла.
`:notags:trunk=`500|450| ...`:hupher:format=`В последующих шагах содержится инструкция по реализации метода PrintDocument().
Шаг 3. Получаем доступ к документу nanoCAD и его настройкам:
// Получение ссылки на активный документ
HostMgd.ApplicationServices.Document doc =
HostMgd.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
nanoCAD.Document comDoc = doc.AcadDocument as nanoCAD.Document;
Здесь происходит симбиоз библиотек, о которых упоминалось выше: с помощью библиотеки hostmgd.dll получаем доступ к активному документу nanoCAD (переменная doc); далее, чтобы получить доступ к тем настройкам, которые нам нужны, запрашиваем из переменной doc COM-документ nanoCAD.
В дальнейшем настройки печати мы будем получать, обращаясь к свойствам и методам переменной comDoc.
Шаг 4. При помощи свойства Plot документа nanoCAD получаем COM-объект Plot, содержащий методы печати.
// Получение ссылки на свойство Plot активного документа
nanoCAD.Plot plot = (nanoCAD.Plot)comDoc.Plot;
Шаг 5. Выбираем принтер и меняем настройки листов для печати.
В предыдущих шагах были проведены подготовительные действия, теперь мы переходим к настройке параметров печати.
За принтер отвечает свойство ConfigName листа документа. Каждому листу можно назначить свой принтер. Для доступа к листам документа используем коллекцию Layouts документа nanoCAD. Перебирая коллекцию, можно изменить настройки каждого листа (назначить всем листам принтер и изменить пользовательские настройки принтера, если был выбран встроенный принтер nanoCAD):
// Назначение принтера всем листам документа
foreach (OdaX.IAcadLayout layout in comDoc.Layouts)
{
layout.ConfigName = "Встроенный pdf-принтер";
nanoCAD.InanoCADPlotCustomParams customPlotSettings =
plot.CustomPlotSettings[layout];
// Изменяем путь сохранения напечатанного документа
customPlotSettings.UseDWGPath = false;
customPlotSettings.FileFolder = "C:\\PlotToDeviceExample";
// Изменяем шаблон имени файла, в который будет напечатан документ
customPlotSettings.UsePredefinedNames = true;
customPlotSettings.FileMask = "<DN>_<LN>";
// Передаем измененные пользовательские настройки в лист
plot.CustomPlotSettings[layout] = customPlotSettings;
}
Здесь свойство UseDWGPath указывает, сохранять напечатанный документ в той же директории, где расположен документ (значение true), или использовать для сохранения директорию, указанную в свойстве FileFolder (значение false). При указании директории необходимо убедиться, что она существует, иначе произойдет ошибка печати.
Свойство UsePredefinedNames указывает, использовать шаблон имени файла, определенный в свойстве FileMask (значение true), или открывать диалоговое окно сохранения файла (значение false).
Для шаблонов имен файлов существует набор символов, которые будут интерпретироваться как автозаполняемые поля. В эти поля будут подставляться соответствующие свойства документа или значения:
Шаг 6. Определяем перечень листов документа, которые будут отправлены на печать для метода SetLayoutsToPlot(). Если не использовать этот метод, на печать будет отправлен активный лист:
// Создание массива имен листов, предназначенных для печати
string[] layoutsToPlot = new string[] {
comDoc.Layouts.Item(1).Name, comDoc.Layouts.Item(2).Name };
// Вызов метода nanoCAD.InanoCADPlot.SetLayoutsToPlot()
plot.SetLayoutsToPlot(layoutsToPlot);
Листы, назначенные для печати этим методом, хранятся в памяти до первого вызова метода печати. После этого необходимо заново вызывать метод SetLayoutsToPlot() перед следующей отправкой документа на печать.
Шаг 7. Отправляем документ на печать. В случае встроенных принтеров, в частности, pdf-принтера, для печати можно использовать как метод PlotToDevice(), так и PlotToFile(). Каждый из методов сработает по-своему при выборе директории сохранения напечатанного файла и его имени. Если использовать метод PlotToDevice(), будут применены пользовательские настройки принтера (customPlotSettings). Если использовать метод PlotToFile(), шаблон имени файла и директория сохранения будут взяты из его параметра при вызове:
// Печать назначенных листов
// В этом случае будет создано два pdf-файла в директории C:\PlotToDeviceExample
// с именами по шаблону "Название документа_Название листа".pdf
plot.PlotToDevice();
// Для наглядности также печать в файл
// После выполнения метода PlotToDevice() назначенные для печати листы
// будут сброшены. Методом PlotToFile() будет напечатан активный лист документа
// и назван по шаблону "myplot_Имя документа_Имя листа".pdf
plot.PlotToFile("C:\\PlotToFileExample\\myplot_<DN>_<LN>");
Метод nanoCAD.InanoCADPlot.PlotToDevice() при печати на pdf-принтере использует пользовательские настройки листов, которые назначены для печати, т.е. путь сохранения C:\PlotToDeviceExample, а в качестве шаблона имени будет применен шаблон «Имя документа_Имя листа.pdf»(«<DN>_<LN>»).
Метод nanoCAD.InanoCADPlot.PlotToFile() при печати использует в качестве пути сохранения и имени файла данные своего параметра plotFile даже если в пользовательских настройках указаны путь и шаблон имени файла.
Параметр plotFile может иметь следующие значения:
При указании в параметре plotFile адреса директории, необходимо убедиться, что она существует.
Далее все рассмотренные шаги приведены в виде команды PlotDocument, которая устанавливает всем листам документа встроенный pdf-принтер. Меняет их пользовательские настройки. Методом nanoCAD.InanoCADPlot.SetLayoutsToPlot() устанавливает два листа документа для печати и печатает эти листы методом nanoCAD.InanoCADPlot.PlotToDevice(), затем для наглядности вызывается метод nanoCAD.InanoCADPlot.PlotToFile().
public partial class Commands
{
[Teigha.Runtime.CommandMethod("PlotDocument")]
public void PrintDocument()
{
// Получение ссылки на активный документ
HostMgd.ApplicationServices.Document doc =
HostMgd.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
nanoCAD.Document comDoc = doc.AcadDocument as nanoCAD.Document;
// Получение ссылки на редактор активного документа
HostMgd.EditorInput.Editor ed = doc.Editor;
// Получение ссылки на активный лист документа
OdaX.AcadLayout activeLayout = comDoc.ActiveLayout;
// Получение ссылки на свойство Plot активного документа
nanoCAD.Plot plot = (nanoCAD.Plot)comDoc.Plot;
// Назначение принтера всем листам документа
foreach (OdaX.IAcadLayout layout in comDoc.Layouts)
{
layout.ConfigName = "Встроенный PDF-принтер";
nanoCAD.InanoCADPlotCustomParams customPlotSettings =
plot.CustomPlotSettings[layout];
// Изменяем путь сохранения напечатанного в файл документа
customPlotSettings.UseDWGPath = false;
customPlotSettings.FileFolder = "C:\\PlotToDeviceExample";
// Изменяем шаблон имени файла, в который будет напечатан документ
customPlotSettings.UsePredefinedNames = true;
customPlotSettings.FileMask = "<DN>_<LN>";
// Передаем измененные пользовательские настройки в лист
plot.CustomPlotSettings[layout] = customPlotSettings;
}
// Назначение активному листу области печати "Границы", если это пространство модели,
// или "Лист", если активный лист не является пространством модели
if (activeLayout.ModelType)
comDoc.ActiveLayout.PlotType = OdaX.AcPlotType.acExtents;
else comDoc.ActiveLayout.PlotType = OdaX.AcPlotType.acLayout;
// Включение режима "Вписать"
comDoc.ActiveLayout.StandardScale = OdaX.AcPlotScale.acScaleToFit;
// Создание массива имен листов, предназначенных для печати
string[] layoutsToPlot = new string[] { comDoc.Layouts.Item(1).Name, comDoc.Layouts.Item(2).Name };
// Вызов метода nanoCAD.InanoCADPlot.SetLayoutsToPlot()
plot.SetLayoutsToPlot(layoutsToPlot);
// Сообщение пользователю
ed.WriteMessage("Листы {0} и {1} были установлены для печати", layoutsToPlot[0],layoutsToPlot[1]);
// Печать назначенных листов
// В этом случае будет создано два pdf-файла в директории C:\PlotToDeviceExample
// с именами по шаблону "Название документа_Название листа".pdf
plot.PlotToDevice();
// Для наглядности также печать в файл
// После выполнения метода PlotToDevice() назначенные для печати листы
// будут сброшены. Методом PlotToFile() будет напечатан активный лист документа
// и назван по шаблону "myplot_Имя документа_Имя листа".pdf
plot.PlotToFile("C:\\PlotToFileExample\\myplot_<DN>_<LN>");
}
}
Шаг 8. Компилируем наше приложение и загружаем полученный dll-файл в nanoCAD.
Если открыть новый документ nanoCAD и запустить в нем команду PlotDocument, то в результате будет создано три файла. Результат работы команды приведен на рисунке.
Было напечатано два листа, назначенных методом nanoCAD.InanoCADPlot.SetLayoutsToPlot() для печати, и при повторном вызове метода печати напечатан один активный лист.
Итак, мы разобрались с применением методов PlotToDevice(), PlotToFile() и SetLayoutsToPlot(), попутно затронув методы настройки параметров печати через API nanoCAD. В следующих статьях более подробно рассмотрим настройку параметров печати листов документа nanoCAD через API.
`:notags:trunk=`500|450| ...`:hupher:format=`Шаг 3. Получаем доступ к документу nanoCAD и его настройкам:
// Получение ссылки на активный документ
HostMgd.ApplicationServices.Document doc =
HostMgd.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
nanoCAD.Document comDoc = doc.AcadDocument as nanoCAD.Document;
Шаг 4. Создаем новый набор параметров и добавляем его в документ:
// Добавление нового набора параметров листа к пространству модели документа
OdaX.IAcadPlotConfiguration plotConfig =
comDoc.PlotConfigurations.Add("NewModelSpaceConfig",true);
// Добавление нового набора параметров печати к листам документа
OdaX.IAcadPlotConfiguration plotConfig1 =
comDoc.PlotConfigurations.Add("NewLayoutConfig",false);
Здесь мы обращаемся к коллекции PlotConfigurations, которая упоминалась выше, и используем один из ее методов.
Метод Add() принимает два параметра:
В чем разница между набором параметров для пространств модели и листа? Фактически только в значении свойства ModelType. Но на практике есть и более глубокие различия (например, когда устанавливается область печати: для пространства модели существует свой набор возможных областей печати, для листа - свой).
Также стоит обратить внимание, что в коллекции PlotConfigurations наборы параметров расположены в алфавитном порядке по имени набора.
Кроме метода Add(), в коллекции PlotConfigurations доступны другие методы, позволяющие:
Шаг 5. Изменяем значения некоторых параметров в первом созданном наборе параметров:
// Добавление принтера в созданный набор параметров печати
plotConfig.ConfigName = plotConfig.GetPlotDeviceNames()[1];
// Получение списка форматов бумаги, доступных для выбранного принтера, и
// добавление формата бумаги в созданный набор параметров печати
plotConfig.CanonicalMediaName =
plotConfig.GetCanonicalMediaNames()[1];
Здесь вы можете видеть пример получения перечня принтеров и форматов бумаги, доступных для документа, а также изменения значений параметров, отвечающих за принтер и формат бумаги:
У читателя наверняка возникнет вопрос, какие еще параметры можно настроить и как это сделать. Для справки пробежимся по основным параметрам набора, которые мы можем менять через API в параллели с настройкой тех же параметров через интерфейс (рис. 2).
Последние три пункта списка предназначены только для листов - в пространстве модели изменение этих свойств через API не будет влиять на конечный результат, а через интерфейс в настройках параметров печати пространства модели их изменить невозможно.
Шаг 6. Копируем набор параметров листов из другого документа nanoCAD во второй созданный набор «NewLayoutConfig».
В приведенном примере копируется набор параметров листов с названием «LayoutPlotConfig» из документа nanoCAD nanoCAD_configs.dwg, расположенного в директории C:\\Work.
В копируемом наборе задано два параметра:
// Получение ссылки на документ, в котором сохранены нужные нам наборы
// параметров листов
HostMgd.ApplicationServices.Document docToCopy =
HostMgd.ApplicationServices.Application.DocumentManager.
Open("C:\\Work\\nanoCAD_configs.dwg", true);
nanoCAD.Document comDocToCopy = docToCopy.AcadDocument as nanoCAD.Document;
// Получение ссылки на набор параметров листа, который нужно скопировать
OdaX.IAcadPlotConfiguration configToCopy =
comDocToCopy.PlotConfigurations.Item(0);
// Копируем набор параметров листа из одного документа в другой
plotConfig1.CopyFrom((OdaX.AcadPlotConfiguration)configToCopy);
// Закрываем документ, из которого копировали набор параметров листа
docToCopy.CloseAndDiscard();
Здесь основное действие выполняет метод
CopyFrom(), который принимает один параметр типа OdaX.AcadPlotConfiguration - набор параметров листов.И это был последний пункт из перечня функций, которые должно выполнять наше приложение. Далее приведен полный текст программы .NET, которая в nanoCAD будет представлять собой команду AddPlotConfiguration.
public partial class Commands
{
[Teigha.Runtime.CommandMethod("AddPlotConfiguration")]
public void AddPlotConfiguration()
{
// Получение ссылки на активный документ
HostMgd.ApplicationServices.Document doc = HostMgd.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
nanoCAD.Document comDoc = doc.AcadDocument as nanoCAD.Document;
// Получение ссылки на редактор активного документа
HostMgd.EditorInput.Editor ed = doc.Editor;
// Добавление нового набора параметров печати к пространству модели документа
OdaX.IAcadPlotConfiguration plotConfig =
comDoc.PlotConfigurations.Add("NewModelSpaceConfig", true);
// Добавление нового набора параметров печати к листам документа
OdaX.IAcadPlotConfiguration plotConfig1 =
comDoc.PlotConfigurations.Add("NewLayoutConfig", false);
// Вывод в консоль nanoCAD наименований наборов параметров печати документа
ed.WriteMessage("Наборы параметров листов документа '{0}' до изменения их параметров:", doc.Name);
foreach (var pc in comDoc.PlotConfigurations)
{
OdaX.IAcadPlotConfiguration config = pc as OdaX.IAcadPlotConfiguration;
ed.WriteMessage("{0}:{1},{2},{3}", config.Name, config.ConfigName, config.CanonicalMediaName,config.PlotType);
}
ed.WriteMessage("");
// Добавление принтера в созданный набор параметров печати
plotConfig.ConfigName = plotConfig.GetPlotDeviceNames()[1];
// Получение списка форматов бумаги, доступных для выбранного принтера, и
// добавление формата бумаги в созданный набор параметров печати
plotConfig.CanonicalMediaName =
plotConfig.GetCanonicalMediaNames()[1];
// Получение ссылки на документ, в котором сохранены нужные нам наборы
// параметров листов
HostMgd.ApplicationServices.Document docToCopy =
HostMgd.ApplicationServices.Application.DocumentManager.
Open("C:\\Work\\nanoCAD_configs.dwg", true);
nanoCAD.Document comDocToCopy =
docToCopy.AcadDocument as nanoCAD.Document;
// Получение ссылки на набор параметров листа, который нужно скопировать
OdaX.IAcadPlotConfiguration configToCopy =
comDocToCopy.PlotConfigurations.Item(0);
// Копируем набор параметров листа из одного документа в другой
plotConfig1.CopyFrom((OdaX.AcadPlotConfiguration)configToCopy);
// Закрываем документ, из которого копировали набор параметров листа
docToCopy.CloseAndDiscard();
// Вывод в консоль nanoCAD наименований наборов параметров печати документа
ed.WriteMessage("Наборы параметров листов документа '{0}' после изменения их параметров:", doc.Name);
foreach (var pc in comDoc.PlotConfigurations)
{
OdaX.IAcadPlotConfiguration config =
pc as OdaX.IAcadPlotConfiguration;
ed.WriteMessage("{0}:{1},{2}",
config.Name, config.ConfigName, config.CanonicalMediaName);
}
}
}
Шаг 7. Компилируем наше приложение и загружаем в nanoCAD.
Теперь после запуска команды AddPlotConfiguration мы увидим в консоли сообщения, показанные на рис. 3.
Первый раз содержимое коллекции PlotConfigurations было выведено в консоль сразу после добавления в нее новых элементов.
Во второй раз содержимое коллекции выведено в консоль после изменения параметров в первой добавленной конфигурации и копирования во вторую добавленную конфигурацию значений параметров из другого документа. Следует обратить внимание, что методом CopyFrom() скопировалось также и название набора параметров.Ради интереса можно проверить через пользовательский интерфейс nanoCAD изменения в окне Диспетчер параметров листов. Картина должна быть такой, как показано на рис. 4 и 5.
Итак, в этой статье мы подробно разобрали процесс создания .NET-приложения, которое добавляет наборы параметров листов в документ nanoCAD, меняет в них параметры и копирует значения параметров из другого документа nanoCAD. Попутно был приведен пример получения перечня доступных принтеров и форматов бумаги. В следующей статье разберемся, как работать со стилями печати через API nanoCAD.
`:notags:trunk=`500|450| ...`:hupher:format=`Автоматизированный тест RunGetAngleTest лишь исполняет те действия, которые ему прислали из команды GetAngleTest.
[TestMethod]
public void RunGetAngleTest()
{ // Запуск команды 'GETANGLETEST' Keyboard.SendKeys(uICommandLineEdit, "GETANGLETEST{Enter}", ModifierKeys.None);
// Проигрывание действий UI this.ProcessUIActions(context);
}
В настоящее время универсальный проигрыватель написан только для Visual Studio Coded UI Tests. Но теоретически для переноса автотестов в другую систему достаточно переписать универсальный проигрыватель - мы использовали только базовый функционал, который есть в каждой системе автоматизированного тестирования.
`:notags:trunk=`500|450| ...`:hupher:format=`И ВСЁ!
Подробнее не пишу: об этом можно прочитать в nanoCAD SDK. Где взять? В Клубе разработчиков nanoCAD, регистрация открыта.
Игру я разделил на несколько классов: класс игры, класс игровой доски, класс информационной панели, класс игровой фишки:
Каждый класс должен быть максимально самостоятельным.
Дальше мне нужно было научиться создавать объекты, менять их и общаться с пользователем.
Прежде чем рисовать "Реверси", требовалось понять - что делать, за что браться. Для того чтобы создать объекты, нужно немного знать о структуре документа. В каждом документе есть база данных. В базе данных хранятся объекты, содержащиеся в чертеже, и их связи друг с другом. Здесь хранится всё: и линии с дугами, и пространство модели, и стили текстов, и многое другое. Добавляя новый объект в чертеж, нужно добавить его в базу данных. А где есть база данных, там есть и транзакции.
Транзакции нужны, чтобы защитить наш документ: если в результате выполнения кода случится сбой, объекты, добавленные этим кодом, не попадут в документ - транзакция будет отменена. Если все завершилось успешно - транзакция подтверждается и объекты будут добавлены.
Database db = Application.DocumentManager. MdiActive Document.Database;
TransactionManager tm = db.TransactionManager;
using (Transaction tr = tm.StartTransaction())
{
tr.Commit();
}
Просто создать объект мало. Он останется никуда не присоединенным, висящим в воздухе. Объект нужно куда-то поместить. Обычно это модельное пространство. В скриптах было что-то похожее (сказал модельному пространству: "Сделай линию" - и она там появится). В .NET немного по-другому: созданный объект нужно добавить в модельное пространство и в транзакцию.
using (Transaction tr = tm.StartTransaction())
{
BlockTable bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead, false) as BlockTable;
BlockTableRecord ms = tr.GetObject(bt[BlockTableRecord.ModelS pace], OpenMode.ForWrite, false) as BlockTableRecord;
Line line = new Line();
ObjectId lid = ms.AppendEntity(line); // добавляем в модельное пространство tr.
AddNewlyCreatedDBObject(line, true); // и в транзакцию
tr.Commit(); // сохраняем изменения
}
Вооружившись знаниями о внутренней кухне документа, можно, наконец, начинать разработку класса игровой доски. Нет доски - нет и партии. Поэтому первое, что я начал делать, - стал рисовать клетки в пространстве документа.
Клетки я делал из штриховок. Открыв в NCadSDK.chm описание объекта Hatch (документация входит в SDK, доступный членам Клуба разработчиков), я почерпнул нужные мне знания. Третий абзац сразу сообщил мне, что штриховка состоит из петель, а список методов объекта штриховки подсказал магическое слово AppendLoop(). "Вот то, что мне нужно!" - подумал я.
Итак, каждую клетку я строил из квадратной полилинии, которую закрашивала штриховка. Все штриховки вместе образовывали квадрат 8 на 8 клеток.
Дальше - по накатанной, всё как в прошлый раз: бордюры и фишки создаю из объектов 3Dmesh. Бордюр - это полигон две на две вершины. Вычисляю координаты вершин, создаю их, добавляю в сеть, сеть добавляю в модель.
using (Transaction tr = tm.StartTransaction())
{
// создаем сеть PolygonMesh mesh = new PolygonMesh(); mesh.NSize = 2; mesh.MSize = 2;
ms.AppendEntity(mesh); tr.AddNewlyCreatedDBObject(mesh, true);
// создаем и добавляем вершины AddVertexToMesh(mesh, new Point3d(col*gridstep, 0,-linehight), tr);
AddVertexToMesh(mesh, new Point3d(col*gridstep, 0, linehight), tr); AddVertexToMesh(mesh, new Point3d(col*gridstep,8*gridstep,-line-hight), tr);
AddVertexToMesh(mesh, new Point3d(col*gridstep,8*gridstep,line hight), tr);
tr.Commit();
}
// создаем вершину сети private void
AddVertexToMesh(PolygonMesh PolyMesh, Point3d Pt3d, Transaction Trans)
{
PolygonMeshVertex PMeshVer = new PolygonMeshVertex(Pt3d); PolyMesh.AppendVertex(PMeshVer);
Trans.AddNewlyCreatedDBObject(PMesh Ver, true);
}
Отлично. Клетки есть, разделители есть. Нарисовать фишку теперь тоже нетрудно. Формулы вычисления координат вершин шарика я взял из скриптовой версии игры. Правда, подправил их, чтобы объект больше походил на игровую фишку "Реверси".
Что у меня получилось в результате - смотрите на рисунке.
Теперь нужно научиться реагировать на действия пользователя.
Тут снова понадобится вспомнить мат-часть. Кроме базы данных, есть еще несколько объектов, которые относятся не к самому документу, а к приложению в целом. Это, например, объект Application - коллекция всех документов, открытых в приложении DocumentCollection. И объект взаимодействия с пользователем - Editor. Есть и другие, но их я сейчас не касаюсь.
У объекта Editor есть ряд методов для взаимодействия с пользователем: запрос объектов, запрос строки, числа, области. Запрос объекта осуществляется методом GetEntity(PromptEntityOptions). Объект PromptEntityOptions - это необязательные параметры. Через этот объект задаются строка приглашения, ключевые слова (если они нужны), выставляются ограничения на выбор объектов. Подобный объект принимают все методы ввода. Принцип хода остался прежним: пользователь выбирает клетку, куда хочет пойти. Клетка - это объект "Штриховка". Поэтому указываю, что принимаем в качестве ввода только объекты штриховки, пустой выбор - запретить, обязательно должен быть объект. И пишем строку приглашения.
Editor ed = Application.DocumentManager.MdiActive Document.Editor;
ObjectId selectObj = ObjectId.Null;
PromptEntityOptions opts = new PromptEntityOptions("Ваш ход.Укажите ячейку");
opts.SetRejectMessage("\nТолько ячейка может быть выбрана");
opts.AddAllowedClass(typeof(Hatch), false);
PromptEntityResult pr = ed.GetEntity(opts);
По клетке определяется, куда именно пользователь хочет поставить свою фишку. Далее алгоритм проверяет, можно ли это сделать. Если да - ход выполняется и нужные фишки переворачиваются.
Как уже сказано, все объекты живут внутри базы данных. Это значит, что для того чтобы прочитать или изменить свойства какого-либо объекта, этот объект нужно открыть. Открытие объектов происхо дит методом транзакции GetObject(). По завершении изменений транзакция подтверждается.
using (Transaction myT = db.TransactionManager.StartTransaction())
{
// pieceld - это id перекрашиваемой фишки в БД
// открываем объект pieceld для изменений - OpenMode.ForWrite
PolygonMesh piece = myTGetObject(this.pieceId, OpenMode.ForWrite) as PolygonMesh;
// присваиваем цвет в зависимости от того, чья фишка
piece.Color = (player == ePlayer.Human) ? Constants.HumanColor : Constants.PcColor;
// подтверждаем транзакцию myTCommit();
}
Для хранения игровой доски в памяти я сделал две структуры данных: массив и словарь. Массив хранит образ доски 8 на 8, а словарь соответствия: элемент клетки - ObjectId-штриховки. Обе структуры хранят ссылки на объекты игровой доски. При таком подходе можно не заботиться о синхронизации. Меняться будет только элемент Piece. А получить его всегда можно по ссылке. Не важно, из массива или из словаря.
Dictionary<ObjectId, Piece> GameDesc = new Dictionary<ObjectId, Piece>();
Piece[,] GameDesc_xy = new Piece[8, 8];
На .NET многие вещи мне удалось сделать красивее и проще, чем на скриптах. Возможности фрэймворка несли приятные вкусности. К примеру, с использованием LINQ структуры данных обрабатывались почти сами собой. Подсчет количества фишек пользователя - в одну строку. Выбор клетки для хода компьютера - один запрос. Красота!
int GetCounterCount(ePlayer player)
{
// подсчет фишек игрока player
return gamedesk.GameDesc.Where(x => x.Value.Player == player).Count();
}
Исходники игры можно взять тут: ftp.nanoCAD.ru. Откройте проект в Visual Studio или SharpDeveloper и скомпилируйте. Пути проекта настроены с расчетом на то, что nanoCAD установлен в стандартную директорию.
Если исходники вам не нужны, а хочется просто посмотреть на "Реверси", можно скачать собранный нами модуль (ftp.nanoCAD.ru.
Для запуска игры загрузите сборку MgdReversi.dll в nanoCAD командой NETLOAD. Теперь можно запускать игру командой PLAY.
Было бы интересно, играя в nanoCAD, остановиться на середине партии, сохранить игру в файл, открыть этот файл в AutoCAD и доиграть - ведь формат файла в обеих системах один и тот же.
Но для этого понадобится переделать архитектуру приложения: сейчас информация о состоянии игры хранится в памяти команды, а нужно ее сохранять в объектах чертежа (поле, фишки), которые сохраняются в файл. Оставим это на будущее.
А до тех пор можно играть в "Реверси" не останавливаясь, от начала и до конца игры, что под AutoCAD, что под nanoCAD - и там и там игра работает одинаково. Достаточно лишь пересобрать "Реверси" под AutoCAD - используя его SDK, ObjectARX, это несложно.
`:notags:trunk=`500|450| ...`:hupher:format=`Некоторое время назад в блоге "Нанософта" на портале habrahabr.ru была опубликована статья о программировании под nanoCAD. В тот же день прошла информация о программировании "Реверси" на Python. Спустя неделю "Реверси" были написаны уже на Silverlight, чуть позже - на Tcl/Tk. Можно смело сказать, что игра "Реверси" пошла в народ. Как раз в это время я занимался изучением возможностей скриптов под nanoCAD. Изучение требовало практики. И я подумал - а чем я хуже? И решил написать эдакий "нано-боян". Так появились 3D Reversi.
Скриптовые возможности CAD-платформ привлекали меня всегда. С помощью скрипта прикручиваешь к большой системе собственный функционал - и дальше используешь уже не только то, что "зашито" в систему разработчиками.
Для изучения симбиоза nanoCAD и JScript я стал писать "несерьезный" скрипт под САПР-систему.
Разработку игры я мысленно разделил на три части: поле игры (интерфейс), интерактив (взаимодействие с пользователем) и сами игровые алгоритмы.
Для разработки игрового поля необходимо было создавать объекты в пространстве модели nanoCAD. Без документации все попытки писать "что-то под что-то" обречены, так что первое, что я сделал, - стал искать описание объектов и функций. Я спросил у Яндекса (почти как в песне) и получил множество ссылок на разные сайты, описывающие объектную модель AutoCAD. К примеру, на сайт vbamodel.narod.ru.
Добавление объектов происходит с помощью коллекции ThisDocument.ModelSpace. Сразу оговорюсь, что я писал на JScript потому как программирую на С++ и синтаксис JS мне ближе, чем синтаксис VB Script.
Поле представляет собой 64 клетки, сетку и фишки. Сетку и фишки я сделал объемными фигурами. А для клеток поля использовал штриховки.
//Создаем рамку для штриховки (переменная shag задает сторону клетки).
var polyline;
var RefPoly = new Array(0,0,0, shag,0,0, shag,shag,0, 0,shag,0);
polyline = ms.AddPolyline(ut.CreateTypedArrayFromJSArray(5, RefPoly));
polyline.Closed = true;
//После чего заштриховываем рамку:
hatch = ms.AddHatch(1,"SOLID",false,0);
hatch.AppendOuterLoop(polyline);
Потом я размножил клетки и расставил их по местам.
Дальше пришла очередь бордюров и игровых фишек. Полностью копировать игру, написанную на Питоне, было неинтересно - требовалась изюминка. Так как nanoCAD может отображать объекты в пространстве, такой изюминкой стала объемность доски и игровых фишек. Поэтому бордюры и фишки я сделал объектами 3DMesh.
Каждое ребро - это просто прямоугольник. А каждая фишка - сфера.
Объемные объекты - это объекты типа 3DMesh. Они создаются методом Add3DMesh(...) коллекции ModelSpace. Это сеть размером M на N клеток. Для ее создания нужно указать в массиве координаты всех вершин сети. По рядам: сперва точки первого ряда, потом второго - и так до M.
//Строим массив координат точек сети:
var mesh = new Array();
var n = 0;
for (i=-1;i<2; ++i)
for (j=0;j<9; ++j)
{
mesh[n] =0;
mesh[++n]=shag*j;
mesh[++n]=shag*i/10;
n++;
}
// Создаем прямоугольник, заданный координатами, лежащий в плоскости YZ.
var mesh3d = ms.Add3DMesh(3,9,ut.CreateTypedArrayFromJSArray(5,mesh));
Потом все же добавил ребрам толщину, чтобы они не исчезали при виде сверху.
Построить сферу оказалось посложнее, пришлось вспомнить аналитическую геометрию времен первого курса. Нет, в библиотеку я не пошел. Пошел в Википедию и узнал, как сферические координаты выражаются через декартовы.
var Vertexs = new Array();
var idx =-1;
var r =22;
for(j=0; j <= 20; ++j)
for(i=0; i < 20; ++i)
{
beta = Math.PI / 19 * j;
alfa = 2 * Math.PI / 20 * i;
Vertexs[++idx] = 25 + r*(Math.cos(alfa)*Math.sin(beta));
Vertexs[++idx] = 25 + r*(Math.sin(alfa)*Math.sin(beta));
Vertexs[++idx] = r*Math.cos(beta);
}
fishka_b = ms.Add3DMesh(20,20,ut.CreateTypedArrayFromJSArray(5,Vertexs));
В результате этих действий получилось такое игровое поле (рис. 1).
Следующий шаг - организация взаимодействия скрипта с пользователем. Нужно спросить, куда игрок хочет поставить свою фишку. Этим занимается функция GetEntity().
// Смотрим, какие объекты выбраны, до тех пор пока не выбран один объект "Штриховка"
while (hatch==null)
{
ut.GetEntity(RefPoly,null,"Ваш ход");
if (RefPoly.length == 1 && RefPoly[0].EntityName == "AcDbHatch")
{
// убеждаемся что эта штриховка - это клетка поля. Она хранит координаты клетки.
if (RefPoly[0].Hyperlinks.Count > 0)
hatch = RefPoly [0];
}
x = parseInt (hatch.Hyperlinks.Item(0).URLDescription);
y = parseInt (hatch.Hyperlinks.Item(0).URLNamedLocation);
}
Тут есть хитрость: чтобы не высчитывать координаты каждой из штриховок, я при создании заложил в каждую из них информацию о местоположении на поле.
// здесь i, j - это координаты клетки
hatch.Hyperlinks.Add("xy",i.toString(),j.toString());
Когда пользователь выбирает клетку для хода, я получаю эти координаты обратно:
x = parseInt (hatch.Hyperlinks.Item(0).URLDescription);
y = parseInt (hatch.Hyperlinks.Item(0).URLNamedLocation);
Подробнее останавливаться на алгоритмах не буду. Писать суперумный AI я не стал, а функцию принятия решения скопировал из исходника "Реверси" на Питоне. Надо сказать, что язык Питон я видел впервые. Всё вполне читается. Адаптация к JScript заняла у меня минут десять.
Теперь о нюансах скриптостроения под nanoCAD.
Не знаю точно, как в VBS, а в JScript все переменные передаются в функции по значению, а не по ссылке. Что это значит? Это значит, что если вы указываете при вызове метода переменную JScript и ожидаете, что после выполнения метода вам вернется результат, вас ждет разочарование - этого не произойдет. Переменная не изменится. Так случится, например, с методом GetEntity(Object, PickedPoint [, Prompt]). Первым параметром в нем является переменная, через которую возвращается набор выбранных объектов. Но в JScript это не работает. Как обойти это ограничение? Нужно передать вместо переменной массив.
var RefPoly = new Array();
ut.GetEntity(RefPoly,null,"Выберите объект");
var entity = RefPoly[0]; // В массиве лежат выбранные элементы.
Второй важный момент заключается в том, что методы, связанные с координатами (например, создание объектов), не принимают скриптовые массивы. Так как JS и VBS - языки слабо типизированные, то их массивы могут содержать элементы разных типов. Это недопустимо. Для приведения к типизированному массиву служат функции объекта Utility CreateTypedArray(varArr,Type,ParamArray) и CreateTypedArrayFromJSArray(Type As Long, varJSArray). Здесь Type - это указание типа элементов массива, а JSArray - сам массив.
// Создаем js-массив с координатами вершин полилинии
var RefPoly = new Array(0,0,0, shag,0,0, shag,shag,0, 0,shag,0);
// Создаем полилинию, передавая в нее координаты вершин, преобразованные к TypedArray.
var polyline = ms.AddPolyline(ut.CreateTypedArrayFromJSArray(5,RefPoly));
Для правильного отображения элементов игры нужно задать стиль освещения моделей. Стиль задается в меню Вид -Стиль. Я задаю стиль "Точное" без показа ребер.
Чтобы запустить "Реверси", пишем в командной строке nanoCAD команду "JS". А потом указываем полный путь к скрипту nanoReversi.js.
Три вечера - и я достиг цели: игра заработала под nanoCAD (рис. 2).
В изомерии "Реверси" выглядят гораздо необычнее (рис. 3). Я был полностью доволен.
Несмотря на то что "Реверси" я написал что называется "just for fun", скрипт демонстрирует возможности ActiveX Automation nanoCAD. А если вспомнить, что в скриптах можно использовать и другие ActiveX-серверы, например ADO для подключения к базам данных, то скриптовые задачи уже не кажутся чем-то несерьезным.
`:notags:trunk=`500|450| ...`:hupher:format=`