Py-Editor 是一个基于 PySide6 的可重用画布框架,用于构建图形化节点编辑器。它提供了创建可视化节点图所需的核心组件,可以轻松扩展以支持各种应用场景,如可视化编程、流程设计、数据处理等。
- 可视化节点编辑:平移、缩放、框选、吸附连线,并在选中节点时突出相关连线
- 可扩展架构:通过注册机制自定义节点和连线的外观与行为
- Undo/Redo 支持:内置命令系统,支持完整的撤销/重做功能
- 序列化能力:支持将画布状态保存为字典格式,便于存储和恢复
- 剪贴板操作:支持复制、剪切和粘贴节点
- 自定义渲染:允许完全控制节点和连线的视觉呈现
包名是 py-editor,需要 Python 3.13 或更高版本。在这个仓库里:
uv sync
uv run python -m py_editor以下是一个简单的示例,展示如何创建一个基本的画布窗口:
from PySide6.QtWidgets import QApplication
from py_editor import CanvasController, CanvasRegistry, CanvasView, Port
app = QApplication([])
registry = CanvasRegistry()
controller = CanvasController()
view = CanvasView(registry=registry)
view.set_controller(controller)
# 创建节点
node_a = controller.create_node(
"Input",
position=(80.0, 120.0),
node_type="demo",
ports=[Port("value", "output")],
)
node_b = controller.create_node(
"Output",
position=(320.0, 120.0),
node_type="demo",
ports=[Port("value", "input")],
)
# 连接节点。connect 会检查输入口占用和成环,并记入撤销栈。
controller.connect(
node_a.id,
node_b.id,
source_port="value",
target_port="value",
)
view.refresh()
view.show()
app.exec()你可以通过注册工厂函数来自定义节点和连线的外观:
from PySide6.QtGui import QColor, QPen
from PySide6.QtCore import Qt
from py_editor import CanvasRegistry, CanvasNodeItem, CanvasEdgeItem
class CustomNodeItem(CanvasNodeItem):
def paint(self, painter, option, widget=None):
# 自定义节点绘制逻辑
painter.setBrush(QColor("#FF6B6B"))
painter.setPen(QPen(QColor("#4ECDC4"), 2))
# 绘制节点形状...
class CustomEdgeItem(CanvasEdgeItem):
def _update_pen(self):
# 自定义连线样式
pen = self.pen()
pen.setColor(QColor("#4ECDC4"))
pen.setWidth(2)
self.setPen(pen)
# 注册自定义工厂函数
registry = CanvasRegistry()
registry.register_node_factory("custom", lambda node, view: CustomNodeItem(node))
registry.register_edge_factory(
"custom",
lambda edge, source_item, target_item, view: CustomEdgeItem(
edge, source_item, target_item
),
)画布状态可以轻松地序列化为字典格式,方便保存和加载:
import json
from py_editor import CanvasState
# 保存画布状态
payload = controller.state.to_dict()
with open("layout.json", "w", encoding="utf-8") as fh:
json.dump(payload, fh, indent=2)
# 加载画布状态
with open("layout.json", "r", encoding="utf-8") as fh:
restored_state = CanvasState.from_dict(json.load(fh))
controller = CanvasController(restored_state)在空白处按住左键拖动可以平移画布。按住 Shift 再拖动是框选。滚轮缩放。从端口拖出连线,松手时吸附到兼容端口;拖动已有连线靠近端点的一段可以改接。拖线说明默认是两端的标题和端口 id,接不上时写明原因。
点中一个节点后,和它相连的线变成蓝色并画在上层,其余的线变淡。按 Esc 取消选择后,线条恢复。右键节点可以复制、删除或断开连线;右键空白处可以排列或适应画面。
Ctrl+Z/Ctrl+Y:撤销 / 重做Ctrl+C/Ctrl+X/Ctrl+V/Ctrl+D:复制 / 剪切 / 粘贴 / 复制一份Delete:删除选中的节点或连线Ctrl+A:全选Ctrl+0:适应全部节点Ctrl+Shift+0:适应选中的节点和连线Esc:取消选择;拖线时取消这次连接Ctrl++/Ctrl+-:放大 / 缩小Ctrl+L:从左到右排列Ctrl+Shift+L:从上到下排列Ctrl+F:按标题查找节点,并把它放到画面里
缩放到节点宽度不足约 48 像素时,卡片改成色块、连线改成直线,整图收成一张图再平移。滚轮最小可缩到 5%。
背景默认是随画面滚动的 24 像素网格,set_grid_enabled(False) 改成纯色。set_snap_to_grid(True) 后,松开节点时坐标落到同一网格上,撤销回到按下前的位置。拖动过程中不吸附。整张图放不进窗口时,右下角出现小地图,点一下或拖动可以把画面移到对应位置。小地图在松开后更新。连线默认不动画;set_edge_animation_enabled(True) 之后,窗口里同时看得见的连线不多时会沿方向流动,缩成色块后停止。
CanvasPolicy 默认每个输入口只保留一条线。端口 multi=True 的输入可以接入多条线。不允许连成环。两端都声明了 kind 且不一致时,连线会被拒绝。只有 controller.connect(...) 会加边,并记入撤销栈。
NodeData: 节点的序列化描述(ID、标题、类型、端口、元数据)EdgeData: 连线的序列化描述(类型、端点、端口、元数据)CanvasState: 内存中的图形结构,包含节点、连线和当前选择CanvasController: 撤销栈上的图编辑接口。加边只走connectCanvasPolicy: 吸附半径、输入口是否单连接、是否允许成环CanvasRegistry: 将节点/连线类型映射到工厂函数的注册表CanvasView: 基于 Qt 的交互式视图组件
画布可以通过以下方式扩展:
- 实现新的命令类(继承
py_editor.commands.CanvasCommand)以支持自定义撤销/重做行为 - 通过
CanvasRegistry.set_connection_preview_factory注册连接预览工厂函数 - 构建监听视图/控制器信号的插件,例如添加跟踪节点集合的分组覆盖层
本项目采用 MIT 许可证。详细信息请参见 LICENSE 文件。