第三章 图形学基础:坐标系与坐标变换、基本图形元素、图形数据结构、图形导入与解析
各位工程师朋友,大家好。我是你们的老朋友,一个在激光切割机边上摸爬滚打了十几年的老工程师。今天咱们聊点硬核的——图形学基础。
你可能会问:“我一个搞激光切割的,学图形学干嘛?” 说白了,激光切割就是让一束光沿着你画好的路径跑。路径怎么来?从图形来。图形怎么存?怎么算?怎么转成机器能懂的语言?这就是图形学要解决的问题。我刚开始带项目那会儿,就因为没搞懂坐标系变换,切出来的零件歪了整整一排,被老板骂得狗血淋头。从那以后,我老老实实把这块补上了。
核心观点:图形学是激光切割路径规划的“数学地基”。地基不稳,上层算法再花哨也是空中楼阁。
3.1 坐标系与坐标变换
先说说坐标系。激光切割里最常见的就两种:世界坐标系和工件坐标系。
- 世界坐标系:固定在机床上的绝对坐标系。你开机后,机器回零,那个零点就是世界坐标系的原点。
- 工件坐标系:固定在工件上的相对坐标系。你把板材往床面上一放,板材的某个角就是工件坐标系的原点。
为什么要区分?因为板材放的位置不可能每次都一模一样。你想想看,如果每次换料都要重新编程,那效率得多低?所以我们用工件坐标系来编程,切割时再通过坐标变换映射到世界坐标系上。
坐标变换说白了就是三个动作:平移、旋转、缩放。数学上用一个4x4的齐次变换矩阵搞定。嗯,这里我直接给代码,你们拿去就能用。
// 二维齐次变换矩阵示例(C语言风格)
typedef struct {
double a, b, c; // 旋转+缩放
double d, e, f; // 旋转+缩放
double g, h, i; // 平移+透视(透视一般设为0,0,1)
} Matrix3x3;
// 平移矩阵
Matrix3x3 translation(double tx, double ty) {
Matrix3x3 m = {1, 0, tx, 0, 1, ty, 0, 0, 1};
return m;
}
// 旋转矩阵(角度制)
Matrix3x3 rotation(double angle) {
double rad = angle * M_PI / 180.0;
Matrix3x3 m = {cos(rad), -sin(rad), 0, sin(rad), cos(rad), 0, 0, 0, 1};
return m;
}
// 应用变换:点p经过矩阵m变换后得到新点
Point transform(Point p, Matrix3x3 m) {
Point result;
result.x = m.a * p.x + m.b * p.y + m.c;
result.y = m.d * p.x + m.e * p.y + m.f;
return result;
}
个人经验:我在调试一台大幅面切割机时,发现切出来的圆总是椭圆。查了半天,原来是X轴和Y轴的脉冲当量不一致。用缩放矩阵修正后,问题立刻解决。所以,坐标变换不只是理论,它是实打实的调试工具。
3.2 基本图形元素:点、线、弧
激光切割的路径,归根结底就是三种基本元素:点、直线、圆弧。别小看这三样,所有复杂的图形都是它们拼出来的。
- 点:就是坐标(x, y)。在切割中,点代表定位孔、打标点、或者路径的起点终点。
- 直线:由起点和终点定义。切割中最常见的路径,简单高效。
- 圆弧:由起点、终点、圆心(或半径)定义。注意,激光切割的圆弧必须用顺圆或逆圆来区分方向,否则切出来是反的。
我见过不少新手,画图时用样条曲线代替圆弧。结果导入切割软件后,样条曲线被离散成无数小线段,切割速度上不去,表面还坑坑洼洼。所以,能用圆弧的地方,千万别偷懒用样条。
避坑指南:我曾经接手过一个项目,客户给的图纸里全是微小的直线段,看起来像圆弧,实际上每段只有0.01mm长。切割时控制器处理不过来,直接卡死。后来我写了个算法,把共线的短线段合并,把近似圆弧的线段拟合成真圆弧,效率提升了5倍。
3.3 图形数据结构:邻接表与邻接矩阵
图形在计算机里怎么存?这就涉及到数据结构了。激光切割的路径本质上是一个图——顶点是路径点,边是路径段。
常用的两种存储方式:
| 数据结构 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 邻接矩阵 | 查询两点是否相连很快(O(1)) | 占用空间大(O(n²)) | 图形顶点少(<100个) |
| 邻接表 | 节省空间(O(n+e)) | 查询稍慢 | 图形顶点多(>100个) |
我个人习惯用邻接表。为什么?因为激光切割的图形动辄几千上万个顶点,用邻接矩阵内存直接爆炸。邻接表虽然查询慢一点,但配合哈希表优化后,实际性能完全够用。
// 邻接表实现(Python风格伪代码)
class Graph:
def __init__(self):
self.adj = {} # 字典:顶点 -> 邻接顶点列表
def add_edge(self, u, v):
if u not in self.adj:
self.adj[u] = []
self.adj[u].append(v)
# 无向图还需添加反向边
if v not in self.adj:
self.adj[v] = []
self.adj[v].append(u)
def get_neighbors(self, u):
return self.adj.get(u, [])
小技巧:在路径规划中,我经常用邻接表配合深度优先搜索(DFS)来检测图形是否连通。如果图形不连通,说明有孤立路径,切割头需要空跑过去,这会浪费大量时间。提前检测并优化路径顺序,能省下不少工时。
3.4 图形导入与解析:DXF格式
DXF是AutoCAD的交换格式,也是激光切割行业的事实标准。几乎所有绘图软件都能导出DXF,几乎所有切割软件都能导入DXF。但中间的水很深,我踩过的坑比你们吃过的盐还多。
DXF文件的结构其实不复杂,就是一堆组码和值的配对。比如:
0
SECTION
2
ENTITIES
0
LINE
8
0 // 图层名
10
0.0 // 起点X
20
0.0 // 起点Y
11
100.0 // 终点X
21
50.0 // 终点Y
0
ENDSEC
解析DXF的核心就是逐行读取组码,根据组码判断数据类型,然后提取数值。我写过一个轻量级的DXF解析器,只提取我们需要的实体(LINE、ARC、CIRCLE、LWPOLYLINE),其他一概忽略,速度飞快。
避坑指南:我曾经遇到一个DXF文件,里面的圆弧用SPLINE表示,而不是ARC。我的解析器没处理SPLINE,结果切出来的零件全是锯齿。后来我加了个拟合算法,把SPLINE转成多段小圆弧,才算搞定。所以,解析DXF时一定要考虑各种“非标准”写法。
知识体系总览
下面这张图是我自己画的,把本章的核心逻辑串起来了。你一看就明白。
从DXF文件到G代码,中间经历了解析→存储→变换三个核心步骤。每一步都有坑,每一步也都有优化空间。掌握了这些,你就能从“只会按按钮的操作工”升级成“能自己写路径优化算法的工程师”。
最后说一句:图形学基础不是一天能啃完的。我建议你先从解析一个简单的DXF文件开始,打印出里面的点和线,看看是不是和你画的一致。亲手做一遍,比看十遍书都管用。
公众号:蓝海资料掘金营,微信deep3321