dsh-plugin-pscad-expert-vyma 1.0.10 → 1.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/index.js +2 -1
- package/package.json +1 -1
- package/plugin.yaml +1 -1
- package/tools/main.py +107 -9
package/README.md
CHANGED
package/index.js
CHANGED
|
@@ -29,7 +29,8 @@ export const Config = Schema.object({
|
|
|
29
29
|
.description('Python 解释器路径;默认自动探测(PATH→py 启动器→常见安装位置),无需手动配置'),
|
|
30
30
|
toolsCli: Schema.string().default('./tools/cli.py'),
|
|
31
31
|
defaultFolder: Schema.string().default(''),
|
|
32
|
-
timeoutMs: Schema.number().default(
|
|
32
|
+
timeoutMs: Schema.number().default(300000)
|
|
33
|
+
.description('单次工具调用总超时(毫秒);PSCAD 冷启动等待上限 180s,需留余量'),
|
|
33
34
|
})
|
|
34
35
|
|
|
35
36
|
// ---------------- Python 解释器自动探测(免 profile YAML 配置) ----------------
|
package/package.json
CHANGED
package/plugin.yaml
CHANGED
package/tools/main.py
CHANGED
|
@@ -68,7 +68,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
|
|
68
68
|
CONFIG_FILE_NAME: str = '.pscad_config.json' # 工作区本地配置文件
|
|
69
69
|
PSCAD_PORT: int = int(os.environ.get("PSCAD_PORT", "54329"))
|
|
70
70
|
DEFAULT_FOLDER: str = os.environ.get("PSCAD_WORK_DIR", "") # 无硬编码;空则回退当前工作目录
|
|
71
|
-
LAUNCH_WAIT_S: int =
|
|
71
|
+
LAUNCH_WAIT_S: int = 180 # PSCAD 冷启动(拉起→自动化端口就绪)实测可能 >60s,放宽到 180s
|
|
72
72
|
CONNECT_TIMEOUT_S: int = 20
|
|
73
73
|
|
|
74
74
|
# 配置缺失时的标准 Agent 返回(裸字典,供大模型识别并触发对话式引导)
|
|
@@ -228,15 +228,31 @@ def init_pscad_environment(exe_path: str, pylib_path: str = '') -> Dict[str, Any
|
|
|
228
228
|
"""
|
|
229
229
|
try:
|
|
230
230
|
if not pylib_path:
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
231
|
+
# ① site-packages 已有 pip 版 mhi-pscad(Python 3.12+ 机器常见):
|
|
232
|
+
# 直接使用(空 pylib 表示不记录路径,运行时优先直导),无需发现
|
|
233
|
+
ok_sp, _ = _trial_import_mhi('')
|
|
234
|
+
if ok_sp:
|
|
235
|
+
pylib_path = ''
|
|
234
236
|
else:
|
|
237
|
+
discovered = _discover_pylib(exe_path)
|
|
238
|
+
if discovered:
|
|
239
|
+
pylib_path = discovered
|
|
240
|
+
else:
|
|
241
|
+
return {'error': 'ConfigMissing',
|
|
242
|
+
'instruction': ('未能在 PSCAD 安装目录自动发现 mhi.pscad,'
|
|
243
|
+
'site-packages 也无可用版本。请确认 PSCAD 5.x '
|
|
244
|
+
'安装完整(应有 bin/win64/mhi.zip),或 '
|
|
245
|
+
'pip install mhi-pscad 后重试,或手动提供 '
|
|
246
|
+
'mhi.pscad 库路径后再调用 init_pscad_environment。')}
|
|
247
|
+
# 保存前试导入验证:防止自动发现/指定路径在当前 Python 下不可导入
|
|
248
|
+
# (如 mhi.zip 2.2.1 依赖 Python 3.12 已移除的 distutils)时,把原本
|
|
249
|
+
# 可用的配置覆盖成坏配置。导入失败一律不保存,返回引导。
|
|
250
|
+
if pylib_path:
|
|
251
|
+
ok_imp, imp_msg = _trial_import_mhi(pylib_path)
|
|
252
|
+
if not ok_imp:
|
|
235
253
|
return {'error': 'ConfigMissing',
|
|
236
|
-
'instruction': ('
|
|
237
|
-
|
|
238
|
-
'bin/win64/mhi.zip),或手动提供 mhi.pscad '
|
|
239
|
-
'库路径后再调用 init_pscad_environment。')}
|
|
254
|
+
'instruction': ('mhi.pscad 无法在当前 Python 下导入,配置未保存:%s'
|
|
255
|
+
% imp_msg)}
|
|
240
256
|
r = save_config(exe_path, pylib_path)
|
|
241
257
|
if r['status'] == 'success':
|
|
242
258
|
r['data']['message'] = ('PSCAD 环境配置已保存。后续连接将自动使用该配置,'
|
|
@@ -246,6 +262,20 @@ def init_pscad_environment(exe_path: str, pylib_path: str = '') -> Dict[str, Any
|
|
|
246
262
|
return _err(exc)
|
|
247
263
|
|
|
248
264
|
|
|
265
|
+
def _trial_import_mhi(pylib: str) -> Tuple[bool, str]:
|
|
266
|
+
"""试导入验证:确认 pylib(目录或 zip;空串=仅 site-packages 直导)可导入 mhi.pscad。
|
|
267
|
+
|
|
268
|
+
不抛异常;失败返回带指引的消息(Python ≥3.12 的 distutils 问题等)。
|
|
269
|
+
"""
|
|
270
|
+
try:
|
|
271
|
+
if pylib and pylib not in sys.path:
|
|
272
|
+
sys.path.insert(0, pylib)
|
|
273
|
+
import mhi.pscad # noqa: F401
|
|
274
|
+
return True, ''
|
|
275
|
+
except Exception as exc:
|
|
276
|
+
return False, _mhi_import_error(exc, pylib or '<site-packages>')
|
|
277
|
+
|
|
278
|
+
|
|
249
279
|
def _cfg_exe() -> Optional[str]:
|
|
250
280
|
"""解析 PSCAD 可执行文件路径:本地配置优先,环境变量降级回退。"""
|
|
251
281
|
cfg = load_config()
|
|
@@ -492,6 +522,57 @@ def _gen_name(comp_type: str) -> str:
|
|
|
492
522
|
# ---------------------------------------------------------------------------
|
|
493
523
|
# 工具集 1:连接与工程加载(懒加载 mhi)
|
|
494
524
|
# ---------------------------------------------------------------------------
|
|
525
|
+
def _persist_case(prj: Any, project_name: str, folder: str) -> Optional[str]:
|
|
526
|
+
"""尽力把会话内新建工程落盘,返回实际 .pscx 路径;找不到返回 None。
|
|
527
|
+
|
|
528
|
+
mhi-pscad 2.9.7 的 create_case(project_name, folder=...) 与 save() 都可能
|
|
529
|
+
不按 folder 写 .pscx(工程仅在 PSCAD 会话内存)。依次尝试:
|
|
530
|
+
① save/save_as/save_case 变体;② prj 路径属性;③ 候选目录全盘搜同名文件。
|
|
531
|
+
"""
|
|
532
|
+
expected = os.path.join(folder, project_name + '.pscx')
|
|
533
|
+
try:
|
|
534
|
+
# ① 常见保存 API
|
|
535
|
+
for meth, args in (('save', ()), ('save_as', (expected,)),
|
|
536
|
+
('save_case', (expected,))):
|
|
537
|
+
try:
|
|
538
|
+
fn = getattr(prj, meth)
|
|
539
|
+
fn(*args)
|
|
540
|
+
except Exception:
|
|
541
|
+
continue
|
|
542
|
+
if os.path.isfile(expected):
|
|
543
|
+
return expected
|
|
544
|
+
# ② prj 自带路径属性
|
|
545
|
+
for attr in ('path', 'filename', 'file_name', 'case_path',
|
|
546
|
+
'project_path'):
|
|
547
|
+
try:
|
|
548
|
+
p = getattr(prj, attr)
|
|
549
|
+
if callable(p):
|
|
550
|
+
p = p()
|
|
551
|
+
if isinstance(p, str) and p.lower().endswith('.pscx') \
|
|
552
|
+
and os.path.isfile(p):
|
|
553
|
+
return p
|
|
554
|
+
except Exception:
|
|
555
|
+
continue
|
|
556
|
+
# ③ 候选目录搜最近生成的同名文件
|
|
557
|
+
import glob as _glob
|
|
558
|
+
bases = [folder, os.getcwd(), os.path.expanduser('~/Documents'),
|
|
559
|
+
os.path.expanduser('~/PSCAD')]
|
|
560
|
+
best = None
|
|
561
|
+
for base in bases:
|
|
562
|
+
try:
|
|
563
|
+
hits = _glob.glob(os.path.join(base, '**',
|
|
564
|
+
project_name + '.pscx'),
|
|
565
|
+
recursive=True)
|
|
566
|
+
except Exception:
|
|
567
|
+
hits = []
|
|
568
|
+
for h in hits:
|
|
569
|
+
if best is None or os.path.getmtime(h) > os.path.getmtime(best):
|
|
570
|
+
best = h
|
|
571
|
+
return best
|
|
572
|
+
except Exception:
|
|
573
|
+
return None
|
|
574
|
+
|
|
575
|
+
|
|
495
576
|
def connect_and_load_project(project_name: str,
|
|
496
577
|
folder: Optional[str] = None,
|
|
497
578
|
create_if_missing: bool = False,
|
|
@@ -528,6 +609,7 @@ def connect_and_load_project(project_name: str,
|
|
|
528
609
|
|
|
529
610
|
folder = folder or (DEFAULT_FOLDER or os.getcwd())
|
|
530
611
|
_prj_folder = folder
|
|
612
|
+
pscx_path: Optional[str] = None # 实际落盘的 .pscx 路径(探测后回报)
|
|
531
613
|
|
|
532
614
|
prj = None
|
|
533
615
|
try:
|
|
@@ -541,13 +623,23 @@ def connect_and_load_project(project_name: str,
|
|
|
541
623
|
try:
|
|
542
624
|
_pscad.load(pscx.replace('\\', '/'))
|
|
543
625
|
prj = _pscad.project(project_name)
|
|
626
|
+
pscx_path = pscx
|
|
544
627
|
except Exception as exc:
|
|
545
628
|
return _err('加载 %s 失败: %s' % (pscx, exc))
|
|
546
629
|
|
|
547
630
|
if prj is None and create_if_missing:
|
|
548
631
|
try:
|
|
632
|
+
# 目录不存在可能导致 create_case 静默不落盘:先确保目标目录存在
|
|
633
|
+
try:
|
|
634
|
+
os.makedirs(folder, exist_ok=True)
|
|
635
|
+
except Exception:
|
|
636
|
+
pass
|
|
549
637
|
_pscad.create_case(project_name, folder=folder)
|
|
550
638
|
prj = _pscad.project(project_name)
|
|
639
|
+
pscx_path = _persist_case(prj, project_name, folder)
|
|
640
|
+
if pscx_path:
|
|
641
|
+
folder = os.path.dirname(pscx_path) # 会话状态跟随真实落盘目录
|
|
642
|
+
_prj_folder = folder
|
|
551
643
|
except Exception as exc:
|
|
552
644
|
return _err('create_case 失败: %s' % exc)
|
|
553
645
|
|
|
@@ -563,7 +655,13 @@ def connect_and_load_project(project_name: str,
|
|
|
563
655
|
except Exception as exc:
|
|
564
656
|
_main = None
|
|
565
657
|
return _err('打开 Main 画布失败: %s' % exc)
|
|
566
|
-
|
|
658
|
+
result = {'project': project_name, 'canvas': 'Main',
|
|
659
|
+
'pscx_path': pscx_path}
|
|
660
|
+
if create_if_missing and not pscx_path:
|
|
661
|
+
result['warning'] = ('create_case 未能在磁盘找到 .pscx(mhi 2.9.7 可能仅'
|
|
662
|
+
'内存建工程):工程仅存在于当前 PSCAD 会话,退出即丢;'
|
|
663
|
+
'建议改用已有模板 .pscx 加载,或提供可写目录。')
|
|
664
|
+
return _ok(result)
|
|
567
665
|
except Exception as exc:
|
|
568
666
|
return _err(exc)
|
|
569
667
|
|