"""DXF 解析测试程序 —— 使用 ezdxf 读取并输出 DXF 文件内容。""" import io import json import sys from datetime import datetime from pathlib import Path import ezdxf # 目录 BASE_DIR = Path(__file__).parent.parent.parent SAMPLE_DIR = BASE_DIR / "samples" OUTPUT_DIR = Path(__file__).parent / "output" #DXF_FILE = SAMPLE_DIR / "decimal-inch-drawing-sheet-size-ASME-Y14.1-cad-block-dwg.dxf" DXF_FILE = SAMPLE_DIR / "洗浴中心C-48.dxf" class OutputRecorder: """同时输出到 stdout 和内存缓冲,最终写入文件。""" def __init__(self): self._buffer = io.StringIO() self._terminal = sys.stdout def write(self, message): self._terminal.write(message) self._buffer.write(message) def flush(self): self._terminal.flush() self._buffer.flush() def getvalue(self): return self._buffer.getvalue() def extract_entity_props(entity): """提取单个实体的关键属性为 JSON 友好格式。""" dxftype = entity.dxftype() props = {} if dxftype == "LINE": props["start"] = list(entity.dxf.start) props["end"] = list(entity.dxf.end) elif dxftype == "CIRCLE": props["center"] = list(entity.dxf.center) props["radius"] = round(entity.dxf.radius, 4) elif dxftype == "ARC": props["center"] = list(entity.dxf.center) props["radius"] = round(entity.dxf.radius, 4) props["start_angle"] = round(entity.dxf.start_angle, 1) props["end_angle"] = round(entity.dxf.end_angle, 1) elif dxftype == "LWPOLYLINE": pts = entity.get_points() props["vertex_count"] = len(entity) props["closed"] = entity.closed props["points"] = [[round(p[0], 4), round(p[1], 4)] for p in pts] elif dxftype == "POLYLINE": verts = list(entity.vertices()) props["vertex_count"] = len(verts) props["points"] = [[round(v.dxf.location[0], 4), round(v.dxf.location[1], 4)] for v in verts] elif dxftype == "INSERT": props["block_name"] = entity.dxf.name props["insert"] = list(entity.dxf.insert) props["scale"] = [round(entity.dxf.xscale, 2), round(entity.dxf.yscale, 2), round(entity.dxf.zscale, 2)] props["rotation"] = round(entity.dxf.rotation, 2) elif dxftype in ("MTEXT", "TEXT"): text = entity.plain_text() if dxftype == "MTEXT" else entity.dxf.text props["text"] = text if hasattr(entity.dxf, "insert"): props["insert"] = list(entity.dxf.insert) if hasattr(entity.dxf, "height"): props["height"] = round(entity.dxf.height, 2) elif dxftype == "DIMENSION": props["text"] = entity.dxf.text if hasattr(entity.dxf, "dimtype"): props["dimtype"] = entity.dxf.dimtype elif dxftype == "HATCH": props["pattern"] = entity.dxf.pattern_name elif dxftype == "SOLID" or dxftype == "TRACE": for vi in range(4): try: v = getattr(entity.dxf, f"vtx{vi}") props[f"vtx{vi}"] = list(v) if v else None except Exception: break elif dxftype == "POINT": props["location"] = list(entity.dxf.location) elif dxftype == "ELLIPSE": props["center"] = list(entity.dxf.center) props["major_axis"] = list(entity.dxf.major_axis) props["ratio"] = round(entity.dxf.ratio, 4) elif dxftype == "SPLINE": props["fit_points"] = [list(p) for p in entity.fit_points] elif dxftype == "3DFACE": for vi in range(4): try: v = getattr(entity.dxf, f"vtx{vi}") props[f"vtx{vi}"] = list(v) if v else None except Exception: break else: for attr in entity.dxf.all_existing_dxf_attribs(): if attr in ("handle", "owner", "layer", "linetype", "color", "lineweight"): continue val = getattr(entity.dxf, attr) if val is not None: props[attr] = list(val) if hasattr(val, "__iter__") and not isinstance(val, str) else val return props def extract_to_dict(doc: ezdxf.document.Drawing, filename: str, parse_time: str): """提取 DXF 文档全部内容为 JSON 可序列化的 dict。""" result = { "file": filename, "parse_time": parse_time, "header": { "dxf_version": doc.dxfversion, "encoding": doc.encoding, "acad_ver": doc.header.get("$ACADVER", None), }, } # 单位 units = doc.header.get("$INSUNITS", None) unit_names = {1: "inches", 2: "feet", 4: "millimeters", 5: "centimeters", 6: "meters"} result["header"]["units"] = unit_names.get(units, str(units)) if units else None # 图形范围 extmin = doc.header.get("$EXTMIN", None) extmax = doc.header.get("$EXTMAX", None) if extmin and extmax: result["header"]["extents"] = {"min": list(extmin), "max": list(extmax)} # 图层 layers = [] for layer in doc.layers: layers.append({ "name": layer.dxf.name, "color": layer.dxf.color, "linetype": layer.dxf.linetype, "lineweight": layer.dxf.lineweight, "on": layer.is_on(), "frozen": layer.is_frozen(), "locked": layer.is_locked(), }) result["layers"] = layers # 实体 entities = [] entity_type_counts = {} for entity in doc.modelspace(): dxftype = entity.dxftype() entity_type_counts[dxftype] = entity_type_counts.get(dxftype, 0) + 1 entities.append({ "index": len(entities) + 1, "type": dxftype, "handle": entity.dxf.handle, "layer": entity.dxf.layer, "properties": extract_entity_props(entity), }) result["entity_summary"] = entity_type_counts result["entities"] = entities # 块定义 blocks = [] for block in doc.blocks: blocks.append({ "name": block.name, "entity_count": len(list(block)), }) result["blocks"] = blocks # 布局 layouts = [] for layout in doc.layouts: try: pw = layout.dxf.paper_width if hasattr(layout.dxf, "paper_width") else None ph = layout.dxf.paper_height if hasattr(layout.dxf, "paper_height") else None layouts.append({"name": layout.name, "paper_width": pw, "paper_height": ph}) except Exception: layouts.append({"name": layout.name, "paper_width": None, "paper_height": None}) result["layouts"] = layouts return result def print_header(title: str): print(f"\n{'=' * 70}") print(f" {title}") print(f"{'=' * 70}") def print_dxf_info(doc: ezdxf.document.Drawing): """打印 DXF 文档基本信息。""" print(f" DXF 版本 : {doc.dxfversion}") print(f" 编码 : {doc.encoding}") print(f" 创建者 : {doc.header.get('$ACADVER', 'N/A')}") # 绘图单位 units = doc.header.get("$INSUNITS", None) unit_map = { 1: "英寸 (Inches)", 2: "英尺 (Feet)", 4: "毫米 (Millimeters)", 5: "厘米 (Centimeters)", 6: "米 (Meters)", } unit_name = unit_map.get(units, f"未知 ({units})") if units else "未指定" print(f" 绘图单位 : {unit_name}") # 图形范围 extmin = doc.header.get("$EXTMIN", None) extmax = doc.header.get("$EXTMAX", None) if extmin and extmax: print(f" 图形范围 : {extmin} -> {extmax}") def print_layers(doc: ezdxf.document.Drawing): """打印所有图层信息。""" layers = list(doc.layers) if not layers: print(" (无图层)") return print(f" 共 {len(layers)} 个图层:") print(f" {'名称':<30} {'颜色':<8} {'线型':<20} {'线宽':<8} {'状态'}") print(f" {'-' * 80}") for layer in layers: name = layer.dxf.name color = layer.dxf.color linetype = layer.dxf.linetype # 线宽:-1 表示 DEFAULT lineweight = layer.dxf.lineweight lw_display = "默认" if lineweight == -1 else str(lineweight) flags = [] if layer.is_on(): flags.append("ON") else: flags.append("OFF") if layer.is_frozen(): flags.append("FROZEN") if layer.is_locked(): flags.append("LOCKED") print(f" {name:<30} {str(color):<8} {linetype:<20} {lw_display:<8} {', '.join(flags)}") def print_entity_summary(doc: ezdxf.document.Drawing): """统计模型空间中各类型实体数量。""" msp = doc.modelspace() entity_types = {} for entity in msp: dtype = entity.dxftype() entity_types[dtype] = entity_types.get(dtype, 0) + 1 if not entity_types: print(" (模型空间无实体)") return print(f" 共 {sum(entity_types.values())} 个实体:") print(f" {'类型':<25} {'数量':<8}") print(f" {'-' * 35}") for dtype, count in sorted(entity_types.items(), key=lambda x: -x[1]): print(f" {dtype:<25} {count:<8}") def print_entity_details(doc: ezdxf.document.Drawing): """打印模型空间中每个实体的详细信息。""" msp = doc.modelspace() entities = list(msp) if not entities: print(" (模型空间无实体)") return print(f" 共 {len(entities)} 个实体:") print(f" {'序号':<6} {'类型':<25} {'句柄':<12} {'图层':<20} {'关键属性'}") print(f" {'-' * 100}") for i, entity in enumerate(entities): dxftype = entity.dxftype() handle = entity.dxf.handle layer = entity.dxf.layer # 提取各类型的关键属性 key_attrs = [] if dxftype == "LINE": key_attrs.append(f"起点={entity.dxf.start}, 终点={entity.dxf.end}") elif dxftype == "CIRCLE": key_attrs.append(f"圆心={entity.dxf.center}, 半径={entity.dxf.radius:.4f}") elif dxftype == "ARC": key_attrs.append(f"圆心={entity.dxf.center}, 半径={entity.dxf.radius:.4f}, 角度={entity.dxf.start_angle:.1f}~{entity.dxf.end_angle:.1f}") elif dxftype == "LWPOLYLINE": count = len(entity) closed = "闭合" if entity.closed else "开放" pts = entity.get_points() first_pt = pts[0] if pts else "N/A" key_attrs.append(f"顶点数={count}, {closed}, 起点={first_pt[:2]}") elif dxftype == "POLYLINE": count = len(list(entity.vertices())) key_attrs.append(f"顶点数={count}") elif dxftype == "INSERT": key_attrs.append(f"块名={entity.dxf.name}, 插入点={entity.dxf.insert}, 缩放={entity.dxf.xscale:.2f},{entity.dxf.yscale:.2f}") elif dxftype == "MTEXT": text = entity.plain_text()[:50] key_attrs.append(f'内容="{text}..."' if len(entity.plain_text()) > 50 else f'内容="{text}"') elif dxftype == "TEXT": text = entity.dxf.text[:50] key_attrs.append(f'内容="{text}..."' if len(entity.dxf.text) > 50 else f'内容="{entity.dxf.text}"') elif dxftype == "DIMENSION": key_attrs.append(f"测量值={entity.dxf.text}") elif dxftype == "HATCH": key_attrs.append(f"图案={entity.dxf.pattern_name}") elif dxftype == "SOLID" or dxftype == "TRACE": key_attrs.append(f"顶点={entity.dxf.vtx0} {entity.dxf.vtx1} {entity.dxf.vtx2} {entity.dxf.vtx3}") elif dxftype == "POINT": key_attrs.append(f"位置={entity.dxf.location}") elif dxftype == "ELLIPSE": key_attrs.append(f"圆心={entity.dxf.center}, 长轴={entity.dxf.major_axis}") elif dxftype == "SPLINE": key_attrs.append(f"拟合点数={len(entity.fit_points)}") elif dxftype == "3DFACE": key_attrs.append(f"顶点={entity.dxf.vtx0} {entity.dxf.vtx1} {entity.dxf.vtx2} {entity.dxf.vtx3}") else: # 通用:列出非默认的 dxf 属性 for attr in entity.dxf.all_existing_dxf_attribs(): if attr in ("handle", "owner", "layer", "linetype", "color", "lineweight"): continue val = getattr(entity.dxf, attr) if val is not None: key_attrs.append(f"{attr}={val}") attrs_str = "; ".join(key_attrs[:3]) if key_attrs else "(无额外属性)" print(f" {i + 1:<6} {dxftype:<25} {handle:<12} {layer:<20} {attrs_str}") def print_blocks(doc: ezdxf.document.Drawing): """打印所有块定义。""" blocks = list(doc.blocks) if not blocks: print(" (无自定义块)") return print(f" 共 {len(blocks)} 个块定义:") print(f" {'名称':<35} {'实体数':<10} {'说明'}") print(f" {'-' * 70}") for block in blocks: name = block.name count = len(list(block)) desc = "" if name.startswith("*"): if name.startswith("*Model_Space"): desc = "(模型空间)" elif name.startswith("*Paper_Space"): desc = "(图纸空间)" elif name.startswith("*D"): desc = "(标注块)" elif name.startswith("*X"): desc = "(外部参照)" elif name.startswith("*U"): desc = "(匿名块)" else: desc = "(特殊块)" print(f" {name:<35} {count:<10} {desc}") def print_layouts(doc: ezdxf.document.Drawing): """打印所有布局(图纸空间)。""" layouts = list(doc.layouts) if not layouts: print(" (无布局)") return print(f" 共 {len(layouts)} 个布局:") print(f" {'名称':<30} {'尺寸 (宽 x 高)'}") print(f" {'-' * 60}") for layout in layouts: name = layout.name try: page_w = layout.dxf.paper_width if hasattr(layout.dxf, 'paper_width') else "?" page_h = layout.dxf.paper_height if hasattr(layout.dxf, 'paper_height') else "?" print(f" {name:<30} {page_w} x {page_h}") except Exception: print(f" {name:<30} N/A") def main(): if not DXF_FILE.exists(): print(f"错误:文件不存在 -> {DXF_FILE}") return OUTPUT_DIR.mkdir(parents=True, exist_ok=True) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") parse_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") output_txt = OUTPUT_DIR / f"{DXF_FILE.stem}_{timestamp}.txt" output_json = OUTPUT_DIR / f"{DXF_FILE.stem}_{timestamp}.json" recorder = OutputRecorder() sys.stdout = recorder try: print(f"解析文件: {DXF_FILE.name}") print(f"路径: {DXF_FILE}") print(f"输出文件: {output_txt.relative_to(BASE_DIR)}") print(f"JSON 文件: {output_json.relative_to(BASE_DIR)}") print(f"解析时间: {parse_time}") doc = ezdxf.readfile(str(DXF_FILE)) # 1. 基本信息 print_header("一、文档基本信息") print_dxf_info(doc) # 2. 图层 print_header("二、图层信息") print_layers(doc) # 3. 实体统计 print_header("三、模型空间实体统计") print_entity_summary(doc) # 4. 实体详情 print_header("四、模型空间实体详情") print_entity_details(doc) # 5. 块定义 print_header("五、块定义") print_blocks(doc) # 6. 布局 print_header("六、布局(图纸空间)") print_layouts(doc) print_header("解析完成") finally: sys.stdout = recorder._terminal # 写入文本文件 output_txt.write_text(recorder.getvalue(), encoding="utf-8") print(f"\n结果已保存到: {output_txt}") # 写入 JSON 文件 json_data = extract_to_dict(doc, DXF_FILE.name, parse_time) output_json.write_text(json.dumps(json_data, ensure_ascii=False, indent=2), encoding="utf-8") print(f"JSON 已保存到: {output_json}") if __name__ == "__main__": main()