feat: 拆分 DXF 解析为模块化结构,新增图层分析和实体分析模块

- 新增 layer_analysis.py:图层实体统计 + 角色推断(22条启发式规则)
- 新增 entity_analysis.py:实体类型统计、INSERT 块聚合、文本提取
- 重构 main.py:调度分析模块,输出 {文件名}_cad_analysis.json
- 新增 README.md 使用说明
- 输出文件名使用模型名作为前缀,避免多次测试覆盖
This commit is contained in:
zwt13703
2026-07-08 14:31:27 +08:00
parent 6501364164
commit 9feeb14910
5 changed files with 428 additions and 394 deletions
+11
View File
@@ -15,3 +15,14 @@
- 布局/图纸空间列表 - 布局/图纸空间列表
4. 运行程序验证,成功解析目标 DXF 文件,输出 20 个实体(9 个 DIMENSION、6 个 MTEXT、5 个 LWPOLYLINE)、13 个块定义、3 个布局。 4. 运行程序验证,成功解析目标 DXF 文件,输出 20 个实体(9 个 DIMENSION、6 个 MTEXT、5 个 LWPOLYLINE)、13 个块定义、3 个布局。
- **执行结果**: 程序正常运行,完整输出了 samples/decimal-inch-drawing-sheet-size-ASME-Y14.1-cad-block-dwg.dxf 的全部结构和内容信息。 - **执行结果**: 程序正常运行,完整输出了 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 规范,为后续墙体识别等功能扩展奠定基础。
+83
View File
@@ -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 文本内容
+148
View File
@@ -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,
}
+94
View File
@@ -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
+87 -389
View File
@@ -1,387 +1,101 @@
"""DXF 解析测试程序 —— 使用 ezdxf 读取并输出 DXF 文件内容""" """DXF 解析工具 —— 读取 DXF 文件,输出图层分析和实体分析结果"""
import io
import json import json
import sys
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
import ezdxf import ezdxf
# 目录 from layer_analysis import analyze_layers
from entity_analysis import analyze_entities
# 路径配置
BASE_DIR = Path(__file__).parent.parent.parent BASE_DIR = Path(__file__).parent.parent.parent
SAMPLE_DIR = BASE_DIR / "samples" SAMPLE_DIR = BASE_DIR / "samples"
OUTPUT_DIR = Path(__file__).parent / "output" 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" DXF_FILE = SAMPLE_DIR / "洗浴中心C-48.dxf"
class OutputRecorder:
"""同时输出到 stdout 和内存缓冲,最终写入文件。"""
def __init__(self): def build_file_info(doc, filename: str) -> dict:
self._buffer = io.StringIO() """提取 DXF 文件基本信息。"""
self._terminal = sys.stdout info = {
"filename": filename,
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, "dxf_version": doc.dxfversion,
"encoding": doc.encoding, "encoding": doc.encoding,
"acad_ver": doc.header.get("$ACADVER", None),
},
} }
# 单位 # 单位
units = doc.header.get("$INSUNITS", None) units = doc.header.get("$INSUNITS", None)
unit_names = {1: "inches", 2: "feet", 4: "millimeters", 5: "centimeters", 6: "meters"} info["units"] = units
result["header"]["units"] = unit_names.get(units, str(units)) if units else None
# 图形范围 # 图形范围
extmin = doc.header.get("$EXTMIN", None) extmin = doc.header.get("$EXTMIN", None)
extmax = doc.header.get("$EXTMAX", None) extmax = doc.header.get("$EXTMAX", None)
if extmin and extmax: if extmin and extmax:
result["header"]["extents"] = {"min": list(extmin), "max": list(extmax)} info["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: else:
flags.append("OFF") info["extents"] = None
if layer.is_frozen():
flags.append("FROZEN") return info
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): def print_summary(file_info: dict, layers: list, entities: dict):
"""统计模型空间中各类型实体数量""" """终端打印解析摘要"""
msp = doc.modelspace() print(f"\n{'=' * 60}")
entity_types = {} print(f" CAD 文件解析报告")
for entity in msp: print(f"{'=' * 60}")
dtype = entity.dxftype() print(f" 文件 : {file_info['filename']}")
entity_types[dtype] = entity_types.get(dtype, 0) + 1 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']}")
if not entity_types: # 图层摘要
print(" (模型空间无实体)") print(f"\n {'' * 56}")
return 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} 个图层")
print(f"{sum(entity_types.values())} 个实体:") # 实体类型统计
type_counts = entities["type_counts"]
print(f"\n {'' * 56}")
print(f" 实体类型统计:")
print(f" {'类型':<25} {'数量':<8}") print(f" {'类型':<25} {'数量':<8}")
print(f" {'-' * 35}") 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}") 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): # 文本
"""打印模型空间中每个实体的详细信息。""" texts = entities["texts"]
msp = doc.modelspace() if texts:
entities = list(msp) print(f"\n {'' * 56}")
if not entities: print(f" 文本内容(共 {len(texts)} 条):")
print(" (模型空间无实体)") for t in texts[:10]:
return print(f" [{t['layer']}] {t['content'][:60]}")
if len(texts) > 10:
print(f" ... 还有 {len(texts) - 10} 条文本")
print(f"{len(entities)} 个实体:") print(f"\n{'=' * 60}")
print(f" {'序号':<6} {'类型':<25} {'句柄':<12} {'图层':<20} {'关键属性'}") print(f" 解析完成")
print(f" {'-' * 100}") print(f"{'=' * 60}\n")
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(): def main():
@@ -390,59 +104,43 @@ def main():
return return
OUTPUT_DIR.mkdir(parents=True, exist_ok=True) 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") 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.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}") print(f"解析时间: {parse_time}")
# 读取 DXF
doc = ezdxf.readfile(str(DXF_FILE)) doc = ezdxf.readfile(str(DXF_FILE))
# 1. 基本信息 # 文件基本信息
print_header("一、文档基本信息") file_info = build_file_info(doc, DXF_FILE.name)
print_dxf_info(doc)
# 2. 图层 # 图层分析
print_header("二、图层信息") layers = analyze_layers(doc)
print_layers(doc)
# 3. 实体统计 # 实体分析
print_header("三、模型空间实体统计") entities = analyze_entities(doc)
print_entity_summary(doc)
# 4. 实体详情 # 添加实体总数到文件信息
print_header("四、模型空间实体详情") file_info["total_entities"] = entities["total_entities"]
print_entity_details(doc)
# 5. 块定义 # 终端打印摘要
print_header("五、块定义") print_summary(file_info, layers, entities)
print_blocks(doc)
# 6. 布局 # 组装并输出 JSON
print_header("六、布局(图纸空间)") output = {
print_layouts(doc) "file_info": file_info,
"layers": layers,
"entities": entities["type_counts"],
"blocks": entities["blocks"],
"texts": entities["texts"],
}
print_header("解析完成") output_json = OUTPUT_DIR / f"{DXF_FILE.stem}_cad_analysis.json"
finally: output_json.write_text(
sys.stdout = recorder._terminal json.dumps(output, ensure_ascii=False, indent=2),
encoding="utf-8",
# 写入文本文件 )
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}") print(f"JSON 已保存到: {output_json}")