Импортиране на графични данни от DXF файл с помощта на C#
Импортиране на графични данни от DXF файл с помощта на C#
Автор: Меншиков П. В.
Когато пишете инженерни програми, често е необходимо да работите с графични данни, като детайлни чертежи или планове на сгради. Да, понякога можете да използвате функциите, предлагани от разработчика, за да разширите функционалността на самата CAD система (например ObjectARX за AutoCAD), но все пак често се налага да работите с чертежи в отделна самостоятелно написана програма. В случай, че програмата е написана "наведнъж" и чертежът не е сложен, е напълно възможно да го кодирате твърдо в самия код. Можете също така да създадете свой собствен формат на изходните данни, четим от програмата и попълнен ръчно от потребителя, може би дори с помощта на удобен потребителски интерфейс. Но какво ще стане, ако чертежът се състои от стотици примитиви - линии, сплайни, дъги от окръжности или ще трябва редовно да използвате програмата с различни чертежи? Може би ще бъде по-бързо (и поне по-интересно) да импортирате графични данни от чертожни файлове от всеки от съществуващите формати в програмата.
Така че, преди да започнете да пишете програма за импортиране, струва си да разгледате DXF формата на теория. DXF файлът е ASCII текстов файл, пълен с така наречените групи. Групата е най-малката структурна единица на файл. Ако отворим който и да е DXF файл, ще видим нещо подобно:
И така нататък. Всеки два реда са група. Първият ред съдържа кода на групата, вторият - стойността. Кодът на групата е идентификатор за това, което групата описва. Например кодове 0-9 се използват за низови данни, а кодове 10-59 се използват за данни с плаваща запетая с двойна точност. Но кодовете не се използват само за указване на типа на дадена стойност.групи; те обозначават и това, което се съдържа в значението на групата. Например код 0 се използва в строго определени случаи - начало на раздел, край на раздел, начало на таблица и т.н., код 2 означава, че стойността на групата описва името (секция, таблица, примитив ...), код 9 се използва само в раздела HEADER и означава името на променлива. Пълен списък на групови кодове с описания можете да намерите в DXF справочника тук и тук.
За удобство в текста на статията групите ще бъдат написани във формата „Код_на_група“: „Стойност“, например 2: ЗАГЛАВКА.
След като вече е ясно от какви "тухли" се състои файлът, можем да разгледаме неговата структура на по-високо ниво. Общата структура на DXF файл е показана на фигура 1.
Фигура 1 - Структурата на DXF файла.
Както можете да видите от диаграмата, DXF файлът се състои от секции, които се наричат секции (SECTION). Всеки такъв раздел започва с две групи - 0:SECTION и 2:Section_name, и завършва с групата 0:ENDSEC. Броят и редът на секциите в различните версии на DXF може да се промени, новите версии добавят нови секции, така че когато четете DXF файл програмно, не трябва да разчитате на факта, че файлът ще има точно такива секции и в този ред. По-добре е да отделите малко повече време и да напишете гъвкав анализатор, който може да чете всяка версия на DXF формата без грешки и да „извади“ необходимите данни от там.
Нека да разгледаме някои секции, които могат да бъдат намерени в почти всяка относително модерна версия на DXF формата - секции HEADER (заглавка), TABLES (таблици), ENTITIES (примитиви) и BLOCKS (блокове от примитиви). След като разберете структурата на тези секции, работата с други секции от по-нови формати няма да бъде трудна - принципът е един и същ навсякъде.
Този раздел съдържа различнирисуване на променливи, които имат собствено име. Тук се съхраняват например името и версията на програмата, която е създала чертежа, позицията на базовата точка на чертежа, максималните и минималните координати в чертежа и др. Лично за мен този раздел се оказа безполезен, т.к. Трябваше ми само геометрията на чертежа, но може да е полезна на някого. И така, структурата на секцията HEADER е показана на фигура 2.
Фигура 2 - Структура на секцията HEADER.
Нека обясня: този раздел може да съдържа произволен брой променливи, всяка от които може да съдържа произволно количество данни от различни типове. Например променливата $EXTMIN съдържа три числа с плаваща запетая с двойна точност (double) - координатите X, Y и Z. Всяка променлива започва с група с 9: Име_на_променлива. Няма специална група, която да маркира края на декларацията на променливата - променливата "завършва", когато се срещне следващата група с код 9 или група 0:ENDSEC.
Тук всичко е малко по-сложно. Този раздел съхранява масиви от данни, например таблица със слоеве с всичките им свойства, лист със стилове и т.н. Този раздел също не ми беше полезен, но за по-сериозни приложения тук явно има доста важна информация.
Общата структура на секцията ТАБЛИЦИ е показана на Фигура 3.
Фигура 3 - Структура на секцията ТАБЛИЦИ.
По принцип всичко трябва да е ясно от схемата - всяка таблица започва с две групи - 0:TABLE и 2:Table_Name, и завършва с групата 0:ENDTAB. В самата таблица най-често има група 70: Number_of_rows_in_table, също така в новите версии на DXF може да има други параметри на таблицата. Описанието на данните, включени в таблицата, се извършва ред по ред, а групата 0: Table_name сигнализира началото на нов ред. Това е подобно на декларирането на променливи в секцията HEADER, но не е и тук."затваряща" група, вътре в един ред може да има произволен брой различни данни
Сега да преминем към забавната част.
Обикновено разделът ENTITIES обикновено се поставя след раздела BLOCKS, но двата раздела не се различават много и е по-добре да започнете с по-простия. Този раздел на файла съхранява данни за примитиви - всъщност това са графичните данни на чертежа. Примитивът е някакъв вид геометрична фигура, като точка, линия, кръг, дъга и др. Има и сложни примитиви, съставени от други примитиви, като полилиния, състояща се от върхове, свързани с линии или дъги.
Общата структура на секцията е показана на фигура 4.
Фигура 4 - Структурата на раздела ENTITIES.
По принцип в този раздел няма изненади - началото на примитива се определя от групата 0:Name_primitive, описанието на примитива завършва със следната група 0:Name_primitive или 0:ENDSEC. Има обаче важен момент, свързан със сложните примитиви - те трябва да се обработват по специални правила, т.к. група 0: Primitive_name може да се появи в тях. В този случай краят на сложния примитив ще падне върху първата група 0:Primitive_name след групата 0:SEQEND.
А сега един малък пример.
Начертахме три последователно свързани линии в AutoCAD:
0 ;Начало на раздел
2 ;Секция Примитиви
0 ;Начало на примитивен ред
5 ;Уникален идентификатор на примитива
8 ;Номер на слоя, на който се намира примитивът