Использование XML‑свойств из кода
Свойства, которые вы объявляете в XML, — это те же свойства, что вы читаете и записываете из
кода. Они доступны из нескольких окружений — CAM API (.NET, C++, …), нативных пакетов
Delphi / C++, .NET‑постпроцессоров и постпроцессоров .sppx — и, что важно,
шаблон доступа во всех них одинаков. Эта глава один раз описывает общий шаблон, а затем
показывает небольшие особенности каждого окружения.
1 Одна модель, один шаблон доступа, много окружений
Вспомните различие типы и экземпляры. Из любого окружения вы работаете с экземплярами: живым деревом свойств конкретного объекта, где каждый узел — это указатель свойства. Через указатель вы читаете или записываете его значение, заходите в потомков, ищете и изучаете его дескриптор для метаданных типа.
Указатель всегда предоставляет одни и те же именованные аксессоры, в каком бы окружении вы ни были:
| Аксессор | Что делает |
|---|---|
Str[name] / Int[name] / Flt[name] / Bol[name] |
читают и записывают значение потомка как строку / целое / double / логическое |
Ptr[name] |
получить указатель на потомка (чтобы зайти в ComplexType) |
Arr[name] |
получить дочерний массив |
ValueAsString / ValueAsInteger / ValueAsDouble / ValueAsBoolean |
собственное значение указателя |
FindChild / FindProperty |
найти потомка по имени |
Имена могут быть составными, и регистр не важен. Вместо пошагового спуска через Ptr
передавайте точечный путь прямо в аксессор, а к элементам массива по ключу или индексу
обращайтесь через (…):
Flt["RoughPasses.StepValue"] # вложенное значение, через точку
Int["controldata.usearc"] # регистронезависимо
Flt["Axes(AxisXPos).Value"] # элемент массива по ключу, затем потомок
Str["Project.Operations(0).Name"] # элемент массива по индексу
Одни и те же имена аксессоров, тот же точечный/регистронезависимый поиск и тот же синтаксис
массивов (key) присутствуют в каждом окружении; различается только языковая обёртка:
| Окружение | Тип указателя | COM‑обёртка? |
|---|---|---|
| CAM API (.NET / C++ / …) | IST_XMLPropPointer |
да |
| Нативные пакеты Delphi / C++ | IST_XMLPropPointer |
нет |
| .NET‑постпроцессоры | INamedProperty |
нет |
Постпроцессоры .sppx (Pascal‑подобный язык) |
встроенный | нет |
Поэтому §2–9 ниже — это вариант .NET CAM API: встречающиеся там ComWrapper, Invoke /
InvokeAndWrap и using — это обёртка .NET‑COM, а не часть модели свойств. Нативное
окружение и постпроцессоры (§10–12) используют те же имена аксессоров без этой церемонии.
Как узнать полное имя свойства
Чтобы пользоваться аксессорами, нужно полное точечное имя свойства. Быстрее всего получить его из работающей программы:
- Откройте инспектор свойств операции и найдите свойство.
- У правого края его строки нажмите кнопку ⋯ (три точки), чтобы открыть меню строки, и выберите «Скопировать имя свойства в буфер обмена».
В том же меню есть «Настроить…», открывающее окно, где показано не только вычисленное значение свойства, но и его исходное выражение — удобно, когда значение параметризовано от других свойств (см. язык выражений). (Это окно есть только в недавних версиях.)
Старые версии — обходной путь. Если окна «Настроить…» нет, воспользуйтесь контекстным меню операции → «Сохранить как пользовательскую операцию». В открывшемся окне найдите свойство в инспекторе, щёлкните по нему правой кнопкой и используйте там «Скопировать имя свойства в буфер обмена». Затем уберите ведущий
RefOperations(0).из скопированного имени — он появляется лишь потому, что это окно показывает копию свойств внутри отдельной сущности UserOperation, а не живую операцию. Останется имя, которое вы передаёте аксессорам.
2 Как получить дерево свойств объекта
Объекты предметной области предоставляют своё дерево свойств через член XMLProp. Для
технологической операции:
using var xmlPropsCom = operationCom.InvokeAndWrap(operation => operation.XMLProp);
XMLProp— это единое дерево свойств объекта. Для свойств, хранящихся только вXMLProp(современный случай), оно авторитетно — для чтения/записи не нужен отдельный шаг фиксации. Но некоторые операции до сих пор дублируют отдельные значения в собственных полях объекта (унаследованный код); тамXMLPropи объект могут разойтись и должны синхронизироваться черезSaveToXMLProp/LoadFromXMLProp— см. §7.
Тот же шаблон с XMLProp применим к станкам, инструментам и другим объектам.
3 Чтение и запись значений по имени
Каждый узел предоставляет типизированные индексаторы по имени потомка: Str, Flt, Int,
Bol. Это идиома, используемая во всех примерах — значение задаётся присваиванием индексатору:
// из FullWorkflow3DProject/TechnologyHelper.cs
using var xmlPropsCom = operationCom.InvokeAndWrap(operation => operation.XMLProp);
xmlPropsCom.Invoke(xmlProps =>
{
xmlProps.Str["DrillingType"] = "HolePocketing"; // записать строку / id варианта перечисления
});
Чтение симметрично:
xmlPropsCom.Invoke(xmlProps =>
{
double step = xmlProps.Flt["StepValue"];
int count = xmlProps.Int["StepCount"];
bool on = xmlProps.Bol["Enabled"];
string mode = xmlProps.Str["DrillingType"];
});
Как и в любом окружении (§1), имя может быть
точечным путём и регистронезависимо, так что можно добраться до вложенного значения за
один вызов, а не спускаться через Ptr (§4):
double step = xmlProps.Flt["RoughPasses.StepValue"];
bool on = xmlProps.Bol["roughpasses.enabled"]; // регистр игнорируется
Сначала проверьте существование
Свойство может отсутствовать на конкретном объекте (например, необязательный или
пользовательский параметр). Защищайте чтение через PropExists:
var value = xmlProps.PropExists["DrillingType"]
? xmlProps.Str["DrillingType"]
: "";
Перечисления — по ID или по индексу. Значение перечисления — это
IDварианта, поэтомуStr["DrillingType"] = "ChipRemoving"задаёт его по ID (IDварианта, а не его подпись — см. §3.2). Можно также читать или записывать его по нулевому индексу варианта черезInt:Int["DrillingType"] = 2выбирает третий вариант, а чтениеInt["DrillingType"]возвращает индекс текущего варианта. (Это работает в каждом окружении.)
Удобный слой‑обёртка (.NET)
Пакет‑хелпер для .NET оборачивает эти индексаторы в методы‑расширения над
ComWrapper<IST_XMLPropPointer>, что позволяет обойтись без явного Invoke:
double step = xmlPropsCom.Flt("StepValue"); // геттер
xmlPropsCom.SetFlt("StepValue", 2.5); // сеттер
xmlPropsCom.SetStr("DrillingType", "ChipRemoving");
string calc = xmlPropsCom.CStr("SomeComputed"); // вычисленное значение вычисляемого свойства
Хелперы есть для каждого типа (Str/Flt/Int/Bol и их формы Set…), плюс Ptr, Arr,
FindProperty, FindPropertyInWholeScope и другие. Используйте любой стиль, подходящий вашему
коду; все они вызывают один и тот же низлежащий интерфейс.
4 Навигация по дереву
Заход в составные типы
Ptr[name] (или хелпер Ptr(name)) спускается в потомка‑ComplexType; дальше продолжайте
типизированными индексаторами. Это отражает точечные пути из вашего XML (RoughPasses.StepValue):
xmlPropsCom.Invoke(xmlProps =>
{
var rough = xmlProps.Ptr["RoughPasses"];
rough.Bol["Enabled"] = true;
rough.Flt["StepValue"] = 1.0;
});
Поиск по имени
Когда не хочется выписывать полный путь, ищите по поддереву:
using var dt = xmlPropsCom.FindPropertyInWholeScope("DrillingType");
string current = dt.ValueAsString();
dt.SetNodeValue("ChipRemoving");
FindPropertyInWholeScope возвращает первое совпадение на любой глубине — предпочитайте
цепочки Ptr (или ограниченный областью FindProperty), когда имя может быть неуникальным.
5 Массивы
Дочерний массив — это IST_XMLPropArray, получаемый через Arr[name]. Он поддерживает
создание и добавление элементов поверх обычных операций с указателем. Вот реальный шаблон
добавления пользовательского элемента массива:
// из ExtensionOperationParamsNet/OperationCustomPropsHelper.cs
const string arrayId = "CustomOperationPropertiesArray";
using var paramArrayCom = ComWrapper.Create(operation.XMLProp.Arr[arrayId]);
if (!operation.XMLProp.PropExists[fullId])
{
var newItem = paramArrayCom.Invoke(a => a.CreateNewItem("CustomOperationProperty"));
newItem.Str["Name"] = paramId;
paramArrayCom.Invoke(a => a.AddItem(newItem));
}
Адресация элемента массива по ключу
Когда у элемента массива есть член Name/ключ, можно обратиться к нему напрямую синтаксисом
пути ArrayName(key) в индексаторе — без ручного перебора:
var fullId = $"{arrayId}({paramId})"; // например, CustomOperationPropertiesArray(MyParam)
operation.XMLProp.Str[fullId] = "true"; // чтение/запись значения элемента
bool exists = operation.XMLProp.PropExists[fullId];
6 Собственное значение указателя
Аксессоры из §3 читают потомка по имени (Str["StepValue"]). Когда у вас уже есть
указатель на само свойство (а не на его родителя), читайте или записывайте его собственное
значение. Это тоже типизировано — ValueAsString, ValueAsDouble, ValueAsInteger,
ValueAsBoolean (см. список аксессоров в §1).
ValueAsString применим к любому типу свойства и возвращает его строковое представление:
double v = somePropCom.ValueAsDouble(); // типизированное собственное значение
string raw = somePropCom.ValueAsString(); // любой тип → его текстовая форма
somePropCom.SetNodeValue("75"); // задать собственное значение указателя
7 Операции: синхронизация полей объекта с XMLProp
(Только для операций.) Для операции XMLProp — это её единое дерево свойств с двумя
задачами: единое дерево времени выполнения (чтобы общий код мог показать любую операцию в
инспекторе и значения можно было копировать между операциями) и форма, в которой свойства
операции сериализуются в/из XML при сохранении или загрузке проекта.
Исторически операции хранили свои значения в обычных полях объекта, а XMLProp был
добавлен позже сверху. Поэтому унаследованные свойства живут в обоих местах — в полях
объекта и в XMLProp — и обе копии нужно держать согласованными:
SaveToXMLProp— заталкивает текущие значения полей операции вXMLProp. Вызывайте перед чтениемXMLProp(например, перед заполнением инспектора из дерева), чтобы дерево отражало актуальное состояние объекта.LoadFromXMLProp— читает значения изXMLPropобратно в поля объекта. Вызывайте после правкиXMLProp(например, после того как пользователь изменил значение в инспекторе), чтобы модель объекта подхватила изменение.
Современные свойства живут только в XMLProp — дублирующего поля нет, поэтому
синхронизация не нужна; достаточно читать/писать XMLProp. Вызовы синхронизации важны в
основном для старых классов, которых всё ещё много.
LoadFromXMLProp также принимает XMLProp другой операции — именно так настройки одной
операции копируются из другой:
operationCom.LoadFromXmlProp(otherXmlPropsCom); // применить чужое дерево свойств к этой операции
SaveToXmlProp() возвращает свойства операции в виде дерева (с уже сброшенными в него полями
объекта) — удобно для снимка, сравнения или переноса.
8 Изучение дескрипторов и значений по умолчанию
Каждый указатель свойства связан со своим дескриптором (IST_XMLPropDescriptor), который
несёт объявленные вами в XML метаданные — имя, подпись, простой тип, значение по умолчанию,
пользовательские атрибуты, наследование. Используйте его, когда нужно рассуждать о схеме, а
не о значении (например, обобщённо перечислить параметры объекта или прочитать пользовательский
атрибут, заданный на дескрипторе).
Значения по умолчанию доступны через IST_PropDefaultsEditor (запрашивается у дескриптора): он
различает системное значение по умолчанию (объявленное в XML дескриптора) и любое
пользовательское переопределение, для метрической и дюймовой систем единиц, и позволяет
читать, задавать или сбрасывать пользовательское значение по умолчанию.
9 Практические заметки
- Освобождайте COM‑обёртки. Указатели свойств и массивы — это COM‑объекты; оборачивайте
их в
using, чтобы они освобождались своевременно. В примерах работа выполняется внутри лямбдInvoke/InvokeAndWrap, которые управляют временем жизни за вас. - Используйте точный
IDиз дескриптора — имена должны совпадать с объявленными в XML. - Задавайте перечисления и логические значения их каноническим текстом —
IDварианта для перечислений; для логических значений в примерах используются строки"true"/"false"черезStr[...]либо типизированный индексаторBol[...]. - Не боритесь с вычисляемыми значениями. Свойство, значение которого определяется
выражением
DefaultValue/Visible, будет пересчитываться; читайте его вычисленный результат черезCStr(илиValueAsCalculatedString), а если нужно хранить фиксированное значение, сделайте его реальным редактируемым полем. - Куда смотреть дальше. Репозиторий
cam-api-examples(Operation/ExtensionOperationParamsNet,FullWorkflow,ProjectMachine) содержит полные компилируемые примеры использования всего вышеописанного.
10 Нативное использование (Delphi / C++)
Нативные пакеты предоставляют тот же указатель свойства через публичный IST_XMLPropPointer.
Нет COM‑обёртки, нет Invoke, нет using: вы держите обычную ссылку на интерфейс и
вызываете те же аксессоры напрямую. Методы объекта предметной области LoadFromXMLProp /
SaveToXMLProp (и LoadConditionsFromXMLProp) получают такой указатель:
procedure LoadFromXMLProp(XMLProp: IST_XMLPropPointer);
var p: IST_XMLPropPointer;
begin
if XMLProp <> nil then with XMLProp do begin
p := FindProp('SafeLevel');
if p <> nil then with p do begin
fZRetract.IsAbs := Int['ReferenceType'] = 0; // те же аксессоры Int[…]/Flt[…]
fZRetract.IncVal := Flt['RelValue'];
fZRetract.AbsVal := Flt['AbsValue'];
end;
end;
end;
Это те же Int[…] / Flt[…] / Str[…] / Bol[…] / FindProp, что и из .NET — просто без
COM‑церемонии.
11 В .NET‑постпроцессорах (INamedProperty)
Постпроцессор на .NET читает именованные свойства команды/проекта, которые ему передали, и
выдаёт УП. Он использует чисто‑.NET‑слой без COM — INamedProperty, но доступ тот же:
индексаторы Str / Int / Flt / Bol / Ptr / Arr плюс ValueAsString /
ValueAsInteger / ValueAsDouble / ValueAsBoolean, с привычными точечными,
регистронезависимыми именами и адресацией массивов (key) / (index):
double x = cmd.Flt["Axes(AxisXPos).Value"];
int origin = cmd.Int["OriginType"];
double ox = cmd.Flt["WCS.OriginPoint.X"];
Разобранный постпроцессор есть в репозитории postprocessors-examples в
Features/NamedParameters/DemoOfWritePPFun (см. WritePPFun.cs). Тот пример — слегка
необычный контекст: он записывает параметры в файл — но доступ к свойствам ровно такой, как
выше.
12 В постпроцессорах .sppx (Pascal‑подобный язык)
Постпроцессоры .sppx пишутся на специальном Pascal‑подобном языке. Набор доступных типов
отличается, но имена методов и способ доступа к свойствам идентичны. В фрагментах ниже
Cmd — один из таких объектов со свойствами — текущая команда CLData — и те же
аксессоры применимы к любому объекту свойств: .Int["…"] / .Flt["…"] / .Str["…"] /
.Bol["…"] для значений и .Ptr["…"] для указателя на дочернее свойство, все с привычными
точечными / (key) именами:
if Cmd.Ptr["Axes(AxisXPos)"] <> 0 then // указатель на дочернее свойство (здесь: существует ли ось?)
X = Cmd.Flt["Axes(AxisXPos).Value"]
if Cmd.Int["Axes(AxisAPos).BrakeState"] > 0 then ...
CurG68_2_X = Round(Cmd.Flt["WCS.OriginPoint.X"], 4)
(реальный пример: MyDocuments\Postprocessors\Mill\Fanuc (30i)_Mill.sppx)
Далее: Пользовательские значения по умолчанию · Назад к оглавлению.