diff --git a/docs/tasks/task_detail_2026_07_08.md b/docs/tasks/task_detail_2026_07_08.md index 931ccbf..8d83b6f 100644 --- a/docs/tasks/task_detail_2026_07_08.md +++ b/docs/tasks/task_detail_2026_07_08.md @@ -15,3 +15,14 @@ - 布局/图纸空间列表 4. 运行程序验证,成功解析目标 DXF 文件,输出 20 个实体(9 个 DIMENSION、6 个 MTEXT、5 个 LWPOLYLINE)、13 个块定义、3 个布局。 - **执行结果**: 程序正常运行,完整输出了 samples/decimal-inch-drawing-sheet-size-ASME-Y14.1-cad-block-dwg.dxf 的全部结构和内容信息。 + +## 会话 ID: 2 +- [2026-07-08 13:58] +- **执行原因**: 将 main.py 单文件脚本拆分为模块化结构(layer_analysis.py + entity_analysis.py + main.py),面向第二阶段墙体识别、面积计算等功能扩展。 +- **执行过程**: + 1. 创建 layer_analysis.py:遍历图层统计实体数量和类型,基于常见中英文命名规则进行角色推断(墙体/门窗/标注/文字/柱/设备/填充等)。 + 2. 创建 entity_analysis.py:实体类型统计、INSERT 按块名聚合(含插入坐标/旋转/缩放)、TEXT/MTEXT 文本内容提取,保留 extract_entity_props() 逻辑。 + 3. 重构 main.py:读取 DXF 后调度两个分析模块,组装输出 output/cad_analysis.json,保留终端摘要打印。 + 4. 创建 README.md 使用说明文档。 + 5. 运行验证:成功解析洗浴中心C-48.dxf(719个实体、12个图层、11种块、71条文本),JSON 输出格式正确。 +- **执行结果**: 模块化拆分完成,代码结构清晰,JSON 输出符合 Task1 规范,为后续墙体识别等功能扩展奠定基础。 diff --git a/experiments/dxf-parser/README.md b/experiments/dxf-parser/README.md new file mode 100644 index 0000000..d8d50a4 --- /dev/null +++ b/experiments/dxf-parser/README.md @@ -0,0 +1,83 @@ +# DXF 解析工具 + +解析 DXF 格式的 CAD 图纸文件,输出结构化的图层分析、实体统计、块引用和文本信息。 + +## 目录结构 + +``` +experiments/dxf-parser/ +├── main.py # 入口脚本 +├── layer_analysis.py # 图层分析模块 +├── entity_analysis.py # 实体分析模块 +├── requirements.txt # 依赖 +├── output/ # 输出目录 +│ └── {文件名}_cad_analysis.json +└── README.md +``` + +## 安装依赖 + +```bash +cd experiments/dxf-parser +pip install -r requirements.txt +``` + +## 使用方式 + +1. 将要解析的 DXF 文件放入 `samples/` 目录 +2. 修改 `main.py` 中的 `DXF_FILE` 变量指向目标文件 +3. 运行: + +```bash +python main.py +``` + +## 输出说明 + +终端会打印解析摘要(图层、实体类型统计、块引用、文本内容),同时在 `output/{文件名}_cad_analysis.json` 中生成结构化 JSON: + +```json +{ + "file_info": { + "filename": "洗浴中心C-48.dxf", + "dxf_version": "AC1032", + "encoding": "gbk", + "units": null, + "extents": { "min": [...], "max": [...] }, + "total_entities": 719 + }, + "layers": [ + { + "name": "DOOR", + "entity_count": 52, + "entity_types": ["INSERT", "LINE"], + "suggested_role": "门窗" + } + ], + "entities": { + "LINE": 321, + "TEXT": 71 + }, + "blocks": [ + { + "block_name": "A$C6B90F135", + "count": 8, + "inserts": [...] + } + ], + "texts": [ + { + "handle": "2A", + "layer": "TEXT", + "type": "TEXT", + "content": "办公室", + "insert": [150, 300] + } + ] +} +``` + +## 模块说明 + +- **layer_analysis.py**:遍历所有图层,统计每层实体数量和类型,基于常见命名规则推断图层角色(仅作建议) +- **entity_analysis.py**:统计模型空间中各实体类型数量,对 INSERT 实体按块名聚合,提取所有 TEXT/MTEXT 文本内容 diff --git a/experiments/dxf-parser/entity_analysis.py b/experiments/dxf-parser/entity_analysis.py new file mode 100644 index 0000000..f4ad00d --- /dev/null +++ b/experiments/dxf-parser/entity_analysis.py @@ -0,0 +1,148 @@ +"""实体分析模块 —— 统计模型空间中各实体类型的数量、INSERT 块聚合、文本提取。""" + + +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 in ("SOLID", "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 analyze_entities(doc) -> dict: + """分析模型空间中的所有实体。 + + Args: + doc: ezdxf Drawing 对象 + + Returns: + dict: { + "total_entities": int, + "type_counts": dict, # {dxftype: count} + "blocks": list[dict], # INSERT 按块名聚合 + "texts": list[dict], # TEXT/MTEXT 文本列表 + } + """ + msp = doc.modelspace() + type_counts = {} + + # INSERT 按块名聚合 + block_map = {} # block_name -> list of insert_info + texts = [] + + for entity in msp: + dxftype = entity.dxftype() + type_counts[dxftype] = type_counts.get(dxftype, 0) + 1 + + if dxftype == "INSERT": + block_name = entity.dxf.name + insert_info = { + "handle": entity.dxf.handle, + "layer": entity.dxf.layer, + "insert_point": list(entity.dxf.insert), + "rotation": round(entity.dxf.rotation, 2), + "scale": [ + round(entity.dxf.xscale, 2), + round(entity.dxf.yscale, 2), + round(entity.dxf.zscale, 2), + ], + } + if block_name not in block_map: + block_map[block_name] = [] + block_map[block_name].append(insert_info) + + elif dxftype in ("TEXT", "MTEXT"): + content = entity.plain_text() if dxftype == "MTEXT" else entity.dxf.text + text_info = { + "handle": entity.dxf.handle, + "layer": entity.dxf.layer, + "type": dxftype, + "content": content, + } + if hasattr(entity.dxf, "insert"): + text_info["insert"] = list(entity.dxf.insert) + texts.append(text_info) + + # 块聚合结果 + blocks = [] + for block_name, inserts in sorted(block_map.items()): + blocks.append({ + "block_name": block_name, + "count": len(inserts), + "inserts": inserts, + }) + + return { + "total_entities": sum(type_counts.values()), + "type_counts": type_counts, + "blocks": blocks, + "texts": texts, + } diff --git a/experiments/dxf-parser/layer_analysis.py b/experiments/dxf-parser/layer_analysis.py new file mode 100644 index 0000000..8d2e699 --- /dev/null +++ b/experiments/dxf-parser/layer_analysis.py @@ -0,0 +1,94 @@ +"""图层分析模块 —— 统计每层实体数量与类型,并基于图层名称进行角色推断。""" + +# 角色推断规则(启发式,基于常见中文/英文命名) +_ROLE_RULES = [ + (["WALL", "墙", "W", "A-WALL", "A-WALL-"], "墙体"), + (["DOOR", "门", "窗", "WINDOW", "D", "M"], "门窗"), + (["DIM", "标注", "DEFPOINTS", "PUB_DIM", "PUB_DIM"], "标注"), + (["TEXT", "文字", "NOTE"], "文字"), + (["COLUMN", "柱", "Z"], "柱"), + (["E", "EQUIP", "设备"], "设备"), + (["HATCH", "填充", "BH"], "填充"), + (["AXIS", "轴", "AXIS"], "轴网"), + (["GRID", "网格"], "网格"), + (["FURN", "家具", "FURNITURE"], "家具"), + (["BEAM", "梁", "B"], "梁"), + (["SLAB", "板"], "板"), + (["STAIR", "楼梯", "S"], "楼梯"), + (["ROOF", "屋顶", "R"], "屋顶"), + (["PIPE", "管", "P"], "管道"), + (["DUCT", "风管"], "风管"), + (["ELEC", "电气", "E-"], "电气"), + (["PLUMB", "给排水", "P-"], "给排水"), + (["FOUND", "基础", "F"], "基础"), + (["SITE", "场地", "总图"], "场地"), + (["CEIL", "天花", "吊顶", "C"], "天花"), + (["FLOOR", "地面", "FL"], "地面"), + (["SECTION", "剖面", "SECT"], "剖面"), + (["ELEV", "立面", "ELEVATION"], "立面"), + (["0", "A-"], "主体结构"), +] + + +def _infer_role(layer_name: str) -> str: + """根据图层名称推断图层角色(仅作建议)。""" + upper_name = layer_name.upper() + for keywords, role in _ROLE_RULES: + for kw in keywords: + if kw in upper_name or kw in layer_name: + return role + return "其他" + + +def analyze_layers(doc) -> list: + """分析所有图层,返回每层的实体统计和角色推断。 + + Args: + doc: ezdxf Drawing 对象 + + Returns: + list[dict]: 每层信息,格式为 { + "name": str, + "entity_count": int, + "entity_types": list[str], + "suggested_role": str + } + """ + msp = doc.modelspace() + + # 按图层聚合实体 + layer_entities = {} # layer_name -> list of dxftype + for entity in msp: + layer_name = entity.dxf.layer + if layer_name not in layer_entities: + layer_entities[layer_name] = [] + layer_entities[layer_name].append(entity.dxftype()) + + # 也包含没有实体的图层(在 doc.layers 中定义但模型空间无实体) + result = [] + seen_layers = set() + + for layer_name, types in layer_entities.items(): + seen_layers.add(layer_name) + unique_types = sorted(set(types)) + result.append({ + "name": layer_name, + "entity_count": len(types), + "entity_types": unique_types, + "suggested_role": _infer_role(layer_name), + }) + + # 补充空图层(在图层定义中存在但无实体) + for layer in doc.layers: + name = layer.dxf.name + if name not in seen_layers: + result.append({ + "name": name, + "entity_count": 0, + "entity_types": [], + "suggested_role": _infer_role(name), + }) + + # 按实体数降序排列 + result.sort(key=lambda x: -x["entity_count"]) + return result diff --git a/experiments/dxf-parser/main.py b/experiments/dxf-parser/main.py index deafed0..679e286 100644 --- a/experiments/dxf-parser/main.py +++ b/experiments/dxf-parser/main.py @@ -1,387 +1,101 @@ -"""DXF 解析测试程序 —— 使用 ezdxf 读取并输出 DXF 文件内容。""" +"""DXF 解析工具 —— 读取 DXF 文件,输出图层分析和实体分析结果。""" -import io import json -import sys from datetime import datetime from pathlib import Path import ezdxf -# 目录 +from layer_analysis import analyze_layers +from entity_analysis import analyze_entities + +# 路径配置 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 文件 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), - }, +def build_file_info(doc, filename: str) -> dict: + """提取 DXF 文件基本信息。""" + info = { + "filename": filename, + "dxf_version": doc.dxfversion, + "encoding": doc.encoding, } # 单位 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 + info["units"] = units # 图形范围 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)} + info["extents"] = {"min": list(extmin), "max": list(extmax)} + else: + info["extents"] = None - # 图层 - 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 + return info -def print_header(title: str): - print(f"\n{'=' * 70}") - print(f" {title}") - print(f"{'=' * 70}") +def print_summary(file_info: dict, layers: list, entities: dict): + """终端打印解析摘要。""" + print(f"\n{'=' * 60}") + print(f" CAD 文件解析报告") + print(f"{'=' * 60}") + print(f" 文件 : {file_info['filename']}") + print(f" 版本 : {file_info['dxf_version']}") + print(f" 编码 : {file_info['encoding']}") + print(f" 实体总数 : {entities['total_entities']}") + if file_info.get("extents"): + print(f" 图形范围 : {file_info['extents']}") + # 图层摘要 + print(f"\n {'─' * 56}") + print(f" 图层分析(共 {len(layers)} 个图层):") + print(f" {'名称':<25} {'实体数':<8} {'角色推断'}") + print(f" {'-' * 50}") + for layer in layers[:20]: # 最多显示前 20 个 + print(f" {layer['name']:<25} {layer['entity_count']:<8} {layer['suggested_role']}") + if len(layers) > 20: + print(f" ... 还有 {len(layers) - 20} 个图层") -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())} 个实体:") + # 实体类型统计 + type_counts = entities["type_counts"] + print(f"\n {'─' * 56}") + print(f" 实体类型统计:") print(f" {'类型':<25} {'数量':<8}") print(f" {'-' * 35}") - for dtype, count in sorted(entity_types.items(), key=lambda x: -x[1]): + for dtype, count in sorted(type_counts.items(), key=lambda x: -x[1]): print(f" {dtype:<25} {count:<8}") + # 块统计 + blocks = entities["blocks"] + if blocks: + print(f"\n {'─' * 56}") + print(f" 块引用统计(共 {len(blocks)} 种块):") + print(f" {'块名':<35} {'引用次数':<10}") + print(f" {'-' * 47}") + for blk in blocks[:15]: + print(f" {blk['block_name']:<35} {blk['count']:<10}") + if len(blocks) > 15: + print(f" ... 还有 {len(blocks) - 15} 种块") -def print_entity_details(doc: ezdxf.document.Drawing): - """打印模型空间中每个实体的详细信息。""" - msp = doc.modelspace() - entities = list(msp) - if not entities: - print(" (模型空间无实体)") - return + # 文本 + texts = entities["texts"] + if texts: + print(f"\n {'─' * 56}") + print(f" 文本内容(共 {len(texts)} 条):") + for t in texts[:10]: + print(f" [{t['layer']}] {t['content'][:60]}") + if len(texts) > 10: + print(f" ... 还有 {len(texts) - 10} 条文本") - 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") + print(f"\n{'=' * 60}") + print(f" 解析完成") + print(f"{'=' * 60}\n") def main(): @@ -390,59 +104,43 @@ def main(): 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 + print(f"解析文件: {DXF_FILE.name}") + print(f"解析时间: {parse_time}") - 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}") + # 读取 DXF + doc = ezdxf.readfile(str(DXF_FILE)) - doc = ezdxf.readfile(str(DXF_FILE)) + # 文件基本信息 + file_info = build_file_info(doc, DXF_FILE.name) - # 1. 基本信息 - print_header("一、文档基本信息") - print_dxf_info(doc) + # 图层分析 + layers = analyze_layers(doc) - # 2. 图层 - print_header("二、图层信息") - print_layers(doc) + # 实体分析 + entities = analyze_entities(doc) - # 3. 实体统计 - print_header("三、模型空间实体统计") - print_entity_summary(doc) + # 添加实体总数到文件信息 + file_info["total_entities"] = entities["total_entities"] - # 4. 实体详情 - print_header("四、模型空间实体详情") - print_entity_details(doc) + # 终端打印摘要 + print_summary(file_info, layers, entities) - # 5. 块定义 - print_header("五、块定义") - print_blocks(doc) + # 组装并输出 JSON + output = { + "file_info": file_info, + "layers": layers, + "entities": entities["type_counts"], + "blocks": entities["blocks"], + "texts": entities["texts"], + } - # 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") + output_json = OUTPUT_DIR / f"{DXF_FILE.stem}_cad_analysis.json" + output_json.write_text( + json.dumps(output, ensure_ascii=False, indent=2), + encoding="utf-8", + ) print(f"JSON 已保存到: {output_json}")