После множества публикаций, где идея была продемонстрирована, развита и обсуждена, мне по работе довелось-таки создать полноценное решение и погонять его всерьёз. Останавливаться на подробностях самой идеи не буду, она высказана много где (начиная с //infostart.ru/public/320691/ и заканчивая моей предыдущей). Концепцию взял у //infostart.ru/public/551576/ и допилил под свой извращённый вкус. Здесь просто выкладываю работающий исходник и обработку, позволяющую всё это эксплуатировать уже на уровне шаблона решения. То есть, можно взять обработку и довольно легко "допилить" под свои нужды. Собственно, она и делалась для себя любимого как универсал)
Обработка предусматривает просмотр, построение, изменение схем согласно любым исходным данным. Там есть специальные шлюзы-интерфейсы, куда можно подключить собственную конкретную механику. Я не стал особенно заморачиваться красотой расположения фигур на схеме - желающие могут почитать теорию графов сами или адаптировать публикацию Ильдаровича, я сделал лишь "квадратно-гнездовое" расположение (с возможностью группировки фигур в блоки или без) и "типа-древовидное", с распихиванием фигур по уровням сначала по длине пути к ним в графе, а потом по незанятым областям.
Фигуры размещаются каждая в свою "зону", линии отрисовываются автоматом. Можно показывать одиночные фигуры. Можно обрабатывать события схемы и её элементов. Для графов со многими исходящими есть варианты показа. Есть удобные визуальные прибамбасы. Форма работает в режиме просмотра и правки схемы.
Также в коде обработки предусмотрен вызов при открытии; есть механизмы работы с таблицей вершин, таблицей рёбер, матрицей переходов - кому что окажется удобнее. Ещё есть механизм отслеживания изменений и их индикации, сравнения данных из БД с данными из самой схемы, схема умеет записываться с сохранением всех внутренних данных и восстанавливаться из ранее записанного. Обработка на УФ для несинхрона.
Часть работы идёт в технике управления xml-json, часть уже в объектной технике графической схемы.
Код снабжён пояснениями, поэтому тут особенно много расписывать не буду. Если у кого возникнут вопросы - постараюсь в теме более подробно рассказать, т.к. заранее вообще не представляю, кого что заинтересует.
Всё сделано сугубо штатными возможностями 1С и промышленно используется уже несколько месяцев. Замечены несколько случаев вроде бы валидного наполнения, которые десериализатор не любит вплоть до того, что падает платформа, но никакой системы в них выявить не удалось. При необходимости также опишу подробнее.
У формы обработки есть встроенная справка, правда, в основном с точки зрения пользователя, но основные фишки там описаны.
Ещё раз подчеркну, что "готовым решением" является в первую очередь код управления схемой, а обработка - это шаблон, заготовка без заточки под конкретные нужды.
Пример вызова (а вообще их много в обработке):
рИмяФигуры="Фигура1";
рТипЭлемента=Тип("ЭлементГрафическойСхемыДействие");
рСхема=Обработки.РаботаСГрафическойСхемой.ИнициализироватьСхему();
эгсФигура=Обработки.РаботаСГрафическойСхемой.ИнициализироватьЭлементСхемы(рСхема,рТипЭлемента,рИмяФигуры);
Если эгсФигура=Неопределено Тогда Возврат КонецЕсли;
//эгсФигура.Вставить("explanation",рИмяФигуры);
//
рКоординаты=Новый Структура;
рКоординаты.Вставить("Лево",40);
рКоординаты.Вставить("Верх",40);
рКоординаты.Вставить("Ширина",300);
рКоординаты.Вставить("Высота",100);
Обработки.РаботаСГрафическойСхемой.УстановитьКоординатыЭлементаСхемы(эгсФигура,рКоординаты);
//
Обработки.РаботаСГрафическойСхемой.УстановитьЗаголовокЭлементаСхемы(эгсФигура,"Некий заголовок");
//
идФигуры=Обработки.РаботаСГрафическойСхемой.ДобавитьЭлементВСхему(рСхема,эгсФигура);
Если не ЗначениеЗаполнено(идФигуры) Тогда Возврат КонецЕсли;
Если выбзнч.Значение<>Тип("ЭлементГрафическойСхемыДекорация") Тогда
рИмяЛинии="Линия1";
эгсЛиния=Обработки.РаботаСГрафическойСхемой.ИнициализироватьЭлементСхемы(рСхема,Тип("ЭлементГрафическойСхемыСоединительнаяЛиния"),рИмяЛинии);
//
рПараметры=Новый Структура("Схема",рСхема);
рПараметры.Вставить("Начало",идФигуры);
//рПараметры.Вставить("Конец",0); // стрелка не ведёт никуда
рПараметры.Вставить("НачалоСторона",ТипСтороныЭлементаГрафическойСхемы.Низ);
Обработки.РаботаСГрафическойСхемой.УстановитьКоординатыЭлементаСхемы(эгсЛиния,рПараметры);
//
идЛинии=Обработки.РаботаСГрафическойСхемой.ДобавитьЭлементВСхему(рСхема,эгсЛиния);
//Если не ЗначениеЗаполнено(идЛинии) Тогда
КонецЕсли;
гс=Обработки.РаботаСГрафическойСхемой.СобратьГрафическуюСхему(рСхема);
Собственно код самой главной исполняемой части:
#Область УправлениеГрафическойСхемой
#Область ОбщееСлужебное
// Возвращает наибольшее числовое значение свойства с именем рКлюч среди значений свойств элементов массива.
// Нечисловые значения свойств игнорируются.
// При ошибке возвращает 0.
//
// Параметры:
// мДляПоиска - массив коллекций, в свойствах элементов которого ведётся рассмотрение;
// рКлюч - имя свойства, чьи значения рассматриваются;
// рСтартовоеЗначение - число, значение, начиная с которого ведётся рассмотрение.
//
Функция ПолучитьМаксимальноеЗначениеСвойства(мДляПоиска,рКлюч,рСтартовоеЗначение=0)
Попытка
максЗначение=рСтартовоеЗначение;
Для каждого знч из мДляПоиска Цикл
рЗначение=СоотСвойство(знч,рКлюч,"Число");
Попытка максЗначение=Макс(максЗначение,СоотСвойство(знч,рКлюч,"Число")) Исключение КонецПопытки;
КонецЦикла;
Возврат максЗначение;
Исключение
Возврат 0;
КонецПопытки;
КонецФункции
// Возвращает элемент массива, значение которого по указанному ключу имеет указанное значение.
// При ошибке или ненахождении возвращает Неопределено.
//
// Параметры:
// мДляПоиска - массив коллекций, в свойствах элементов которого ведётся рассмотрение;
// рКлюч - имя свойства, чьи значения рассматриваются;
// рИскомое - образец (значение любого типа), на равенство которому рассматриваются значения коллекций.
//
Функция НайтиПоЗначениюКлючаВМассивеСтруктур(мДляПоиска,рКлюч,рИскомое)
Попытка
рЧисло=?(ТипЗнч(рИскомое)=Тип("Число"),"Число","");
Для каждого знч Из мДляПоиска Цикл
Если СоотСвойство(знч,рКлюч,рЧисло)=рИскомое Тогда Возврат знч КонецЕсли;
КонецЦикла;
Возврат Неопределено;
Исключение
Возврат Неопределено;
КонецПопытки;
КонецФункции
// Используется в первичной и вторичной механике для определения, чем будет коллекция - структурой или соответствием. По умолчанию структура.
Функция НовыйСоответствиеСтруктура()
Возврат Новый Структура;
КонецФункции
// Получает строковое представление значения свойства элемента ГС по имени этого свойства. Используется в первичной и вторичной механике.
// Если напрямую не указан нужный тип, то число и дата преобразовываются в строку (краевые пробелы НЕ обрезаются),
// в остальных случаях возвращается значение без преобразования типов.
// При ошибке возвращает Неопределено.
//
// Параметры:
// рЭлемент - структура или соответствие, описывающее элемент схемы;
// рИмяСвойства - строка, имя свойства как ключ;
// рНужныйТип - строка, строковое представление того типа, который должен быть у результата (обычно "Число", "Дата" итд).
//
Функция СоотСвойство(рЭлемент,рИмяСвойства,рНужныйТип="") Экспорт
рТипЭлемента=ТипЗнч(рЭлемент);
Попытка
Если рТипЭлемента=Тип("Структура") Тогда
рЗначение=Неопределено;
Если не рЭлемент.Свойство(рИмяСвойства,рЗначение) Тогда Возврат "" КонецЕсли;
ИначеЕсли рТипЭлемента=Тип("Соответствие") Тогда
рЗначение=рЭлемент.Получить(рИмяСвойства);
Если рЗначение=Неопределено Тогда Возврат "" КонецЕсли;
Иначе
Сообщить("В получение свойства передан объект типа "+Строка(рТипЭлемента)+", он не может быть обработан!");
Возврат "";
КонецЕсли;
Исключение
Сообщить("Ошибка при получении свойства "+рИмяСвойства+": "+ОписаниеОшибки());
Возврат "";
КонецПопытки;
//
рТип=ТипЗнч(рЗначение);
Если не ПустаяСтрока(рНужныйТип) и рТип=Тип(рНужныйТип) Тогда
// преобразовывать тип не надо
Иначе
Если рТип=Тип("Число") Тогда
рЗначение=Формат(рЗначение,"ЧГ=0");
ИначеЕсли рТип=Тип("Дата") Тогда
рЗначение=Формат(рЗначение,"ДФ=ггггММддччммсс");
ИначеЕсли рТип=Тип("Строка") Тогда
//рЗначение=СокрЛП(рЗначение); // БЕЗ обрезки пробелов!
Иначе
// преобразовывать тип не надо
КонецЕсли;
КонецЕсли;
Возврат рЗначение;
КонецФункции
#КонецОбласти
#Область ДействияПервичногоПостроения
// Действия первичного построения необходимы для построения схемы на основе атомарных данных, компонуют коллекцию коллекций свойств объектов схемы,
// которая затем будет через JSON преобразована в строку и десериализована в объект "ГрафическаяСхема".
// Рекомендуется ИнициализироватьСхему, затем создать нужные элементы, используя ИнициализироватьЭлементСхемы, УстановитьКоординатыЭлементаСхемы
// и УстановитьЗаголовокЭлементаСхемы, затем СобратьГрафическуюСхему.
// Обычно при первичном построении большинство коллекций это структуры, а не соответствия.
// Важно! ИнициализироватьСхему это единственная функция, которая применима только на начальном этапе, т.к. работает всегда со структурой, а не соответствием.
// Возвращает соответствие с одним элементом #value, типа Структура; состав структуры аналогичен сериализованным свойствам графической схемы.
// При ошибке возвращает Неопределено.
//
// Параметры:
// рПараметры - структура, описывающая настройки схемы; необязательный, если не указан, схема создаётся с настройками по умолчанию;
// параметры можно заполнять из данных уже имеющейся ГС с помощью ЗаполнитьЗначенияСвойств.
// Ключи структуры:
// ИспользоватьСетку (булево), по умолчанию Истина;
// РежимОтрисовкиСетки (свойство ГС), по умолчанию Линии;
// ГоризонтальныйШагСетки (число), по умолчанию 20;
// ВертикальныйШагСетки (число), по умолчанию 20.
//
Функция ИнициализироватьСхему(рПараметры=Неопределено) Экспорт
Попытка
Если ТипЗнч(рПараметры)<>Тип("Структура") Тогда рПараметры=Новый Структура КонецЕсли;
рИспользоватьСетку=?(рПараметры.Свойство("ИспользоватьСетку"),рПараметры.ИспользоватьСетку,Истина);
рРежимОтрисовкиСетки=?(рПараметры.Свойство("РежимОтрисовкиСетки"),рПараметры.РежимОтрисовкиСетки,РежимОтрисовкиСеткиГрафическойСхемы.Линии);
рГоризонтальныйШагСетки=?(рПараметры.Свойство("ГоризонтальныйШагСетки"),рПараметры.ГоризонтальныйШагСетки,20);
рВертикальныйШагСетки=?(рПараметры.Свойство("ВертикальныйШагСетки"),рПараметры.ВертикальныйШагСетки,20);
//
Если рИспользоватьСетку Тогда
Если рРежимОтрисовкиСетки=РежимОтрисовкиСеткиГрафическойСхемы.Линии Тогда
рОтрисовка="Lines";
ИначеЕсли рРежимОтрисовкиСетки=РежимОтрисовкиСеткиГрафическойСхемы.Точки Тогда
рОтрисовка="Dots";
ИначеЕсли рРежимОтрисовкиСетки=РежимОтрисовкиСеткиГрафическойСхемы.ШахматнаяСетка Тогда
рОтрисовка="Chess";
Иначе
рОтрисовка="None";
КонецЕсли;
Иначе
рОтрисовка="None";
КонецЕсли;
струСхема=Новый Структура;
струСхема.Вставить("backColor", Новый Соответствие);
струСхема.backColor.Вставить("#type", "jv8ui:Color");
струСхема.backColor.Вставить("#value", "{http://v8.1c.ru/8.1/data/ui/style}FieldBackColor");
струСхема.Вставить("enableGrid",рИспользоватьСетку);
струСхема.Вставить("drawGridMode",рОтрисовка);
струСхема.Вставить("gridHorizontalStep",рГоризонтальныйШагСетки);
струСхема.Вставить("gridVerticalStep",рВертикальныйШагСетки);
струСхема.Вставить("bpUUID", "00000000-0000-0000-0000-000000000000");
струСхема.Вставить("useOutput", "Auto");
струСхема.Вставить("printPropItem", Новый Массив);
струСхема.printPropItem.Добавить(Новый Структура("key,val",6,10 ));
струСхема.printPropItem.Добавить(Новый Структура("key,val",7,10 ));
струСхема.printPropItem.Добавить(Новый Структура("key,val",8,10 ));
струСхема.printPropItem.Добавить(Новый Структура("key,val",9,10 ));
струСхема.printPropItem.Добавить(Новый Структура("key,val",13,0 ));
струСхема.printPropItem.Добавить(Новый Структура("key,val",16,0 ));
//струСхема.Вставить("item", Новый Массив); // добавлять только если есть хотя бы один элемент
рСтруктурыТиповЭлементовГС=ПостроитьКэшСтруктурВсехТиповЭлементовСхемы(); // для элементов этой схемы
значСхема=Новый Соответствие;
значСхема.Вставить("#value",струСхема);
значСхема.Вставить("СтруктурыТиповЭлементов",рСтруктурыТиповЭлементовГС);
Возврат значСхема;
Исключение
Сообщить("ИнициализироватьСхему, общая ошибка: "+ОписаниеОшибки());
Возврат Неопределено;
КонецПопытки;
КонецФункции
// Возвращает массив строковых кратких названий элементов ГС.
//
Функция ПостроитьНотациюТиповЭлементовСхемы()
спТиповЭлементовГС=Новый СписокЗначений;
спТиповЭлементовГС.Добавить(0,"Декорация");
//спТиповЭлементовГС.Добавить(1,"ДекоративнаяЛиния"); // потом продумать вопрос, когда по коду определяется тип - неоднозначная ситуация!
спТиповЭлементовГС.Добавить(1,"СоединительнаяЛиния");
спТиповЭлементовГС.Добавить(2,"Старт");
спТиповЭлементовГС.Добавить(3,"Завершение");
спТиповЭлементовГС.Добавить(4,"Условие");
спТиповЭлементовГС.Добавить(5,"Действие");
спТиповЭлементовГС.Добавить(6,"ВыборВарианта");
спТиповЭлементовГС.Добавить(7,"Разделение");
спТиповЭлементовГС.Добавить(8,"Слияние");
спТиповЭлементовГС.Добавить(9,"Обработка");
спТиповЭлементовГС.Добавить(10,"ВложенныйБизнесПроцесс");
Возврат спТиповЭлементовГС;
КонецФункции
// Возвращает число, соотвтетствующее типу стороны элемента ГС.
// При отсутствии подходящих значений или при ошибке возвращает Неопределено.
//
// Параметры:
// рТип - значение типа ТипСтороныЭлементаГрафическойСхемы, если задание стороны делается для любого элемента ГС, кроме Выбора,
// или структура с обязательными ключами ТипСтороны и НомерВарианта, если задание стороны делается для варианта элемента Выбор.
//
Функция ПреобразоватьТипСтороныЭлементаВЧисло(рТипИлиВариант)
Попытка
// Кроме основных 1-5, используются стороны вариантов (только для элемента ВыборВарианта):
//// чётные - левые стороны, нечётные - правые
//// поэтому 1 вариант левая сторона - это 6, а 1 вариант правая сторона - 7
//// для 2 варианта: 8 (лево) и 9 (право) соответственно
Если ТипЗнч(рТипИлиВариант)=Тип("Структура") Тогда
// это указание на вариант выбора
Если рТипИлиВариант.ТипСтороны=ТипСтороныЭлементаГрафическойСхемы.Лево Тогда
коэф=0;
ИначеЕсли рТипИлиВариант.ТипСтороны=ТипСтороныЭлементаГрафическойСхемы.Право Тогда
коэф=1;
Иначе
Возврат Неопределено;
КонецЕсли;
Возврат 2*(3+рТипИлиВариант.НомерВарианта)+коэф;
Иначе
Если рТипИлиВариант=ТипСтороныЭлементаГрафическойСхемы.Лево Тогда Возврат 1
ИначеЕсли рТипИлиВариант=ТипСтороныЭлементаГрафическойСхемы.Верх Тогда Возврат 2
ИначеЕсли рТипИлиВариант=ТипСтороныЭлементаГрафическойСхемы.Право Тогда Возврат 3
ИначеЕсли рТипИлиВариант=ТипСтороныЭлементаГрафическойСхемы.Низ Тогда Возврат 4
ИначеЕсли рТипИлиВариант=ТипСтороныЭлементаГрафическойСхемы.Центр Тогда Возврат 5
Иначе Возврат Неопределено;
КонецЕсли;
КонецЕсли;
Исключение
Сообщить("ПреобразоватьТипСтороныЭлементаВЧисло, общая ошибка: "+ОписаниеОшибки());
Возврат Неопределено;
КонецПопытки;
КонецФункции
// Возвращает числовой код типа элемента ГС согласно внутренней нотации.
// При ошибке возвращает Неопределено.
//
// Параметры:
// рТипЭлемента - число (от 0 до 10), строковое представление типа или Тип элемента ГС (например, Тип("ЭлементГрафическойСхемыДействие")).
//
Функция ПолучитьКодТипаЭлементаСхемы(рТипЭлемента)
Попытка
спТиповЭлементовГС=ПостроитьНотациюТиповЭлементовСхемы();
Если ТипЗнч(рТипЭлемента)=Тип("Число") Тогда // сразу задан код
Если спТиповЭлементовГС.НайтиПоЗначению(рТипЭлемента)=Неопределено Тогда Возврат Неопределено КонецЕсли;
рКодТипаЭлемента=рТипЭлемента;
ИначеЕсли ТипЗнч(рТипЭлемента)=Тип("Строка") Тогда // строка с кратким именем
рКодТипаЭлемента=Неопределено;
Для каждого знч Из спТиповЭлементовГС Цикл
Если СокрЛП(знч.Представление)=СокрЛП(рТипЭлемента) Тогда
рКодТипаЭлемента=знч.Значение; Прервать;
КонецЕсли;
КонецЦикла;
Иначе // тип
рКодТипаЭлемента=Неопределено;
Для каждого знч Из спТиповЭлементовГС Цикл
Если ТипЗнч(рТипЭлемента)=Тип("ЭлементГрафическойСхемы"+СокрЛП(знч.Представление))
или рТипЭлемента=Тип("ЭлементГрафическойСхемы"+СокрЛП(знч.Представление))
Тогда
рКодТипаЭлемента=знч.Значение; Прервать;
КонецЕсли;
КонецЦикла;
КонецЕсли;
Возврат рКодТипаЭлемента;
Исключение
Сообщить("ПолучитьКодТипаЭлементаСхемы, общая ошибка: "+ОписаниеОшибки());
Возврат Неопределено;
КонецПопытки;
КонецФункции
// Возвращает структуру, состав которой аналогичен сериализованным свойствам указанного элемента графической схемы.
// Кроме описанных в этой коллекции, всегда добавляются ключи Имя (строковое имя элемента) и Тип (по параметру рТипЭлемента).
// При ошибке возвращает Неопределено.
//
// Параметры:
// рТипЭлемента - число (от 0 до 10), строковое представление типа или Тип элемента ГС (например, Тип("ЭлементГрафическойСхемыДействие")).
//
Функция ПостроитьСтруктуруДляТипаЭлементаСхемы(рТипЭлемента)
Попытка
рКодТипаЭлемента=ПолучитьКодТипаЭлементаСхемы(рТипЭлемента);
Если рКодТипаЭлемента=Неопределено Тогда Возврат Неопределено КонецЕсли;
струЭлемента=Новый Структура; // здесь это может быть структурой; потом оно при необходимости преобразуется в соответствие
струЭлемента.Вставить("itemType", рКодТипаЭлемента);
струЭлемента.Вставить("lineColor", Новый Соответствие);
струЭлемента.lineColor.Вставить("#type", "jv8ui:Color");
струЭлемента.lineColor.Вставить("#value", "{http://v8.1c.ru/8.1/data/ui/style}BorderColor");
//
струЭлемента.Вставить("backColor", Новый Соответствие);
струЭлемента.backColor.Вставить("#type", "jv8ui:Color");
струЭлемента.backColor.Вставить("#value", "auto");
//
струЭлемента.Вставить("textColor", Новый Соответствие);
струЭлемента.textColor.Вставить("#type", "jv8ui:Color");
струЭлемента.textColor.Вставить("#value", "{http://v8.1c.ru/8.1/data/ui/style}FormTextColor");
струЭлемента.Вставить("alignHor", "Center");
струЭлемента.Вставить("alignVer", "Center");
струЭлемента.Вставить("currentLanguage", "#");
струЭлемента.Вставить("picturePlacement", "Left");
струЭлемента.Вставить("textFont", Новый Структура("kind","AutoFont"));
струЭлемента.Вставить("tipText", Новый Соответствие);
струЭлемента.Вставить("transparent", Ложь);
струЭлемента.Вставить("hyperlink", Ложь);
струЭлемента.Вставить("itemTitle", Новый Массив);
струЭлемента.Вставить("groupNum", 0);
Если рКодТипаЭлемента=0 Тогда
струЭлемента.Вставить("angle", Новый Соответствие);
струЭлемента.angle.Вставить("#type", "jxs:decimal");
струЭлемента.angle.Вставить("#value", 0);
струЭлемента.Вставить("flipMode", 0);
струЭлемента.Вставить("shape", "Block");
Иначе
струЭлемента.Вставить("border", Новый Структура("width,gap,style",Новый Соответствие,Ложь,Новый Соответствие));
струЭлемента.border.width.Вставить("#type", "jxs:decimal");
струЭлемента.border.width.Вставить("#value", 1);
струЭлемента.border.style.Вставить("#type", "jsch:ConnectorLineType");
струЭлемента.border.style.Вставить("#value", "Solid");
струЭлемента.Вставить("point", Новый Массив);
КонецЕсли;
Если рКодТипаЭлемента=1 Тогда
струЭлемента.Вставить("beginArrowStyle", "None");
струЭлемента.Вставить("connectFromItemId", -1); // Если decorativeLine=Истина, то можно и из ниоткуда
струЭлемента.Вставить("connectFromPortIndex", 0);
струЭлемента.Вставить("connectToItemId", -1);
струЭлемента.Вставить("decorativeLine", Истина); // Если Ложь, то будет неубираемая "пристегнутая" линия к объекту
струЭлемента.Вставить("endArrowStyle", "Filled");
струЭлемента.Вставить("portIndexFrom", 4);
струЭлемента.Вставить("portIndexTo", 0);
струЭлемента.Вставить("textPos", "FirstSegment");
Иначе
струЭлемента.Вставить("rectBottom", 40);
струЭлемента.Вставить("rectLeft", 60);
струЭлемента.Вставить("rectRight", 80);
струЭлемента.Вставить("rectTop", 20);
струЭлемента.Вставить("picture", Новый Соответствие);
струЭлемента.Вставить("pictureStyle", 4);
КонецЕсли;
Если рКодТипаЭлемента >= 2 Тогда
струЭлемента.Вставить("pointUUID", Строка(Новый УникальныйИдентификатор));
струЭлемента.Вставить("passageState", 0);
струЭлемента.Вставить("tableCode", 0);
КонецЕсли;
Если рКодТипаЭлемента=4 Тогда
струЭлемента.Вставить("falsePortIndex", 1);
струЭлемента.Вставить("truePortIndex", 3);
ИначеЕсли рКодТипаЭлемента=5 Тогда
струЭлемента.Вставить("addrZoneDivideYPos", 16);
струЭлемента.Вставить("groupAddressing", Ложь);
струЭлемента.Вставить("isAddrZoneDivideValid", Истина);
струЭлемента.Вставить("explanation", "");
ИначеЕсли рКодТипаЭлемента=6 Тогда
струЭлемента.Вставить("transition", Новый Массив);
ИначеЕсли рКодТипаЭлемента=10 Тогда
струЭлемента.Вставить("subprocessUUID", "00000000-0000-0000-0000-000000000000");
КонецЕсли;
Возврат струЭлемента;
Исключение
Сообщить("ПостроитьСтруктуруДляТипаЭлементаСхемы, общая ошибка: "+ОписаниеОшибки(),СтатусСообщения.Важное);
Возврат Неопределено;
КонецПопытки;
КонецФункции
// Возвращает соответствие, где ключ - Тип элемента ГС, а значение - структура, соответствующая этому типу.
// Рекомендуется к использованию перед началом работы с ГС, для её ключа СтруктурыТиповЭлементов.
// При ошибке возвращает Неопределено.
//
Функция ПостроитьКэшСтруктурВсехТиповЭлементовСхемы() Экспорт
Попытка
спТиповЭлементовГС=ПостроитьНотациюТиповЭлементовСхемы();
соотСтруктурТипов=Новый Соответствие;
//
Для каждого знч Из спТиповЭлементовГС Цикл
струЭлемента=ПостроитьСтруктуруДляТипаЭлементаСхемы(знч.Значение);
Если струЭлемента=Неопределено Тогда Продолжить КонецЕсли;
соотСтруктурТипов.Вставить(Тип("ЭлементГрафическойСхемы"+СокрЛП(знч.Представление)),струЭлемента);
КонецЦикла;
Возврат соотСтруктурТипов;
Исключение
Сообщить("ПостроитьКэшСтруктурВсехТиповЭлементовСхемы, общая ошибка: "+ОписаниеОшибки(),СтатусСообщения.Важное);
КонецПопытки;
КонецФункции
// Возвращает для конкретного элемента ГС его структуру, состав её аналогичен сериализованным свойствам, полученным в ПостроитьСтруктуруДляТипаЭлементаСхемы.
// При ошибке возвращает Неопределено.
//
// Параметры:
// рТипЭлемента - Тип элемента ГС, например, Тип("ЭлементГрафическойСхемыДействие");
// рИмяЭлемента - строка, уникальное имя элемента в пределах схемы. Проверка уникальности происходит в момент добавления элемента в коллекцию элементов ГС.
//
Функция ИнициализироватьЭлементСхемы(рСхема,рТипЭлемента,рИмяЭлемента="") Экспорт
Попытка
струЭлементаОбразец=рСхема.Получить("СтруктурыТиповЭлементов").Получить(рТипЭлемента);
Если струЭлементаОбразец=Неопределено Тогда
Сообщить("ИнициализироватьЭлементСхемы: структура элемента с типом """+Строка(рТипЭлемента)+""" не найдена!");
Возврат Неопределено;
КонецЕсли;
струЭлемента=НовыйСоответствиеСтруктура();
// в любом случае копируем так
Для каждого киз Из струЭлементаОбразец Цикл
Если ТипЗнч(киз.Значение)=Тип("Массив") Тогда знч=Новый Массив Иначе знч=киз.Значение КонецЕсли; // иначе по-дурному кэширует
струЭлемента.Вставить(киз.Ключ,знч);
КонецЦикла;
// служебные, для удобства работы, перед сериализацией будут удалены.
струЭлемента.Вставить("Имя",рИмяЭлемента);
струЭлемента.Вставить("Тип",рТипЭлемента);
струЭлемента.Вставить("itemCode",рИмяЭлемента);
струЭлемента.Вставить("itemId",Неопределено); // определяется при добавлении в коллекцию элементов ГС
струЭлемента.Вставить("itemTabOrder",Неопределено); // определяется при добавлении в коллекцию элементов ГС
струЭлемента.Вставить("zOrder",Неопределено); // определяется при добавлении в коллекцию элементов ГС
Если рТипЭлемента=Тип("ЭлементГрафическойСхемыДействие")
или рТипЭлемента=Тип("ЭлементГрафическойСхемыВложенныйБизнесПроцесс")
Тогда
струЭлемента.Вставить("taskDescription",рИмяЭлемента); // Должно быть задано при добавлении; возможно=itemCode
КонецЕсли;
Возврат струЭлемента;
Исключение
Сообщить("ИнициализироватьЭлементСхемы, общая ошибка: "+ОписаниеОшибки(),СтатусСообщения.Важное);
Возврат Неопределено;
КонецПопытки;
КонецФункции
// Расставляет все точки, нужные для отрисовки элемента ГС, согласно указанным координатам, определяя прямоугольную область "обитания" элемента ГС и его фигуру.
//
// Параметры:
// струЭлемента - структура/соответствие, соответствующая типу элемента (определяемая ранее в ПостроитьСтруктуруДляТипаЭлементаСхемы);
// рПараметры - структура, где:
// для не-линий обязательны числовые: ключи Лево, Верх, Ширина, Высота;
// для линий обязательны ключи: Схема (структура), Начало и Конец (структуры или id); и необязательны ДекоративнаяЛиния (булево),
// НачалоСторона и КонецСторона (типа ТипСтороныЭлементаГрафическойСхемы).
// При этом, если нужно, чтобы стрелка никуда не вела, следует НЕ указывать вообще ключ "Конец".
//
Процедура УстановитьКоординатыЭлементаСхемы(струЭлемента,рПараметры=Неопределено) Экспорт
Попытка
рТипЭлемента=СоотСвойство(струЭлемента,"Тип");
//
Если рТипЭлемента=Тип("ЭлементГрафическойСхемыДекоративнаяЛиния")
или рТипЭлемента=Тип("ЭлементГрафическойСхемыСоединительнаяЛиния")
Тогда
рНачало=?(ТипЗнч(рПараметры.Начало)=Тип("Структура"),рПараметры.Начало.ItemId,рПараметры.Начало);
Если рПараметры.Свойство("Конец") Тогда
рКонец=?(ТипЗнч(рПараметры.Конец)=Тип("Структура"),рПараметры.Конец.ItemId,рПараметры.Конец);
Иначе
// линия никуда не ведёт
рКонец=0;
КонецЕсли;
//
струЭлемента.Вставить("decorativeLine",?(рПараметры.Свойство("ДекоративнаяЛиния"),рПараметры.ДекоративнаяЛиния,Ложь));
струЭлемента.Вставить("connectFromItemId",рНачало);
струЭлемента.Вставить("connectToItemId",рКонец);
//
рТипСтороныНач=?(рПараметры.Свойство("НачалоСторона"),ПреобразоватьТипСтороныЭлементаВЧисло(рПараметры.НачалоСторона),Неопределено);
рТипСтороныНач=?(рТипСтороныНач=Неопределено,1,рТипСтороныНач); // лево
струЭлемента.Вставить("portIndexFrom",рТипСтороныНач);
//
рТипСтороныКон=?(рПараметры.Свойство("КонецСторона"),ПреобразоватьТипСтороныЭлементаВЧисло(рПараметры.КонецСторона),Неопределено);
Если рКонец<>0 Тогда
рТипСтороныКон=?(рТипСтороныКон=Неопределено,2,рТипСтороныКон); // верх
струЭлемента.Вставить("portIndexTo",рТипСтороныКон);
КонецЕсли;
рПортВарианта=СоотСвойство(струЭлемента,"connectFromPortIndex");
//
// Рисуем только начало и окончание. Остальное система дополнит сама при отрисовке.
рКоординатыНач=РассчитатьКоординатыЭлемента(рПараметры.Схема,рНачало,рТипСтороныНач,рПортВарианта);
Если рКонец<>0 Тогда
рКоординатыКон=РассчитатьКоординатыЭлемента(рПараметры.Схема,рКонец,рТипСтороныКон);
Если рКоординатыНач=Неопределено или рКоординатыКон=Неопределено Тогда
Сообщить("УстановитьКоординатыЭлементаСхемы, ошибка расчёта координат элемента "+СокрЛП(струЭлемента.Имя)+"!");
Возврат;
КонецЕсли;
КонецЕсли;
//
струЭлемента.Вставить("connectFromPortIndex",рПортВарианта);
мКоординат=СоотСвойство(струЭлемента,"point");
Если ТипЗнч(мКоординат)<>Тип("Массив") Тогда мКоординат=Новый Массив КонецЕсли;
соотНач=НовыйСоответствиеСтруктура();
соотНач.Вставить("x",рКоординатыНач.x);
соотНач.Вставить("y",рКоординатыНач.y);
мКоординат.Добавить(соотНач);
Если рКонец<>0 Тогда
соотКон=НовыйСоответствиеСтруктура();
соотКон.Вставить("x",рКоординатыКон.x);
соотКон.Вставить("y",рКоординатыКон.y);
мКоординат.Добавить(соотКон);
Иначе
соотКон=НовыйСоответствиеСтруктура();
соотКон.Вставить("x",рКоординатыНач.x);
соотКон.Вставить("y",рКоординатыНач.y+20);
мКоординат.Добавить(соотКон);
КонецЕсли;
//
// случай, когда линия идет снизу на верхнюю границу (ставим точку середины)
Если рКонец<>0 и СоотСвойство(мКоординат[0],"y","Число") > СоотСвойство(мКоординат[1],"y","Число") И СоотСвойство(струЭлемента,"portIndexTo","Число")=2 Тогда
х1=РассчитатьКоординатыЭлемента(рПараметры.Схема,СоотСвойство(струЭлемента,"connectFromItemId","Число"),3).x;
х2=РассчитатьКоординатыЭлемента(рПараметры.Схема,СоотСвойство(струЭлемента,"connectToItemId","Число"),1).x;
Если х1<>Неопределено и х2<>Неопределено Тогда
xmid=Цел((х1+х2)/2);
рВертШагСетки=СоотСвойство(СоотСвойство(рПараметры.Схема,"#value"),"gridVerticalStep");
ymid=СоотСвойство(мКоординат[1],"y","Число")-рВертШагСетки;
соотСред=НовыйСоответствиеСтруктура();
соотСред.Вставить("x",xmid);
соотСред.Вставить("y",ymid);
мКоординат.Вставить(1,соотСред);
КонецЕсли;
КонецЕсли;
//
струЭлемента.Вставить("point",мКоординат);
Иначе
// координаты и размер
//Если рКоординаты.Лево=Неопределено И рКоординаты.Верх<>Неопределено Тогда // ищем максимальный X
// рКоординаты.Лево=ПолучитьМаксимальноеЗначениеКлючаВМассивеСтруктур(струСхемы.item,"rectRight") + струСхемы.gridHorizontalStep;
//КонецЕсли;
//Если рКоординаты.Верх=Неопределено И рКоординаты.Лево<>Неопределено Тогда // ищем максимальный Y
// рКоординаты.Верх=ПолучитьМаксимальноеЗначениеКлючаВМассивеСтруктур(струСхемы.item,"rectBottom") + струСхемы.gridVerticalStep;
//КонецЕсли;
струЭлемента.Вставить("rectLeft",рПараметры.Лево);
струЭлемента.Вставить("rectTop",рПараметры.Верх);
струЭлемента.Вставить("rectRight",рПараметры.Лево+рПараметры.Ширина);
струЭлемента.Вставить("rectBottom",рПараметры.Верх+рПараметры.Высота);
РасставитьТочкиФигурыЭлемента(струЭлемента);
КонецЕсли;
Исключение
Сообщить("УстановитьКоординатыЭлементаСхемы, общая ошибка: "+ОписаниеОшибки(),СтатусСообщения.Важное);
КонецПопытки;
КонецПроцедуры
// Собственно готови данные для отрисовки конкретной фигуры элемента ГС. Вспомогательная для УстановитьКоординатыЭлементаСхемы.
//
// Параметры:
// струЭлемента - структура/соответствие, соответствующая типу элемента (определяемая ранее в ПостроитьСтруктуруДляТипаЭлементаСхемы);
// рМодификатор - число; до 10 включительно совпадает с нотацией типа элемента ГС, значения с 11 по 13 - нетипичные геометрические фигуры.
//
Процедура РасставитьТочкиФигурыЭлемента(струЭлемента,рМодификатор=Неопределено)
Попытка
itemType=?(рМодификатор=Неопределено,СоотСвойство(струЭлемента,"itemType","Число"),рМодификатор);
//
rectLeft=СоотСвойство(струЭлемента,"rectLeft","Число");
rectTop=СоотСвойство(струЭлемента,"rectTop","Число");
rectRight=СоотСвойство(струЭлемента,"rectRight","Число");
rectBottom=СоотСвойство(струЭлемента,"rectBottom","Число");
мТочек1=Новый Массив;
Если itemType=0 ИЛИ itemType=5 ИЛИ itemType=9 ИЛИ itemType=10 Тогда // Декорация/Действие/Обработка/ВложенныйПроцесс: Прямоугольник
мТочек1.Добавить(Новый Структура("x,y",rectLeft,rectTop));
мТочек1.Добавить(Новый Структура("x,y",rectRight-1,rectTop));
мТочек1.Добавить(Новый Структура("x,y",rectRight-1,rectBottom-1));
мТочек1.Добавить(Новый Структура("x,y",rectLeft,rectBottom-1));
//
ИначеЕсли itemType=2 Тогда // Старт: Прямоугольник + треугольник снизу (высота треугольника=5)
dy=Цел((rectRight-rectLeft)/2/Sqrt(3));
dy=?(rectBottom-dy<=rectTop,Цел((rectBottom-rectTop)/2),dy); // корректировка запредельных значений
мТочек1.Добавить(Новый Структура("x,y",rectLeft,rectTop));
мТочек1.Добавить(Новый Структура("x,y",rectRight-1,rectTop));
мТочек1.Добавить(Новый Структура("x,y",rectRight-1,rectBottom-dy));
мТочек1.Добавить(Новый Структура("x,y",Цел((rectLeft+rectRight)/2),rectBottom-1));
мТочек1.Добавить(Новый Структура("x,y",rectLeft,rectBottom-dy));
//
ИначеЕсли itemType=3 Тогда // Завершение: Прямоугольник + треугольник сверху (высота треугольника=5)
dy=Цел((rectRight-rectLeft)/2/Sqrt(3));
dy=?(rectTop+dy>=rectBottom,Цел((rectRight-rectLeft)/2),dy); // корректировка запредельных значений
мТочек1.Добавить(Новый Структура("x,y",Цел((rectLeft+rectRight)/2),rectTop));
мТочек1.Добавить(Новый Структура("x,y",rectRight-1,rectTop+dy));
мТочек1.Добавить(Новый Структура("x,y",rectRight-1,rectBottom-1));
мТочек1.Добавить(Новый Структура("x,y",rectLeft,rectBottom-1));
мТочек1.Добавить(Новый Структура("x,y",rectLeft,rectTop+dy));
//
ИначеЕсли itemType=4 Тогда // Условие: Шестиугольник (расчет середины высоты:dy/2,угол 60 градусов)
dx=Цел((rectBottom-rectTop)/2/Sqrt(3)); // ДельтаX при 60 градусном уклоне
dx=?(2*dx>(rectRight-rectLeft),Цел((rectRight-rectLeft)/4),dx); // корректировка запредельных значений
мТочек1.Добавить(Новый Структура("x,y",rectLeft,Цел((rectTop+rectBottom)/2)));
мТочек1.Добавить(Новый Структура("x,y",rectLeft+dx,rectTop));
мТочек1.Добавить(Новый Структура("x,y",rectRight-1-dx,rectTop));
мТочек1.Добавить(Новый Структура("x,y",rectRight-1,Цел((rectTop+rectBottom)/2)));
мТочек1.Добавить(Новый Структура("x,y",rectRight-1-dx,rectBottom-1));
мТочек1.Добавить(Новый Структура("x,y",rectLeft+dx,rectBottom-1));
//
ИначеЕсли itemType=6 Тогда // ВыборВарианта: Прямоугольник
мТочек1.Добавить(Новый Структура("x,y",rectLeft,rectTop));
мТочек1.Добавить(Новый Структура("x,y",rectRight-1,rectTop));
мТочек1.Добавить(Новый Структура("x,y",rectRight-1,rectBottom-1));
мТочек1.Добавить(Новый Структура("x,y",rectLeft,rectBottom-1));
//
ИначеЕсли itemType=7 Тогда // Разделение: Треугольник, острый угол вниз
dx=Цел((rectRight-rectLeft)/2)-1;
мТочек1.Добавить(Новый Структура("x,y",rectLeft,rectTop));
мТочек1.Добавить(Новый Структура("x,y",rectLeft+2*dx,rectTop));
мТочек1.Добавить(Новый Структура("x,y",rectLeft+dx,rectBottom-1));
//
ИначеЕсли itemType=8 Тогда // Слияние: Треугольник, острый угол вверх
dx=Цел((rectRight-rectLeft)/2)-1;
мТочек1.Добавить(Новый Структура("x,y",rectLeft,rectBottom-1));
мТочек1.Добавить(Новый Структура("x,y",rectLeft+2*dx,rectBottom-1));
мТочек1.Добавить(Новый Структура("x,y",rectLeft+dx,rectTop));
//
ИначеЕсли itemType=11 Тогда // (нетиповые фигуры) Галстук - бабочка
dx=Цел((rectBottom-rectTop)/2/Sqrt(3)); // ДельтаX при 60 градусном уклоне
dx=?(2*dx>(rectRight-rectLeft),Цел((rectRight-rectLeft)/4),dx); // корректировка запредельных значений
мТочек1.Добавить(Новый Структура("x,y",rectLeft,Цел((rectTop+rectBottom)/2)));
мТочек1.Добавить(Новый Структура("x,y",rectLeft+dx,rectTop));
мТочек1.Добавить(Новый Структура("x,y",Цел((rectLeft+rectRight)/2),Цел((rectTop+rectBottom)/2)));
мТочек1.Добавить(Новый Структура("x,y",rectRight-1-dx,rectTop));
мТочек1.Добавить(Новый Структура("x,y",rectRight-1,Цел((rectTop+rectBottom)/2)));
мТочек1.Добавить(Новый Структура("x,y",rectRight-1-dx,rectBottom-1));
мТочек1.Добавить(Новый Структура("x,y",Цел((rectLeft+rectRight)/2),Цел((rectTop+rectBottom)/2)));
мТочек1.Добавить(Новый Структура("x,y",rectLeft+dx,rectBottom-1));
//
ИначеЕсли itemType=12 Тогда // (нетиповые фигуры) Звезда шерифа
// сторона маленького треугольника
dc=Цел((rectRight-rectLeft)/3);
dchalf=Цел(dc/2);
dchigh=Цел(dchalf*Sqrt(3));
//dx=Цел((rectBottom-rectTop)/2/Sqrt(3)); // ДельтаX при 60 градусном уклоне
мТочек1.Добавить(Новый Структура("x,y",rectLeft,rectTop+dchigh));
мТочек1.Добавить(Новый Структура("x,y",rectLeft+dc,rectTop+dchigh));
мТочек1.Добавить(Новый Структура("x,y",rectLeft+dc+dchalf,rectTop));
мТочек1.Добавить(Новый Структура("x,y",rectRight-1-dc,rectTop+dchigh));
мТочек1.Добавить(Новый Структура("x,y",rectRight-1,rectTop+dchigh));
мТочек1.Добавить(Новый Структура("x,y",rectRight-1-dchalf,rectTop+2*dchigh));
мТочек1.Добавить(Новый Структура("x,y",rectRight-1,rectTop+3*dchigh));
мТочек1.Добавить(Новый Структура("x,y",rectRight-1-dc,rectTop+3*dchigh));
мТочек1.Добавить(Новый Структура("x,y",rectLeft+dc+dchalf,rectBottom-1));
мТочек1.Добавить(Новый Структура("x,y",rectLeft+dc,rectTop+3*dchigh));
мТочек1.Добавить(Новый Структура("x,y",rectLeft,rectTop+3*dchigh));
мТочек1.Добавить(Новый Структура("x,y",rectLeft+dchalf,rectTop+2*dchigh));
//
ИначеЕсли itemType=13 Тогда // (нетиповые фигуры) Круг
// сторона маленького треугольника
radius=Мин(Цел((rectRight-rectLeft)/2),Цел((rectBottom-rectTop)/2));
xcenter=rectLeft + radius;
ycenter=rectTop + radius;
pi=3.141592635897;
НачУгол=-20;
КонУгол=200;
ШагГрад=10;
у=НачУгол;
Пока у<=КонУгол Цикл
Угол=у*pi/180;
x=Окр(Cos(Угол)*radius);
y=Окр(Sin(Угол)*radius);
мТочек1.Добавить(Новый Структура("x,y",xcenter+x,ycenter+y));
у=у + ШагГрад;
КонецЦикла;
//
КонецЕсли;
Если ТипЗнч(НовыйСоответствиеСтруктура())=Тип("Соответствие") Тогда // преобразуем
мТочек2=Новый Массив;
Для каждого рТочка1 Из мТочек1 Цикл
соот=Новый Соответствие;
Для каждого киз Из рТочка1 Цикл
соот.Вставить(киз.Ключ,киз.Значение);
КонецЦикла;
мТочек2.Добавить(соот);
КонецЦикла;
Иначе
мТочек2=мТочек1;
КонецЕсли;
струЭлемента.Вставить("point",мТочек2);
Исключение
Сообщить("РасставитьТочкиФигурыЭлемента, общая ошибка: "+ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
// Устанавливает элементу ГС заголовок без локализации языка, по умолчанию.
//
// Параметры:
// струЭлемента - структура/соответствие, соответствующая типу элемента (определяемая ранее в ПостроитьСтруктуруДляТипаЭлементаСхемы);
// рЗаголовок - строка, произвольная надпись на элементе.
//
Процедура УстановитьЗаголовокЭлементаСхемы(струЭлемента,рЗаголовок) Экспорт
соот=НовыйСоответствиеСтруктура();
соот.Вставить("lang","#");
соот.Вставить("content",рЗаголовок);
мЗаголовка=СоотСвойство(струЭлемента,"itemTitle");
Если ТипЗнч(мЗаголовка)<>Тип("Массив") Тогда мЗаголовка=Новый Массив КонецЕсли;
мЗаголовка.Добавить(соот);
струЭлемента.Вставить("itemTitle",мЗаголовка);
КонецПроцедуры
// Добавляет структуру, описывающую элемент ГС, в соответствующую коллекцию элементов самой структуры ГС.
// Возвращает уникальный в пределах схемы id добавленного элемента, 1 и более. При ошибке возвращает Неопределено.
//
// Параметры:
// рСхема - соответствие структур, массивов и прочих коллекций, созданное ИнициализироватьСхему и заполненное в ходе построения схемы;
// струЭлемента - структура/соответствие, соответствующая типу элемента (определяемая ранее в ПостроитьСтруктуруДляТипаЭлементаСхемы).
//
Функция ДобавитьЭлементВСхему(рСхема,струЭлемента) Экспорт
Попытка
рИмяЭлемента=СокрЛП(СоотСвойство(струЭлемента,"itemCode"));
струИмеющегося=ПолучитьЭлементСхемыПоИмени(рСхема,рИмяЭлемента);
Если струИмеющегося<>Неопределено Тогда // такой уже есть
Возврат СоотСвойство(струИмеющегося,"itemId","Число");
КонецЕсли;
струСхемы=Неопределено;
мЭлементовГС=ПолучитьКоллекциюЭлементовСхемы(рСхема,струСхемы);
// Ищем максимальный itemId
струЭлемента.Вставить("itemId",ПолучитьМаксимальноеЗначениеСвойства(мЭлементовГС,"itemId")+1);
// Ищем такой же itemCode (заимствованный код; возможно, нужен рефакторинг!)
//Если НайтиПоЗначениюКлючаВМассивеСтруктур(струСхемы.item,"itemCode",струЭлемента.itemCode) <> Неопределено Тогда
// _базоваяЧасть=струЭлемента.itemCode;
// _нс=1;
// Пока НайтиПоЗначениюКлючаВМассивеСтруктур(струСхемы.item,"itemCode",_базоваяЧасть + Формат(_нс,"ЧГ=")) <> Неопределено Цикл
// _нс=_нс + 1;
// КонецЦикла;
// струЭлемента.itemCode=_базоваяЧасть + Формат(_нс,"ЧГ=");
//КонецЕсли;
// Ищем максимальный itemTabOrder
// пока так. Это порядок обхода. Возможно нужно более продвинутое вычисление сделать
// (считать все подчиненные и соединенные элементы последнего и прибавлять на их количество к максимальному)
струЭлемента.Вставить("itemTabOrder",ПолучитьМаксимальноеЗначениеСвойства(мЭлементовГС,"itemTabOrder")+5);
// Ищем максимальный zOrder
струЭлемента.Вставить("zOrder",ПолучитьМаксимальноеЗначениеСвойства(мЭлементовГС,"zOrder",-1)+1);
// собственно добавление в коллекцию элементов схемы
мЭлементовГС.Добавить(струЭлемента);
струСхемы.Вставить("item",мЭлементовГС);
рСхема.Вставить("#value",струСхемы);
Возврат СоотСвойство(струЭлемента,"itemId","Число");
Исключение
Сообщить("ДобавитьЭлементВСхему, общая ошибка: "+ОписаниеОшибки(),СтатусСообщения.Важное);
Возврат Неопределено;
КонецПопытки;
КонецФункции
// Добавляет вариант в элемент типа "Выбор". Элемент уже должен быть инициализирован. Возвращает порядковый номер варианта, начиная с 0.
// При ошибке возвращает Неопределено.
//
// Параметры:
// струЭлемента - структура/соответствие, соответствующая типу элемента (определяемая ранее в ПостроитьСтруктуруДляТипаЭлементаСхемы);
// рИмяВарианта - строка, имя варианта, уникальное в пределах выбора;
// рЗаголовокВарианта - строка, произвольная надпись на варианте.
//
Функция ДобавитьВариантВыбора(струЭлемента,рИмяВарианта,рЗаголовокВарианта) Экспорт
Попытка
соотЗаголовка=НовыйСоответствиеСтруктура();
соотЗаголовка.Вставить("lang","#");
соотЗаголовка.Вставить("content",рЗаголовокВарианта);
мЗаголовка=Новый Массив;
мЗаголовка.Добавить(соотЗаголовка);
соотВарианта=НовыйСоответствиеСтруктура();
соотВарианта.Вставить("name",рИмяВарианта);
соотВарианта.Вставить("description",мЗаголовка);
соотВарианта.Вставить("backColor",СоотСвойство(струЭлемента,"backColor"));
мВариантов=СоотСвойство(струЭлемента,"transition");
Если ТипЗнч(мВариантов)<>Тип("Массив") Тогда мВариантов=Новый Массив КонецЕсли;
мВариантов.Добавить(соотВарианта);
струЭлемента.Вставить("transition",мВариантов);
Возврат мВариантов.Количество()-1;
Исключение
Сообщить("ДобавитьВариантВыбора, общая ошибка: "+ОписаниеОшибки());
Возврат Неопределено;
КонецПопытки;
КонецФункции
// Получает координаты для работы с портом (точкой входа/выхода соединительной линии); возвращает структуру с ключами x,y;
// используется при первичной работе со схемой (далее можно обращаться к свойствам элемента как объекта);
// При ошибке возвращает Неопределено.
//
// Параметры:
// рСхема - соответствие структур, массивов и прочих коллекций, созданное ИнициализироватьСхему и заполненное в ходе построения схемы;
// itemId - уникальный в пределах схемы идентификатор элемента;
// portIndex - код типа стороны элемента получателя (см. ПреобразоватьТипСтороныЭлементаВЧисло), ни в коем случае убирать "Знач"!
// connectFromPortIndex - код типа стороны элемента отправителя (см. ПреобразоватьТипСтороныЭлементаВЧисло), изменяется внутри функции.
//
Функция РассчитатьКоординатыЭлемента(рСхема,itemId,Знач portIndex,connectFromPortIndex=0)
Попытка
рРезультат=Новый Структура("x,y",0,0);
струСхемы=Неопределено;
мЭлементовГС=ПолучитьКоллекциюЭлементовСхемы(рСхема,струСхемы);
Если мЭлементовГС.Количество()=0 Тогда
Сообщить("Внутренняя ошибка: коллекция элементов схемы пуста!",СтатусСообщения.Важное);
Иначе
струЭлемента=НайтиПоЗначениюКлючаВМассивеСтруктур(мЭлементовГС,"itemId",itemId);
Если струЭлемента=Неопределено Тогда
Сообщить("Внутренняя ошибка: элемент не найден по своему id "+СокрЛП(itemId)+"!",СтатусСообщения.Важное);
Возврат рРезультат;
КонецЕсли;
КонецЕсли;
// Порты:
//1: Лево
//2: Верх
//3: Право
//4: Низ
//5: Центр
//6: Вариант 1 Лево
//7: Вариант 1 Право
//8: Вариант 2 Лево
//9: Вариант 2 Право
// Каждый вариант - это 18 точек по шкале Y от rectBottom. Середина=rectBottom - (18/2)
dy=0;
Если portIndex > 5 Тогда
caseCount=СоотСвойство(струЭлемента,"transition").Количество();
connectFromPortIndex=Цел((portIndex-6)/2);
portIndex=1 + (portIndex%2)*2;
dy=(caseCount - connectFromPortIndex - 1)*18 + 18/2;
КонецЕсли;
рЛево=СоотСвойство(струЭлемента,"rectLeft","Число");
рВерх=СоотСвойство(струЭлемента,"rectTop","Число");
рПраво=СоотСвойство(струЭлемента,"rectRight","Число");
рНиз=СоотСвойство(струЭлемента,"rectBottom","Число");
Если portIndex=1 Тогда
рРезультат.x=рЛево;
рРезультат.y=?(dy>0,рНиз-1-dy,Цел((рВерх+рНиз+1)/2));
ИначеЕсли portIndex=2 Тогда
рРезультат.x=Цел((рЛево+рПраво+1)/2);
рРезультат.y=рВерх;
ИначеЕсли portIndex=3 Тогда
рРезультат.x=рПраво;
рРезультат.y=?(dy>0,рНиз-1-dy,Цел((рВерх+рНиз+1)/2));
ИначеЕсли portIndex=4 Тогда
рРезультат.x=Цел((рЛево+рПраво+1)/2);
рРезультат.y=рНиз;
ИначеЕсли portIndex=5 Тогда
рРезультат.x=Цел((рЛево+рПраво+1)/2);
рРезультат.y=Цел((рВерх+рНиз+1)/2);
КонецЕсли;
Возврат рРезультат;
Исключение
Сообщить("РассчитатьКоординатыЭлемента, общая ошибка: "+ОписаниеОшибки());
Возврат Неопределено;
КонецПопытки;
КонецФункции
#КонецОбласти
#Область ДействияВторичнойНастройки
// Действия вторичной настройки полезны, когда объект "ГрафическаяСхема" уже есть, и надо изменить свойство элемента, не имеющее во встроенном языке
// возможности записи, т.е. которые только на чтение. Важно: вторичная работа ведётся уже с соответствиями, а не со структурами, т.е. все коллекции данных,
// бывшие структурами при первичном построении, теперь будут являться соответствиями.
// Используется сериализация схемы в строку, чтение строки через JSON в коллекцию коллекций, и работа с их элементами и значениями.
// Рекомендуется использовать РазобратьГрафическуюСхему, затем выполнить настройку нужных свойств, затем СобратьГрафическуюСхему.
// Возвращает массив структур или соответствий, содержащий элементы ГС. В случае необходимости дополняет коллекции до минимально правильного состава.
// При ошибке или отсутствии данных возвращает пустой массив.
//
// Параметры:
// рСхема - соответствие структур, массивов и прочих коллекций, созданное ИнициализироватьСхему и заполненное в ходе построения схемы, или сериализованное из ГС;
// знчСхемы - значение, хранимое в основном ключе #value схемы, структура или соответствие. Может изменяться внутри функции; необязательное.
//
Функция ПолучитьКоллекциюЭлементовСхемы(рСхема,знчСхемы=Неопределено) Экспорт
Попытка
Если ТипЗнч(рСхема)<>Тип("Соответствие") Тогда рСхема=Новый Соответствие КонецЕсли;
//
знчСхемы=СоотСвойство(рСхема,"#value");
Если ТипЗнч(знчСхемы)<>Тип("Структура") и ТипЗнч(знчСхемы)<>Тип("Соответствие") Тогда
рСхема.Вставить("#value",Новый Соответствие);
знчСхемы=СоотСвойство(рСхема,"#value");
КонецЕсли;
//
мЭлементовГС=СоотСвойство(знчСхемы,"item");
Если ТипЗнч(мЭлементовГС)<>Тип("Массив") Тогда мЭлементовГС=Новый Массив КонецЕсли;
//
знчСхемы.Вставить("item",мЭлементовГС);
рСхема.Вставить("#value",знчСхемы);
//
Возврат мЭлементовГС;
Исключение
Сообщить("ПолучитьКоллекциюЭлементовСхемы, общая ошибка: "+ОписаниеОшибки(),СтатусСообщения.Важное);
Возврат Новый Массив;
КонецПопытки;
КонецФункции
// Устанавливает ZOrder (порядок плана отображения относительно других перекрывающих фигур) для всех элементов указанного типа.
//
// Параметры:
// рСхема - соответствие структур, массивов и прочих коллекций, созданное ИнициализироватьСхему и заполненное в ходе построения схемы, или сериализованное из ГС;
// рТипЭлемента - число (от 0 до 10), строковое представление типа или Тип элемента ГС (например, Тип("ЭлементГрафическойСхемыДействие"));
// ZOrder - число, приоритет по возрастанию (старший накрывает младших), изменяется внутри процедуры.
//
Процедура УстановитьZOrderВсемЭлементам(рСхема,рТипЭлемента,ZOrder) Экспорт
Попытка
рКодТипаЭлемента=ПолучитьКодТипаЭлементаСхемы(рТипЭлемента);
Если рКодТипаЭлемента=Неопределено Тогда Возврат КонецЕсли;
знчСхемы=Неопределено;
мЭлементовГС=ПолучитьКоллекциюЭлементовСхемы(рСхема,знчСхемы);
Если мЭлементовГС.Количество()=0 Тогда Возврат КонецЕсли; // нет ни одного элемента
//
Для каждого рЭлементГС из мЭлементовГС цикл
Если СоотСвойство(рЭлементГС,"itemType","Число")=рКодТипаЭлемента Тогда
рЭлементГС.Вставить("zOrder",ZOrder);
ZOrder=ZOrder+1;
КонецЕсли;
КонецЦикла;
знчСхемы.Вставить("item",мЭлементовГС);
рСхема.Вставить("#value",знчСхемы);
Исключение
Сообщить("УстановитьZOrderВсемЭлементам, общая ошибка: "+ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
// Возвращает элемент схемы как соответствие или структуру из массива item схемы.
// При ошибке или отсутствии возвращает Неопределено.
//
// Параметры:
// рСхема - соответствие структур, массивов и прочих коллекций, созданное ИнициализироватьСхему и заполненное в ходе построения схемы, или сериализованное из ГС;
// рИмяЭлемента - строка, уникальное имя элемента в пределах схемы.
//
Функция ПолучитьЭлементСхемыПоИмени(рСхема,рИмяЭлемента) Экспорт
Попытка
мЭлементовГС=ПолучитьКоллекциюЭлементовСхемы(рСхема);
Если мЭлементовГС.Количество()=0 Тогда Возврат Неопределено КонецЕсли; // нет ни одного элемента
//
Для каждого рЭлементГС Из мЭлементовГС Цикл
Если СокрЛП(СоотСвойство(рЭлементГС,"itemCode"))=рИмяЭлемента Тогда
Если ТипЗнч(рЭлементГС)=Тип("Соответствие") Тогда
// приводим к структуре (изначально он ею и был, поэтому все ключи применимы)
стру=Новый Структура;
Для каждого киз Из рЭлементГС Цикл
стру.Вставить(киз.Ключ,киз.Значение);
КонецЦикла;
Возврат стру;
ИначеЕсли ТипЗнч(рЭлементГС)=Тип("Структура") Тогда
Возврат рЭлементГС;
Иначе
Возврат Неопределено;
КонецЕсли;
КонецЕсли;
КонецЦикла;
//
Возврат Неопределено;
Исключение
Сообщить("ПолучитьЭлементСхемыПоИмени, общая ошибка: "+ОписаниеОшибки());
Возврат Неопределено;
КонецПопытки;
КонецФункции
// Устанавливает элементу массива item схемы соответствие/структуру с новыми значениями ключей свойств, т.е. по сути обновляет свойства элемента в коллекции.
// Возвращает "ссылку" на структуру/соответствие конкретного обработанного элемента ГС. При ошибке возвращает Неопределено.
//
// Параметры:
// рСхема - соответствие структур, массивов и прочих коллекций, созданное ИнициализироватьСхему и заполненное в ходе построения схемы, или сериализованное из ГС;
// рИмяЭлемента - строка, уникальное имя элемента в пределах схемы;
// рЗначение - структура, содержащая значения ключей, описывающих свойства элемента согласно нотации и ПостроитьСтруктуруДляТипаЭлементаСхемы.
//
Функция ОбновитьЭлементСхемыПоИмени(рСхема,рИмяЭлемента,рЗначение) Экспорт
Попытка
знчСхемы=Неопределено;
мЭлементовГС=ПолучитьКоллекциюЭлементовСхемы(рСхема,знчСхемы);
Если мЭлементовГС.Количество()=0 Тогда Возврат Неопределено КонецЕсли; // нет ни одного элемента
//
Для каждого рЭлементГС Из мЭлементовГС Цикл
Если СокрЛП(СоотСвойство(рЭлементГС,"itemCode"))=рИмяЭлемента Тогда
Для каждого киз Из рЗначение Цикл // и никаких ЗаполнитьЗначенияСвойств!
рЭлементГС.Вставить(киз.Ключ,киз.Значение);
КонецЦикла;
Прервать;
КонецЕсли;
КонецЦикла;
//
знчСхемы.Вставить("item",мЭлементовГС);
рСхема.Вставить("#value",знчСхемы);
//
Возврат рЭлементГС;
Исключение
Сообщить("ОбновитьЭлементСхемыПоИмени, общая ошибка: "+ОписаниеОшибки());
Возврат Неопределено;
КонецПопытки;
КонецФункции
// Удаляет элемент из коллекции элементов схемы, в т.ч. при необходимости и связанные с ним.
//
// Параметры:
// рСхема - соответствие структур, массивов и прочих коллекций, созданное ИнициализироватьСхему и заполненное в ходе построения схемы, или сериализованное из ГС;
// рИмяЭлемента - строка, уникальное имя элемента в пределах схемы;
// рУдалятьСвязанныеЛинии - булево; имеет смысл только при удалении фигуры (не Линии), по умолчанию Ложь;
// если Истина, то линии, входящие в фигуру и исходящие из неё, также будут удалены;
// если Ложь, то линии, входящие в фигуру и исходящие из неё, будут вести "в никуда" (в связочные id ставится -1).
//
Процедура УдалитьЭлементСхемыПоИмени(рСхема,рИмяЭлемента,рУдалятьСвязанныеЛинии=Ложь) Экспорт
Попытка
знчСхемы=Неопределено;
мЭлементовГС=ПолучитьКоллекциюЭлементовСхемы(рСхема,знчСхемы);
Если мЭлементовГС.Количество()=0 Тогда Возврат КонецЕсли; // нет ни одного элемента
мНенужных=Новый Массив;
пози=Неопределено;
Для й=0 По мЭлементовГС.Количество()-1 Цикл
Если СокрЛП(СоотСвойство(мЭлементовГС[й],"itemCode"))=рИмяЭлемента Тогда
мНенужных.Добавить(мЭлементовГС.Получить(й)); Прервать;
КонецЕсли;
КонецЦикла;
Если мНенужных.Количество()=1 Тогда
рУдаляемый=мНенужных.Получить(0);
рТипЭлемента=СоотСвойство(рУдаляемый,"itemType","Число");
Если рТипЭлемента=Неопределено Тогда Возврат КонецЕсли;
Если рТипЭлемента=1 Тогда // это линия
// пока ничего не делаем
Иначе
идФигуры=СоотСвойство(рУдаляемый,"itemId","Число");
Для каждого рЭлемент Из мЭлементовГС Цикл
Если СоотСвойство(рЭлемент,"itemType","Число")<>1 Тогда Продолжить КонецЕсли; // не линия
Если СоотСвойство(рЭлемент,"connectFromItemId","Число")=идФигуры Тогда
Если рУдалятьСвязанныеЛинии Тогда
мНенужных.Добавить(рЭлемент);
Иначе
рЭлемент.Вставить("connectFromItemId",-1); // обрезаем
КонецЕсли;
КонецЕсли;
Если СоотСвойство(рЭлемент,"connectToItemId","Число")=идФигуры Тогда
Если рУдалятьСвязанныеЛинии Тогда
мНенужных.Добавить(рЭлемент);
Иначе
рЭлемент.Вставить("connectToItemId",-1); // обрезаем
КонецЕсли;
КонецЕсли;
КонецЦикла; // по поиску линий, связанных с удаляемой фигурой
КонецЕсли; // если удаляется фигура/линия
КонецЕсли;
//
// собственно удаляем
Для каждого рЭлемент Из мНенужных Цикл
мЭлементовГС.Удалить(мЭлементовГС.Найти(рЭлемент));
КонецЦикла;
знчСхемы.Вставить("item",мЭлементовГС);
рСхема.Вставить("#value",знчСхемы);
Исключение
Сообщить("УдалитьЭлементСхемыПоИмени, общая ошибка: "+ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
// Возвращает структуру ВариантаВыбора по её имени или порядковому номеру среди вариантов.
// При ошибке или отсутствии данных возвращает Неопределено.
//
// Параметры:
// рЭлемент - структура/соответствие, соответствующая типу элемента (определяемая ранее в ПостроитьСтруктуруДляТипаЭлементаСхемы);
// рИмяИлиНомер - строковое имя или числовой номер (от 0 по возрастанию в порядке №№ вариантов в выборе).
//
Функция ПолучитьВариантВыбораПоИмениИлиНомеру(рЭлемент,рИмяИлиНомер) Экспорт
Попытка
мВариантов=СоотСвойство(рЭлемент,"transition");
Если ТипЗнч(мВариантов)<>Тип("Массив") Тогда Возврат Неопределено КонецЕсли;
//
Если ТипЗнч(рИмяИлиНомер)=Тип("Строка") Тогда // по имени
Для й=0 По мВариантов.Количество()-1 Цикл
рВариант=мВариантов.Получить(й);
Если СокрЛП(СоотСвойство(рВариант,"name"))<>СокрЛП(рИмяИлиНомер) Тогда Продолжить КонецЕсли;
стру=Новый Структура;
стру.Вставить("НомерВарианта",й);
Для каждого киз Из рВариант Цикл
стру.Вставить(киз.Ключ,киз.Значение);
КонецЦикла;
Возврат стру;
КонецЦикла;
ИначеЕсли ТипЗнч(рИмяИлиНомер)=Тип("Число") Тогда
Если мВариантов.Количество()<=рИмяИлиНомер Тогда Возврат Неопределено КонецЕсли;
Возврат мВариантов.Получить(рИмяИлиНомер);
КонецЕсли;
//
Возврат Неопределено;
Исключение
Сообщить("ПолучитьВариантВыбораПоИмениИлиНомеру, общая ошибка: "+ОписаниеОшибки());
Возврат Неопределено;
КонецПопытки;
КонецФункции
// Заполняет структуру ВариантаВыбора по её имени или порядковому номеру среди вариантов переданными значениями коллекции.
// Если варианта с таким именем/номером нет, ничего не делает.
//
// Параметры:
// рЭлемент - структура/соответствие, соответствующая типу элемента (определяемая ранее в ПостроитьСтруктуруДляТипаЭлементаСхемы);
// рИмяИлиНомер - строковое имя или числовой номер (от 0 по возрастанию в порядке №№ вариантов в выборе);
// рЗначение - структура/соответствие данных конкретного варианта выбора.
//
Процедура ОбновитьВариантВыбораПоИмениИлиНомеру(рЭлемент,рИмяИлиНомер,рЗначение) Экспорт
Попытка
мВариантов=СоотСвойство(рЭлемент,"transition");
Если ТипЗнч(мВариантов)<>Тип("Массив") Тогда Возврат КонецЕсли;
//
Если мВариантов.Количество()<=рИмяИлиНомер Тогда Возврат КонецЕсли;
//
Если ТипЗнч(рИмяИлиНомер)=Тип("Строка") Тогда // по имени
Для й=0 По мВариантов.Количество()-1 Цикл
рВариант=мВариантов.Получить(й);
Если СокрЛП(СоотСвойство(рВариант,"name"))=СокрЛП(рИмяИлиНомер) Тогда
Для каждого киз Из рЗначение Цикл
рВариант.Вставить(киз.Ключ,киз.Значение);
КонецЦикла;
Прервать;
КонецЕсли;
КонецЦикла;
ИначеЕсли ТипЗнч(рИмяИлиНомер)=Тип("Число") Тогда
рВариант=мВариантов.Получить(рИмяИлиНомер);
Для каждого киз Из рЗначение Цикл
рВариант.Вставить(киз.Ключ,киз.Значение);
КонецЦикла;
КонецЕсли;
//
рЭлемент.Вставить("transition",мВариантов);
//
Исключение
Сообщить("ОбновитьВариантВыбораПоИмениИлиНомеру, общая ошибка: "+ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
// Удаляет элемент ВариантаВыбора по её имени или порядковому номеру среди вариантов из массива вариантов выбора.
// Если варианта с таким именем/номером нет, ничего не делает.
//
// Параметры:
// рЭлемент - структура/соответствие, соответствующая типу элемента (определяемая ранее в ПостроитьСтруктуруДляТипаЭлементаСхемы);
// рИмяИлиНомер - строковое имя или числовой номер (от 0 по возрастанию в порядке №№ вариантов в выборе).
//
Процедура УдалитьВариантВыбораПоИмениИлиНомеру(рЭлемент,рИмяИлиНомер) Экспорт
Попытка
мВариантов=СоотСвойство(рЭлемент,"transition");
Если ТипЗнч(мВариантов)<>Тип("Массив") Тогда Возврат КонецЕсли;
//
Если ТипЗнч(рИмяИлиНомер)=Тип("Строка") Тогда // по имени
пози=Неопределено;
Для й=0 По мВариантов.Количество()-1 Цикл
рВариант=мВариантов.Получить(й);
Если СокрЛП(СоотСвойство(рВариант,"name"))=СокрЛП(рИмяИлиНомер) Тогда
пози=й; Прервать;
КонецЕсли;
КонецЦикла;
ИначеЕсли ТипЗнч(рИмяИлиНомер)=Тип("Число") Тогда
Если мВариантов.Количество()<=рИмяИлиНомер Тогда Возврат КонецЕсли;
пози=рИмяИлиНомер;
Иначе
Возврат;
КонецЕсли;
//
Если пози<>Неопределено Тогда
мВариантов.Удалить(пози);
рЭлемент.Вставить("transition",мВариантов);
КонецЕсли;
//
Исключение
Сообщить("УдалитьВариантВыбораПоИмениИлиНомеру, общая ошибка: "+ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
#КонецОбласти
// Преобразует соответствие структур и массивов в графическую схему. Возвращает объект типа "Графическая схема".
// При ошибке возвращает Неопределено.
//
// Параметры:
// рСхема - соответствие структур, массивов и прочих коллекций, созданное ИнициализироватьСхему и заполненное в ходе построения схемы.
//
Функция СобратьГрафическуюСхему(рСхема) Экспорт
Попытка
// убираем служебные поля
Попытка рСхема.Удалить("СтруктурыТиповЭлементов") Исключение КонецПопытки;
//
знчСхемы=Неопределено;
мЭлементовГС=ПолучитьКоллекциюЭлементовСхемы(рСхема,знчСхемы);
Если мЭлементовГС.Количество()=0 Тогда
знчСхемы.Удалить("item");
Иначе
Для каждого рЭлементГС Из мЭлементовГС Цикл
Попытка рЭлементГС.Удалить("Имя") Исключение КонецПопытки;
Попытка рЭлементГС.Удалить("Тип") Исключение КонецПопытки;
КонецЦикла;
знчСхемы.Вставить("item",мЭлементовГС);
КонецЕсли;
рСхема.Вставить("#value",знчСхемы);
// приводим из коллекций в строку JSON
рЗапись=Новый ЗаписьJSON;
рЗапись.УстановитьСтроку(); // кодировка и прочее тут неважно
ЗаписатьJSON(рЗапись,рСхема);
//
строСхемы=рЗапись.Закрыть();
//сообщить("Сборка: "+Символы.ПС+стросхемы);
//
// десериализуем в ГС
рЧтение=Новый ЧтениеJSON;
рЧтение.УстановитьСтроку(строСхемы);
Возврат СериализаторXDTO.ПрочитатьJSON(рЧтение,Тип("ГрафическаяСхема"));
Исключение
Сообщить("СобратьГрафическуюСхему, общая ошибка: "+ОписаниеОшибки());
Возврат Неопределено;
КонецПопытки;
КонецФункции
// Преобразует графическую схему в соответствие соответствий и массивов. Возвращает соответствие.
// Внимание! Даже если ранее в свойствах коллекций были структуры, они заменятся на соответствия, т.к. только такое восстановление из JSON возможно.
// Дописывает служебные поля: в коллекцию самой схемы дописывает СтруктурыТиповЭлементов (кэш структур всех типов элементов ГС),
// в коллекцию каждого элемента ГС дописывает Имя (строковое имя элемента) и Тип (тип в нотации 1С, например, Тип("ЭлементГрафическойСхемыДействие")).
// При ошибке возвращает Неопределено.
//
// Параметры:
// рГС - объект типа "Графическая схема".
//
Функция РазобратьГрафическуюСхему(рГС) Экспорт
Попытка
// нормализуем ГС (по неизвестным причинам, обратная сериализация не понимает, например. жирный шрифт)
рШрифт=Новый Шрифт; // по умолчанию (восстанавливать оформление, если надо, потом и другими средствами)
Для каждого рЭлементГС Из рГС.ЭлементыГрафическойСхемы Цикл
Попытка рЭлементГС.Шрифт=рШрифт Исключение КонецПопытки;
КонецЦикла;
// сериализуем ГС
рЗапись=Новый ЗаписьJSON;
рЗапись.УстановитьСтроку();
СериализаторXDTO.ЗаписатьJSON(рЗапись,рГС,НазначениеТипаXML.Явное);
//
строСхемы=рЗапись.Закрыть();
//сообщить("Разборка: "+Символы.ПС+стросхемы);
//
// приводим из строки JSON в коллекцию (все структуры будут отражены как соответствия!)
рЧтение=Новый ЧтениеJSON;
рЧтение.УстановитьСтроку(строСхемы);
рСхема=ПрочитатьJSON(рЧтение,Истина);
Если ТипЗнч(рСхема)<>Тип("Соответствие") Тогда Возврат Неопределено КонецЕсли;
// добавляем служебные поля
рСхема.Вставить("СтруктурыТиповЭлементов",ПостроитьКэшСтруктурВсехТиповЭлементовСхемы());
// добавляем служебные тип и имя к каждому элементу и вносим всё обратно в схему
знчСхемы=Неопределено;
мЭлементовГС=ПолучитьКоллекциюЭлементовСхемы(рСхема,знчСхемы);
Если мЭлементовГС.Количество()>0 Тогда
спТипов=ПостроитьНотациюТиповЭлементовСхемы();
Для каждого рЭлементГС Из мЭлементовГС Цикл
рЭлементГС.Вставить("Имя",СоотСвойство(рЭлементГС,"itemCode"));
знчТип=спТипов.НайтиПоЗначению(СоотСвойство(рЭлементГС,"itemType","Число"));
Если знчТип<>Неопределено Тогда
рЭлементГС.Вставить("Тип", Тип("ЭлементГрафическойСхемы"+СокрЛП(знчТип.Представление)));
Иначе
рЭлементГС.Вставить("Тип",Неопределено);
КонецЕсли;
КонецЦикла;
КонецЕсли;
//
знчСхемы.Вставить("item",мЭлементовГС);
рСхема.Вставить("#value",знчСхемы);
Возврат рСхема;
Исключение
Сообщить("РазобратьГрафическуюСхему, общая ошибка: "+ОписаниеОшибки());
Возврат Неопределено;
КонецПопытки;
КонецФункции
#КонецОбласти
Кому пригодится или кого заинтересует - будет хорошо.
Ещё раз спасибо гигантам, на плечах которых стоит это решение. В первую очередь Diversus'у, высказавшему идею ещё в далёкие времена.