Введение: продолжим рассмотрение пакета Xbim Essentials (см. предыдущую часть тут), и сегодня рассмотрим его опции по созданию новых элементов (генерацию IFC файла), коснемся немного темы структуры файла IFC. В качестве рабочего кейса попробуем реализовать создание элемента типа "поверхность" как геометрическое представление структуры IfcSite.
1. Готовим исходные данные
Перед тем как генерировать новый IFC-элемент типа поверхности, давайте сперва посмотрим, как в целом может выглядеть поверхность в IFC-файлах. Глобально, это 2 случая - плоское представление и выдавленное 3D-тело; нас, естественно, будет интересовать первый вариант. В качестве примера обратимся к простой поверхности в среде Civil 3D:
Примечание: здесь и далее будем рассматривать простейшую поверхность только для того, чтобы ускорить процессы обработки данных. От размера поверхности разрабатываемая процедура никак не зависит (мы так делаем сугубо для удобства отладки).
Экспортируем данную поверхность в LandXML-представление, а также в Revit как набор 3D-граней
2. Формируем структуру элементов (теоретически)
Теперь необходимо сформировать понимание, как строится структура подчиненности у элементов и какие элементы необходимо будет программно файлу задать - чтобы поверхность отображалась и инициализировалась корректно.
2.1 Анализ в Xbim Xplorer
Для этого давайте опустимся до конца (до исходников поверхности), и пойдем к началу - для лучшего восприятия материала:
2.2 Составление блок-схемы
Теперь давайте составим вспомогательную блок-схему, иллюстрирующую логику подчиненности элементов внутри файла IFC
3. Закладываем структуру кода
Теперь, предварительно определившись с структурой файла, перейдем к написанию методов для генерации отдельных элементов. Работа будет состоять из трех глобальных частей - создание нового файла IFC, реализация чтение файла LabdXML и формирование на его основе геометрического представления поверхности, добавление данного геометрического представления в элемент поверхности.
Примечание: в общем случае, пакет Xbim Essentials не предназначен под генерацию IFC-файлов; в нём есть набор базовых типов элементов, доступных для создания, прочие же имеют тип ReadOnly и предназначены только для считывания. В силу этого - нам потребуется реализовать свои методы для генерации отдельных элементов файла IFC.
3.1 Создание файла
3.2 Создание точек в файле
Теперь будем изменять данный файл, добавляя в него информацию о точках (элемент IfcCartesianPoint). В силу того, что точки в файле LandXML уже пронумерованы, возникнет ситуация, что при добавлении граней, надо будет парсить IFC-файл с целью поиска, какой номер стал у той или иной точки. Чтобы этого не делать, создадим временный список типа List <string> PointsNums, который будет хранить номер точки из файла LandXML и новый номер точки (позицию строки в IFC), разделенные к примеру, символом "|".
Корректнее двигаться по описанию отдельных граней (какими точками она определяется), и данные точки заносить в IFC-файл, причем обязательно фиксируя номера точек в список (я для удобства сделал второй временный список PointsNumbers)
Дадим разъяснение для блока коды выше.
Имеется внутренний метод AddInfoAboutPoint, принимающий на вход номер точки по структуре файла LandXML. Номер сразу же фиксируется во временном списке PointsNumbers (чтобы не добавить эту же точку ещё раз в файл, если она будет присутствовать в другой грани). Далее идёт запрос к структуре <Pnts> файла LandXML с целью найти точку с таким порядковым номером, и из её значения извлекаются координаты этой точки (X,Y,Z) в виде строчного массива PointCoordinates. Далее точки преобразуются в тип Double, умножаются на 1000 (так как в файле LandXML единицы измерения - метры, а в IFC - мм) и записываются в структуру элемента IfcCartesianPoint. После этого в другой временный список PointsNums записывается номер точки по файлу LandXML + табулятор '|' + новый номер точки (строка IFC - метод EntityLabel).
Имеется внутренний метод IsContain, возвращающий булеву истину/ложь, если заданное значение уже имеется во временном списке PointsNumbers (только номера точек).
Активируются оба этих метода при переборе граней в рамках блока <Faces> в файле LandXML. Из каждой грани берется набор точек, которые её образуют, каждая точка сперва ищется в списке уже имеющихся точек PointsNumbers, если её там нет - то запускается метод AddInfoAboutPoint, добавляющий её в IFC-файл.
По окончанию, транзакция на изменение закрывается. и файл сохраняется. Причем несмотря на тип "SaveAs", он просто дозаписывается.
3.3 Создание набора граней
Здесь уже наступают сложности, так как элементы IfcPolyLoop, IfcFaceOuterBound, IfcFace, IfcConnectedFaceSet в рамках элементов геометрического представления поверхности не имеют методов создания (только чтения), поэтому элементы выше нам предстоит генерировать вручную.
Перед тем как это делать, необходимо в файле IFC избавиться от "формального окончания" - строк ниже:
ENDSEC;
END-ISO-10303-21;
Для этого мы введём 2 вспомогательных метода:
Дадим разъяснение методу выше:
Есть вспомогательный метод GetNewNum, анализирующий раннее сформированный временный список PointsNums, на предмет получения строки с новым именем строки (элемент IfcCartesianPoint) для данной точки - вершины триангуляции, на вход он принимает номер точки по файлу LandXML.
В рамках цикла foreach по переборку каждой грани происходит процесс формирования массива из номеров строк IFC для данных точек; далее эти точки записываются в структуру IFCPOLYLOOP, которая записывается в с IFCFACEOUTERBOUND, которая входит в IFCFACE. А вот набор разных IFCFACE - это уже структура для IFCCONNECTEDFACESET.
По завершению действий наш временный список можно очистить:
PointsNums.Clear();
3.4 Создание стиля поверхности
На самом деле, стиль не является обязательным, но тем не менее в рамках знакомства, его всё равно реализуем. К слову, методы Xbim Essentials также не позволяют данные элементы создавать (только читать).
Здесь будет проще всего взять за основу то, как стиль задан для поверхности в рамках существующего IFC-файла, и подменить в нем номера строк для нашего случая.
3.5 Размещаем поверхность в структуре IfcSite
Теперь осталось лишь встроить нашу поверхность в площадку IfcSite (которую, к слову, потребуется сперва ещё и создать).
Перед этим я добавил в метод CreateBasicIfc_File() операцию по определению номера для элемента IfcRepresentationContext
Здесь мы уже работает с IFC как со структурой (вернули ему привычный вид методом RepairEndOdFile()). Создаем элемент IfcSite и указываем, что его Representation = нашей созданной структуре IfcProductDefinitionShape.
Теперь запускаем наш код и пробуем подгрузить файл в Xbim Xplorer:
4. Важные комментарии
Структуру-то мы сформировали, но теперь необходимо дать комментарий к сделанным изменениям. Я миновал вопрос привязки площадки IfcLocalPlacement к нашей структуре IfcSite. Она должна быть как минимум быть, но по нулям:
Примечание: выставление для IfcLocalPlacement ненулевой точки оправдано при сборке модели в общих координатах (например, в Revit в параметры IfcLocalPlacement идут координаты точки съемки, а вся внутренняя геометрия разбивается относительно внутренних координат). При этом если импортировать итоговый IFC в САПР, то координаты IfcLocalPlacement останутся без внимания, и объект встанет только во внутренних координатах. Поэтому для изменения координат файла мы будем руководствоваться принципом предварительного пересчета каждой точки (см. статью за дополнительными пояснениями).
5. Выводы:
В данной статье мы рассмотрели итеративный процесс по формированию IFC-файла с помощью Nuget-пакета Xbim Essentials со структурой поверхности. В большей степени мы генерировали элементы IFC вручную из-за отсутствия их поддержки в пакете, но отдельные вещи пробовали создавать методами пакета. В качестве проверки - мы использовали среду Xbim Xplorer.
Исходный код я пока не выкладываю, его надо ещё причесать, отладить - но в целом, он будет доступен на репозитории здесь, где уже есть первая попытка (генерация поверхности на базе шаблона файла).