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
+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
+92 -394
View File
@@ -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}")