DXF 导入 .NET:读取和查看 AutoCAD 格式文件
读取 AutoCAD DXF 文件的 C# 源代码
问题与解决方案
工业、建筑和许多其他领域的软件开发都需要处理 CAD 图形。最受欢迎的 CAD 格式是 AutoCAD DWG 和 AutoCAD DXF,后者是“简化版”的 dwg - 一种供开发人员使用的特殊格式。问题是 DXF 和 DWG 格式非常复杂。它们有数十个对象,数百种交互技巧和数千个属性。Autodesk 的官方 DXF 参考手册有 256 页,但它未能描述许多重要事实。因此,经常需要进行 CAD 图形开发,但实现起来并不容易。本文将介绍如何用 C# 编写 DXF 读取器,会遇到哪些问题,当然,您还可以找到 C# 源代码示例,根据 MPL 许可证免费使用。

DXF 结构
DXF 是 Autodesk 的一种开放式 ASCII 格式,您可以在网上轻松找到其文档。下面是一些关于它的说明。下面是一个非常简单的例子,用于展示主要部分:
0
SECTION
2
ENTITIES
0
LINE
10
39.19953392043317
20
36.4554281665769
30
0.0
11
39.19953392043322
21
736.4554281665768
31
0.0
0
ENDSEC
0
EOF
0
- 引入扩展符号名称,跟随“0”
SECTION
, ENDSEC
- 部分的开始/结束。部分可以包括 Header、Entities、Objects。在上面的代码中,您只能看到 Entities
部分,其中包含实体。
LINE
- 开始 LINE
实体描述。Line
10
39.19953392043317
表示 X1 双精度值。20 后的值为 Y1,30 后的值为 Z1(2D 图形中为 0)。11、21 和 31 代码分别对应 X2、Y2、Z2。因此,这里我们看到一条坐标为 (39.19.., 36, 45.. - 39,19.., 736,45..) 的线 - 这是一条垂直线。
所以我们的目标是读取这个 ASCII 格式。我们需要将文件加载到流中,然后逐行读取 - 偶数行(0, 2, 4...)是 CODE,奇数行(1, 3, 5...)是 VALUE,并重复此过程直到文件末尾“EOF
”。
// take a pair of lines in DXF file, repeat this till "EOF":
public void Next()
{
FCode = Convert.ToInt32(FStream.ReadLine()); //code
FValue = FStream.ReadLine(); // value
}
// for code=0 we create entities. Entities here are not only those which visible in AutoCAD.
// Entities can be also names of Sections and many other internal DXF objects.
// This method is called for all FCode == 0
public DXFEntity CreateEntity()
{
DXFEntity E;
switch (FValue)
{
case "ENDSEC":
return null; // here we do not create entity
case "ENDBLK":
return null;
case "ENDTAB":
return null;
case "LINE": // for "LINE" value we create DXFLine object
E = new DXFLine();
break;
case "SECTION": // "SECTION" will be object to store other objects like Line
E = new DXFSection();
break;
case "BLOCK": // create block object
E = new DXFBlock();
break;
case "INSERT": // insert is reference to block.
E = new DXFInsert();
break;
case "TABLE":
E = new DXFTable();
break;
case "CIRCLE":
E = new DXFCircle();
break;
case "LAYER":
E = new DXFLayer();
break;
case "TEXT":
E = new DXFText();
break;
case "MTEXT":
E = new DXFMText();
break;
case "ARC":
E = new DXFArc();
break;
case "ELLIPSE":
E = new DXFEllipse();
break;
default: // there are many other objects are possible. For them we create empty Entity
E = new DXFEntity();
break;
}
// each Entity will need reference to the Base object Converter, which stores all Entities.
E.Converter = this;
return E; // return Entity and after it is added to the array of Entities
}
读取实体属性的方法与上述方法基本相似,但有一个重要的注意事项:不同的实体具有相同的属性和不同的属性。例如,许多实体都有“基点” - 在 DXF 中,由代码 10 (x)、20 (y)、30(z) 描述
LINE
10
39.19953392043317
20
36.4554281665769
30
0.0
这些代码对于 LINE、CIRCLE、ELLIPSE、TEXT 以及许多其他实体都是相同的。因此,我们可以创建面向对象的结构来读取和存储属性,以避免重复编码。我们将把 Layer 和 Base Point 存储在实体“DXFVisibleEntity
”中,该实体将是所有可见实体的祖先。看看读取这些属性的代码:
//base class for all visible entities
public class DXFVisibleEntity : DXFEntity
{
//Base point (x, y, z) for all entities
public DXFImport.SFPoint Point1 = new SFPoint();
// virtual function ReadProperty() is overridden in all descendants of Entity to read
//specific properties
public override void ReadProperty()
{
// for the different codes we read values
switch (Converter.FCode)
{
//read Layer
case 8:
layer = Converter.LayerByName(Converter.FValue);
break;
//read Coordinates
case 10: //X
Point1.X = Convert.ToSingle(Converter.FValue, Converter.N);
break;
case 20: //Y
Point1.Y = Convert.ToSingle(Converter.FValue, Converter.N);
break;
//read Color
case 62:
FColor = CADImage.IntToColor(Convert.ToInt32(Converter.FValue, Converter.N));
break;
}
}
}
我们使用相同的方法读取 LINE 的第二个坐标,Circle 的半径等等。
DXF 文件和 DXF 导入 .NET 结构

此图显示了 DXF 文件和项目中的 C# 源代码之间的主要部分。虚线代表 DXF 文件对象和 C# 中编程的对象之间的关联。
CADImage
是一个用于从 DXF 文件加载和绘制到 Graphics 的类。它在字段中存储 DXF 实体
public DXFSection FEntities;
在图中,它是 DXFSection
。
DXFEntity
是所有 Entities 类的基类。DXFBlocks
和 DXFSection
类不可见。DXFVisibleEntity
类是所有可见实体(invisible - are "DXFTable
", "DXFLayer
", etc.)的祖先。
顺便说一句,上面的图是用 DXF 格式制作的 :) 在 ABViewer
软件中。
CAD 技巧
如果您不熟悉 AutoCAD,请注意 DXF 实体的结构。有一个特殊的实体“Block
”,在 CAD 图形中可能有很多“inserts”。Block 只是一个实体集合(包括嵌套 Block),可以被插入多次。
注意
Block changes many properties of element when showing it.
So if you want to know the color of entity, it is not enough to read
"Entity.Color" - it is necessary to see all the Inserts and Blocks,
in which this entity can be included. To get the correct color in DXF Import
.NET project we made the following function: EntColor(DXFEntity E, DXFInsert Ins).
请使用 EntColor()
函数获取正确的 Color
类型,即使文件中没有 Blocks。请注意,最常见的颜色是“ByLayer
”,为了读取正确的颜色,我们需要从 Layer
实体读取颜色。此功能也在此函数中提供。
下面是 EntColor
函数。它有很多技巧,例如检查 layer.name == "0"
- 在 DXF 中,图层“0
”是特殊的,颜色为“ByLayer
”的元素将从 Block 获取颜色,如果它们位于“0
”图层上。
//Use this func to know the color of Entity, DXFInsert is Insert entity or null.
public static Color EntColor(DXFEntity E, DXFInsert Ins)
{
DXFInsert vIns = Ins;
DXFEntity Ent = E;
Color Result = DXFConst.clNone;
if(Ent is DXFVisibleEntity) Result = E.FColor;
/*if(Ent is Polyline)
Result = ((Polyline)Ent).Pen.Pen.Color;*/
if(E.layer == null) return Result;
/* algorithm is rather difficult here. This is the way, how AutoCAD works with the colors,
if you try to create entities in AutoCAD, you will see how they use Colors */
if((Result == clByLayer)||(Result == clByBlock))
{
if((vIns == null)||((Result == clByLayer)&&(Ent.layer.name != "0")))
{
if(Result == clByLayer)
{
if(Ent.layer.color != clNone)
Result = Ent.layer.color;
else Result = Color.Black;
}
}
else
{
while(vIns != null)
{
Result = vIns.color;
if((Result != clByBlock) && !((Result == clByLayer) &&
(vIns.layer.name == "0")))
{
if(Result == clByLayer)
Result = vIns.layer.color;
break;
}
if((vIns.owner == null)&&(Result == clByLayer))
Result = vIns.layer.color;
vIns = vIns.owner;
}
}
}
if((Result == clByLayer)||(Result == clByBlock))
Result = clNone;
return Result;
}
如何使用该软件
主代码在 DXFImport.cs 中。您可以直接在项目中将此文件用作读取和可视化 DXF 文件的示例。
使用 DXFImport.cs 的示例代码是 Form1.cs。
如何查看实体
在 Form1.cs 中,我们使用 Form1_Paint
事件。
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
//FCADImage - base class to reference to DXF
if (FCADImage == null)
return;
FCADImage.Draw(e.Graphics); // CADImage.Draw() accepts Graphics to draw to
}
我们可以使用 FCADImage.Draw()
将内容绘制到任何 Graphics 对象 - 例如,打印机。FCADImage.Draw()
函数应使用特殊算法,其中每个实体都根据块/插入/图层参数绘制。
public void Draw(Graphics e)
{
if (FMain == null)
return;
FGraphics = e;
// Iterate begins to over the entities to draw all of them
FEntities.Iterate(new CADEntityProc(DrawEntity), FParams);
}
请注意 FEntities.Iterate()
函数,它允许访问所有实体,包括它们在块中的位置。它是这样工作的:有一个名为 DXFGroup
的类,它是 Entity
的祖先,可以存储 Entities
数组。例如,Block
是 DXFGroup
的祖先,可以存储许多实体,如 LINE
。为了更好地统一,我们可以为所有图形使用一个基础组实体对象,该实体将包含所有实体的数组,每个实体也可以是 Group
- 经典的“Tree
”。
Iterate 方法最终应该为每个 Entity
调用 Draw()
,以便将其绘制到 Graphics
。
protected static void DrawEntity(DXFEntity Ent)
{
Ent.Draw(FGraphics);
}
Draw()
方法在 Entity
的后代中被重写,以绘制特定的实体。让我们详细看看 DXFLine
中的实现。
// draw line
public override void Draw(System.Drawing.Graphics G)
{
// points P1 (x1, y1) and P2 (x2, y2) of Line
SFPoint P1, P2;
// read color via EntColor to get real color
Color RealColor = DXFConst.EntColor(this, Converter.FParams.Insert);
// Read point 1 -convert global coordinates to the screen coordinates:
P1 = Converter.GetPoint(Point1);
//read point 2
P2 = Converter.GetPoint(Point2);
if (FVisible)
G.DrawLine(new Pen(RealColor, 1), P1.X, P1.Y, P2.X, P2.Y);
}
- 我们通过
DXFConst.EntColor(this, Converter.FParams.Insert);
获取实际颜色,如我之前所述。 - 点从全局坐标转换为屏幕坐标的函数是
GetPoint()
。GetPoint
不仅进行全局到屏幕的转换,还使用块偏移量和块内部的比例。因此,它简化了开发工作,无需“查看”当前正在绘制哪个块 - Block 更改“FParams.matrix
”以绘制自身。所有实体坐标都使用此“FParams.matrix
”。 - 实体被绘制到给定的
Graphics G
。
因此,您可以绘制到打印机、光栅图像或其他 Graphics
。
DXF 导入 .NET 参考
public class DXFConst
存储常量和基本函数。
public class DXFMatrix
用于处理坐标的类。
public struct FRect
描述 CAD 图形所在的 3D 空间(全局坐标)。
public struct CADIterate
处理“Iterate
”函数时,存储实体所需的所有参数。
public class CADImage
用于绘制 CAD 图形的类。
public class DXFEntity
所有 DXF 实体的基类。
public class DXFGroup : DXFEntity
所有组实体的基类。
public class DXFTable : DXFGroup
用于读取 DXF“Table”部分的类 - 这里只读取 Layers。
public class DXFVisibleEntity : DXFEntity
所有可见实体(不可见实体是“DXFTable
”、“DXFLayer
”等)的基类。
public class DXFCustomVertex: DXFVisibleEntity
DXF 中 3D 点的特殊类。
public class DXFText: DXFCustomVertex
存储和绘制“Text
”DXF 实体。
以下类用于特定的 DXF 实体:
public class DXFLine : DXFVisibleEntity
public class DXFArc: DXFCircle
public class DXFEllipse: DXFArc
public class DXFLayer: DXFEntity
public class DXFBlock : DXFGroup
用于处理 DXF Block 的类。
public class DXFInsert : DXFVisibleEntity
用于处理 DXF 中的“Insert
”的类,对于 AutoCAD 用户来说,这就是“Block reference”。Blocks 不可见,Inserts 可见。
结论
本文的目的是提供一些关于如何编写 DXF 读取器的建议。DXF 文件结构不像其逻辑表示那样困难。本文探讨了基本的 DXF 格式问题,并展示了如何找到解决方案。示例源代码是用 C# 编写的,对于需要访问 DXF 文件的所有人来说都可能很有用。
Code Project 上的其他项目
在 Code Project 上,您已经可以找到另一个 DXF 读取器:
与上述项目相比,我们的项目的主要优点是:
- Block / Inserts
- 图层
- 文本
该 DXF 读取器中未包含这些内容。
历史
这是 DXF Import .NET 的第一个开源版本。