ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Python自动化:PSCAD波形图批量生成Word报告全流程

Python自动化:PSCAD波形图批量生成Word报告全流程 搞电力系统仿真的朋友应该都有过这种体验PSCAD里把模型搭好、仿真跑完波形一个个调出来看觉得结果没问题了接下来才是最折磨人的一步——把波形图整理进Word报告。手动截图、裁剪、统一尺寸、调整对齐再配上图注和说明一篇项目报告动辄几十张图光排版就能耗掉大半天。如果中间改了一次参数重新仿真那整套截图工作基本要重来一遍。今天分享的这套Python自动化流程就是专门解决这个痛点的。我平时主要做新能源并网仿真每次出报告都要倒腾几十张波形图从最早纯粹手工到后来半自动再到现在的全流程脚本算是把这条链路彻底理顺了。整套流程跑下来从PSCAD仿真结束到Word文档排版完成5分钟以内可以搞定WPS上同样能用。这篇博文会把我的完整方案拆开来讲包括两条技术路线怎么选、PSCAD侧怎么准备、Word文档怎么批量组装、WPS兼容层怎么写以及我在实际项目里踩过的坑。适合电力专业的研究生、设计院工程师以及所有需要高频产出PSCAD仿真报告的人阅读。1. 内容整体设计与思路拆解1.1 手动流程的痛点到底在哪先说一个最直观的场景你做完了PSCAD仿真波形窗口里有线路电压、电流、有功功率、无功功率好几个曲线现在要把它们放到Word里。手动操作通常是这么几步先逐个激活波形窗口用截图工具框选图形区域保存成图片再打开Word一张张插入图片手动把尺寸改成统一宽度居中对齐最后在图片下方输入“图3-2 并网点电压波形”之类的图注。听起来不难但几十张图重复操作下来枯燥不说还特别容易出错。尤其是当Word设置里图片默认不是嵌入型或者上一张图的尺寸没改好后面的图全跟着歪掉返工成本很高。更麻烦的是改参数重仿真的场景。电网模型里一个控制参数从0.5改成0.8仿真结果全变了波形图自然得全部重截。手动流程下这意味着把前面所有截图工作重新做一遍没有任何复用价值。我最初决定做这个自动化脚本就是被这种重复劳动逼的。1.2 两条技术路线截屏方案与数据重绘方案围绕“把PSCAD波形放进Word”这件事我试过两条主流路线分别对应不同的使用场景。第一条是界面截屏方案。用Python控制鼠标键盘激活PSCAD的波形窗口对指定区域截图再用PIL统一裁剪最后插入Word。优点是实现简单所见即所得PSCAD里波形是什么样Word里就是什么样缺点是依赖界面环境窗口位置变了、系统缩放比例不同、PSCAD主题配色改了都可能影响截图结果。第二条是数据重绘方案。PSCAD支持把波形数据导出成CSV或文本文件然后用Python的matplotlib等库重新画图最后插入Word。优点是图片质量可控线条、字体、坐标轴、DPI都能按论文或报告规范统一设置而且只要数据文件在随时可以重新生成图片改参数后重仿真也不怕缺点是需要额外处理数据列的映射关系PSCAD导出的数据格式需要熟悉一下。1.3 方案选型判断标准我的建议是如果只是临时出几张波形图对图的要求不高选截屏方案脚本量小、见效快如果是要写毕业论文、设计报告或者图表数量大、需要统一风格重绘一定要选数据重绘方案。很多实际项目是两种混着用——用截屏方案快速预览用重绘方案出最终报告。这个判断标准我踩过几次坑才总结出来初期图省事全用截屏结果到了写论文阶段几十张图风格不统一分辨率还不高最后全部重画一遍比一开始就用重绘方案更浪费时间。2. 环境准备把“地基”打牢2.1 Python环境与依赖库无论选哪条路线Python环境是基础。我推荐用Python 3.9到3.11之间的版本太新或太老的版本在某些库上容易出兼容性问题。建议先建一个独立的虚拟环境避免和系统Python、其他项目互相污染。需要安装的依赖库有以下这些我按用途列出来pywin32提供win32gui、win32com等Windows系统接口用于窗口查找和Office/WPS调用pyautogui模拟鼠标键盘操作截屏方案的核心Pillow图像裁剪、格式转换、尺寸调整matplotlib、pandas、numpy数据重绘方案的核心python-docx直接生成和编辑docx文档不依赖Word/WPS应用安装命令很简单在虚拟环境里执行pip install pywin32 pyautogui pillow matplotlib pandas numpy python-docx这里有个容易忽略的点pywin32安装后如果要在命令行下用win32com有时需要先执行python Scripts/pywin32_postinstall.py -install注册一下否则某些接口会报错。多花这一步能省后面很多排查时间。2.2 PSCAD侧准备与数据导出设置PSCAD没有公开的Python API所以自动化的关键是利用它的外部文件接口。先说截屏方案脚本需要保证波形窗口处于可见且置顶状态。我在实际使用中会在PSCAD里把要导出的波形窗口单独拖到副显示器或屏幕固定位置这样脚本的截图坐标不会乱。再说数据重绘方案的关键准备。PSCAD的波形窗口Curve Window支持右键菜单中的“Export As”导出数据常见格式有CSV和文本文件。导出时要注意两点一是时间轴的列名PSCAD默认可能是Time或t二是多个变量导出时会生成多列列名就是信号路径比如BRK_Line_Voltage:1这种带前缀的格式读数据时要想办法把列名清洗掉。实际操作中我会在PSCAD波形窗口里先只勾选当前关注的曲线再导出这样CSV的列结构干净Python读取时不需要太多复杂的筛选逻辑。2.3 Word/WPS对象模型基础如果你之前只接触过手动写Word文档可能对“对象模型”这个概念比较陌生。简单说Word和WPS都提供了COM接口允许外部程序像操作一个对象一样去控制软件本身。Word对应的是Word.ApplicationWPS文字对应的是Kwps.ApplicationWPS表格对应的是Ket.Application。这两种接口在基本操作上几乎一致但细节上存在差异。我用python-docx比较多因为这个库直接操作docx文件不依赖Word或WPS应用是否安装。它的原理是把docx当成一个包含若干XML文件的压缩包python-docx在内存中解析并修改这些XML最后重新打包成docx。好处是生成的文档两个Office能顺利打开坏处是它不能覆盖Word/WPS的所有功能比如某些高级页面设置和域代码处理就比较费劲。所以在实际项目里我通常以python-docx为主碰到它搞不定的高级特性时再用COM接口通过Word或WPS应用来补一刀。3. 核心实现从PSCAD波形到Word文档3.1 方案A基于pyautogui的界面截图自动化先讲最简单、也最容易上手的截屏方案。整体流程分四步定位波形窗口、激活置顶、区域截图、批量插入Word。窗口定位是第一个难点PSCAD窗口的标题栏一般包含“Graph”或“Curve”字样我可以先用win32gui枚举所有可见窗口找到标题匹配的那一个import win32gui def find_window(title_part): result [] def cb(hwnd, extra): if win32gui.IsWindowVisible(hwnd): text win32gui.GetWindowText(hwnd) if title_part in text: result.append((hwnd, text)) win32gui.EnumWindows(cb, None) return result # 示例查找标题带 Graph 的窗口 windows find_window(Graph) for hwnd, text in windows: print(hwnd, text)找到窗口后用win32gui.SetForegroundWindow(hwnd)把窗口置前再调用win32gui.GetWindowRect(hwnd)获取窗口的屏幕坐标范围。窗口置顶后建议等待一两秒避免界面还没刷新完就截图。截图用pyautoguiimport pyautogui import time rect (left, top, right, bottom) pyautogui.FAILSAFE True # 鼠标甩到左上角可紧急停止 time.sleep(0.5) img pyautogui.screenshot(regionrect) img.save(waveform_raw.png)实测下来屏幕缩放比例常常让坐标偏移。Windows如果设了125%或150%缩放pyautogui拿到的坐标和实际像素对不上。解决办法是在脚本开头加DPI感知让系统按物理像素返回坐标import ctypes try: ctypes.windll.shcore.SetProcessDpiAwareness(1) except Exception: pass我自己的习惯是需要截图的波形窗口固定放在主屏幕左侧并统一窗口尺寸这样坐标值基本稳定。如果你有副屏也可以把波形窗口拖到副屏固定位置但要注意截屏区域不能跨显示器边界。3.2 共享代码用PIL统一裁剪与命名不管哪个方案截完图后续处理都建议用Pillow统一做一轮裁剪和重命名。波形图的四周通常有空白边框截图出来如果直接插进Word视觉上会显得很散。我的做法是先用getbbox()自动去掉四周纯色边缘再统一设置宽度并在文件名里加入序号和图号方便后续插图序。from PIL import Image im Image.open(waveform_raw.png) im im.convert(RGB) # 去掉纯白或纯黑边缘具体颜色阈值按实际背景调整 bg im.getpixel((0, 0)) bbox im.point(lambda p: 255 if p ! bg else 0).getbbox() if bbox: im im.crop(bbox) # 统一宽度为1400像素高度按比例缩放 width 1400 ratio width / im.width im im.resize((width, int(im.height * ratio)), Image.LANCZOS) im.save(fig_001.png)3.3 方案B基于CSV导出的数据重绘数据重绘方案是我处理正式报告最推荐的做法它的可复现性比截屏高一个量级。PSCAD导出CSV后先用pandas读进来关键是搞清楚列名结构。PSCAD导出的CSV可能是双行头部第一行是变量描述第二行是单位或信号名。我一般在读取后做一次列名清洗import pandas as pd df pd.read_csv(case1_line_voltage.csv, skiprows1) # 去掉列名中的特殊字符和多余空格 df.columns [c.strip().replace( , _).replace(:, _) for c in df.columns] print(df.columns.tolist()) print(df.head())数据清洗完之后用matplotlib重绘。为了让报告风格统一我封装了一个画图函数把字体、线宽、图例位置、坐标轴字号和图片尺寸全部固定下来import matplotlib matplotlib.use(Agg) import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei] plt.rcParams[axes.unicode_minus] False plt.rcParams[figure.dpi] 200 plt.rcParams[savefig.dpi] 300 def plot_waveform(time_series, data_series, ylabel, title, output_path): fig, ax plt.subplots(figsize(7, 3.2)) ax.plot(time_series, data_series, linewidth1.0, colorcrimson) ax.set_xlabel(时间 (s), fontsize9) ax.set_ylabel(ylabel, fontsize9) ax.set_title(title, fontsize10) ax.grid(alpha0.3) ax.tick_params(labelsize8) fig.tight_layout() fig.savefig(output_path) plt.close(fig)这里要特别提醒中文字体问题。matplotlib默认字体不包含中文如果不配置font.sans-serif图上的“时间”“电压”等文字会变成一个个小方框。上面代码里我已经配了SimHei和微软雅黑但前提是系统里确实装了这些字体。如果跑在精简版Windows或服务器上可能需要先把字体文件放到对应目录再用font_manager.addfont加载。3.4 批量组装Word文档并统一排版图片准备好之后就用python-docx来组装Word。我一般会先生成一个统一风格的报告模板里面预设好页边距、正文样式和标题样式Python脚本只需要往模板里追加段落和图片。插入图片时最需要注意的是图片宽度和对齐方式。python-docx里可以用document.add_picture()直接插入但默认图片宽度可能很大所以一定要显式设置widthCm()。居中对齐要找到图片所在段落设置alignment为WD_ALIGN_PARAGRAPH.CENTER。另外Word文档里图片和文字之间最好留一段空白不然排版会很挤。from docx import Document from docx.shared import Cm from docx.enum.text import WD_ALIGN_PARAGRAPH doc Document(report_template.docx) figures [ (fig_001.png, 图3-1 并网点电压波形), (fig_002.png, 图3-2 并网点电流波形), ] for img_path, caption in figures: p doc.add_paragraph() run p.add_run() run.add_picture(img_path, widthCm(14)) p.alignment WD_ALIGN_PARAGRAPH.CENTER cap doc.add_paragraph() cap.paragraph_format.alignment WD_ALIGN_PARAGRAPH.CENTER cap.paragraph_format.space_after Cm(0.5) cap.add_run(caption).font.size Pt(9) doc.save(仿真报告_autogen.docx)这一段代码看着简单但在给上百张图片的文档批量插入时非常稳定。唯一要注意的是python-docx往段落里插入图片时图片是内联在run里的如果后续还需要在图下面加图注要新建段落不要和图片在同一段落里叠加文字。4. WPS兼容方案详解4.1 WPS与Word的COM接口差异很多人的电脑只装了WPS没有MS Office。python-docx生成的docx文件WPS能正常打开这一点问题不大。但如果你的脚本里用了win32com来操作Word应用就需要注意WPS和Word的COM接口名称不一样。Word的COM入口是Word.ApplicationWPS文字是Kwps.ApplicationWPS表格是Ket.ApplicationWPS演示是Kwpp.Application。我在写工具时通常会把应用启动封装成一个函数按顺序尝试多个ProgIDimport win32com.client def create_office_app(): for prog_id in (Word.Application, Kwps.Application, wps.Application): try: app win32com.client.DispatchEx(prog_id) app.Visible True return app, prog_id except Exception: continue raise RuntimeError(未检测到可用的Word或WPS应用)这里用DispatchEx而不是Dispatch是因为DispatchEx每次都会启动一个独立的Office进程脚本结束后可以用app.Quit()干净退出不容易受之前残留进程影响。4.2 兼容层封装与自动检测再进一步我通常在脚本里加一个“应用类型检测”的环节拿到ProgID后记录当前是Word还是WPS后续涉及到两者行为差异的地方就根据类型走不同的分支。比如设置页面边框、插入页眉页脚这样的操作WPS和Word在部分参数上会有细微差别。WPS要支持COM操作前提是系统里安装了WPS的VBA组件。默认安装WPS时可能不带这个组件需要在WPS安装目录的“加载项”或“组件”选项里手动勾选“VBA for WPS”。没有这一步的话Kwps.Application会创建失败。这个点很容易被忽略我帮同事部署脚本时就卡在这里半小时最后发现是WPS没装VBA组件。4.3 样式与排版需要避开的坑先说图片格式。PSCAD或matplotlib生成的图片建议统一用PNG格式。Word和WPS对PNG支持都很好尽量不要用EMF或WMF这些矢量格式虽然在Word里表现不错但WPS打开时偶尔出现渲染异常或尺寸错乱。如果你实在需要矢量图也要把图片以兼容模式插入后再检查一遍。再说字体。Word里常用的“等线”“微软雅黑”在WPS里一般没问题但某些特殊的西文字体比如“Arial Unicode MS”WPS里可能找不到替代字体导致排版回退、文字偏移。最稳妥的做法是脚本里只设置中文字体名并且在中文字体名后接着设置一个通用西文字体比如SimSun配Times New Roman这样WPS渲染时不会乱。还有一个容易被忽略的是纸张大小和页边距。python-docx设置页边距时用的单位如果没有转成厘米直接填了数值很可能被当成Twips或Point处理导致WPS里页边距异常。我建议统一用Cm()包装。5. 常见问题与排查实录5.1 PSCAD窗口定位失败症状是find_window(Graph)返回空列表。排查方向有三个一是PSCAD窗口的标题里不是“Graph”而是具体的波形名称比如“Line Voltage”或“Case1_Curve”可以先把所有可见窗口标题打印出来肉眼确认后再改匹配关键字二是PSCAD波形窗口最小化到了任务栏IsWindowVisible会返回False需要先恢复窗口三是脚本权限不够PSCAD以管理员身份运行而Python终端是普通权限时某些窗口属性读不到建议两边都以管理员权限启动。5.2 坐标偏移与DPI缩放坐标偏移最典型的表现是截图区域偏左上或偏右下截出来的图画面不完整。这是系统缩放比例导致的。Windows默认150%缩放时pyautogui的坐标基准和物理屏幕像素不一致。解决方法是在脚本开头调用SetProcessDpiAwareness(1)让pyautogui按物理像素工作。另外如果你的PSCAD窗口不是标准Windows标题栏而是自绘边框GetWindowRect返回的坐标有时会包含阴影边框截图时需要多裁剪几个像素。5.3 matplotlib中文乱码画图时中文变方框是因为当前matplotlib配置中没有可用的中文字体。临时解决方法是from matplotlib import font_manager font_path C:/Windows/Fonts/simhei.ttf font_manager.fontManager.addfont(font_path) plt.rcParams[font.sans-serif] [SimHei]一劳永逸的做法是把常用中文字体路径加入matplotlib的font_manager中或者干脆在配置文件里把默认字体改成Microsoft YaHei。另外别忘了设置axes.unicode_minusFalse否则坐标轴负号会变成乱码方块。5.4 图片清晰度与Word文档体积有人用matplotlib画图时把dpi设成100图插入Word后屏幕上看着还行一打印或者放大就糊了。我的经验是保存时dpi至少设置为200插入Word时再把图片宽度统一为14厘米左右这样既能保证打印清晰又不会因为分辨率过高导致docx文件动辄几十MB。如果确实图片非常多可以先统一把PNG转成JPG再插入体积能下降不少但要注意透明背景会被填成黑色。5.5 WPS打开docx后样式丢失最典型的是页边距变了或者图片位置偏移。这通常是python-docx生成的oxml里某些属性和WPS解析器不兼容。一个有效规避方法用python-docx生成文档后不要急着收工先用脚本里的create_office_app()打开它再用COM方式“另存为”一次docx强制WPS/Word把文件重新规范一遍。这个操作并不复杂但能明显减少不同Office版本间打开跳格式的问题。5.6 脚本在别人电脑上跑不起来我遇到过不少场景脚本在自己电脑上一切正常拷到同事实机上却各种报错。最常见的三个原因Python环境没装全、系统没有PSCAD或PSCAD版本不同导致波形窗口标题变了、WPS没有VBA组件。我建议把脚本做成交互式配置的形式首次运行时让用户填PSCAD窗口标题关键字、图片输出目录、Word模板路径并列出自检清单这样换电脑时容易排查。6. 我的一些额外建议最后说几个我自己实际项目里沉淀出来的小习惯。一是图片和Word模板一定要用绝对路径。刚开始我用相对路径脚本和文件放在同一个目录下自己跑没问题后来放到Windows任务计划程序里做定时批量出报告发现经常找不到文件排查半天才意识到是任务计划程序的默认工作目录不是脚本所在目录。从那以后所有路径都用pathlib.Path拼接绝对路径再也没出过这种低级问题。二是批量处理前先跑一次“预演模式”。我脚本里会加一个dry_runTrue的参数在这种模式下只打印将要处理的文件清单和插入位置不真正操作PSCAD、不写Word文件。第一次在陌生电脑上跑时先预演一遍确认流程和预期一致再全自动执行能省很多调试时间。三是把PSCAD数据文件的整理当成一个独立环节。导出的CSV文件命名规则越规范越好比如case1_2024_abc_start.csv这种带场景、时间、工况的格式。后面重绘脚本只要按文件名规律批量读取就能自动生成整套报告配图这才是数据重绘方案最大的价值——图和报告可以完全分离管理。这套流程我用了大半年最大的感受是把重复劳动交给脚本之后节省下来的时间可以用来反复检查仿真结果的合理性而不是把精力浪费在画图排版上。希望这篇分享对你有帮助。如果你也在做电力仿真报告遇到什么新的坑欢迎在评论区交流。
返回列表