feat: CAD 解析第一次验证
This commit is contained in:
@@ -0,0 +1,450 @@
|
||||
"""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()
|
||||
@@ -0,0 +1 @@
|
||||
ezdxf>=1.4.0
|
||||
Reference in New Issue
Block a user