Hlavní navigace

Načítání a zobrazení souborů ve formátu DXF

5. 4. 2007
Doba čtení: 12 minut

Sdílet

V dnešním článku si na praktickém příkladu ukážeme, jak je možné načítat a zobrazovat 2D výkresy (resp. jejich části), které jsou uloženy ve vektorovém formátu DXF. Výsledkem naší snahy bude jednoduchý prohlížeč DXF souborů využívající pro vykreslování všech grafických entit multiplatformní knihovnu OpenGL.

Obsah

1. Načítání a analýza souborů DXF
2. Grafické entity použité v 2D výkresech
3. Důležité parametry 2D entit
4. Jednoduchý prohlížeč 2D výkresů uložených ve formátu DXF
5. Parsing údajů uložených v DXF a interní reprezentace vybraných 2D entit
6. Vykreslení 2D entit s pomocí funkcí OpenGL
7. Možné další rozšíření funkcionality prohlížeče
8. Odkazy na další informační zdroje
9. Obsah dalšího pokračování tohoto seriálu

1. Načítání a analýza souborů DXF

Dnes si na praktickém příkladu ukážeme jednu variantu načítání a následného zobrazení DXF souborů. Při práci s tímto grafickým vektorovým formátem je nejsložitější samotné načítání výkresů. Formát DXF je totiž navržen poněkud volně a některé informace o grafických entitách, ze kterých se výkresy skládají, vůbec nemusí být v souborech typu DXF uloženy. Také nemusí být dodržena posloupnost uložení jednotlivých parametrů (atributů) grafických entit, proto se nemůžeme například spoléhat na to, že u úseček nejdříve načteme souřadnice prvního vrcholu a až následně souřadnice vrcholu druhého. Z tohoto důvodu bude dále uvedený prohlížeč souborů typu DXF vytvořen tak, aby dokázal načíst a zobrazit i DXF soubory s nestandardním uspořádáním jednotlivých údajů o grafických entitách.

dxf_1
Obrázek 1: Screenshot demonstrační aplikace se zobrazeným výkresem, výkres byl vytvořen v AutoCADu

2. Grafické entity použité v 2D výkresech

Již v předchozí části tohoto seriálu jsme si řekli, že se soubory typu DXF používají jak pro ukládání plošných výkresů (2D výkresů), tak i pro přenos trojrozměrných modelů mezi různými grafickými programy, typicky mezi specializovaným programem pro pořizování dat, CADem a následně programem typu CAM. Původním posláním souborového formátu DXF byla podpora komunikace AutoCADu (pravděpodobně nejznámějšího produktu firmy AutoDesk) s okolními aplikacemi, takže možnosti DXF reflektují i rozšiřující se možnosti tohoto známého programu od jednoduchých 2D výkresů přes parametrické plochy až po objemové modelování (CSG). Mezi grafické entity, jež jsou v souborech DXF popsané svou geometrií a dalšími důležitými atributy, které je možné použít v plošných výkresech, patří především:

Jméno entity Význam
POINT bod, jehož značka může být natočena různým směrem (natočení je zadáno úhlem)
LINE úsečka zadaná dvojicí bodů, snad nejpoužívanější entita v CADech
POLYLINE polyčára složená z úseček, oblouků nebo pomocí spline (také velmi často používaná)
TRACE „stopa“, což je vlastně úsečka s nenulovou tloušťkou
CIRCLE kružnice zadaná středem a poloměrem
ARC kruhový oblouk zadaný středem, poloměrem a dvojicí omezujících úhlů
TEXT textový řetězec zadaný bodem vložení, rotací, výškou a dalšími atributy
SHAPE značka (obdoba neměnného bloku) zadaná středem, velikostí, úhlem natočení a jménem značky
INSERT blok složený z jedné i více entit sdružených pod jedním jménem

3. Důležité parametry 2D entit

Ke každé grafické entitě uložené v souboru typu DXF je přiřazeno několik číselných a/nebo textových parametrů (atributů). Nejdůležitější jsou samozřejmě informace o geometrii entity, tj. o jejím umístění v ploše výkresu či prostoru 3D modelu, velikosti apod. Nesmíme však zapomenout ani na další atributy, které mohou určovat způsob zobrazení entity. Mezi tyto atributy patří označení hladiny, ve které entita leží (hladiny je možné vypnout či zmrazit), barva entity, styl čáry, kterou je entita vykreslena, apod. V následujících tabulkách jsou vypsány typické parametry přiřazované k entitám použitým v plošných výkresech. Některé parametry jsou povinné (geometrické informace), další se v souborech typu DXF nemusí objevit – potom je zapotřebí použít inicializační hodnoty, načtené například z hlavičky DXF.

Entita typu „POINT“

Číslo skupiny Význam
8 označení hladiny
62 barva entity
10 x-ová souřadnice bodu
20 y-ová souřadnice bodu
30 z-ová souřadnice bodu
50 úhel natočení bodu

Entita typu „LINE“

Číslo skupiny Význam
8 označení hladiny
62 barva entity
10 x-ová souřadnice prvního vrcholu
20 y-ová souřadnice prvního vrcholu
30 z-ová souřadnice prvního vrcholu
11 x-ová souřadnice druhého vrcholu
21 y-ová souřadnice druhého vrcholu
31 z-ová souřadnice druhého vrcholu

Entita typu „CIRCLE“

Číslo skupiny Význam
8 označení hladiny
62 barva entity
10 x-ová souřadnice středu
20 y-ová souřadnice středu
30 z-ová souřadnice středu
40 poloměr kružnice

Entita typu „ARC“

Číslo skupiny Význam
8 označení hladiny
62 barva entity
10 x-ová souřadnice středu
20 y-ová souřadnice středu
30 z-ová souřadnice středu
40 poloměr oblouku
50 počáteční úhel
51 koncový úhel

Entita typu „TEXT“

Číslo skupiny Význam
8 označení hladiny
62 barva entity
10 x-ová souřadnice bodu vložení
20 y-ová souřadnice bodu vložení
30 z-ová souřadnice bodu vložení
40 výška textu
50 úhel natočení
1 vlastní řetězec (maximálně 256 znaků)

Entita typu „TRACE“

Číslo skupiny Význam
8 označení hladiny
62 barva entity
10 x-ová souřadnice prvního vrcholu
20 y-ová souřadnice prvního vrcholu
30 z-ová souřadnice prvního vrcholu
11 x-ová souřadnice druhého vrcholu
21 y-ová souřadnice druhého vrcholu
31 z-ová souřadnice druhého vrcholu
12 x-ová souřadnice třetího vrcholu
22 y-ová souřadnice třetího vrcholu
32 z-ová souřadnice třetího vrcholu
13 x-ová souřadnice čtvrtého vrcholu
23 y-ová souřadnice čtvrtého vrcholu
33 z-ová souřadnice čtvrtého vrcholu

Entita typu „SHAPE“

Číslo skupiny Význam
8 označení hladiny
62 barva entity
10 x-ová souřadnice bodu vložení
20 y-ová souřadnice bodu vložení
30 z-ová souřadnice bodu vložení
40 velikost
50 úhel natočení
51 úhel sklonu
41 relativní měřítko ve směru osy x
2 jméno tvaru

Entita typu „POLYLINE“

Číslo skupiny Význam
40 počáteční tloušťka polyčáry
41 koncová tloušťka polyčáry
70 návěstí – bitové pole s dalšími informacemi o polyčáře
71 počet vrcholů ve směru M (3D plocha)
72 počet vrcholů ve směru N (3D plocha)
73 vyhlazení plochy ve směru M
74 vyhlazení plochy ve směru N
75 druh vyhlazení plochy
66 návěstí křivky

Entita typu „INSERT“

Číslo skupiny Význam
10 x-ová souřadnice bodu vložení
20 y-ová souřadnice bodu vložení
30 z-ová souřadnice bodu vložení
41 měřítko ve směru osy x
42 měřítko ve směru osy y
43 měřítko ve směru osy z
50 úhel natočení
44 mezery mezi řádky
45 mezery mezi sloupci
70 počet sloupců – 1
71 počet řádků – 1
2 jméno bloku
66 bitové pole s dalšími informacemi o bloku

dxf_2
Obrázek 2: Další screenshot demonstrační aplikace se zobrazeným výkresem

4. Jednoduchý prohlížeč 2D výkresů uložených ve formátu DXF

Prohlížeč DXF souborů, jehož zdrojový soubor je dostupný pod tímto odkazem (HTML verze se zvýrazněním syntaxe), je naprogramovaný v céčku a pro zobrazování využívá možnosti grafické knihovny OpenGL. Při spuštění aplikace se musí jako první parametr zadat jméno souboru uloženého ve formátu DXF. Následně je soubor načten, zanalyzován a podporované grafické entity (úsečka, kružnice, oblouk, text) jsou uloženy do lineárního seznamu pro další zpracování.

Ovládání zobrazení je jednoduché. Levým tlačítkem myši se výkresem pohybuje, pravým tlačítkem se mění měřítko. Místo myši lze použít i kurzorové klávesy spolu s klávesami [Page Up] a [Page Down] (změna měřítka). Klávesami [C] a [L] se přepíná mezi barevným zvýrazněním hladin (layers) nebo přímo uložených barev entit (v obou případech se jedná o nepravé barvy). Tlačítkem Q a ESC se program ukončí.

dxf_3
Obrázek 3: Další screenshot demonstrační aplikace se zobrazeným výkresem (obsahující pouze entity LINE)

5. Parsing údajů uložených v DXF a interní reprezentace vybraných 2D entit

Nejsložitější částí celého demonstračního příkladu je parser a analyzér DXF formátu. Samotný parsing na té nejnižší úrovni složitý není, protože jsou všechny údaje rozloženy do skupin (groups), což je dvojice řádků, kde na prvním řádku je uloženo číslo skupiny a na řádku druhém hodnota daného parametru či atributu. Složitější je však analýza na vyšší úrovni, kdy musí program z kontextu určit, ke které entitě se daná skupina vztahuje. Kontextovost je možné programově zařídit mnoha způsoby; já jsem zvolil jednu z forem konečného automatu (KA), který na základě načtených čísel skupin mění svůj stav a přitom volá registrované callback funkce. Přechod mezi stavy konečného automatu a ukazatel na callback funkci je uložen ve struktuře se jménem t_transition:

typedef struct t_transition {
    t_dxf_status status;          // předchozí stav konečného automatu
    char         *input;          // načtený kód skupiny
    t_dxf_status newStatus;       // nový stav konečného automatu
    void     (*fce)(char *input); // volaná callback funkce
} t_transition; 

Ve skutečnosti implementovaný konečný automat mění svůj stav i při přijetí parametru/atributu skupiny, to je zajištěno speciálními callback funkcemi. Tyto funkce jsou určeny zejména pro ukládání načtených atributů do pomocných struktur. Aktuální konfigurace konečného automatu vypadá následovně:

static const t_transition transitions[]={
    {Unknown,    "  0",         Delimiter,     NULL},
    {Delimiter,  "SECTION",     Section,       NULL},
    {Section,    "  2",         SecName,       NULL},
    {SecName,    "HEADER",      Header,        callback_begin_header},
    {SecName,    "BLOCKS",      Blocks,        callback_begin_blocks},
    {SecName,    "ENTITIES",    Entities,      callback_begin_entities},
    {Header,     "  0",         Header,        NULL},
    {Header,     "ENDSEC",      Unknown,       callback_end_header},
    {Blocks,     "  0",         Blocks,        NULL},
    {Blocks,     "ENDSEC",      Unknown,       callback_end_blocks},
    {Entities,   "  0",         Entities,      NULL},
    {Entities,   "ENDSEC",      Unknown,       callback_end_entities},

    {Entities,   "LINE",        Line,          NULL},
    {Line,       "  8",         LineLayer,     callback_line_layer},
    {Line,       " 62",         LineColor,     callback_line_color},
    {Line,       " 10",         LineX1,        callback_line_x1},
    {Line,       " 20",         LineY1,        callback_line_y1},
    {Line,       " 11",         LineX2,        callback_line_x2},
    {Line,       " 21",         LineY2,        callback_line_y2},
    {Line,       "  0",         Entities,      callback_line_store},

    {Entities,   "CIRCLE",      Circle,        NULL},
    {Circle,     "  8",         CircleLayer,   callback_circle_layer},
    {Circle,     " 62",         CircleColor,   callback_circle_color},
    {Circle,     " 10",         CircleX,       callback_circle_x},
    {Circle,     " 20",         CircleY,       callback_circle_y},
    {Circle,     " 40",         CircleR,       callback_circle_r},
    {Circle,     "  0",         Entities,      callback_circle_store},

    {Entities,   "ARC",         ArcEntity,     NULL},
    {ArcEntity,  "  8",         ArcLayer,      callback_arc_layer},
    {ArcEntity,  " 62",         ArcColor,      callback_arc_color},
    {ArcEntity,  " 10",         ArcX,          callback_arc_x},
    {ArcEntity,  " 20",         ArcY,          callback_arc_y},
    {ArcEntity,  " 40",         ArcR,          callback_arc_r},
    {ArcEntity,  " 50",         ArcU1,         callback_arc_u1},
    {ArcEntity,  " 51",         ArcU2,         callback_arc_u2},
    {ArcEntity,  "  0",         Entities,      callback_arc_store},

    {Entities,   "TEXT",        TextEntity,    NULL},
    {TextEntity, "  8",         TextLayer,     callback_text_layer},
    {TextEntity, " 62",         TextColor,     callback_text_color},
    {TextEntity, " 10",         TextX,         callback_text_x},
    {TextEntity, " 20",         TextY,         callback_text_y},
    {TextEntity, "  1",         TextText,      callback_text_text},
    {TextEntity, "  0",         Entities,      callback_text_store},

    {End,        "",            End,           NULL} // jen ukonceni pro pruchod stavy KA
}; 

Poslední stav slouží pouze jako zarážka při procházení funkcí konečného automatu. Místo ukazatelů na callback funkce může být zadána i hodnota NULL, v tomto případě se samozřejmě žádná callback funkce nezavolá. Všimněte si také toho, že na ukončení definice entity v souboru DXF reagujeme tak, že se zavolá callback funkce callback_*_store, která právě načtenou a zrekonstruovanou entitu uloží do lineárního seznamu pro následné vykreslení. Procedura, která provádí přechod konečného automatu z jednoho stavu do druhého na základě načteného textového řádku ze souboru, vypadá následovně:

while (fgets(line, MAX_LINE_LENGTH, fin)) { // projit vsechny radky
    lines++;
    line[strlen(line)-1]=0;                 // oriznout <CR>
    if (special_callback!=NULL) {           // specialni callback funkce -> nacteni atributu
        special_callback(line);
    }
    else {                                  // normalni callback funkce -> prechod KA
        for (t=0; transitions[t].status!=End; t++) {
            if ((status==transitions[t].status) && (!strcmp(transitions[t].input, line))) {
                if (transitions[t].fce!=NULL) transitions[t].fce(line);
                status=transitions[t].newStatus;
                break;
            }
        }
    }
} 

6. Vykreslení 2D entit s pomocí funkcí OpenGL

Pokud již máme naplněný seznam grafických entit, je jejich vykreslení poměrně jednoduché. Bod a úsečka se vykreslí přímo pomocí příslušných funkcí grafické knihovny OpenGL. Kružnice a kruhový oblouk nemají přímou podporu OpenGL (grafické akcelerátory totiž pracují pouze s lineárními prvky, protože jsou založeny na lineárních interpolátorech), proto jsou tyto entity vykresleny lomenou čarou – v případě kružnice se jedná o LINE_LOOP (uzavřená lomená čára), v případě oblouku a LINE_STRIP (neuzavřená lomená čára).

Přesnost vykreslení, tj. počet liniových částí, ze kterých se kružnice či oblouk skládají, je zadaný symbolickými konstantami. Text je pro jednoduchost vykreslován pouze v jedné velikosti pomocí funkcí z nadstavbové knihovny GLUT. Část zdrojového kódu, která se stará o vykreslování, vypadá následovně (funkce setColor() pouze simuluje barvovou paletu pomocí osmi základních barev):

//-----------------------------------------------------------------------------
// nastaveni barvy vykreslovani
//-----------------------------------------------------------------------------
void setColor(int color)
{
    // velmi zjednodusena paleta
    static float palette[][3]={
        {1.0, 1.0, 1.0},
        {0.0, 0.0, 1.0},
        {0.0, 1.0, 0.0},
        {0.0, 1.0, 1.0},
        {1.0, 0.0, 0.0},
        {1.0, 0.0, 1.0},
        {1.0, 1.0, 0.0},
        {0.5, 0.5, 0.5},
    };
    float r, g, b;
    r=palette[color & 0x07][0];
    g=palette[color & 0x07][1];
    b=palette[color & 0x07][2];
    glColor3f(r, g, b);
}



//-----------------------------------------------------------------------------
// vykresleni bodu
//-----------------------------------------------------------------------------
void drawPoint(void *entity)
{
    GfxPoint *p=(GfxPoint*)entity;
    if (colorStyle)
        setColor(p->color);
    else
        setColor(p->layer);
    glBegin(GL_POINTS);
    glVertex2d(p->x, p->y);
    glEnd();
}



//-----------------------------------------------------------------------------
// vykresleni usecky
//-----------------------------------------------------------------------------
void drawLine(void *entity)
{
    GfxLine *l=(GfxLine*)entity;
    if (colorStyle)
        setColor(l->color);
    else
        setColor(l->layer);
    glBegin(GL_LINES);
    glVertex2d(l->x1, l->y1);
    glVertex2d(l->x2, l->y2);
    glEnd();
}



//-----------------------------------------------------------------------------
// vykresleni kruznice
//-----------------------------------------------------------------------------
void drawCircle(void *entity)
{
#define SEGMENTS 100
    int i;
    GfxCircle *c=(GfxCircle*)entity;
    if (colorStyle)
        setColor(c->color);
    else
        setColor(c->layer);
    glBegin(GL_LINE_LOOP);
    for (i=0; i<SEGMENTS; i++)
        glVertex2d(c->x+c->r*cos(i*6.28/SEGMENTS), c->y+c->r*sin(i*6.28/SEGMENTS));
    glEnd();
}



//-----------------------------------------------------------------------------
// vykresleni oblouku
//-----------------------------------------------------------------------------
void drawArc(void *entity)
{
#define SEGMENTS_A 50
#ifndef M_PI
#define M_PI 3.1415927
#endif
    float u;
    float a, b;
    GfxArc *arc=(GfxArc*)entity;
    if (colorStyle)
        setColor(arc->color);
    else
        setColor(arc->layer);

    if (arc->u1 > arc->u2) {
        a=(arc->u2)*M_PI/180.0+M_PI;
        b=(arc->u1)*M_PI/180.0+M_PI;
    }
    else {
        a=(arc->u1)*M_PI/180.0;
        b=(arc->u2)*M_PI/180.0;
    }
    glBegin(GL_LINE_STRIP);
        for (u=a; u<=b; u+=(b-a)/SEGMENTS_A) {
            glVertex2d(arc->x+arc->r*cos(u), arc->y+arc->r*sin(u));
        }
    glEnd();
}



//-----------------------------------------------------------------------------
// vykresleni textu
//-----------------------------------------------------------------------------
void drawText(void *entity)
{
    char *c;
    GfxText *t=(GfxText*)entity;
    if (colorStyle)
        setColor(t->color);
    else
        setColor(t->layer);
    glPushMatrix();
    glTranslatef(t->x, t->y, 0);
    glScalef(0.05, 0.05, 0.05);   // meritko (velikost) neodpovida skutecnosti!
    for (c=t->str; *c!=0; c++) {
        glutStrokeCharacter(GLUT_STROKE_ROMAN, *c);
    }
    glPopMatrix();
}



//-----------------------------------------------------------------------------
// prekresleni cele vektorove kresby
//-----------------------------------------------------------------------------
void redrawDrawing(double scale,                    // meritko obrazce
                   double xpos,                     // posun obrazce
                   double ypos)
{
    Item *item=p_first;
    float  scale2=mouse.ztran0/100.0+1.0;           // zmena meritka zadana mysi

    // posun a zmena meritka kresby
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(xpos, ypos, 0);                    // posun zadany klavesnici
    glTranslatef(mouse.xtran0, -mouse.ytran0, 0);   // posun zadany mysi

    // posunuti pocatku (pro zmenu meritka vuci stredu okna
    // je nutna mala programova uprava)
    glTranslatef(window.width/2, window.height/2, 0);
    glScalef(scale, scale, scale);                  // zmena meritka zadana klavesnici
    glScalef(scale2, scale2, scale2);               // zmena meritka zadana mysi
    glTranslatef(-window.width/2, -window.height/2, 0);

    // projit celym seznamem a aplikovat v nem ulozene prikazy
    while (item!=NULL) {
        Type type=item->type;
        switch (type) {
            case T_POINT:  drawPoint(item->entity);  break;
            case T_LINE:   drawLine(item->entity);   break;
            case T_CIRCLE: drawCircle(item->entity); break;
            case T_ARC:    drawArc(item->entity);    break;
            case T_TEXT:   drawText(item->entity);   break;
            default: break;
        }
        item=item->next;
    }
} 

dxf_4
Obrázek 4: Poslední screenshot demonstrační aplikace se zobrazeným výkresem (testování funkčnosti entit LINE a CIRCLE, výkres vytvořen v QCADu)

7. Možné další rozšíření funkcionality prohlížeče

Zde prezentovaný interaktivní prohlížeč DXF souborů je opravdu velmi jednoduchý a v žádném případě nenačítá a nezobrazuje všechny grafické entity, které se v těchto souborech mohou nacházet. Vzhledem ke způsobu konstrukce konečného automatu (KA) použitého při analýze DXF a pružným callback funkcím je však možné tento prohlížeč dále rozšiřovat a upravovat tak jeho funkcionalitu. Mezi užitečná rozšíření patří zejména:

  • Načítání a zobrazování dalších 2D entit: stopy TRACE, elipsy (ELLIPSE) a zejména polyčáry (POLYLINE), které jsou velmi často používanou entitou.
  • Podpora pro tvary (SHAPE), což jsou vlastně malé pojmenované vektorové objekty vkládané do výkresu.
  • Načítání bloků a jejich rozklad na jednotlivé entity (INSERT, BLOCK).
  • Zapínání a vypínání jednotlivých hladin ve výkrese.
  • Načítání dalších atributů entit, zejména typů čar.

8. Odkazy na další informační zdroje

  1. Aaron A. Collins:
    AutoCAD DXF file to DKB Data File Converter,
    version 1.0
  2. Autodesk, Inc.:
    AutoLISP Release 9, Programmer's re­ference,
    Autodesk Ltd., Oct. 1987
  3. Autodesk, Inc.:
    AutoLISP Release 10, Programmer's re­ference,
    Autodesk Ltd., Sept. 1988
  4. Autodesk, Inc.:
    Drawing Interchange and File Formats, AutoCAD Release 12
    Copyright © 1982–1990, 1992, Autodesk, Inc.
  5. Autodesk, Inc.:
    AutoCAD Reference Manual
    Autodesk, Inc., 1995
  6. AutoCAD DXF (Wikipedia):
    http://en.wiki­pedia.org/wiki/Au­toCAD_DXF
  7. Open Design Aliance:
    http://www.open­design.com/

9. Obsah dalšího pokračování tohoto seriálu

V následujícím pokračování seriálu o grafických formátech dokončíme rozsáhlou kapitolu věnovanou vektorovému formátu DXF. Ukážeme si strukturu DXF použitou při práci s trojrozměrnými modely, samozřejmě spolu s jednoduchým demonstračním příkladem (bude se jednat o interaktivní prohlížeč 3D modelů). Uvidíme, že v případě trojrozměrných modelů soubory typu DXF obsahují odlišnou množinu entit, než tomu je v případě uložených 2D výkresů.

Byl pro vás článek přínosný?

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.