前言
大家是否在项目中遇到过这样的需求:需要开发一个专业的路径绘制工具,支持工业级精度和复杂路径操作?传统的 GDI+ 性能有限,WPF 又过于复杂。
本文将使用 C# + SkiaSharp 开发一个完整的工业级路径绘制系统。手把手教大家如何设计专业的 UI 布局、实现高性能图形渲染、处理复杂路径算法,并支持导出多种工业格式(如 G 代码、DXF 等)。不管是 CAD 软件开发,还是工业控制系统,这套解决方案都能节省大量开发时间。
问题分析:工业绘图软件的核心痛点
传统方案的局限性
在开发工业级绘图软件时,我们常常面临以下挑战:
-
性能瓶颈:GDI+ 在处理大量图形元素时性能急剧下降
-
精度问题:浮点运算误差影响工业级精度要求
-
格式兼容:需支持多种工业标准格式输出
-
UI 复杂性:专业软件需要丰富的交互体验
SkiaSharp 的优势
SkiaSharp 作为 Google Skia 的 .NET 绑定,为我们提供了:
-
硬件加速:GPU 渲染支持,性能提升 10 倍以上
-
跨平台:Windows、macOS、Linux 全平台支持
-
工业精度:支持亚像素级精确渲染
-
丰富 API:完整的 2D 图形绘制能力
系统架构
核心类结构
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SkiaSharp;
namespace AppIndustrialPathDrawing.Models
{
public class PathPoint
{
public float X { get; set; }
public float Y { get; set; }
public PathPointType Type { get; set; }
public float[] ControlPoints { get; set; }
public string Description { get; set; }
public DateTime CreatedTime { get; set; }
public PathPoint()
{
CreatedTime = DateTime.Now;
Type = PathPointType.Line;
}
public PathPoint(float x, float y, PathPointType type = PathPointType.Line) : this()
{
X = x;
Y = y;
Type = type;
}
public SKPoint ToSKPoint()
{
return new SKPoint(X, Y);
}
public override string ToString()
{
return $"({X:F2}, {Y:F2}) - {Type}";
}
}
public enum PathPointType
{
Move, // 移动到点
Line, // 直线到点
Curve, // 曲线到点
Arc, // 弧线到点
Cubic, // 三次贝塞尔曲线
Quadratic // 二次贝塞尔曲线
}
}
工业路径核心类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SkiaSharp;
namespace AppIndustrialPathDrawing.Models
{
public class IndustrialPath
{
public string Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public List<PathPoint> Points { get; set; }
public PathType Type { get; set; }
public DrawingSettings Settings { get; set; }
public DateTime CreatedTime { get; set; }
public DateTime ModifiedTime { get; set; }
public float TotalLength { get; private set; }
public float MaxVelocity { get; set; } = 100.0f; // mm/s
public float Acceleration { get; set; } = 50.0f; // mm/s²
public float Tolerance { get; set; } = 0.01f; // mm
public string MaterialType { get; set; } = "Steel";
public float ToolDiameter { get; set; } = 5.0f; // mm
public IndustrialPath()
{
Id = Guid.NewGuid().ToString();
Points = new List<PathPoint>();
Settings = new DrawingSettings();
CreatedTime = DateTime.Now;
ModifiedTime = DateTime.Now;
}
public void AddPoint(PathPoint point)
{
Points.Add(point);
ModifiedTime = DateTime.Now;
CalculateLength();
}
public bool RemovePoint(PathPoint point)
{
bool removed = Points.Remove(point);
if (removed)
{
ModifiedTime = DateTime.Now;
CalculateLength();
}
return removed;
}
public void CalculateLength()
{
TotalLength = 0;
for (int i = 1; i < Points.Count; i++)
{
var p1 = Points[i - 1];
var p2 = Points[i];
switch (p2.Type)
{
case PathPointType.Line:
TotalLength += CalculateLineLength(p1, p2);
break;
case PathPointType.Curve:
case PathPointType.Cubic:
case PathPointType.Quadratic:
TotalLength += CalculateCurveLength(p1, p2);
break;
case PathPointType.Arc:
TotalLength += CalculateArcLength(p1, p2);
break;
}
}
}
private float CalculateLineLength(PathPoint p1, PathPoint p2)
{
float dx = p2.X - p1.X;
float dy = p2.Y - p1.Y;
return (float)Math.Sqrt(dx * dx + dy * dy);
}
private float CalculateCurveLength(PathPoint p1, PathPoint p2)
{
// 简化一些
return CalculateLineLength(p1, p2) * 1.2f;
}
private float CalculateArcLength(PathPoint p1, PathPoint p2)
{
// 简化一些
return CalculateLineLength(p1, p2) * 1.57f; // π/2 近似
}
/// <summary>
/// 创建 SkiaSharp 路径对象
/// </summary>
public SKPath CreateSKPath()
{
var path = new SKPath();
if (Points.Count == 0) return path;
var firstPoint = Points[0];
path.MoveTo(firstPoint.X, firstPoint.Y);
for (int i = 1; i < Points.Count; i++)
{
var point = Points[i];
switch (point.Type)
{
case PathPointType.Line:
path.LineTo(point.X, point.Y);
break;
case PathPointType.Quadratic:
if (point.ControlPoints != null && point.ControlPoints.Length >= 2)
{
path.QuadTo(point.ControlPoints[0], point.ControlPoints[1],
point.X, point.Y);
}
else
{
path.LineTo(point.X, point.Y);
}
break;
case PathPointType.Cubic:
if (point.ControlPoints != null && point.ControlPoints.Length >= 4)
{
path.CubicTo(point.ControlPoints[0], point.ControlPoints[1],
point.ControlPoints[2], point.ControlPoints[3],
point.X, point.Y);
}
else
{
path.LineTo(point.X, point.Y);
}
break;
case PathPointType.Arc:
var rect = new SKRect(point.X - 50, point.Y - 50, point.X + 50, point.Y + 50);
path.ArcTo(rect, 0, 90, false);
break;
default:
path.LineTo(point.X, point.Y);
break;
}
}
return path;
}
public SKRect GetBounds()
{
if (Points.Count == 0) return SKRect.Empty;
float minX = Points.Min(p => p.X);
float maxX = Points.Max(p => p.X);
float minY = Points.Min(p => p.Y);
float maxY = Points.Max(p => p.Y);
return new SKRect(minX, minY, maxX, maxY);
}
}
public enum PathType
{
Linear, // 直线路径
Curved, // 曲线路径
Complex, // 复杂路径
Machining, // 加工路径
Welding, // 焊接路径
Cutting // 切割路径
}
}
工业格式导出实战
G 代码导出 — CNC 机床标准
public static class IndustrialExporter
{
/// <summary>
/// 导出G代码 - 工业制造标准
/// </summary>
public static bool ExportToGCode(IndustrialPath path, string filePath)
{
try
{
var gcode = new StringBuilder();
// G代码文件头
gcode.AppendLine("; Generated by Industrial Path Drawing System");
gcode.AppendLine($"; Date: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
gcode.AppendLine($"; Tool Diameter: {path.ToolDiameter}mm");
gcode.AppendLine($"; Feed Rate: {path.MaxVelocity}mm/min");
gcode.AppendLine();
// 机床初始化
gcode.AppendLine("G21 ; Set units to millimeters");
gcode.AppendLine("G90 ; Absolute positioning");
gcode.AppendLine("G94 ; Feed rate per minute");
gcode.AppendLine($"F{path.MaxVelocity:F0}");
gcode.AppendLine();
// 移动到起始点
if (path.Points.Count > 0)
{
var start = path.Points[0];
gcode.AppendLine($"G0 X{start.X:F3} Y{start.Y:F3} ; Rapid move to start");
gcode.AppendLine("M3 S1000 ; Start spindle");
float plungeDepth = path.ToolDiameter / 2;
gcode.AppendLine($"G1 Z-{plungeDepth:F3} F300 ; Plunge");
}
// 生成路径G代码
GeneratePathGCode(gcode, path);
// 程序结束
gcode.AppendLine("G0 Z5 ; Retract");
gcode.AppendLine("M5 ; Stop spindle");
gcode.AppendLine("G28 ; Home");
gcode.AppendLine("M30 ; End program");
File.WriteAllText(filePath, gcode.ToString(), Encoding.UTF8);
return true;
}
catch (Exception ex)
{
throw new ExportException($"G代码导出失败: {ex.Message}", ex);
}
}
/// <summary>
/// 路径转G代码核心算法
/// </summary>
private static void GeneratePathGCode(StringBuilder gcode, IndustrialPath path)
{
for (int i = 1; i < path.Points.Count; i++)
{
var point = path.Points[i];
var prevPoint = path.Points[i - 1];
switch (point.Type)
{
case PathPointType.Line:
gcode.AppendLine($"G1 X{point.X:F3} Y{point.Y:F3}");
break;
case PathPointType.Arc:
// 顺时针圆弧
float radius = CalculateArcRadius(prevPoint, point);
gcode.AppendLine($"G2 X{point.X:F3} Y{point.Y:F3} R{radius:F3}");
break;
case PathPointType.Curve:
// 曲线转换为多段直线
var segments = InterpolateCurve(prevPoint, point, 10);
foreach (var segment in segments)
{
gcode.AppendLine($"G1 X{segment.X:F3} Y{segment.Y:F3}");
}
break;
}
}
}
}
DXF 格式导出 — CAD 标准
/// <summary>
/// DXF格式导出 - AutoCAD兼容
/// </summary>
public static bool ExportToDXF(IndustrialPath path, string filePath)
{
try
{
using var writer = new StreamWriter(filePath, false, Encoding.ASCII);
// DXF文件头
WriteDXFHeader(writer);
// 图层定义
WriteDXFLayers(writer);
// 实体部分
writer.WriteLine("0");
writer.WriteLine("SECTION");
writer.WriteLine("2");
writer.WriteLine("ENTITIES");
// 绘制路径实体
WritePathEntities(writer, path);
// 文件结尾
writer.WriteLine("0");
writer.WriteLine("ENDSEC");
writer.WriteLine("0");
writer.WriteLine("EOF");
return true;
}
catch (Exception ex)
{
throw new ExportException($"DXF导出失败: {ex.Message}", ex);
}
}
应用场景
工业应用实例
这套系统已成功应用于:
-
CNC 数控加工:路径规划和 G 代码生成
-
激光切割:复杂图形的路径优化
-
3D 打印:打印路径可视化和编辑
-
机器人导航:路径规划和轨迹优化
扩展功能
// 路径优化算法
public static class PathOptimizer
{
/// <summary>
/// 道格拉斯-普克算法简化路径
/// </summary>
public static List<PathPoint> SimplifyPath(List<PathPoint> points, float tolerance)
{
if (points.Count < 3) return points;
// 实现道格拉斯-普克算法
return DouglasPeucker(points, 0, points.Count - 1, tolerance);
}
}
// 碰撞检测
public static class CollisionDetector
{
/// <summary>
/// 检查路径是否与障碍物碰撞
/// </summary>
public static bool CheckPathCollision(IndustrialPath path, List<SKRect> obstacles)
{
var pathBounds = path.CreateSKPath().Bounds;
return obstacles.Any(obstacle =>
SKRect.Intersect(pathBounds, obstacle) != SKRect.Empty);
}
}
常见提醒
SkiaSharp 使用注意事项
1、内存泄漏:务必正确释放 SKPath、SKPaint 等资源
2、坐标系统:注意 SkiaSharp 的坐标原点在左上角
3、线程安全:SkiaSharp 对象不是线程安全的
4、性能监控:大量路径点时要注意渲染性能
解决方案
// 正确的资源管理
using (var paint = new SKPaint())
using (var path = new SKPath())
{
// 绘制操作
canvas.DrawPath(path, paint);
} // 自动释放资源
// 线程安全的画布更新
private void SafeUpdateCanvas()
{
if (InvokeRequired)
{
Invoke(new Action(() => skCanvas.Invalidate()));
}
else
{
skCanvas.Invalidate();
}
}
总结
通过本项目,我们构建了一个具备工业级精度、高性能渲染能力和多格式导出支持的路径绘制系统。其核心优势在于:
1、技术选型合理:SkiaSharp + WinForms 组合兼顾性能与开发效率
2、架构清晰解耦:路径数据、渲染逻辑、导出模块彼此独立,易于维护和扩展
3、贴近工业场景:支持 G 代码、DXF 等真实生产环境所需的格式输出
4、可扩展性强:预留了路径优化、碰撞检测等高级功能接口
可进一步集成实时通信协议(如 Modbus)、三维路径支持(STEP/IGES)或 AI 辅助路径生成,打造更完整的工业软件生态。
关键词
C#、SkiaSharp、工业路径绘制、G代码导出、DXF格式、高性能图形渲染、路径算法、数控加工、激光切割、机器人导航、WinForms、跨平台绘图、道格拉斯-普克算法、碰撞检测、工业物联网
mp.weixin.qq.com/s/16zTfkqsr4WcUUOW-E19lw
最后
如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。
也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!
优秀是一种习惯,欢迎大家留言学习!