feat: 添加 EGM 计算动画可视化功能

在 web 界面中实现 EGM 计算过程的动画展示,包括地线保护弧、导线暴露弧和地面线的动态绘制。重构 main.py 以支持可选的动画参数传递,并新增 Animation.vue 组件和 WebAnimation 类实现前后端交互。
This commit is contained in:
dmy
2026-03-03 15:58:57 +08:00
parent cee451914a
commit a65ce23cee
5 changed files with 409 additions and 18 deletions

View File

@@ -23,6 +23,126 @@ from core import Parameter
from main import parameter_display, run_egm
class WebAnimation:
"""
Web 动画类,将 Python 端的 Animation 调用映射到前端 JavaScript
对应 Animation.vue 的功能
"""
def __init__(self, window=None):
self._window = window
self._disable = True # 默认禁用
def set_window(self, window):
"""设置窗口对象"""
self._window = window
def enable(self, enabled: bool):
"""
启用/禁用动画
对应 animation.py 的 enable 方法和 Animation.vue 的 enable 方法
"""
self._disable = not enabled
if self._window:
js_code = f'if(window.animationApi){{window.animationApi.enable({str(enabled).lower()})}}'
self._window.evaluate_js(js_code)
def init_fig(self):
"""初始化画布"""
if self._disable or not self._window:
return
js_code = 'if(window.animationApi){window.animationApi.initFig()}'
self._window.evaluate_js(js_code)
def add_rs(self, rs: float, rs_x: float, rs_y: float):
"""
添加地线保护弧RS 圆)
对应 animation.py 的 add_rs 方法
"""
if self._disable or not self._window:
return
js_code = f'if(window.animationApi){{window.animationApi.addRs({rs}, {rs_x}, {rs_y})}}'
self._window.evaluate_js(js_code)
def add_rc(self, rc: float, rc_x: float, rc_y: float):
"""
添加导线暴露弧RC 圆)
对应 animation.py 的 add_rc 方法
"""
if self._disable or not self._window:
return
js_code = f'if(window.animationApi){{window.animationApi.addRc({rc}, {rc_x}, {rc_y})}}'
self._window.evaluate_js(js_code)
def add_rg_line(self, line_func):
"""
添加地面线RG 线)
对应 animation.py 的 add_rg_line 方法
Args:
line_func: 一个函数,接收 x 返回 y
"""
if self._disable or not self._window:
return
# 生成线上的点,传递给前端
# 由于无法直接传递函数,我们预先计算一些点
import numpy as np
x_points = np.linspace(0, 300, 50)
y_points = [line_func(x) for x in x_points]
points = list(zip(x_points.tolist(), y_points))
js_code = f'''
if(window.animationApi){{
window.animationApi.addRgLine({json.dumps(points)})
}}
'''
self._window.evaluate_js(js_code)
def add_expose_area(
self,
rc_x: float,
rc_y: float,
intersection_x1: float,
intersection_y1: float,
intersection_x2: float,
intersection_y2: float
):
"""
添加暴露弧区域(两条红线)
对应 animation.py 的 add_expose_area 方法
"""
if self._disable or not self._window:
return
js_code = f'''if(window.animationApi){{
window.animationApi.addExposeArea(
{rc_x}, {rc_y},
{intersection_x1}, {intersection_y1},
{intersection_x2}, {intersection_y2}
)
}}'''
self._window.evaluate_js(js_code)
def clear(self):
"""清除画布"""
if self._disable or not self._window:
return
js_code = 'if(window.animationApi){window.animationApi.clear()}'
self._window.evaluate_js(js_code)
def pause(self):
"""
暂停并刷新
对应 animation.py 的 pause 方法
"""
if self._disable or not self._window:
return
js_code = 'if(window.animationApi){window.animationApi.pause()}'
self._window.evaluate_js(js_code)
# 添加延迟以便动画可见
import time
time.sleep(0.1) # 增加延迟,让用户看清动画
class WebHandler:
"""Web日志处理器"""
def __init__(self, callback=None):
@@ -86,6 +206,7 @@ class EGMWebApp:
self._loguru_handler_id = None
self._log_queue: queue.Queue = queue.Queue()
self._running = False
self.animation = WebAnimation() # Web 动画实例
def _process_log_queue(self):
"""处理日志队列,在主线程中定时调用"""
@@ -220,8 +341,8 @@ class EGMWebApp:
logger.info("开始执行 EGM 计算...")
# 调用 main.py 的核心计算函数
result = run_egm(para)
# 调用 main.py 的核心计算函数,传递 animation 对象
result = run_egm(para, self.animation)
self.add_log("info", "EGM 计算完成")
@@ -467,6 +588,7 @@ def start_webview():
# 将窗口对象传递给 API
api.window = window
api.animation.set_window(window)
# 启动
logger.info("启动 EGM Web 界面...")