import ezdxf import matplotlib.pyplot as plt import matplotlib.patches as patches from pathlib import Path import argparse import os import sys from ezdxf.addons.drawing import RenderContext, Frontend from ezdxf.addons.drawing.matplotlib import MatplotlibBackend from ezdxf.addons.drawing.properties import Configuration from ezdxf import bbox # 配置中文字体支持 import matplotlib.font_manager as fm plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'DejaVu Sans'] # 支持中文显示 plt.rcParams['axes.unicode_minus'] = False # 正常显示负号 # 字体目录配置 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) FONT_DIR = os.path.join(SCRIPT_DIR, "fonts") # 纸张尺寸定义 (mm) PAPER_SIZES_MM = { "A4": {"landscape": (297, 210), "portrait": (210, 297)}, "A3": {"landscape": (420, 297), "portrait": (297, 420)}, "A2": {"landscape": (594, 420), "portrait": (420, 594)}, "A1": {"landscape": (841, 594), "portrait": (594, 841)}, "A0": {"landscape": (1189, 841), "portrait": (841, 1189)}, } PRINTABLE_BLOCKS = { "A$C2EB80DB8": ("A1", "portrait"), "A$C1CC9093B": ("A1", "landscape"), "A$C6D564680": ("A2", "portrait"), "新块": ("A3", "landscape"), "新块1": ("A4", "portrait"), } def export_frame_to_pdf_matplotlib(doc, search_space, block_ref, settings, output_filename): """使用ezdxf专业渲染系统导出PDF,支持完整的CAD实体和中文字体""" block_name = block_ref.dxf.name paper_size_key, orientation = settings try: page_dimensions = PAPER_SIZES_MM[paper_size_key][orientation] except KeyError: print(f" - 错误:为块 '{block_name}' 提供的设置无效") return print(f" - 图框: '{block_name}', 设置: {paper_size_key} {orientation} ({page_dimensions[0]}x{page_dimensions[1]}mm)") try: # 配置字体支持 if FONT_DIR not in ezdxf.options.support_dirs: ezdxf.options.support_dirs.append(FONT_DIR) print(f" - 已添加字体目录: {FONT_DIR}") # 获取块中的所有实体 entities = list(block_ref.virtual_entities()) if not entities: print(f" - 警告:块 '{block_name}' 是空的,跳过") return print(f" - 找到 {len(entities)} 个实体") # 计算边界框 bbox_result = bbox.extents(entities) if bbox_result.extmin is None or bbox_result.extmax is None: print(f" - 警告:块 '{block_name}' 的边界为空,跳过") return minx, miny = bbox_result.extmin.x, bbox_result.extmin.y maxx, maxy = bbox_result.extmax.x, bbox_result.extmax.y print(f" - 边界: ({minx:.2f}, {miny:.2f}) 到 ({maxx:.2f}, {maxy:.2f})") # 配置渲染器 - 参考export_dxf.py的成功配置 config = Configuration.defaults().with_changes( pdsize=0, # 点的大小设置为0,避免3D点处理问题 ) # 设置正确的PDF纸张尺寸(英寸) fig_width_in = page_dimensions[0] / 25.4 fig_height_in = page_dimensions[1] / 25.4 print(f" - 设置PDF尺寸: {fig_width_in:.2f}x{fig_height_in:.2f}英寸") # 创建matplotlib图形,固定尺寸 fig = plt.figure(figsize=(fig_width_in, fig_height_in)) ax = fig.add_axes([0, 0, 1, 1]) # 设置视图边界为块的边界 ax.set_xlim(minx, maxx) ax.set_ylim(miny, maxy) ax.set_aspect('equal') ax.axis('off') print(f" - 正确的方法:先渲染整个布局,再裁剪到块区域...") # 正确的方法:渲染整个布局的所有内容,然后设置视图为块的边界 try: ctx = RenderContext(doc) backend = MatplotlibBackend(ax) frontend = Frontend(ctx, backend, config=config) print(f" - 渲染整个布局的内容(包含图纸的所有图形)...") # 渲染整个布局 frontend.draw_layout(search_space, finalize=True) print(f" - 布局渲染完成,视图已设置为块 '{block_name}' 的边界区域") except Exception as layout_error: print(f" - 完整布局渲染失败: {layout_error}") print(f" - 尝试带过滤的布局渲染...") try: # 过滤掉3D坐标问题的实体 def safe_filter(entity): entity_type = entity.dxftype() # 跳过可能导致3D坐标问题的实体 problematic_types = {"HATCH", "SOLID", "REGION", "BODY", "3DSOLID", "MESH", "SURFACE"} return entity_type not in problematic_types ctx = RenderContext(doc) backend = MatplotlibBackend(ax) frontend = Frontend(ctx, backend, config=config) frontend.draw_layout(search_space, finalize=True, filter_func=safe_filter) print(f" - 过滤渲染成功") except Exception as filter_error: print(f" - 过滤渲染也失败: {filter_error}") print(f" - 使用matplotlib直接渲染所有实体...") # 最后备用方案:matplotlib直接渲染所有布局实体 ax.clear() ax.set_xlim(minx, maxx) ax.set_ylim(miny, maxy) ax.set_aspect('equal') ax.axis('off') all_entities = list(search_space.query('*')) # 获取布局中的所有实体 drawn_count = 0 print(f" - 开始渲染布局中的 {len(all_entities)} 个实体...") for entity in all_entities: try: entity_type = entity.dxftype() # 检查实体是否在块的边界区域内 if hasattr(entity.dxf, 'start') and hasattr(entity.dxf, 'end'): # 线条类实体 start = entity.dxf.start end = entity.dxf.end if not (minx <= start.x <= maxx and miny <= start.y <= maxy and minx <= end.x <= maxx and miny <= end.y <= maxy): continue # 跳过不在区域内的实体 elif hasattr(entity.dxf, 'center'): # 圆形类实体 center = entity.dxf.center if not (minx <= center.x <= maxx and miny <= center.y <= maxy): continue elif hasattr(entity.dxf, 'insert'): # 文本类实体 insert = entity.dxf.insert if not (minx <= insert.x <= maxx and miny <= insert.y <= maxy): continue # 渲染在区域内的实体 if entity_type == "LINE": start = entity.dxf.start end = entity.dxf.end ax.plot([start.x, end.x], [start.y, end.y], 'k-', linewidth=0.5) drawn_count += 1 elif entity_type == "CIRCLE": center = entity.dxf.center radius = entity.dxf.radius circle = patches.Circle((center.x, center.y), radius, fill=False, edgecolor='black', linewidth=0.5) ax.add_patch(circle) drawn_count += 1 elif entity_type == "ARC": center = entity.dxf.center radius = entity.dxf.radius start_angle = entity.dxf.start_angle end_angle = entity.dxf.end_angle arc = patches.Arc((center.x, center.y), 2*radius, 2*radius, theta1=start_angle, theta2=end_angle, edgecolor='black', linewidth=0.5) ax.add_patch(arc) drawn_count += 1 elif entity_type in ["TEXT", "MTEXT"]: insert_point = entity.dxf.insert if hasattr(entity.dxf, 'insert') else (0, 0, 0) text_content = entity.dxf.text if hasattr(entity.dxf, 'text') else "" if text_content.strip(): ax.text(insert_point.x, insert_point.y, text_content, fontsize=6, ha='left', va='bottom', color='black') drawn_count += 1 elif entity_type == "LWPOLYLINE": points = list(entity.get_points()) if len(points) > 1: x_coords = [p[0] for p in points] y_coords = [p[1] for p in points] ax.plot(x_coords, y_coords, 'k-', linewidth=0.5) drawn_count += 1 elif entity_type == "DIMENSION": # 处理尺寸标注 try: if hasattr(entity, 'get_measurement'): # 简化:绘制尺寸线的基本线条 if hasattr(entity.dxf, 'defpoint') and hasattr(entity.dxf, 'defpoint2'): p1 = entity.dxf.defpoint p2 = entity.dxf.defpoint2 if hasattr(entity.dxf, 'defpoint2') else p1 ax.plot([p1.x, p2.x], [p1.y, p2.y], 'k-', linewidth=0.3) drawn_count += 1 except: pass elif entity_type == "INSERT": # 处理嵌套INSERT块 - 这是关键的缺失部分! try: nested_entities = list(entity.virtual_entities()) insert_drawn = 0 for nested_entity in nested_entities: try: nested_type = nested_entity.dxftype() if nested_type == "LINE": start = nested_entity.dxf.start end = nested_entity.dxf.end ax.plot([start.x, end.x], [start.y, end.y], 'k-', linewidth=0.5) insert_drawn += 1 elif nested_type == "CIRCLE": center = nested_entity.dxf.center radius = nested_entity.dxf.radius circle = patches.Circle((center.x, center.y), radius, fill=False, edgecolor='black', linewidth=0.5) ax.add_patch(circle) insert_drawn += 1 elif nested_type in ["TEXT", "MTEXT"]: insert_point = nested_entity.dxf.insert if hasattr(nested_entity.dxf, 'insert') else (0, 0, 0) text_content = nested_entity.dxf.text if hasattr(nested_entity.dxf, 'text') else "" if text_content.strip(): ax.text(insert_point.x, insert_point.y, text_content, fontsize=6, ha='left', va='bottom', color='blue') # 用蓝色区分嵌套块文字 insert_drawn += 1 elif nested_type == "LWPOLYLINE": points = list(nested_entity.get_points()) if len(points) > 1: x_coords = [p[0] for p in points] y_coords = [p[1] for p in points] ax.plot(x_coords, y_coords, 'k-', linewidth=0.5) insert_drawn += 1 except: continue if insert_drawn > 0: drawn_count += insert_drawn except: pass except Exception as e: continue # 最后,确保渲染当前目标块本身的内容(框架和标签) print(f" - 正在渲染目标块 '{block_name}' 本身的内容...") block_drawn = 0 for block_entity in entities: # entities 是当前块的虚拟实体 try: entity_type = block_entity.dxftype() if entity_type == "LINE": start = block_entity.dxf.start end = block_entity.dxf.end ax.plot([start.x, end.x], [start.y, end.y], 'r-', linewidth=1.0) # 用红色突出显示块框架 block_drawn += 1 elif entity_type in ["TEXT", "MTEXT"]: insert_point = block_entity.dxf.insert if hasattr(block_entity.dxf, 'insert') else (0, 0, 0) text_content = block_entity.dxf.text if hasattr(block_entity.dxf, 'text') else "" if text_content.strip(): ax.text(insert_point.x, insert_point.y, text_content, fontsize=8, ha='left', va='bottom', color='red', weight='bold') # 用红色粗体显示块标签 block_drawn += 1 elif entity_type == "LWPOLYLINE": points = list(block_entity.get_points()) if len(points) > 1: x_coords = [p[0] for p in points] y_coords = [p[1] for p in points] ax.plot(x_coords, y_coords, 'r-', linewidth=1.0) # 用红色显示块边框 block_drawn += 1 elif entity_type == "INSERT": # 处理目标块内的嵌套块 nested_entities = list(block_entity.virtual_entities()) for nested in nested_entities: try: if nested.dxftype() == "LINE": start = nested.dxf.start end = nested.dxf.end ax.plot([start.x, end.x], [start.y, end.y], 'r-', linewidth=0.8) block_drawn += 1 except: pass except Exception as e: continue print(f" - 目标块本身渲染完成,绘制了 {block_drawn} 个块实体") print(f" - matplotlib区域渲染总计完成,绘制了 {drawn_count + block_drawn} 个实体") # 保存为PDF,使用固定尺寸,不自动调整边界 plt.savefig(str(output_filename), format='pdf', dpi=300, bbox_inches=None, pad_inches=0) plt.close() print(f" - 已保存到: {output_filename}") except Exception as e: print(f" - 错误:处理块 '{block_name}' 时出现异常: {e}") return def main(): parser = argparse.ArgumentParser(description="使用matplotlib渲染DXF块为PDF") parser.add_argument("input_dxf", type=Path, help="DXF文件路径") parser.add_argument("-o", "--output", type=Path, default=Path("plots_output"), help="输出目录") parser.add_argument("-l", "--layout", type=str, default=None, help="布局名称") args = parser.parse_args() output_dir = args.output output_dir.mkdir(exist_ok=True) try: doc = ezdxf.readfile(args.input_dxf) except Exception as e: print(f"错误:无法读取文件 '{args.input_dxf}': {e}") return if args.layout: try: search_space = doc.layouts.get(args.layout) print(f"--- 目标空间: 布局 '{args.layout}' ---") except KeyError: available = ", ".join(doc.layouts.names()) print(f"错误: 找不到布局 '{args.layout}'。可用布局: [{available}]") return else: search_space = doc.modelspace() print("--- 目标空间: 模型空间 ---") all_block_references = search_space.query('INSERT') frame_references = [ref for ref in all_block_references if ref.dxf.name in PRINTABLE_BLOCKS] if not frame_references: print("未找到任何配置的可打印图框块") return print(f"共找到 {len(frame_references)} 个待处理的图框块") for i, block_ref in enumerate(frame_references): block_name = block_ref.dxf.name settings = PRINTABLE_BLOCKS[block_name] print(f"\n处理第 {i+1} 个图框: '{block_name}'") output_filename = output_dir / f"{block_name}_{i+1}.pdf" export_frame_to_pdf_matplotlib(doc, search_space, block_ref, settings, output_filename) print("\n处理完毕!") if __name__ == "__main__": main()