摘要:本文简要介绍了一下DXF文件的组成。重点讲述了怎样使用C语言来读取DXF文件中的实体信息。
关键字:C、DXF
Abstract: The paper present the basic parts of DXF file. And focus on how to use C read the entity’s information from DXF file.
Key Words: C, DXF
DXF是Drawing eXchange File的缩写,意思为图形交换文件,在工程制图中有广泛的应用,掌握了DXF文件的读写对编写CAD软件时的图形信息的交换有重要意义。它有两种格式:一种是ASCII DXF格式;一种是二进制DXF格式。ASCII DXF文件格式是ASCII 文字格式的AutoCAD图形的完整表示,这种文件格式易于被其它程序处理。二进制格式的DXF文件与ASCII格式的DXF文件包含的信息相同,但格式上二进制格式比ASCII格式更精简,能够节省百分之二十五的文件空间。AutoCAD能够更快地对其执行读写操作(通常能快五倍)。这可能是对ASCII格式的DXF文件操作时有ASCII与二进制形式的转换,因而花费时间较多。本文主要讨论ASCII格式的DXF文件,因为它可读性强。
一、ASCII格式的DXF文件的组成
先来介绍一下ASCII格式的DXF文件的组成。(小提示:打开AutoCAD,新建一个空的文件,然后再输出为DXF文件,并用记事本打开DXF文件,就可以看到它的所有代码了,这样有助于你更好地理解DXF文件的组成。还有按一下F1,打开AutoCAD的帮助文件,找到DXF参考,它是权威具体的资料。)
用记事本打开一个DXF文件,你可以发现它里面有这样一些代码:
0
SECTION
2
HEADER
9
$ACADVER
1
AC1015
……
即里面总是数字和字符串/数字在交替的出现。数字就叫做代码(通常称为组码),紧跟组码数字的称为关联值对。(以下内容来自DXF参考)DXF文件本质上由代码及关联值对组成。代码(通常称为组码)表明其后的值的类型。使用这些组码和值对,可以将DXF文件组织到由记录组成的区域中,这些记录由组码和数据项目组成。在DXF文件,每个组码和值各占一行。表1为组码值类型表的部分:
表1 组码值类型表(部分)
一个完整的ASCII格式的DXF文件结构如下:
l HEADER段。它包含图形的基本信息。它由AutoCAD数据库版本号和一些系统变量组成。每个参数都包含一个变量名称及其关联的值。
l CLASSES段。包含应用程序定义的类的信息,这些类的实例出现在数据库的BLOCKS、ENTITIES和OBJECTS段中。类定义在类的层次结构中是固定不变的。
l TABLES段。包含以下符号表的定义:
APPID(应用程序标识表)
BLOCK_RECORD(块参照表)
DIMSTYLE(标注样式表)
LAYER(图层表)
LTYPE(线型表)
STYLE(文字样式表)
UCS(用户坐标系表)
VIEW(视图表)
VPORT(视口配置表)
l BLOCKS段。包含构成图形中每个块参照的块定义和图形图元。
l ENTITIES段。包含图形中的图形对象(图元),其中包括块参照(插入图元)。这里的信息很重要。
l OBJECTS段。包含图形中的非图形对象。除图元、符号表记录以及符号表以外的所有对象都存储在此段。OBJECTS段中的条目样例是包含多线样式和组的词典。
l THUMBNAILIMAGE段。包含图形的预览图像数据。此段为可选。
每个段都以一个后跟字符串SECTION的组码0开始,其后是组码2和表示该段名称的字符串(例如,HEADER)。每个段都由定义其元素的组码和值组成。每个段都以一个后跟字符串ENDSEC的组码0结束。举两个例子:
1.以下是 DXF™ 文件 HEADER 段的样例:
0 HEADER 段的开始
SECTION
2
HEADER
9 每出现一个标题变量便重复一次
$<变量>
<组码>
<值>
0 HEADER 段的结尾
ENDSEC
2.以下是 DXF 文件 ENTITIES 段的样例:
0 ENTITIES 段的开始
SECTION
2
ENTITIES
0 每个图元定义有一个条目,如LINE,CIRCLE
<图元类型>
5
<句柄>
330
<指向所有者的指针>
100
AcDbEntity
8
<图层>
100
AcDb<类名>
.
. <数据>
.
0 ENTITIES 段的结尾
ENDSEC
因此你需要什么信息就可以在相应的段中寻找。例如你需要得到DXF文件的版本信息就可在HEADER段中寻找。需要图形的信息就可到ENTITIES段中寻找。
再强调一下实体段:实体段记录了除块段出现的实体以外的所有绘图实体内容,包括每个实体的名称、所在图层、线型、颜色代码等等。由于定义一个实体所有组码在某一实体的任意组码在其值与默认值相同时可以省略不写。因此用户在读取DXF文件时应注意:
1. 定义一个实体的数据是以“0”组码开始,而以另一个“0”组码的出现表示结束;
2. 某一实体的定义数据顺序不固定。
因此用户在编写DXF文件处理程序时不能按顺序固定的格式处理,而只能按组码的同现来记录数据。
二、读取DXF文件流程
有了以上知识就可读懂DXF文件并从中提取我们所需要的信息了,而我们所需要的信息大多在ENTITIES段中。先讲一下大概的处理方法。
输入DXF文件名
打开DXF文件
读取一个记录
HEADER
TABLES
ENTITIES
BLOCKS
EOF
处理HEADER
处理TABLES
处理ENTITIES
处理BLOCKS
结束
图2 DXF文件处理流程
如图2所示为DXF文件处理流程。可以从DXF文件中检索,当检索到与某个段时就转到那个段的处理程序去处理。如检索到HEADER段就转入HEADER 段的处理程序去处理。
图形的大部分信息都在实体ENTITIES段中,因此读取实体段的内容很重要。读取实体段的数据首先要考虑读取数据的存储方式,然后再进行后一步的处理或存入数据文件中。此处用链表结构来存储。
各个实体的数据分成两块:公共数据块和特殊数据块。公共数据块存储每个实体都具有特征参数,如所在图层,实体标识,线型名……
特殊数据块存储每个实体特有的数据,如实体LINE,它里面有两个端点的坐标值;实体CIRCLE中有圆心坐标值和半径值等。
单个实体的数据处理方法:读取一个实体的数据首先根据组码“0”后的实体标识字符串来确定其为哪一种实体,然后再根据这个实体的具体情况来读取数据。
下面为用C具体实现的代码。因为是处理ASCII文件,只需要用到C文件处理的两个标准函数:fprintf()和fscanf()。文件中有一个位置指针,指向当前读写位置。如果顺序读写一个文件,每次读写完一个字符后,该指针自动指向下一个字符的位置。
三、读取DXF文件信息的小程序
先来看一个用C来读取HEADER段中的版本号的小程序。
/*--------------------------------------------------
* Header.C
*读取DXF文件中HEADER段中DXF文件版本号的小程序。
* [email protected] 02-05-08 18:55
*----------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define STRLEN 30
int main(int argc, char *argv[])
{
int code; /*存储组码*/
char codevalue[STRLEN]; /*存储组码对应的值*/
FILE *dxf; /*文件指针*/
char filename[STRLEN]; /*文件名*/
char suffix[6] = ".dxf"; /*只输入文件名打开DXF文件,不用输后缀.*/
printf("请输入文件名:");
gets(filename);
strcat(filename,suffix);
dxf = fopen(filename,"r"); /*打开文件进行读操作*/
if(!dxf) {
printf("打开文件出错!\n按任意键退出...");
getch();
exit(0);
}
else {
printf("文件已经打开.\n正在读取...\n");
}
while(!feof(dxf)) {
fscanf(dxf,"%d",&code);
fscanf(dxf,"%s",codevalue);
if(code == 2 && strcmp(codevalue,"HEADER")==0) {
fscanf(dxf,"%d",&code);
fscanf(dxf,"%s",codevalue);
if(strcmp(codevalue,"$ACADVER")==0) {
fscanf(dxf,"%d",&code);
fscanf(dxf,"%s",codevalue);
if(strcmp(codevalue,"AC1006")==0) {
printf("AutoCAD版本为R10.");
}
else
if(strcmp(codevalue,"AC1009")==0) {
printf("AutoCAD版本为R11和R12.");
}
else
if(strcmp(codevalue,"AC1012")==0) {
printf("AutoCAD版本为R13.");
}
else
if(strcmp(codevalue,"AC1014")==0) {
printf("AutoCAD版本为R14.");
}
else
if(strcmp(codevalue,"AC1015")==0) {
printf("AutoCAD版本为AutoCAD 2000.");
}
else
if(strcmp(codevalue,"AC1018")==0) {
printf("AutoCAD版本为AutoCAD 2004.");
}
else {
printf("不能识别的AutoCAD版本!");
break;
}
}
}
}
fclose(dxf); /*关闭文件*/
printf("\n文件已经关闭.");
printf("\nPress any key to halt...");
getch();
}
上述代码为打开一个DXF文件后就顺序读取只到文件结束,当遇到组码为2且组码对应的组值为字符串“HEADER”时就对HEADER段进行处理。在HEADER段中,若遇到组值“$ACADVER”时,则下一个组码对应的值就为AutoCAD的版本信息。具体版本信息可参考AutoCAD帮助文件DXF参考中的HEADER段->HEADER段组码部分。
若对理解了上述程序后,就可以读取DXF文件中实体ENTITIES部分的有用的数据了。首先要确定实体的数据结构。下面的代码为实体数据结构的定义,你也可以根据需要扩展这些实体的内容。程序的主要思路来自参考文献2。
#define STRLEN 60
#define DATASIZE sizeof(EntityData)
/*-----每个实体的结构-----*/
//你可在在此添加其它的实体
//为了提高精度,变量可定义为双精度型
typedef struct tagLine{
float x1,y1,z1;
float x2,y2,z2;
}LINE;
typedef struct tagCircle{
float x,y,z;
float radius;
}CIRCLE;
/*------------------------*/
typedef union specialData{
LINE line;
CIRCLE circle;
}privateData;
/*------实体的数据结构-------*/
typedef struct commonData{
char id[STRLEN]; /*实体标识字符串*/
char layer[STRLEN]; /*层名字符串*/
privateData data; /*特有数据块*/
struct commonData *next; /*用于构建链表*/
}EntityData;
定义完数据结构后,就可以用链表结构来存储实体中有用的信息了。以下程序为读取实体LINE的有关信息的代码。
/*------------------------------------------
*Entity.C 读取实体LINE部分内容。
*[email protected] 02-05-08 19:52
*-------------------------------------------*/
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <string.h>
#define STRLEN 60
#define DATASIZE sizeof(EntityData)
/*-----每个实体的结构-----*/
//你可在在此添加其它的实体
typedef struct tagLine{
float x1,y1,z1;
float x2,y2,z2;
}LINE;
typedef struct tagCircle{
float x,y,z;
float radius;
}CIRCLE;
/*------------------------*/
typedef union specialData{
LINE line;
CIRCLE circle;
}privateData;
typedef struct commonData{
char id[STRLEN]; /*实体标识字符串*/
char layer[STRLEN]; /*层名字符串*/
privateData data; /*特有数据块*/
struct commonData *next; /*用于构建链表*/
}EntityData;
/*----------函数声明部分-----------*/
void print(EntityData *entity);
/*---------------------------------*/
int main(int argc,char *argv[])
{
int code;
float value;
char codevalue[STRLEN];
FILE *dxf;
char filename[STRLEN];
char suffix[6] = ".dxf";
EntityData *entity,*entity1,*entity2;
printf("请输入DXF文件名:");
gets(filename);
strcat(filename,suffix);
dxf = fopen(filename,"r");
if(!dxf) {
printf("打开文件出错!\n可能不存在此文件.\n");
printf("按任意键退出...");
getch();
exit(0);
}
else {
printf("正在读取文件...\n");
}
entity = entity2 = (EntityData *)malloc(DATASIZE);
while(!feof(dxf)) {
fscanf(dxf,"%d",&code);
fscanf(dxf,"%s",codevalue);
if(code == 2 && strcmp(codevalue,"ENTITIES")==0) {
while(strcmp(codevalue,"ENDSEC")) {
fscanf(dxf,"%d",&code);
fscanf(dxf,"%s",codevalue);
if(code == 0 && strcmp(codevalue,"LINE")==0) {
entity1 = (EntityData *)malloc(DATASIZE);
strcpy(entity1->id,codevalue);
fscanf(dxf,"%d",&code);
while(code) {
switch(code) {
case 8:
fscanf(dxf,"%s",codevalue);
fscanf(dxf,"%d",&code);
strcpy(entity1->layer,codevalue);
break;
case 10:
fscanf(dxf,"%f",&value);
fscanf(dxf,"%d",&code);
entity1->data.line.x1 = value;
break;
case 20:
fscanf(dxf,"%f",&value);
fscanf(dxf,"%d",&code);
entity1->data.line.y1 = value;
break;
case 30:
fscanf(dxf,"%f",&value);
fscanf(dxf,"%d",&code);
entity1->data.line.z1 = value;
break;
case 11:
fscanf(dxf,"%f",&value);
fscanf(dxf,"%d",&code);
entity1->data.line.x2 = value;
break;
case 21:
fscanf(dxf,"%f",&value);
fscanf(dxf,"%d",&code);
entity1->data.line.y2 = value;
break;
case 31:
fscanf(dxf,"%f",&value);
fscanf(dxf,"%d",&code);
entity1->data.line.z2 = value;
break;
default: {
fscanf(dxf,"%s",codevalue);
fscanf(dxf,"%d",&code);
}
}
}
entity2->next = entity1;
entity2 = entity1;
}
}
entity2->next = NULL;
}
}
entity = entity->next; //第一个实体区为空,所以使头指针移向下一个实体
print(entity); //输出链表
printf("\nPress any key to halt...");
getch();
return 0;
}
//输出链表
void print(EntityData *entity)
{
int i=0;
EntityData *pointer;
pointer = entity;
if(pointer != NULL) {
do{
i++;
pointer = pointer->next;
}while(pointer != NULL);
}
printf("\nOutput LinkList:");
printf("\nDXF文件中总共有%d条直线:\n",i);
i = 1;
pointer = entity;
if(pointer != NULL) {
do{
printf("第%d条直线:\n",i);
printf("X1=%f\tY1=%f\tZ1=%f\n",pointer->data.line.x1,
pointer->data.line.y1,pointer->data.line.z1);
printf("X2=%f\tY2=%f\tZ2=%f\n",pointer->data.line.x2,
pointer->data.line.y2,pointer->data.line.z2);
pointer = pointer->next;
i++;
}while(pointer !=NULL);
}
}
这个程序可以读取DXF文件中的有关直线的信息。你可以用AutoCAD绘制一些直线并另存为DXF格式,然后运行此程序,输入文件名就可以得到DXF文件中有关直线的条数和每条直线两个端点的坐标信息了。
四、总结
通过编写读取ASCII的DXF文件的有关信息的程序,对C的文件操作和ASCII的DXF文件有了一定的理解。在得到ASCII的DXF文件中的实体的信息后就可以用TC中的”graphics.h”图形操作函数或用Windows的GDI或 调用OpenGL的API绘制出相应的图形了。OpenGL的API功能强大,且对三维图形有很好的支持,因此在得到DXF文件中的有关信息后可以很方便地用OpenGL来处理得到美观的图形。
由于本人不是计算机专业的,水平有限,请多指点。
参考文献:
1. AutoCAD 帮助文件——DXF参考;
2. 李建明,陆润明,李学志编著, AutoCAD R12应用与开发教程 学苑出版社 1994
3. 谭浩强主编 C程序设计第二版 清华大学出版社 1999;