Импорт графических данных из DXF файла средствами C# |
Автор: Меньшиков П. В.
Дата: 2010-05-29 |
При написании инженерных программ часто возникает необходимость работать с графическими данными, например чертежами деталей или планами зданий. Да, иногда можно использовать предложенные разработчиком возможности для расширения функционала самой CAD-системы (например, ObjectARX для AutoCAD), но все же часто возникает необходимость работать с чертежами в отдельной самописной программе. В том случае, если программа пишется «на один раз» и чертеж не сложный, вполне можно жестко задать его в самом коде. Также можно создать свой формат исходных данных, читаемый программой, и заполняемый пользователем вручную, возможно даже с помощью какого-то удобного пользовательского интерфейса. Но что, если чертеж состоит из сотни примитивов – линий, сплайнов, дуг окружностей, или использовать программу придется регулярно с разными чертежами? Возможно, быстрее (ну и как минимум интереснее) будет сделать в программе импорт графических данных из файлов чертежей какого-либо из существующих форматов.
Формат DXF, разработанный Autodesk в качестве универсального формата обмена чертежами между разными CAD-системами, наиболее подходит для этой задачи – этот формат открыт и бесплатен (в отличие, например, от DWG – SDK RealDWG для разработки приложений, работающих с этим форматом, стоит 9000$), широко распространен (практически все CAD-системы умеют делать экспорт в DXF) и удобен для программного считывания. Полную спецификацию формата можно найти на сайте Autodesk. Существуют также готовые модули для различных сред программирования, позволяющие осуществлять импорт из различных графических форматов, в том числе и DXF, однако они тоже в большинстве своем стоят денег, и включение их в свой проект может быть сопряжено с некоторыми сложностями – такие модули часто слабо документированы, спроектированы не самым лучшим образом, не обладают достаточным количеством функций, или наоборот, имеют в своем распоряжении столько возможностей, что поиск нужных превращается в тот еще квест. Рассмотрев все эти возможности, автор решил самостоятельно написать импорт из DXF, чего и Вам рекомендует.
Итак, прежде чем приступить к написанию программы импорта, стоит рассмотреть в теории формат DXF. DXF-файл представляет собой текстовый файл в формате ASCII, заполненный так называемыми группами. Группа является минимальной структурной единицей файла. Если открыть любой DXF файл, мы увидим примерно следующее:
0
SECTION
2
HEADER
9
$ACADVER
1
AC1009
И так далее. Каждые две строчки и являются группой. В первой строчке записывается код группы, во второй – значение. Код группы – это идентификатор того, что эта группа описывает. Например, коды 0-9 используются для обозначения строковых данных, а коды 10-59 – данных в формате с плавающей запятой двойной точности (double). Но коды используются не только для указания типа значения группы; они также обозначают, что именно содержится в значении группы. Например, код 0 используется в строго определенных случаях – начало секции, конец секции, начало таблицы и т.д., код 2 означает, что в значении группы описано имя (секции, таблицы, примитива…), код 9 используется только в секции HEADER и означает имя переменной. Полный список групповых кодов с описанием можно найти в DXF Reference, здесь и здесь.
Для удобства в дальнейшем в тексте статьи группы будут записываться в виде «Код_группы»:«Значение», например 2:HEADER.
Теперь, когда понятно, из каких «кирпичиков» состоит файл, можно рассмотреть его структуру на более высоком уровне. Общая структура DXF-файла представлена на рисунке 1.

Рисунок 1 – Структура DXF файла.
Как видно из схемы, DXF-файл состоит из разделов, которые именуются секциями (SECTION). Каждая такая секция начинается с двух групп – 0:SECTION и 2:Имя_секции, и заканчивается группой 0:ENDSEC. Количество и порядок секций в различных версиях DXF могут меняться, в новых версиях добавляются новые разделы, поэтому при чтении DXF файла программными средствами не стоит делать ставку на то, что в файле будут именно такие секции и именно в таком порядке. Лучше потратить чуть больше времени и написать гибкий парсер, который сможет прочитать любую версию формата DXF без ошибок и «выудить» оттуда нужные данные.
Рассмотрим некоторые секции, которые можно встретить практически в любой относительно современной версии формата DXF – секции HEADER (заголовок), TABLES (таблицы), ENTITIES (примитивы) и BLOCKS (блоки примитивов). Разобравшись в структуре этих секций, работа с любыми другими секциями из более новых форматов не составит труда – принцип везде один и тот же.
Секция HEADER
В этой секции хранятся различные переменные чертежа, имеющие свое имя. Например, здесь хранится название и версия программы, создавшей чертеж, положение базовой точки чертежа, максимальные и минимальные координаты в чертеже и т.д. Лично мне этот раздел оказался бесполезен, т.к. мне нужна была только геометрия чертежа, но кому-то может и пригодится. Итак, структура секции HEADER показана на рисунке 2.

Рисунок 2 – Структура секции HEADER.
Поясню: в этой секции может содержаться произвольное количество переменных, каждая из которых может содержать произвольное количество данных разных типов. К примеру, переменная $EXTMIN содержит три числа с плавающей запятой двойной точности (double) – координаты X, Y и Z. Каждая переменная начинается с группы с 9:Имя_переменной. Специальной группы, обозначающей конец описания переменной, нет – переменная «кончается», когда встречается следующая группа с кодом 9 или группа 0:ENDSEC.
Секция TABLES
Здесь все несколько сложнее. В этой секции хранятся массивы данных, например, таблица слоев со всеми их свойствами, таблица стилей и т.д. Мне эта секция также не пригодилась, но для более серьезных приложений здесь явно находится довольно важная информация.
Общая структура секции TABLES показана на рисунке 3.

Рисунок 3 – Структура секции TABLES.
В принципе, из схемы должно быть все понятно – каждая таблица начинается с двух групп – 0:TABLE и 2:Имя_таблицы, и заканчивается группой 0:ENDTAB. Внутри самой таблицы чаще всего присутствует группа 70:Кол-во_строк_в_таблице, также в новых версиях DXF могут быть и другие параметры таблицы. Описание данных, входящих в таблицу, ведется построчно, и группа 0:Имя_таблицы сигнализирует о начале новой строки. Это похоже на описание переменных в секции HEADER, здесь также нет «закрывающей» группы, внутри одной строки может быть любое количество различных данных
А теперь приступаем к самому интересному.
Секция ENTITIES
Вообще-то, секция ENTITIES обычно располагается после секции BLOCKS, однако эти две секции мало чем отличаются, и начать лучше с более простой. В этом разделе файла хранятся данные о примитивах – собственно, это и есть графические данные чертежа. Примитив – это какая-то геометрическая фигура, например точка, линия, окружность, дуга и т.д. Существуют также сложные примитивы, состоящие из других примитивов, например полилиния (POLYLINE), состоящая из вертексов, соединенных прямыми или дугами.
Общая структура секции показана на рисунке 4.

Рисунок 4 – Структура секции ENTITIES.
В принципе, никаких неожиданностей в этой секции нет – начало примитива определяется группой 0:Имя_примитива, заканчивается описание примитива следующей группой 0:Имя_примитива или 0:ENDSEC. Однако есть важный момент, связанный со сложными примитивами - их надо обрабатывать по особым правилам, т.к. группа 0:Имя_примитива может встречаться внутри них. В этом случае конец сложного примитива будет приходиться на первую группу 0:Имя_примитива после группы 0:SEQEND.
А теперь небольшой пример.
Нарисовали в AutoCAD три последовательно соединенные линии:

Сохранили в формате DXF, открываем текстовым редактором, смотрим, как это выглядит в тексте (для удобства чтения группы разделены пустыми строками):
0 ;Начало секции
SECTION
2 ;Секция Примитивы
ENTITIES
0 ;Начало примитива Линия
LINE
5 ;Уникальный идентификатор примитива
1EC
8 ;Номер слоя, на котором расположен примитив
0
10 ;Координата Х начальной точки
0.0
20 ;Координата Y начальной точки
0.0
30 ;Координата Z начальной точки
0.0
11 ;Координата X конечной точки
452.55480855023052
21 ;Координата Y конечной точки
601.16555418995085
31 ;Координата Z конечной точки
0.0
0 ;Начало примитива Линия
LINE
5 ;и т.д.
1ED
8
0
10
452.55480855023052
20
601.16555418995085
30
0.0
11
291.779542423983
21
601.16555418995085
31
0.0
0
LINE
5
1EE
8
0
10
291.779542423983
20
601.16555418995085
30
0.0
11
672.97250179134153
21
321.2681517584133
31
0.0
0 ;Конец секции
ENDSEC
На вид ничего сложного, не так ли? В принципе, так оно и есть, хотя даже в примере с линиями в описании примитивов может присутствовать намного больше параметров. Полный список всех групповых кодов для примитивов и их значений можно, опять же, посмотреть в DXF Reference, тут.
Секция BLOCKS
В этой секции также содержится описание примитивов, однако здесь они объединены в блоки. Каждый блок имеет уникальный идентификатор и название и может использоваться в секции ENTITIES с помощью примитива INSERT. Таким образом, можно, например, один раз нарисовать какую-то сложную фигуру, объединить входящие в нее примитивы в блок, назвать, скажем «ФИГУРНОЕ_ОТВЕРСТИЕ» и использовать его несколько раз в секции ENTITIES, вместо того, чтобы несколько раз рисовать одну и ту же фигуру. Преимуществом перед обычным копированием-вставкой является в основном то, что при изменении описания этого блока в секции BLOCKS на чертеже автоматически изменятся все вставки этого блока. Общая структура секции BLOCKS показана на рисунке 5.

Рисунок 5 – Структура секции BLOCKS.
Несколько странным в данном случае является то, что часть параметров блока определяются после группы 0:BLOCK, а остальные – после, казалось бы, закрывающей группы 0:ENDBLK. Какая в этом логика, для меня осталось загадкой. Описания параметров и групповых кодов для секции BLOCKS можно подсмотреть в DXF Reference тут.
Пример использования блоков. Описание блока в секции BLOCKS:
0
BLOCK
8
0
2
ВНЕШНИЙ_КОНТУР
70
0
10
0.0
20
0.0
30
0.0
3
ВНЕШНИЙ_КОНТУР
1
0
LINE
5
24C
8
0
10
0.0
20
0.0
30
0.0
11
0.0000000000000568
21
520.92016302072079
31
0.0
0
LINE
5
24D
8
0
10
0.0000000000000568
20
520.92016302072079
30
0.0
11
259.3183916651933
21
520.92016302072079
31
0.0
0
ARC
5
24E
8
0
10
259.3183916651933
20
355.05503667966877
30
0.0
40
165.86512634105199
50
0.0
51
90.0
0
LINE
5
24F
8
0
10
425.18351800624532
20
355.05503667966877
30
0.0
11
578.28001640457023
21
209.9230504388434
31
0.0
0
LINE
5
250
8
0
10
578.28001640457023
20
209.9230504388434
30
0.0
11
578.28001640457023
21
0.0000000000000568
31
0.0
0
LINE
5
251
8
0
10
578.28001640457023
20
0.0000000000000568
30
0.0
11
0.0
21
0.0
31
0.0
0
ENDBLK
5
24B
8
0
Использование этого блока в секции ENTITIES:
0
INSERT
5
252
8
0
2
ВНЕШНИЙ_КОНТУР
10
260.65144090254489
20
503.97895235368458
30
0.0
И вот как это выглядит на чертеже:

На этом первая часть статьи, посвященная краткому описанию формата DXF, заканчивается. Во второй части будут рассмотрены уже более практические вопросы написания импорта из DXF файла на языке C#.
Рейтинг:  |
Просмотров: 76327 |
 |
Гости не имеют права добавлять комментарии и проставлять рейтинг. |
|
Автор: Dmitry (2011-02-21, 13:20)
ага, спасибо. жаль, что вторая часть канула в небытие.. у меня просто дипломный проект на эту тему. как раз импорт и редактирование примитивов. странно, что информации на эту тему совсем нет.. даже не знаю как составить теоретическую часть. [Ответить]