【WPF】 基于 Canvas 读取并渲染 DXF 文件的技术指南

【WPF】 基于 Canvas 读取并渲染 DXF 文件的技术指南

一、DXF 文件格式概述

1.1 什么是 DXF?

DXF(Drawing Exchange Format)是 AutoCAD 推出的开放矢量图形交换格式,用于在不同 CAD 软件之间传递二维和三维图形数据。对于 WPF 开发者而言,DXF 是连接传统 CAD 设计与现代桌面应用的重要桥梁。

1.2 DXF 文件结构

DXF 文件采用 ASCII 文本格式,由多个段(Section)组成,每个段包含特定的数据类型:

段名 作用 关键内容
HEADER 全局参数 图形单位、插入基点、绘图范围等
CLASSES 自定义类定义 应用程序自定义对象信息
TABLES 各种表格 图层表、线型表、文字样式表、视图表等
BLOCKS 块定义 可复用的图块集合
ENTITIES 实体数据 核心段,包含所有图形对象
OBJECTS 非图形对象 字典、组、布局等

ENTITIES 段是渲染工作的核心,包含以下常见实体类型:

  • LINE:直线段

  • CIRCLE:圆

  • ARC:圆弧

  • LWPOLYLINE / POLYLINE:多段线(轻量多段线和旧版多段线)

  • TEXT / MTEXT:单行文字和多行文字

  • ELLIPSE:椭圆

  • SPLINE:样条曲线

  • INSERT:块引用(图块插入)

  • HATCH:填充图案

二、WPF 渲染架构设计

2.1 坐标系统映射

这是 DXF 渲染中最关键的环节。DXF 使用右手坐标系,而 WPF Canvas 使用屏幕坐标系,两者存在本质差异:

特性 DXF 坐标系 WPF Canvas
原点位置 左下角或自定义 左上角
Y 轴方向 向上为正 向下为正
单位 毫米/英寸等(可配置) 设备无关像素(DIP)
坐标范围 可能非常大(米级) 通常较小(像素级)

坐标转换策略:

  • Y 轴翻转:将 DXF 的 Y 坐标取反,适应 WPF 的向下为正

  • 原点平移:根据图形边界框(Bounding Box)计算偏移量,使图形居中显示

  • 比例缩放:根据 Canvas 实际尺寸与图形范围的比例进行等比缩放

  • 单位换算:将 DXF 的绘图单位(如毫米)转换为 WPF 的像素单位

2.2 图层与样式映射

DXF 的图层(Layer)信息需要映射到 WPF 的绘图属性:

  • 颜色:DXF 使用 ACI 颜色索引(1-255),需建立索引到 Brush 的映射表

  • 线型:实线、虚线、点划线等,对应 WPF 的 StrokeDashArray

  • 线宽:DXF 的线宽值映射到 WPF 的 StrokeThickness

  • 可见性:图层的开关状态控制对应 WPF 元素的 Visibility

三、核心实体渲染方案

3.1 直线(LINE)

DXF 的 LINE 实体包含起点和终点坐标 (x1, y1, z1)(x2, y2, z2)

在 WPF 中对应 Line 元素:

  • 设置 X1, Y1X2, Y2 属性

  • 应用坐标转换后的值

  • 绑定图层样式(颜色、线宽等)

处理要点:

  • 忽略 Z 坐标(纯 2D 显示时)或用于深度排序

  • 考虑线型比例(LTSCALE)对虚线显示的影响

3.2 圆与圆弧(CIRCLE / ARC)

圆(CIRCLE):

  • 圆心坐标 (cx, cy, cz)

  • 半径 r

对应 WPF 的 Ellipse 元素,但需注意:

  • Ellipse 的宽度和高度为直径(2*r

  • 通过 Canvas.LeftCanvas.Top 定位左上角(需将圆心坐标转换为左上角坐标:left = cx - r, top = cy - r

圆弧(ARC):

  • 包含圆心、半径、起始角度和终止角度

WPF 没有原生的圆弧元素,需要使用 Path 配合 ArcSegment

  • 计算起始点和终止点坐标

  • 设置 ArcSegmentSize(半径)、Point(终点)、IsLargeArc(大弧标志)、SweepDirection(扫掠方向)

3.3 多段线(LWPOLYLINE / POLYLINE)

多段线是 CAD 中最复杂的实体之一,支持:

  • 直线段和圆弧段混合

  • 可变宽度(起点宽度和终点宽度)

  • 凸度(Bulge)定义圆弧段

渲染策略:

  1. 纯直线多段线:使用 Polyline 元素,将顶点列表绑定到 Points 属性

  2. 含圆弧的多段线:需要分段处理:

    • 遍历顶点,根据凸度值判断是直线还是圆弧

    • 凸度 bulge = tan(θ/4),其中 θ 为圆弧圆心角

    • 将圆弧段转换为多个小直线段(离散化)或使用 PathArcSegment

  3. 带宽度的多段线:

    • 简单处理:使用 StrokeThickness 模拟(但不支持渐变宽度)

    • 精确处理:计算每段的轮廓多边形,使用 PolygonPath 填充

3.4 文字(TEXT / MTEXT)

TEXT(单行文字):

  • 插入点坐标

  • 文字内容

  • 文字高度

  • 旋转角度

  • 对齐方式(左对齐、居中、右对齐等)

对应 WPF 的 TextBlock

  • 设置 TextFontSize(需根据缩放比例调整)

  • 使用 RenderTransform 实现旋转

  • 根据对齐方式计算偏移量

MTEXT(多行文字):

  • 支持多行、段落格式、特殊符号

  • 需要解析控制码(如 \P 换行、\H 高度调整等)

  • 在 WPF 中可使用多个 TextBlock 组合或 RichTextBox

字体映射问题:

  • DXF 可能使用 SHX 字体(AutoCAD 专用),WPF 不支持

  • 建立字体映射表:SHX 字体 → 系统 TrueType 字体(如 txt.shxArial

  • 处理缺失字体时的回退策略

3.5 椭圆(ELLIPSE)

DXF 的 ELLIPSE 实体使用参数方程定义:

  • 中心点

  • 长轴端点(定义长轴方向和长度)

  • 短轴与长轴的比例

  • 起始参数和终止参数(用于部分椭圆)

WPF 的 Ellipse 只能绘制正椭圆(轴对齐),对于倾斜椭圆需要使用 Path

  • 计算椭圆的几何参数

  • 使用 MatrixTransformGeometry.Transform 实现旋转和缩放

  • 部分椭圆需要裁剪或使用 ArcSegment 近似

3.6 样条曲线(SPLINE)

DXF 支持 B 样条和 NURBS 样条曲线。

渲染方案:

  1. 离散化逼近:将样条曲线分割为大量小直线段

    • 根据曲率自适应分割(曲率大处分割更密)

    • 使用 PolylinePathPolyLineSegment 绘制

  2. WPF 原生支持:WPF 的 PathGeometry 支持贝塞尔曲线,但 DXF 的 NURBS 需要转换为贝塞尔形式,数学处理较复杂

精度控制:

  • 设置弦高误差(Chord Height Tolerance)控制离散化精度

  • 平衡渲染质量与性能

3.7 填充(HATCH)

DXF 的 HATCH 实体定义填充区域:

  • 边界环(外边界和内边界,即岛)

  • 填充图案(实体填充、预定义图案、自定义图案)

  • 图案比例和角度

WPF 渲染方案:

  1. 实体填充:使用 PathFill 属性,通过 FillRule 处理岛(EvenOdd 或 NonZero)

  2. 图案填充:

    • 预定义图案(如 ANSI31 斜线):通过 DrawingBrushVisualBrush 实现

    • 自定义图案:解析图案定义,动态生成 DrawingBrush

    • 注意图案的原点和角度需要随图形变换

  3. 渐变填充:DXF 的渐变填充(GRADIENT)可映射到 WPF 的 LinearGradientBrushRadialGradientBrush

四、代码实现

4.1 下载安装netDxf库

4.2 读取dxf文件,数据存储到DataTable

C# 复制代码
using netDxf;
using netDxf.Entities;
/// <summary>
    /// 读取CAD的Dxf文件数据
    /// </summary>
    public class DxfHelperHtility
    {
        /// <summary>
        /// 读取CAD文件数据
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public static DataTable ReadDxf(string fileName)
        {
            DataTable dt = CreatDataTable();
            try
            {
                // 加载DXF文件
                DxfDocument dxf = DxfDocument.Load(fileName);
                netDxf.Tables.UCS ucs =dxf.DrawingVariables.CurrentUCS;
                netDxf.Collections.ImageDefinitions dd =dxf.ImageDefinitions;
                bool bRow = false;
                // 遍历所有实体
                foreach (EntityObject entity in dxf.Entities.All)
                {
                    bRow = false;
                    //添加数据行
                    DataRow drRow = dt.NewRow();
                    switch (entity)
                    {
                        case Line line:
                            //直线
                            drRow["DrawingType"] = 0;
                            drRow["StartPointX"] = ToConver(line.StartPoint.X, ucs.Origin.X);
                            drRow["StartPointY"] = ToConver(line.StartPoint.Y, ucs.Origin.Y);
                            drRow["EndPointX"] = ToConver(line.EndPoint.X, ucs.Origin.X);
                            drRow["EndPointY"] = ToConver(line.EndPoint.Y, ucs.Origin.Y);
                            drRow["Thickness"] = line.Thickness;
                            bRow = true;
                            break;
                        case Circle circle:
                            //圆形
                            drRow["DrawingType"] = 1;
                            drRow["StartPointX"] = ToConver(circle.Center.X, ucs.Origin.X);
                            drRow["StartPointY"] = ToConver(circle.Center.Y, ucs.Origin.Y);
                            drRow["Radius"] = circle.Radius;
                            drRow["Thickness"] = circle.Thickness;
                            bRow = true;
                            break;
                        case Arc arc:
                            //弧形
                            drRow["DrawingType"] = 2;
                            drRow["StartPointX"] = ToConver(arc.Center.X, ucs.Origin.X);
                            drRow["StartPointY"] = ToConver(arc.Center.Y, ucs.Origin.Y);
                            drRow["Radius"] = arc.Radius;
                            drRow["StartAngle"] = arc.StartAngle;
                            drRow["EndAngle"] = arc.EndAngle;
                            drRow["Thickness"] = arc.Thickness;
                            bRow = true;
                            break;
                        case Text text:
                            //文本
                            break;
                        case Ellipse ellipse:
                            //椭圆
                            drRow["DrawingType"] = 4;
                            drRow["StartPointX"] = ToConver(ellipse.Center.X, ucs.Origin.X);
                            drRow["StartPointY"] = ToConver(ellipse.Center.Y, ucs.Origin.Y);
                            drRow["MajorAxis"] = ellipse.MajorAxis;
                            drRow["MinorAxis"] = ellipse.MinorAxis;
                            drRow["StartAngle"] = ellipse.StartAngle;
                            drRow["EndAngle"] = ellipse.EndAngle;
                            drRow["Thickness"] = ellipse.Thickness;
                            bRow = true;
                            break;
                        case Polyline2D polyline2D:
                            drRow["DrawingType"] = 5;
                            drRow["Thickness"] = polyline2D.Thickness;
                            string data = "";
                            Polyline2DVertex startVertex = polyline2D.Vertexes[0];
                            Polyline2DVertex nextVertex;
                            double radius = 0;
                            for (int i = 0; i < polyline2D.Vertexes.Count; i++)
                            {
                                radius = 0;
                                Polyline2DVertex curVertex = polyline2D.Vertexes[i];
                                if (curVertex.Bulge != 0)
                                {
                                    //有凸度,计算半径
                                    if (i == polyline2D.Vertexes.Count)
                                    {
                                        nextVertex = startVertex;
                                    }
                                    else
                                    {
                                        nextVertex = polyline2D.Vertexes[i + 1];
                                    }
                                    radius = CalBulgeRadius(curVertex.Position.X, curVertex.Position.Y, nextVertex.Position.X, nextVertex.Position.Y, curVertex.Bulge);
                                }
                                data += string.Format("X:{0},Y:{1},Bulge:{2},StartWidth:{3},EndWidth:{4},Radius:{5}|", ToConver(curVertex.Position.X, ucs.Origin.X), ToConver(curVertex.Position.Y, ucs.Origin.Y), curVertex.Bulge, curVertex.StartWidth, curVertex.EndWidth, radius);
                            }
                            drRow["Data"] = data;
                            bRow = true;
                            break;
                        default:
                            break;
                    }

                    if (bRow)
                    {
                        dt.Rows.Add(drRow);
                    }
                }
            }
            catch (Exception ex)
            {
                
            }
            return dt;
        }

        /// <summary>
        /// 坐标转换
        /// </summary>
        /// <param name="value">坐标值</param>
        /// <param name="ucs">用户原点坐标</param>
        /// <returns></returns>
        private static int ToConver(double value,double ucs)
        {
            int result = 0;
            result = (int)Math.Round(value- ucs);
            return result;
        }

        /// <summary>
        /// 动态创建表格列
        /// </summary>
        /// <returns></returns>
        private static DataTable CreatDataTable()
        {
            DataTable dt = new DataTable();
            try
            {
                //添加列
                dt.Columns.Add("DrawingType", typeof(int));//类型
                dt.Columns.Add("StartPointX", typeof(double));//起点X坐标
                dt.Columns.Add("StartPointY", typeof(double));//起点Y坐标
                dt.Columns.Add("EndPointX", typeof(double));//终点X坐标
                dt.Columns.Add("EndPointY", typeof(double));//起点Y坐标
                dt.Columns.Add("Radius", typeof(double));//圆半径
                dt.Columns.Add("StartAngle", typeof(double));//起点角度
                dt.Columns.Add("EndAngle", typeof(double));//终点角度
                dt.Columns.Add("MajorAxis", typeof(double));//椭圆长轴
                dt.Columns.Add("MinorAxis", typeof(double));//椭圆短轴
                dt.Columns.Add("Thickness", typeof(double));//线宽
                dt.Columns.Add("Data", typeof(string));//线宽
            }
            catch (Exception ex)
            {

            }
            return dt;
        }
        /// <summary>
        /// 根据两点和凸度,计算半径
        /// </summary>
        /// <param name="x1"></param>
        /// <param name="y1"></param>
        /// <param name="x2"></param>
        /// <param name="y2"></param>
        /// <param name="bulge"></param>
        /// <returns></returns>
        public static double CalBulgeRadius(double x1, double y1, double x2, double y2, double bulge)
        {
            double radius = 0;
            try
            {
                if (bulge == 0)
                    return radius;
                //计算顶点角度
                double cicleAngle = Math.Atan(bulge) * 4;
                //两点之间的距离
                double pointLen = Math.Sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
                //根据正玄值反推
                radius = (pointLen / 2) / Math.Sin(cicleAngle / 2);
                return Math.Abs(radius);
            }
            catch (Exception ex)
            {

            }
            return radius;
        }
    }

特别注意:如果用户画的图不是默认原点,而是自定义原点,需要获取用户坐标原点,其他线段点需要减去用户坐标原点才能得到正确的坐标

C# 复制代码
//获取用户坐标系(UCS)的原点坐标
  netDxf.Tables.UCS ucs =dxf.DrawingVariables.CurrentUCS;

至此完成了dxf文件读取并将数据存储到DataTable,供后续处理。

4.3 DataTable数据转换成通用数据,供上层使用

C# 复制代码
/// <summary>
        /// 读取CAD文件数据
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public static List<DxfModel> ReadDxf(string path)
        {
            List<DxfModel> list = new List<DxfModel>();
            DataTable dt = DxfHelperHtility.ReadDxf(path);
            if (dt == null || dt.Rows.Count <= 0)
            {
                return list;
            }
            for (int i = 0; i < dt.Rows.Count; i++)
            {
                DxfModel dxfModel = new DxfModel();
                int drawingType = Convert.ToInt16(dt.Rows[i]["DrawingType"].ToString().Trim());
                switch (drawingType)
                {
                    case 0:
                        //直线
                        dxfModel.DrawingType = drawingType;
                        dxfModel.StartPointX= Convert.ToInt16(dt.Rows[i]["StartPointX"].ToString().Trim());
                        dxfModel.StartPointY = Convert.ToInt16(dt.Rows[i]["StartPointY"].ToString().Trim());
                        dxfModel.EndPointX = Convert.ToInt16(dt.Rows[i]["EndPointX"].ToString().Trim());
                        dxfModel.EndPointY = Convert.ToInt16(dt.Rows[i]["EndPointY"].ToString().Trim());
                        dxfModel.Thickness = Convert.ToDouble(dt.Rows[i]["Thickness"].ToString().Trim());
                        list.Add(dxfModel);
                        break;
                    case 1:
                        //圆形
                        dxfModel.DrawingType = drawingType;
                        dxfModel.StartPointX = Convert.ToInt16(dt.Rows[i]["StartPointX"].ToString().Trim());
                        dxfModel.StartPointY = Convert.ToInt16(dt.Rows[i]["StartPointY"].ToString().Trim());
                        dxfModel.Radius = Convert.ToDouble(dt.Rows[i]["Radius"].ToString().Trim());
                        dxfModel.Thickness = Convert.ToDouble(dt.Rows[i]["Thickness"].ToString().Trim());
                        list.Add(dxfModel);
                        break;
                    case 2:
                        //圆弧
                        dxfModel.DrawingType = drawingType;
                        dxfModel.StartPointX = Convert.ToInt16(dt.Rows[i]["StartPointX"].ToString().Trim());
                        dxfModel.StartPointY = Convert.ToInt16(dt.Rows[i]["StartPointY"].ToString().Trim());
                        dxfModel.Radius = Convert.ToDouble(dt.Rows[i]["Radius"].ToString().Trim());
                        dxfModel.StartAngle = Convert.ToDouble(dt.Rows[i]["StartAngle"].ToString().Trim());
                        dxfModel.EndAngle = Convert.ToDouble(dt.Rows[i]["EndAngle"].ToString().Trim());
                        dxfModel.Thickness = Convert.ToDouble(dt.Rows[i]["Thickness"].ToString().Trim());
                        list.Add(dxfModel);
                        break;
                    case 3:
                        //文本
                        break;
                    case 4:
                        //椭圆
                        dxfModel.DrawingType = drawingType;
                        dxfModel.StartPointX = Convert.ToInt16(dt.Rows[i]["StartPointX"].ToString().Trim());
                        dxfModel.StartPointY = Convert.ToInt16(dt.Rows[i]["StartPointY"].ToString().Trim());
                        dxfModel.MajorAxis = Convert.ToDouble(dt.Rows[i]["MajorAxis"].ToString().Trim());
                        dxfModel.MinorAxis = Convert.ToDouble(dt.Rows[i]["MinorAxis"].ToString().Trim());
                        dxfModel.StartAngle = Convert.ToDouble(dt.Rows[i]["StartAngle"].ToString().Trim());
                        dxfModel.EndAngle = Convert.ToDouble(dt.Rows[i]["EndAngle"].ToString().Trim());
                        dxfModel.Thickness = Convert.ToDouble(dt.Rows[i]["Thickness"].ToString().Trim());
                        list.Add(dxfModel);
                        break;
                    case 5:
                        //Polyline2D
                        dxfModel.DrawingType = drawingType;
                        dxfModel.Thickness = Convert.ToDouble(dt.Rows[i]["Thickness"].ToString().Trim());
                        //解析图形坐标
                        string[] datas = dt.Rows[i]["Data"].ToString().Trim().Split('|');
                        for (int k = 0; k < datas.Count() - 1; k++)
                        {
                            Polyline polyline = new Polyline();
                            string[] messageData = datas[k].Split(',');
                            polyline.X = Convert.ToDouble(messageData[0].Split(':')[1]);
                            polyline.Y = Convert.ToDouble(messageData[1].Split(':')[1]);
                            polyline.Bulge = Convert.ToDouble(messageData[2].Split(':')[1]);
                            polyline.StartWidth = Convert.ToDouble(messageData[3].Split(':')[1]);
                            polyline.EndWidth = Convert.ToDouble(messageData[4].Split(':')[1]);
                            polyline.Radius = Convert.ToDouble(messageData[5].Split(':')[1]);
                            dxfModel.Polylines.Add(polyline);
                        }
                        list.Add(dxfModel);
                        break;
                    default:
                        break;
                }
            }
            return list;
        }

 /// <summary>
    /// CAD图形实体类
    /// </summary>
    public class DxfModel
    {
        /// <summary>
        /// 图形类型
        /// 0:直线
        /// 1:圆形
        /// 2:圆弧
        /// 3:文本
        /// 4:椭圆
        /// 5:Polyline2D
        /// </summary>
        public int DrawingType;
        /// <summary>
        /// 起点X坐标
        /// </summary>
        public double StartPointX;
        /// <summary>
        /// 起点Y坐标
        /// </summary>
        public double StartPointY;
        /// <summary>
        /// 终点X坐标
        /// </summary>
        public double EndPointX;
        /// <summary>
        /// 终点Y坐标
        /// </summary>
        public double EndPointY;
        /// <summary>
        /// 圆半径
        /// </summary>
        public double Radius;
        /// <summary>
        /// 起点角度
        /// </summary>
        public double StartAngle;
        /// <summary>
        /// 终点角度
        /// </summary>
        public double EndAngle;
        /// <summary>
        /// 椭圆长轴
        /// </summary>
        public double MajorAxis;
        /// <summary>
        /// 椭圆短轴
        /// </summary>
        public double MinorAxis;
        /// <summary>
        /// 线宽
        /// </summary>
        public double Thickness;
        /// <summary>
        /// 其他类型数据
        /// </summary>
        public string Data;
        /// <summary>
        /// Polyline数据
        /// </summary>
        public List<Polyline> Polylines = new List<Polyline>();
    }

    /// <summary>
    /// Polyline 实体
    /// </summary>
    public class Polyline
    {
        /// <summary>
        /// 坐标X
        /// </summary>
        public double X;
        /// <summary>
        /// 坐标Y
        /// </summary>
        public double Y;
        /// <summary>
        /// 凸度
        /// </summary>
        public double Bulge;
        public double StartWidth;
        public double EndWidth;
        /// <summary>
        /// 半径
        /// </summary>
        public double Radius;
    }

4.4 基于Canvas画图

Java 复制代码
/// <summary>
   /// 画图
     /// </summary>
     private void Draw()
     {
         try
         {
             if (string.IsNullOrWhiteSpace(DxfName))
             {
                 return;
             }
             Path path = new Path();
             PathGeometry pathGeometry = new PathGeometry();
             PathFigure pathFigure = new PathFigure();
             Point startPoint = new Point();
             Point endPoint = new Point();
             Point prePoint = startPoint;
             List<DxfModel> list =FileManageBll.ReadDxf(DxfName);
             foreach (var item in list)
             {
                 switch (item.DrawingType)
                 {
                     case 0://直线
                         // 创建Path对象
                         path = new Path();
                         path.Stroke = System.Windows.Media.Brushes.White;
                         path.StrokeThickness = 1;
                         // 创建PathGeometry对象
                         pathGeometry = new PathGeometry();
                         // 创建PathFigure对象
                         pathFigure = new PathFigure();
                         pathFigure.StartPoint = new System.Windows.Point(item.StartPointX, -item.StartPointY);
                         // 创建LineSegment对象并添加到PathFigure
                         pathFigure.Segments.Add(new LineSegment(new System.Windows.Point(item.EndPointX, -item.EndPointY), true));
                         // 将PathFigure添加到PathGeometry
                         pathGeometry.Figures.Add(pathFigure);
                         // 设置Path的Data属性为PathGeometry对象
                         path.Data = pathGeometry;
                         // 将path添加到myCanvas1中
                         this.mainWindow.myCanvas.Children.Add(path);
                         break;
                     case 1://圆
                         System.Windows.Shapes.Ellipse ellipse = new System.Windows.Shapes.Ellipse
                         {
                             Width = item.Radius,
                             Height = item.Radius,
                             StrokeThickness = 1,
                             Stroke = System.Windows.Media.Brushes.White
                         };
                         Canvas.SetLeft(ellipse, item.StartPointX);
                         Canvas.SetTop(ellipse, -item.StartPointY);
                         this.mainWindow.myCanvas.Children.Add(ellipse);
                         break;
                     case 2: //圆弧
                         // 将角度转换为弧度
                         double startRadians = item.StartAngle * Math.PI / 180;
                         double endRadians = item.EndAngle * Math.PI / 180;

                         // 计算起点和终点的坐标(注意Y轴方向)
                         startPoint = new Point(
                             item.StartPointX + item.Radius * Math.Cos(startRadians),
                             -item.StartPointY - item.Radius * Math.Sin(startRadians));
                         endPoint = new Point(
                            item.StartPointX + item.Radius * Math.Cos(endRadians),
                            -item.StartPointY - item.Radius * Math.Sin(endRadians));

                         bool IsLargeArc = false;
                         if ((item.EndAngle - item.StartAngle + 360) % 360 >= 180)
                         {
                             //大弧
                             IsLargeArc = true;
                         }
                         SweepDirection direction = SweepDirection.Counterclockwise;
                         //if (item.StartAngle > item.EndAngle)
                         //{
                         //    //逆时针
                         //    direction = SweepDirection.Clockwise;
                         //}
                         path = new Path();
                         pathGeometry = new PathGeometry();
                         ArcSegment arc = new ArcSegment(endPoint, new Size(item.Radius, item.Radius), 0, IsLargeArc, direction, true);

                         PathFigure figure = new PathFigure();
                         figure.StartPoint = startPoint;
                         figure.Segments.Add(arc);
                         pathGeometry.Figures.Add(figure);
                         path.Data = pathGeometry;
                         path.Stroke = Brushes.White;
                         path.StrokeThickness = 1;
                         this.mainWindow.myCanvas.Children.Add(path);
                         break;
                     case 3:

                         break;
                     case 4:
                         break;
                     case 5://Polyline2D
                         startPoint = new Point(Convert.ToInt32(item.Polylines[0].X), -Convert.ToInt32(item.Polylines[0].Y));
                         double Bulge = item.Polylines[0].Bulge;
                         pathGeometry = new PathGeometry();
                         pathFigure = new PathFigure();
                         pathFigure.StartPoint = startPoint;
                         endPoint = new Point();
                         prePoint = startPoint;
                         double radius = item.Polylines[0].Radius;
                         PathSegment segment;
                         //循环各点连接成图形
                         for (int i = 1; i < item.Polylines.Count; i++)
                         {
                             endPoint = new Point(Convert.ToInt32(item.Polylines[i].X), -Convert.ToInt32(item.Polylines[i].Y));
                             segment = GetSegment(endPoint, Bulge, radius);
                             pathFigure.Segments.Add(segment);
                             radius = item.Polylines[i].Radius;
                             Bulge = item.Polylines[i].Bulge;
                             prePoint = endPoint;
                         }
                         //最后坐标连接首个坐标,完成闭合
                         segment = GetSegment(startPoint, Bulge, radius);
                         pathFigure.Segments.Add(segment);
                         pathGeometry.Figures.Add(pathFigure);
                         path = new Path
                         {
                             Stroke = Brushes.White,
                             StrokeThickness = 1,
                             Data = pathGeometry
                         };
                         this.mainWindow.myCanvas.Children.Add(path);
                         break;
                     default:
                         break;
                 }
             }
         }
         catch (Exception ex)
         {
         }
     }

     /// <summary>
     /// 获取Segment
     /// </summary>
     /// <param name="messageData"></param>
     /// <param name="StartPoint"></param>
     /// <param name="Bulge"></param>
     /// <returns></returns>
     private PathSegment GetSegment(Point point, double Bulge,double radius)
     {
         PathSegment pathSegment = null;
         try
         {
             if (Bulge == 0)
             {
                 pathSegment = new LineSegment(point, true);
             }
             else
             {
                 bool IsLargeArc = false;
                 SweepDirection sweepDirection = SweepDirection.Counterclockwise;
                 sweepDirection = SweepDirection.Counterclockwise;
                 //大于1优弧
                 IsLargeArc = Math.Abs(Bulge) > 1;
                 if (Bulge < 0)
                 {
                     //小于0顺时针
                     sweepDirection = SweepDirection.Clockwise;
                 }
                 pathSegment = new ArcSegment(point, new Size(radius, radius), 0, IsLargeArc, sweepDirection, true);
             }
         }
         catch (Exception ex)
         {
         }
         return pathSegment;
     }

4.5 效果图

五、典型应用场景

5.1 PCB 设计预览

结合您之前的 PCB Gerber 图形匹配项目背景,DXF 渲染可用于:

  • 显示 PCB 板框、定位孔、安装孔

  • 叠加显示元器件布局(与 Gerber 层对齐)

  • 实现 DXF 与 Gerber 的坐标系统一和对齐

关键考虑:

  • DXF 和 Gerber 的坐标原点可能不同,需要基准点对齐

  • 单位统一(DXF 通常为毫米,Gerber 可能为英寸或毫米)

  • 亚像素级精度要求下,WPF 的 UseLayoutRoundingSnapToDevicePixels 设置

5.2 机械图纸查看器

  • 支持多布局(Model Space 和 Paper Space)

  • 尺寸标注解析与显示

  • 测量工具(距离、角度、面积)

5.3 CAD 数据转换中间件

  • 将 DXF 转换为 WPF 的 XAML 格式

  • 导出为 PNG/SVG 等图像格式

  • 作为打印预览引擎

六、常见问题与解决方案

6.1 坐标显示错位

原因:DXF 的插入基点(Base Point)或 UCS(用户坐标系)变换未应用

解决:在解析时检查并应用 INSBASE 系统变量和实体级别的坐标变换矩阵

6.2 文字显示异常

原因:字体缺失、文字高度为 0、编码问题(DXF 可能使用 ANSI 或 Unicode)

解决:

  • 建立全面的字体映射表

  • 设置默认文字高度

  • 正确处理 DXF 的组码 1(字符串值)编码

6.3 性能瓶颈

原因:实体数量过多(>10,000)、复杂样条曲线、大量填充

解决:

  • 实施空间索引和视口裁剪

  • 样条曲线预离散化并缓存

  • 填充图案使用位图缓存

  • 考虑使用 WriteableBitmap 进行底层像素操作(极端性能需求)

6.4 块引用(INSERT)处理

DXF 的块引用需要递归解析:

  • 读取 BLOCKS 段定义块内容

  • 在 ENTITIES 段遇到 INSERT 时,实例化块并应用变换(位置、旋转、缩放)

  • 处理嵌套块(块中包含块引用)

  • 注意属性文字(ATTRIB)的关联显示

七、总结

在 WPF 中基于 Canvas 渲染 DXF 文件,核心挑战在于坐标系统映射、复杂实体几何转换和大规模数据性能优化。

推荐的技术路线:

  1. 解析层:选择成熟的 DXF 解析库(或自行实现轻量解析器),重点处理 ENTITIES 段

  2. 转换层:建立 DXF 实体到 WPF 元素的映射工厂,处理坐标变换和样式映射

  3. 渲染层:使用 Canvas 作为根容器,分层组织,实施虚拟化和缓存策略

  4. 交互层:封装缩放、平移、选择等操作,提供流畅的用户体验

对于需要高精度图形匹配的场景(如 PCB 制造),建议将 DXF 解析后的几何数据与图像处理管线结合,实现矢量数据与光栅数据的亚像素级配准。

相关推荐
天下无敌笨笨熊1 小时前
SNMP协议开发心得
网络协议·c#
创可贴治愈心灵2 小时前
AI浪潮下C#就业前景剖析:深耕C#为主,按需选修Java与Python
java·人工智能·c#
专注VB编程开发20年4 小时前
开发VS2026插件最佳方案:老式VSIX EnvDTE
ide·c#·visual studio
AC赳赳老秦4 小时前
用 OpenClaw 整理团队技术分享:自动提取 PPT 内容、生成文字稿、同步到知识库
开发语言·python·自动化·powerpoint·wpf·deepseek·openclaw
专注VB编程开发20年4 小时前
VS2026最新ide插件VisualStudio.Extensibility进程外 OOP 新模型
ide·c#·visual studio
scan7245 小时前
短期记忆记忆存储在内存里,一个会话里的多轮对话
开发语言·c#
JaydenAI5 小时前
[MAF预定义Agent中间件-01]LoggingAgent——在Agent调用前后输出日志
ai·c#·agent·maf·agent管道·agent中中间件
JaydenAI5 小时前
[MAF预定义的AIContextProvider-10]TodoProvider——用TodoList驱动Agent的任务执行
ai·c#·agent·maf
淡水瑜5 小时前
C# 实操
开发语言·c#