目录

二次开发实战

本章通过具体示例,演示如何使用 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 文件时,句柄必须唯一且格式正确:

常见 DXF 读写问题

字符编码问题

坐标精度问题

可选组码

本章小结

本章通过 Python 代码示例,演示了 DXF 文件的基本读写方法和常见处理技巧。实际项目开发中,建议优先使用成熟的第三方库(如 ezdxf)以提高开发效率,但理解底层 DXF 格式对于调试和优化仍然十分重要。