====== 高级 DXF 主题 ======
本章介绍 DXF 二次开发中涉及的进阶主题,包括句柄参照、扩展数据、子类标记、对象坐标系和任意轴算法等。
===== 句柄与参照 =====
==== 句柄(Handle)概念 ====
句柄(组码 **5**)是 DXF 文件中每个对象/图元的唯一标识符,格式为最多 16 个十六进制数字的字符串。句柄在整个 DXF 文件中唯一,由 AutoCAD 自动分配。
句柄的关键特性:
* 每次新建文件或保存文件时重新生成
* 在 INSERT 和 XREF 操作期间**会**进行转换
* 用于建立对象之间的引用关系
==== 句柄参照类型 ====
DXF 使用不同的组码范围来表示不同类型的句柄参照:
^ 组码范围 ^ 参照类型 ^ 说明 ^
| 320-329 | 任意句柄 | "按原样"获取,在 INSERT/XREF 操作期间不进行转换 |
| 330-339 | 软指针 | 指向其他对象,不表示所有权关系 |
| 340-349 | 硬指针 | 指向其他对象的强引用 |
| 350-359 | 软所有者 | 所有者可以删除被所有者,但不强制执行 |
| 360-369 | 硬所有者 | 所有者可以删除被所有者,强制执行 |
| 390-399 | PlotStyle 句柄 | 用于打印样式对象 |
==== 指针与所有权的区别 ====
* **指针参照**(Pointer):对象 A 引用对象 B,但 A 并不拥有 B。例如,图元引用一个 LAYER,但图元不拥有该图层。
* **所有者参照**(Owner):对象 A 拥有对象 B,当 A 被删除时,B 也会被删除。例如,LAYOUT 词典拥有多个 LAYOUT 对象。
==== 组码 1005 的特殊用途 ====
组码 1005 在扩展数据中用于存储句柄值,其特殊之处在于不进行 INSERT/XREF 转换。在扩展数据中使用句柄时,需使用 1005 组码而非 330-369 组码。
===== 子类标记 =====
==== 子类标记的概念 ====
组码 **100** 用于存储**子类数据标记**(Subclass Marker),该标记指示从哪个 ObjectARX 派生类定义了后续的数据。子类标记是理解 DXF 对象继承关系的关键。
子类标记示例:
* **AcDbEntity** — 所有图元的基本类
* **AcDbCircle** — 圆的具体类
* **AcDbLine** — 直线的具体类
* **AcDbPolyline** — 多段线的具体类
* **AcDbBlockReference** — 块参照
==== 继承链与子类标记 ====
一个对象的完整继承链通过多个子类标记体现。以直线为例:
0 ; 图元开始
LINE
5
42
100
AcDbEntity ; 通用图元数据(图层、颜色、线型等)
100
AcDbLine ; 直线特定数据(起点、终点坐标)
10
0.0
20
0.0
11
100.0
21
100.0
从 ObjectARX 派生的每个具体类**必须**拥有子类标记。
===== 扩展数据(XDATA) =====
扩展数据(Extended Data,简称 XDATA)允许应用程序在 DXF 对象或图元中存储自定义数据。所有扩展数据都以组码 **-3** 为标记。
==== XDATA 结构 ====
-3 ; 扩展数据开始标记(固定)
1001
APP_NAME ; 注册的应用程序名
1000
... ; 扩展数据内容
==== XDATA 支持的组码 ====
^ 组码 ^ 说明 ^
| 1000 | ASCII 字符串(最多 255 字节) |
| 1001 | 注册的应用程序名(最多 31 字节) |
| 1002 | 控制字符串("{" 开始, "}" 结束) |
| 1003 | 图层名 |
| 1004 | 二进制数据块(最多 127 字节) |
| 1005 | 图元句柄 |
| 1010 | 三维点 |
| 1011 | 三维世界空间位置 |
| 1012 | 三维世界空间位移 |
| 1013 | 三维空间方向 |
| 1040 | 双精度浮点值 |
| 1041 | 距离值 |
| 1042 | 缩放比例 |
| 1070 | 16 位有符号整数 |
| 1071 | 32 位有符号长整数 |
==== XDATA 组织方式 ====
XDATA 使用 **1002** 组码来组织数据层次:
-3
1001
MY_APP ; 应用程序名
1002
{ ; 开始一个列表
1000
Item 1
1000
Item 2
1002
{ ; 嵌套列表
1040
1.5
1040
2.5
1002
} ; 结束嵌套
1002
} ; 结束列表
==== 注册应用程序名 ====
使用自定义 XDATA 前,应用程序名必须在 **APPID** 符号表中注册。例如:
0
APPID
2
MY_APP
70
0
===== 对象坐标系(OCS) =====
==== OCS 简介 ====
对象坐标系(Object Coordinate System,OCS)是通过**拉伸方向**(组码 210/220/230)和**任意轴算法**建立的坐标系。在 OCS 中定义点的图元,其点坐标使用该图元特有的坐标系。
使用 OCS 的图元类型:LINE、CIRCLE、ARC、TEXT 等。
不使用 OCS 的图元类型:LWPOLYLINE(使用标高+OCS)、3DFACE(使用世界坐标系)、POLYLINE(使用世界坐标系)。
==== OCS 与 WCS 的转换 ====
拉伸方向(组码 210/220/230)默认为 **(0, 0, 1)**,表示图元位于世界坐标系的 XY 平面。当拉伸方向为单位矢量 (0, 0, 1) 时,OCS 与 WCS 一致。
如果拉伸方向不是 (0, 0, 1),则需要使用任意轴算法将 OCS 坐标转换为 WCS 坐标。
===== 任意轴算法 =====
任意轴算法(Arbitrary Axis Algorithm,AAA)用于计算 OCS 的 X 轴和 Y 轴方向。该算法是处理非平面图元的关键。
==== 算法步骤 ====
假设拉伸方向为 **N**(单位矢量):
1. 如果 |N| 非常接近 (0, 0, 1)(容差 1/64):
X_axis = (1, 0, 0)
Y_axis = (0, 1, 0)
结束
2. 如果 |N| 非常接近 (0, 0, -1)(容差 1/64):
X_axis = (-1, 0, 0)
Y_axis = (0, 1, 0)
结束
3. 否则:
W = (0, 0, 1) (世界坐标 Z 轴)
X_axis = W × N (叉积,归一化)
Y_axis = N × X_axis (叉积,归一化)
==== 算法实现(Python) ====
import math
def arbitrary_axis_algorithm(normal):
"""计算任意轴算法,返回 OCS 的 X 轴和 Y 轴方向"""
x, y, z = normal
# 检查是否接近 (0,0,1) 或 (0,0,-1)
if abs(x) < 1/64 and abs(y) < 1/64 and abs(z) > 0:
if z > 0:
return (1, 0, 0), (0, 1, 0)
else:
return (-1, 0, 0), (0, 1, 0)
# 计算 X 轴 = W × N
wx, wy, wz = 0, 0, 1
x_axis = (
wy * z - wz * y, # Y*Z - Z*Y
wz * x - wx * z, # Z*X - X*Z
wx * y - wy * x # X*Y - Y*X
)
# 归一化 X 轴
length = math.sqrt(x_axis[0]**2 + x_axis[1]**2 + x_axis[2]**2)
if length > 0:
x_axis = (x_axis[0]/length, x_axis[1]/length, x_axis[2]/length)
# 计算 Y 轴 = N × X_axis
y_axis = (
y * x_axis[2] - z * x_axis[1],
z * x_axis[0] - x * x_axis[2],
x * x_axis[1] - y * x_axis[0]
)
# 归一化 Y 轴
length = math.sqrt(y_axis[0]**2 + y_axis[1]**2 + y_axis[2]**2)
if length > 0:
y_axis = (y_axis[0]/length, y_axis[1]/length, y_axis[2]/length)
return x_axis, y_axis
def ocs_to_wcs(point_ocs, normal):
"""将 OCS 坐标转换为 WCS 坐标"""
x_axis, y_axis = arbitrary_axis_algorithm(normal)
# OCS point = (u, v, w)
# WCS point = origin + u*X_axis + v*Y_axis + w*normal
u, v, w = point_ocs
wx = u * x_axis[0] + v * y_axis[0] + w * normal[0]
wy = u * x_axis[1] + v * y_axis[1] + w * normal[1]
wz = u * x_axis[2] + v * y_axis[2] + w * normal[2]
return (wx, wy, wz)
===== 扩展词典与永久反应器 =====
==== 扩展词典 ====
扩展词典(Extension Dictionary)允许在对象上附加自定义词典。使用组码 **102** 后跟 **{ACAD_REACTORS** 或 **{ACAD_XDICTIONARY** 来标识。
102
{ACAD_XDICTIONARY ; 扩展词典开始
360
FD ; 硬所有者句柄(指向词典对象)
102
} ; 扩展词典结束
==== 永久反应器 ====
永久反应器(Persistent Reactor)使得对象之间可以建立事件响应关系。当被监控的对象发生变化时,反应器对象会自动收到通知。
102
{ACAD_REACTORS ; 反应器链开始
330
F0 ; 软指针句柄(指向所有者)
102
} ; 反应器链结束
===== 本章小结 =====
本章介绍了 DXF 开发中的高级话题。句柄管理确保了对象间的正确引用关系;子类标记使得 DXF 能够表示复杂的类继承结构;扩展数据为应用程序提供了自定义存储能力;对象坐标系和任意轴算法则是处理三维空间中非平面图元的基础。掌握这些高级概念是开发专业 DXF 应用程序的必要条件。