====== 二次开发实战 ======
本章通过具体示例,演示如何使用 Python 编写 DXF 文件的读写程序。示例采用纯 Python 实现,不依赖第三方 CAD 库,便于理解 DXF 格式的核心原理。
===== 读取 DXF 文件的基本算法 =====
无论使用何种编程语言,读取 DXF 文件的基本算法如下:
1. 打开 DXF 文件,逐行读取
2. 每两行为一个"组"(组码 + 值)
3. 根据组码 0 的值判断当前所在的段或图元类型
4. 在适当的段内处理对应的组码
5. 遇到 0/ENDSEC 表示当前段结束
6. 遇到 0/EOF 表示文件结束
===== Python 读取 DXF 文件 =====
以下是一个简单的 Python DXF 读取器示例,展示了如何解析 DXF 文件中的图元:
def read_dxf(filepath):
"""读取 DXF 文件,返回所有图元的列表"""
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
lines = f.readlines()
# 将 DXF 解析为组(group)列表
groups = []
i = 0
while i < len(lines) - 1:
code = lines[i].strip()
value = lines[i + 1].strip()
if code:
groups.append((int(code) if code.lstrip('-').isdigit() else code, value))
i += 2
# 按图元分组
entities = []
current_entity = None
section = None
for code, value in groups:
if code == 0:
if value == 'SECTION':
if current_entity:
entities.append(current_entity)
current_entity = {'type': 'SECTION'}
elif value == 'ENDSEC':
if current_entity:
entities.append(current_entity)
current_entity = None
elif value == 'EOF':
break
else:
# 新的图元开始,保存上一个图元
if current_entity and current_entity.get('type') not in ('SECTION',):
entities.append(current_entity)
current_entity = {'type': value}
else:
if current_entity is not None:
if code in (10, 11, 12, 13, 210):
# 点的 X 坐标,等待 Y 和 Z
current_entity.setdefault(code, {})['x'] = float(value)
elif code in (20, 21, 22, 23, 220):
pt_code = code - 10 # 对应的 X 组码
current_entity.setdefault(pt_code, {})['y'] = float(value)
elif code in (30, 31, 32, 33, 230):
pt_code = code - 20 # 对应的 X 组码
current_entity.setdefault(pt_code, {})['z'] = float(value)
else:
current_entity[code] = value
return entities
def print_entities(entities, entity_type=None):
"""打印图元信息"""
for ent in entities:
if entity_type and ent.get('type') != entity_type:
continue
print(f"\n--- {ent.get('type', 'UNKNOWN')} ---")
if ent.get('type') == 'LINE':
print(f" From: ({ent.get(10,{}).get('x',0)}, {ent.get(10,{}).get('y',0)})")
print(f" To: ({ent.get(11,{}).get('x',0)}, {ent.get(11,{}).get('y',0)})")
print(f" Layer: {ent.get(8, '0')}")
elif ent.get('type') == 'CIRCLE':
print(f" Center: ({ent.get(10,{}).get('x',0)}, {ent.get(10,{}).get('y',0)})")
print(f" Radius: {ent.get(40, 'N/A')}")
elif ent.get('type') == 'TEXT':
print(f" Text: {ent.get(1, '')}")
print(f" Position: ({ent.get(10,{}).get('x',0)}, {ent.get(10,{}).get('y',0)})")
print(f" Height: {ent.get(40, 'N/A')}")
elif ent.get('type') == 'LWPOLYLINE':
print(f" Vertices count: {ent.get(90, 0)}")
else:
for k, v in ent.items():
if k != 'type':
print(f" {k}: {v}")
# 使用示例
if __name__ == '__main__':
ents = read_dxf('sample.dxf')
print_entities(ents, 'LINE')
print_entities(ents, 'CIRCLE')
===== Python 写入 DXF 文件 =====
以下示例演示如何创建一个包含直线、圆和文字的 DXF 文件:
def write_dxf(filepath, entities):
"""将图元写入 DXF 文件"""
with open(filepath, 'w', encoding='utf-8') as f:
def w(code, value):
f.write(f"{code}\n{value}\n")
# HEADER 段
w(0, 'SECTION')
w(2, 'HEADER')
w(9, '$ACADVER')
w(1, 'AC1027')
w(0, 'ENDSEC')
# CLASSES 段(空)
w(0, 'SECTION')
w(2, 'CLASSES')
w(0, 'ENDSEC')
# TABLES 段
w(0, 'SECTION')
w(2, 'TABLES')
# LAYER 表
w(0, 'TABLE')
w(2, 'LAYER')
w(5, 'F1')
w(100, 'AcDbSymbolTable')
w(70, '1') # 只包含 0 图层
w(0, 'LAYER')
w(5, 'F2')
w(100, 'AcDbLayerTableRecord')
w(2, '0')
w(70, '0')
w(62, '7') # 白色
w(6, 'CONTINUOUS')
w(370, '-3') # 默认线宽
w(0, 'ENDTAB')
# LTYPE 表
w(0, 'TABLE')
w(2, 'LTYPE')
w(5, 'F3')
w(100, 'AcDbSymbolTable')
w(70, '1')
w(0, 'LTYPE')
w(5, 'F4')
w(100, 'AcDbLinetypeTableRecord')
w(2, 'CONTINUOUS')
w(70, '0')
w(3, 'Solid line')
w(72, '65')
w(73, '0')
w(40, '0.0')
w(0, 'ENDTAB')
# BLOCK_RECORD 表
w(0, 'TABLE')
w(2, 'BLOCK_RECORD')
w(5, 'F5')
w(100, 'AcDbSymbolTable')
w(70, '1')
w(0, 'BLOCK_RECORD')
w(5, 'F6')
w(100, 'AcDbBlockTableRecord')
w(2, '*MODEL_SPACE')
w(0, 'ENDTAB')
w(0, 'ENDSEC')
# BLOCKS 段
w(0, 'SECTION')
w(2, 'BLOCKS')
w(0, 'BLOCK')
w(5, 'F7')
w(100, 'AcDbBlockBegin')
w(2, '*MODEL_SPACE')
w(70, '0')
w(10, '0.0')
w(20, '0.0')
w(30, '0.0')
w(0, 'ENDBLK')
w(5, 'F8')
w(100, 'AcDbBlockEnd')
w(0, 'ENDSEC')
# ENTITIES 段
w(0, 'SECTION')
w(2, 'ENTITIES')
# 写入用户定义的图元
for ent in entities:
for code, value in ent.items():
w(code, value)
w(0, 'ENDSEC')
w(0, 'EOF')
def create_sample_dxf():
"""创建一个示例 DXF 文件"""
entities = [
# 直线
{'0': 'LINE', '5': 'F9', '100': 'AcDbLine',
'8': '0', '10': '0.0', '20': '0.0', '30': '0.0',
'11': '100.0', '21': '100.0', '31': '0.0'},
# 圆
{'0': 'CIRCLE', '5': 'FA', '100': 'AcDbCircle',
'8': '0', '10': '50.0', '20': '50.0', '30': '0.0',
'40': '25.0'},
# 文字
{'0': 'TEXT', '5': 'FB', '100': 'AcDbText',
'8': '0', '10': '10.0', '20': '10.0', '30': '0.0',
'1': 'Hello DXF', '40': '5.0', '7': 'STANDARD', '50': '0.0'},
]
write_dxf('output.dxf', entities)
print("DXF 文件已创建: output.dxf")
if __name__ == '__main__':
create_sample_dxf()
===== 使用 Python 解析 DXF 图元坐标 =====
以下是一个更实用的函数,用于提取常见图元的坐标信息:
def get_entity_coords(entity):
"""提取图元的主要坐标信息"""
etype = entity.get('type', '')
result = {'type': etype}
if etype == 'LINE':
result['start'] = (entity.get(10,{}).get('x',0),
entity.get(10,{}).get('y',0))
result['end'] = (entity.get(11,{}).get('x',0),
entity.get(11,{}).get('y',0))
elif etype == 'CIRCLE':
result['center'] = (entity.get(10,{}).get('x',0),
entity.get(10,{}).get('y',0))
result['radius'] = float(entity.get(40, 0))
elif etype == 'ARC':
result['center'] = (entity.get(10,{}).get('x',0),
entity.get(10,{}).get('y',0))
result['radius'] = float(entity.get(40, 0))
result['start_angle'] = float(entity.get(50, 0))
result['end_angle'] = float(entity.get(51, 0))
elif etype == 'TEXT':
result['position'] = (entity.get(10,{}).get('x',0),
entity.get(10,{}).get('y',0))
result['text'] = entity.get(1, '')
result['height'] = float(entity.get(40, 0))
elif etype == 'LWPOLYLINE':
vertices = []
for k, v in entity.items():
if isinstance(k, int) and k == 10:
vertices.append(v)
result['vertices'] = vertices
result['closed'] = int(entity.get(70, 0)) & 1
elif etype == 'INSERT':
result['block_name'] = entity.get(2, '')
result['position'] = (entity.get(10,{}).get('x',0),
entity.get(10,{}).get('y',0))
result['scale'] = (float(entity.get(41, 1)),
float(entity.get(42, 1)))
return result
===== 高级解析技巧 =====
==== 使用第三方库 ====
对于 Python 开发,以下第三方库可简化 DXF 处理:
| 库名 | 说明 | 安装方式 |
| ezdxf | 功能最全面的 Python DXF 库 | pip install ezdxf |
| dxfwrite | 轻量级 DXF 写入库 | pip install dxfwrite |
使用 ezdxf 的示例:
import ezdxf
# 读取 DXF
doc = ezdxf.readfile('drawing.dxf')
msp = doc.modelspace()
# 遍历所有直线
for line in msp.query('LINE'):
print(f"LINE: {line.dxf.start} -> {line.dxf.end}")
# 创建 DXF
doc = ezdxf.new('R2010')
msp = doc.modelspace()
msp.add_line((0, 0), (100, 100))
msp.add_circle((50, 50), 25)
msp.add_text('Hello', height=5).set_pos((10, 10))
doc.saveas('output.dxf')
==== 句柄管理 ====
在手动创建 DXF 文件时,句柄必须唯一且格式正确:
* 句柄是十六进制字符串,如 "F1"、"2A"、"FF"、"100" 等
* 每个对象和图元必须有唯一的句柄
* 不同段之间的句柄不能重复
===== 常见 DXF 读写问题 =====
==== 字符编码问题 ====
* DXF 文件可能使用不同的编码(ANSI、UTF-8、GB2312 等)
* 建议在读取时尝试 UTF-8,若失败则回退到系统默认编码
* 写入时推荐使用 UTF-8 编码以确保兼容性
==== 坐标精度问题 ====
* DXF 使用 64 位双精度浮点数存储坐标
* 写入时推荐保留足够的小数位数(至少 6 位)
* 对于整数坐标,也应写入 ".0" 格式(如 "100.0")以保持一致性
==== 可选组码 ====
* 许多组码在值等于默认值时可以省略
* 例如:颜色号为 256(BYLAYER)时可省略组码 62
* 线型名为 "BYLAYER" 时可省略组码 6
===== 本章小结 =====
本章通过 Python 代码示例,演示了 DXF 文件的基本读写方法和常见处理技巧。实际项目开发中,建议优先使用成熟的第三方库(如 ezdxf)以提高开发效率,但理解底层 DXF 格式对于调试和优化仍然十分重要。