简介:电力系统后台作图软件是一套面向电力行业工程师与SCADA系统开发者的可视化绘图程序,用于变电站接线图、输配电网络及设备连接关系的快速建模,包含发电机、变压器、断路器、隔离开关等完整电力符号库,并支持设备状态异常时的图标闪烁告警,方便调度人员第一时间发现故障。压缩包共108个文件,大小5.45MB,属于典型的Windows图形界面工程:以cpp源文件、h头文件、obj目标文件为主,辅以bmp/ico图标资源和dsp/dsw等工程配置文件,pdb调试信息与rc资源脚本也一并保留,便于直接还原工程并跟踪绘图与消息响应逻辑。目前已有909人学习,包内还涵盖了自动布线、图层管理、故障模拟等功能模块对应的代码与位图资源,适合希望掌握电力系统后台绘图软件开发思路,或在此基础上构建定制化SCADA界面的开发者参考。
1. 电力系统作图:不是画线,是管住电气符号本身
做电力系统后台图,最耗时间的从来不是画那条母线,而是找符号、拖符号、对端子、改编号。我见过不少同事用通用绘图软件画单线图,画到后面符号缩放变形、端子对不上、打印出来线宽全乱,最后整个工程文件成了没人敢碰的黑匣子。这套电力系统后台作图软件的核心价值,是把电力符号做成带电气属性、能校验、能联动台账的工程化对象,而不是一堆散装线段。适合配电设计、继保调试、工厂电气运维这类天天跟单线图、接线图、二次回路打交道的人,画完能直接导图、出清单、反查设备,不是只图个好看。
2. 符号库与图形内核:为什么这套符号扛得住电力图场景
2.1 电力符号不是“画”出来的,是带电气属性的对象
普通的绘图软件里,一个断路器就是几根线和一个矩形,你把图拉大,它还是一堆几何图元。但电力系统图纸里,一个断路器背后带着额定电流、开断能力、所在间隔、操作机构型号,这些属性必须跟着符号走,而不是记在画图人的脑子里。
这套软件的符号定义走的是“JSON 描述属性 + SVG 描述图形”的双格式结构。JSON 里记录的是电气工程关心的字段,比如电压等级、端子编号、设备类型,SVG 负责呈现外观。一个断路器符号的 JSON 大致长这样:
{ "symbolId": "CB_12KV_01", "name": "12kV真空断路器", "category": "switchgear", "voltageLevel": "12kV", "ports": [ { "id": "P1", "x": 0, "y": -60, "direction": "top" }, { "id": "P2", "x": 0, "y": 60, "direction": "bottom" } ], "attributes": { "ratedCurrent": "630A", "breakingCapacity": "25kA", "symbolVersion": "1.2" } }这里的关键是ports数组。每个端子都用相对于符号基准点的坐标定义,方向字段标明端子朝哪个方向引出导线。这么做的好处是,符号在图纸上旋转、镜像后,程序能立刻算出端子的世界坐标,不用去解析图形里那几条线的几何关系。
参数说明:symbolId全库唯一,是后续关联台账和导出清单的主键;direction字段决定自动连线时导线从哪边引出,写错了会导致导线绕路甚至交叉。符号基准点我一般固定在图形的几何中心,绝不放角落。曾经图省事把基准点定在左下角,结果后面所有关于旋转和镜像的坐标计算全部要额外补偿,纯粹给自己找事。
2.2 图形内核:SVG 与新层叠加逻辑
图形部分用 SVG 而不是位图或 Canvas,原因有三。第一,SVG 是矢量格式,符号在 100% 和 500% 缩放下边缘都清晰,打印出图不会出现马赛克。第二,SVG 的 XML 结构方便程序做局部替换,比如把符号里的文字标注单独抽出来改编号。第三,SVG 天然支持图层和分组,符号的图形、端子标记、动态标注可以拆成独立分组控制显隐。
一个符号的 SVG 定义里,比较合理的分组方式是这样:
<svg viewBox="-100 -100 200 200" xmlns="http://www.w3.org/2000/svg"> <g id="symbol-body"> <rect x="-40" y="-40" width="80" height="80" fill="#fff" stroke="#000" stroke-width="2"/> <circle cx="0" cy="35" r="6" fill="#fff" stroke="#000"/> <circle cx="0" cy="-35" r="6" fill="#fff" stroke="#000"/> </g> <g id="port-markers" display="none"> <rect x="-8" y="-68" width="16" height="16" fill="#0f0"/> <rect x="-8" y="52" width="16" height="16" fill="#0f0"/> </g> </svg>viewBox统一用-100 -100 200 200,也就是符号坐标系原点在中心,横向纵向都是 -100 到 100。port-markers分组默认隐藏,只在编辑状态下显示,方便看图的人确认端子的实际位置。
逻辑说明:程序加载 SVG 时不是直接把整个文件塞进画布,而是先把symbol-body组里的图形解析出来用于渲染,再把 JSON 里的ports坐标换算成 SVG 坐标,在调试模式下叠加绿色方框。这样画的时候看到的是设备图形,但程序内部始终按端子坐标在做逻辑判断。
参数说明:stroke-width="2"在屏幕上看着合适,打印时却偏细。后面会讲到导出环节怎么单独处理线宽,这里先记住一个原则:SVG 里的线宽是屏幕显示值,出图线宽由导出参数另算。
2.3 图层分层:基准层、符号层、标注层各管一摊
后台作图最怕的是图面混乱——母线、符号、标注全在一个图层里,想整体改个线型都无从下手。这套资源里默认分三层:基准层画母线、接地网、电缆沟走向,符号层放置设备符号实例,标注层放回路编号、设备编号和注释文字。
图层分完之后有个操作习惯值得借鉴:符号层锁定,禁止直接在这个层画线。所有一次接线都在基准层画,符号只是“放”到线上。这样做的好处是,后期替换设备型号时只动符号层,基准层的拓扑关系完全不受影响。
图层管理还有个容易被忽略的点:图层顺序决定遮挡关系。基准层必须在最底,符号层中间,标注层最顶。标注一旦被符号盖住,打印出图时编号就看不清。软件里还提供批量置底、置顶的操作,但我不建议依赖它,从源头按层画才是正路。
3. 把符号库拉起来:从零搭一个配电工程模板
3.1 工程目录结构与初始化脚本
拿到这套资源后,第一步不是开画布,而是按它的工程目录规范建项目。目录结构直接决定后面符号引用、台账导出、批量导图能不能跑通。标准的工程目录长这样:
project_root/ ├── drawings/ # 图纸文件,每张图一个 json ├── symbols/ # 符号库 │ ├── switchgear/ # 开关设备 │ ├── transformer/ # 变压器 │ ├── protection/ # 继电保护 │ └── misc/ # 杂项符号 ├── templates/ # 图框模板 ├── exports/ # 导出结果 └── project.json # 工程配置文件可以用一段脚本自动建目录并生成空的工程配置:
#!/bin/bash # init_electrical_project.sh PROJECT_NAME=$1 mkdir -p "$PROJECT_NAME"/{drawings,symbols/switchgear,symbols/transformer,symbols/protection,symbols/misc,templates,exports} cat > "$PROJECT_NAME/project.json" <<EOF { "projectName": "$PROJECT_NAME", "voltageLevels": ["10kV", "0.4kV"], "defaultLayerOrder": ["base", "symbol", "annotation"], "snapThreshold": 12, "defaultLineWidthPrint": 0.5 } EOF echo "工程目录创建完成:$PROJECT_NAME"参数说明:snapThreshold是连线吸附阈值,单位是像素,设 12 是我习惯的值。太小了鼠标要对得很准才触发吸附,太大了容易把不该连的端子吸到一起。defaultLineWidthPrint是打印线宽,0.5mm 对应国标里一般图线的要求,母线这类粗线导出时可以单独覆盖。
脚本执行完,再把symbols目录下的符号 JSON 和 SVG 成对放进对应子目录,工程就算立起来了。注意drawings目录下每张图是独立的 JSON 文件,但里面引用的符号 id 必须和symbols目录里的symbolId完全一致,大小写都不能差。
3.2 连线吸附规则与接线绘制流程
画一次接线图时,最常见的操作是“放符号—连线—再放符号—再连线”。这套资源把连线做成自动吸附模式:当鼠标拖动导线端点靠近某个符号的端子到阈值范围内时,端点自动锁定到端子上,并且导线起点记录的是端子 id 而不是坐标。
吸附规则参数表:
| 参数 | 推荐值 | 作用 |
|---|---|---|
| snapThreshold | 12px | 吸附触发距离,太大易误连 |
| portSnapRadius | 8px | 端子识别半径,需小于符号最小边距 |
| wireRoutingMode | orthogonal | 导线走线方式,正交更符合电力图习惯 |
| allowCrossing | true | 允许导线交叉,交叉点不做电气连接 |
手工画线的过程里,最实用的是正交走线。两个符号之间连线,程序自动走出横平竖直的路径,遇到障碍物自动绕开。这个功能在画母线分段和手车柜间隔时特别省时间。要注意的是,它绕障时可能多走一段不必要的路径,所以画完后建议选中导线看一眼实际路径,必要时手动拖动中间节点修正。
线路画完后,我习惯先跑一遍“端子连接检查”脚本,看看有没有放了符号却没接线的悬空端子:
#!/usr/bin/env python3 # check_unconnected_ports.py import json, sys def main(drawing_path): with open(drawing_path, 'r', encoding='utf-8') as f: drawing = json.load(f) connected_ports = set() for wire in drawing['wires']: connected_ports.add((wire['fromSymbol'], wire['fromPort'])) connected_ports.add((wire['toSymbol'], wire['toPort'])) problems = [] for inst in drawing['symbolInstances']: symbol_def = inst['symbolDef'] for port in symbol_def['ports']: key = (inst['instanceId'], port['id']) if key not in connected_ports: problems.append(f"{inst['instanceId']} 的端子 {port['id']} 未接线") if problems: print("发现未连接端子:") for p in problems: print(" " + p) sys.exit(1) else: print("所有端子均已连接。") if __name__ == "__main__": main(sys.argv[1])逻辑说明:脚本先从所有导线里收集已经建立连接的端点对,再去遍历图里的符号实例,逐个核对 JSON 里定义的端子是否出现在连接集合中。凡是没出现的端子,说明它在图上悬空着,多半是画图时漏了一根线。
这个脚本适合画完一段接线后立刻跑一遍。它输出的未连接端子会精确到符号实例和端子号,直接定位到图面上就能补线。对于变压器这类带中性点端子的符号,悬空是正常的,可以加白名单跳过,别一刀切全报错。
4. 符号与模型联动:让图纸能算、能出单线图清单
4.1 符号实例到设备台账的映射规则
画图只是第一步,电力系统后台图的真正价值在于图上的每个符号都能对应到一台真实设备。这套资源里,符号实例 id 采用设备编码规则,图纸文件和设备台账靠这个 id 建立关联。
| 设备编码前缀 | 设备类型 | 电压等级 | 典型符号 id 示例 |
|---|---|---|---|
| CB | 断路器 | 10kV | CB_T1_L1_01 |
| DS | 隔离开关 | 10kV | DS_T1_BUS_01 |
| CT | 电流互感器 | 10kV | CT_T1_501_02 |
| TR | 变压器 | 10kV/0.4kV | TR_1T_800kVA |
| MC | 接触器 | 0.4kV | MC_M1_01 |
符号 id 里的分段信息不是随便起的。比如CB_T1_L1_01,第一段是设备类型,第二段是所属的配电间隔,第三段是回路编号,最后是序号。这样设计后,台账导入时只需要一个简单脚本解析 id 的各个字段,就能自动把图上的符号实例和设备属性表对齐。
实际项目中,台账数据通常出自物资管理系统,格式是 Excel 或 CSV。常见的做法是把台账导出成 CSV,然后写脚本按symbolId匹配设备名称、规格型号、安装位置回填到图纸的标注层。这里有一个容易踩的坑:台账里的设备编号可能前后有空格、全角半角混用,匹配前一律做 strip 和半角转换。
4.2 批量导出单线图与设备清单
图纸画好、台账关联好之后,出图是最后一步。这套资源的导出脚本支持三种产物:SVG 原图、PDF 打印图、设备清单 CSV。导出参数集中在脚本里,按项目要求调整。
#!/usr/bin/env python3 # export_drawings.py import json, subprocess, csv, os def export_svg_to_pdf(svg_path, pdf_path, line_width=0.5): # 将SVG中的线宽统一替换为打印宽度 with open(svg_path, 'r', encoding='utf-8') as f: svg_content = f.read() svg_content = svg_content.replace('stroke-width="2"', f'stroke-width="{line_width}"') with open(svg_path, 'w', encoding='utf-8') as f: f.write(svg_content) subprocess.run(['inkscape', svg_path, '--export-filename=' + pdf_path], check=True) def export_device_list(drawing, csv_path): with open(csv_path, 'w', newline='', encoding='utf-8') as f: writer = csv.writer(f) writer.writerow(['symbolId', 'deviceName', 'voltageLevel', 'ratedCurrent', 'location']) for inst in drawing['symbolInstances']: writer.writerow([ inst['instanceId'], inst.get('deviceName', ''), inst.get('voltageLevel', ''), inst.get('ratedCurrent', ''), inst.get('location', '') ]) # 主流程:遍历drawings目录下所有图纸文件 for drawing_file in os.listdir('drawings'): if not drawing_file.endswith('.json'): continue with open(os.path.join('drawings', drawing_file), 'r', encoding='utf-8') as f: drawing = json.load(f) export_svg_to_pdf( os.path.join('drawings', drawing_file.replace('.json', '.svg')), os.path.join('exports', drawing_file.replace('.json', '.pdf')) ) export_device_list(drawing, os.path.join('exports', drawing_file.replace('.json', '_清单.csv')))逻辑说明:export_svg_to_pdf函数先把 SVG 里所有 2 像素宽的线条替换成 0.5mm 宽度,再调 Inkscape 的命令行接口做格式转换。替换操作必须放在转换之前,否则打印出来的图所有线条都是一个宽度,母线、接地线、控制线的粗细区别全没了。
参数说明:line_width=0.5是常规线的打印宽度,单位是毫米。母线这类需要强调的粗线如果在 SVG 里用了stroke-width="6",这里会被一刀切替换掉,所以有特殊线宽要求的图,要么单独处理,要么在 SVG 里改用自定义 class 再做映射,别用裸的stroke-width属性写死。
导出的设备清单 CSV 可以直接当备件清册用。以前我画完一张高压配电图,统计开关柜里的设备数量要花半小时,现在脚本跑完一秒出结果,准确性还高——人工数错的问题彻底没有了。
5. 避坑与常见问题排查:五个典型翻车场景
5.1 符号缩放后端子漂移,导线跟不上
现象:把符号整体放大 1.5 倍后,端子标记显示在新的位置,但已经画好的导线端点还连接在旧的坐标上,导线看上去和符号脱开了。
原因:符号缩放操作只更新了 JSON 里的scale字段,没有重新计算端子的世界坐标。连线存储的坐标是在旧尺寸下记录的,缩放后程序没有触发布线刷新。
解决:软件里放符号、缩放符号必须走统一的“更新符号几何”入口,这个入口会重算ports的世界坐标并通知所有关联导线更新端点。如果符号已经被缩放但导线没跟上,选中受影响导线,右键执行“重新连接端子”,程序会按新坐标重新吸附。从那以后我每次缩完符号都顺手执行一次连接刷新,不再有拖线的情况。
5.2 打印时所有线宽全变成细线,母线成了头发丝
现象:SVG 预览正常,导出的 PDF 里粗线、细线全部一个宽度,母线看起来像细导线。
原因:前面导出脚本里演示过,裸的stroke-width会被一刀切替换。实际工程里母线、接地线往往直接用stroke-width="6"这类固定值,替换逻辑把它们和细线同等处理了。
解决:画图时所有线条统一用 class 区分,母线用class="bus-bar",控制线用class="control-wire",导出时按 class 映射到不同打印宽度。我给这个项目写过一个样式表,bus-bar映射 1.2mm,control-wire映射 0.35mm,效果符合识图习惯。
5.3 符号库加载失败,报重复 id
现象:从别的工程拷了一个变压器符号进symbols/transformer,再打开工程时报“symbolId 重复,库加载终止”。
原因:两个符号的 JSON 文件里symbolId都是TR_10KV_1000,文件名不同但内部 id 相同。程序加载时按 id 建索引,后加载的覆盖先加载的,最终检测到冲突。
解决:拷入符号后第一时间检查 id 的唯一性。我习惯在执行加载前跑一段 grep 命令,把整个 symbols 目录里的symbolId全部提出来排序去重看有没有重复:
grep -h '"symbolId"' symbols/**/*.json | sort | uniq -d输出为空说明无重复。如果有重复,改掉其中一个的 id,同时记得去图纸文件里检查有没有引用旧 id 的实例。改 id 不是只改 JSON 里的字段,所有关联的引用都要跟着改,所以新建符号时命名规范一点,能省掉后期大量返工。
5.4 T 型连接点无法吸附,三通总是差一点
现象:两根导线需要形成 T 型分支,但导线端点靠近另一根导线的中部时就是不触发吸附,手动拖动又对不准。
原因:默认的吸附规则只对符号端子生效,导线对导线没有吸附判定。T 型连接需要程序识别“导线上的任意点”作为锚点,这超出了基础吸附逻辑。
解决:画 T 型分支时先画主线路,再从分支点引出一段短线,把这段短线的端点拖到主线上,程序检测到端点落在某条导线线段上时自动创建分支节点。如果依旧吸不上,确认分支点不在导线拐角处——拐角处是两段线段的交界,程序可能不知道你要连哪一段。
5.5 跨页面连线后设备清单丢失设备
现象:一张图上的变压器符号通过跨页连接符连到另一张图,导出设备清单时变压器没出现在任何一张图的清单里。
原因:跨页引用在脚本里被当成单独的导线记录,它引用的符号实例在另一张图中,但导出程序没有做跨图纸数据合并。
解决:导出清单时先合并所有图纸的符号实例,再去重。我给导出脚本加过一段预处理,把工程配置里声明的图纸全部读进来,按symbolId对齐后统一导出,这样跨页设备只出现一次,且归属到它的主页图纸。
6. 进阶:用图框模板把出图时间压到十分钟之内
图纸画完了,图框、标题栏、会签栏这些格式化的东西反而成了后期最磨人的环节。这套资源的图框模板可以做到批量套用,我的做法是准备两个标准模板:A3 横向用于系统图,A4 纵向用于二次回路图。模板文件就是一个带特定图框的 SVG,图框里留了#PROJECT_NAME#、#DRAWING_NAME#、#PAGE_NO#这类占位符。
套模板时用一段脚本把占位符替换成实际值,输出到导出目录:
#!/usr/bin/env python3 # apply_template.py import sys, os, re def apply_template(template_path, output_path, params): with open(template_path, 'r', encoding='utf-8') as f: content = f.read() for key, value in params.items(): content = content.replace(f'#{key}#', value) with open(output_path, 'w', encoding='utf-8') as f: f.write(content) # 遍历工程中所有绘制完成的SVG,套用A3横向模板 drawing_params = { 'PROJECT_NAME': '某配电改造工程', 'DRAWING_NAME': '10kV高压一次系统图', 'PAGE_NO': '01/08', 'DATE': '2025-06-01' } apply_template('templates/a3_landscape.svg', 'exports/01_系统图.svg', drawing_params)模板替换之外,还有一个小技巧能把整套图纸的编号统一下来:每张图文件名统一用“页码_图名.svg”的格式,导出时按文件名排序,自动填入图框的页码区。这套流程跑顺之后,一套八张图的配电工程,从画完到套好图框打包发出去,十分钟足够了。
以前我第一套工程图没有做模板,每张图手工挪图框、改标题栏,光这些就搭进去一个下午。后来我强制自己每接到新项目先花二十分钟确认模板、占位符、批次导出参数,之后再画图就只管内容不管格式。从那以后每次新建工程,我都先复制一套模板目录,把工程名替换掉再动笔。希望帮到你。
本文还有配套的精品资源,点击获取