跳轉至

外部控制(命令列與具名管道)

ReciPro 的巨集不僅可以在內建編輯器中執行,也可以從應用程式外部執行。共有兩種機制:

機制 形式 典型用途
命令列 (/m /o /x) 一次性:啟動 ReciPro → 執行巨集 →(可選)結束 批次處理、CI、排程工作
具名管道 (ReciPro.Macro.v1) 互動式:向執行中的 ReciPro 傳送巨集並接收結果 Python/Jupyter 指令碼、儀器控制電腦、與其他軟體整合

兩者都使用與編輯器相同的引擎執行巨集,因此可使用全部的內建 API


命令列執行

ReciPro.exe /m <macro.mcr> [/o <result.txt>] [/x]
參數 意義
/m 啟動後執行引數中找到的第一個實際存在的 *.mcr 檔。
/o <檔案> 靜默模式:不顯示任何對話方塊;巨集的 print() 輸出與錯誤追蹤會寫入 <檔案>(UTF-8)。失敗時處理程序結束碼為 1
/x 巨集結束後關閉 ReciPro(批次使用時建議——結束碼要等處理程序結束才會回傳給呼叫端)。

未使用 /o 時,錯誤會以一般對話方塊顯示(開發巨集時很方便)。加上 /o 則完全無人值守:語法錯誤、執行期錯誤、找不到巨集檔,甚至寫入結果檔失敗,都會以結束碼 1 收場。

範例:批次檔

ReciPro.exe /m C:\work\saed_series.mcr /o C:\work\result.txt /x
if errorlevel 1 (
    echo 巨集執行失敗:
    type C:\work\result.txt
)

範例:PowerShell

$p = Start-Process ReciPro.exe -ArgumentList '/m','C:\work\job.mcr','/o','C:\work\result.txt','/x' -Wait -PassThru
if ($p.ExitCode -ne 0) { Get-Content C:\work\result.txt }

具名管道接聽器

ReciPro 執行期間,可透過 Windows 具名管道 \\.\pipe\ReciPro.Macro.v1 接受其他程式送來的巨集。用戶端寫入巨集,ReciPro 執行後用戶端讀回結果——這是簡單的要求/回應流程,可從 Python、PowerShell、C# 或任何能開啟檔案的語言使用。

啟用

接聽器預設為關閉。請在主視窗中啟用:

選項 → Accept external macro commands (named pipe)

此設定會在下次啟動時保留。

安全性提醒

啟用期間,以相同 Windows 使用者身分執行的任何處理程序都能在 ReciPro 內執行巨集程式碼。其他使用者(以及其他電腦)的連線會被拒絕。請僅在確實使用外部控制時啟用。

通訊協定

項目 規格
管道名稱 \\.\pipe\ReciPro.Macro.v1 (本機,且僅限相同使用者)
要求 巨集原始碼以 UTF-8 寫入,並以單一 NUL 位元組 (0x00) 結尾。最大 1 MiB,須在 30 秒內送達。
回應 寫入 UTF-8 JSON {"output":"...","error":"..."} 後伺服器關閉連線(讀取至 EOF)。
output 巨集寫入 stdout/stderr 的全部內容(與 GUI 編輯器不同,這裡可以使用 print())。
error 成功時為空字串;失敗時為 Python 追蹤、語法錯誤或通訊協定錯誤訊息。
連線 一次連線 = 一個巨集。命令依抵達順序逐一執行。
狀態 Python 範圍與巨集編輯器共用,並在命令之間持續保留——在某個命令中定義的變數,下一個命令仍看得到。
執行個體 若同時執行多個 ReciPro 處理程序,只有第一個會接聽。

回應會在巨集執行完畢後才送出,因此耗時的模擬只是讓用戶端多等一會兒——完成偵測是自動的。

Python:最小範例

用戶端就是一般的 CPython——numpypandas、Jupyter 都能使用。管道本身不需要任何額外套件:

with open(r'\\.\pipe\ReciPro.Macro.v1', 'r+b', buffering=0) as f:
    f.write('print(ReciPro.CrystalList.Count)'.encode('utf-8') + b'\0')
    print(f.read().decode('utf-8'))     # {"output":"68\r\n","error":""}

Python:可重複使用的輔助函式

以下範例都使用這個小函式:

import json

PIPE = r'\\.\pipe\ReciPro.Macro.v1'

def recipro(code):
    """在 ReciPro 中執行 IronPython 巨集,並以 str 回傳其列印輸出。"""
    with open(PIPE, 'r+b', buffering=0) as f:
        f.write(code.encode('utf-8') + b'\0')
        res = json.loads(f.read().decode('utf-8'))
    if res['error']:
        raise RuntimeError(res['error'])
    return res['output']

Python:讀取 CIF 並儲存 SAED 圖樣

recipro('''
ReciPro.File.ReadCrystal(r"C:\\data\\rutile.cif")
ReciPro.DifSim.Open()
ReciPro.DifSim.Source_Electron()
ReciPro.DifSim.Energy = 200
ReciPro.Dir.ProjectAlongAxis(0, 0, 1)
ReciPro.DifSim.SaveAsPng(r"C:\\data\\rutile_001.png")
''')

Python:批次處理大量 CIF

import glob, os

recipro('ReciPro.DifSim.Open(); ReciPro.DifSim.Source_Electron(); ReciPro.DifSim.Energy = 200')

for cif in glob.glob(r'C:\data\*.cif'):
    png = os.path.splitext(cif)[0] + '_SAED.png'
    recipro(f'''
ReciPro.File.ReadCrystal(r"{cif}")
ReciPro.Dir.ProjectAlongAxis(0, 0, 1)
ReciPro.DifSim.SaveAsPng(r"{png}")
''')
    print('done:', cif)

Python:傾斜序列

for i in range(10):
    recipro(f'''
ReciPro.Dir.RotateAroundAxisInDeg(1, 0, 0, 1)
ReciPro.DifSim.SaveAsPng(r"C:\\data\\tilt_{i:02d}.png")
''')

Python:將繞射斑資料匯入 pandas

ReciPro.DifSim.SpotInfo() 會回傳 CSV 字串;以 print() 輸出後在用戶端解析:

import io, pandas as pd

csv_text = recipro('print(ReciPro.DifSim.SpotInfo())')
df = pd.read_csv(io.StringIO(csv_text))
print(df.head())

Python:狀態在命令之間持續保留

recipro('n = ReciPro.CrystalList.Count')   # 先定義一個變數...
print(recipro('print(n * 2)'))             # ...之後的命令即可使用

PowerShell

function Invoke-ReciProMacro([string]$Code) {
    $pipe = [System.IO.Pipes.NamedPipeClientStream]::new('.', 'ReciPro.Macro.v1', [System.IO.Pipes.PipeDirection]::InOut)
    $pipe.Connect(5000)
    $bytes = [System.Text.Encoding]::UTF8.GetBytes($Code) + [byte[]]@(0)
    $pipe.Write($bytes, 0, $bytes.Length); $pipe.Flush()
    $ms = [System.IO.MemoryStream]::new(); $buf = New-Object byte[] 4096
    while (($n = $pipe.Read($buf, 0, $buf.Length)) -gt 0) { $ms.Write($buf, 0, $n) }
    $pipe.Dispose()
    [System.Text.Encoding]::UTF8.GetString($ms.ToArray()) | ConvertFrom-Json
}

$r = Invoke-ReciProMacro 'print(ReciPro.Crystal.Name)'
$r.output      # 列印輸出
$r.error       # 成功時為空

C

using System.IO.Pipes;
using System.Text;
using System.Text.Json;

static string ReciPro(string code)
{
    using var pipe = new NamedPipeClientStream(".", "ReciPro.Macro.v1", PipeDirection.InOut);
    pipe.Connect(5000);
    var req = Encoding.UTF8.GetBytes(code + "\0");
    pipe.Write(req, 0, req.Length);
    using var ms = new MemoryStream();
    pipe.CopyTo(ms);
    var res = JsonSerializer.Deserialize<JsonElement>(ms.ToArray());
    var error = res.GetProperty("error").GetString();
    if (error!.Length > 0) throw new InvalidOperationException(error);
    return res.GetProperty("output").GetString()!;
}

Console.WriteLine(ReciPro("print(ReciPro.CrystalList.Count)"));

錯誤處理

巨集失敗時,error 內是一般的 Python 追蹤,output 內則是失敗前列印的內容:

{"output":"before error\r\n",
 "error":"Traceback (most recent call last):\r\n  File \"<string>\", line 2, in <module>\r\nNameError: name 'foo' is not defined"}

通訊協定違規也以相同形式回報(errorProtocol error: 開頭):缺少 NUL 結尾、要求超過 1 MiB、非有效的 UTF-8,或要求送達耗時超過 30 秒。

注意事項與限制

  • 30 秒的期限僅適用於要求的傳輸——巨集的執行可以任意久;回應會在執行結束時送回。
  • JSON 回應中的非 ASCII 字元會逸出為 \uXXXX(JSON 標準);任何 JSON 剖析器都能還原。
  • 若 ReciPro 未執行(或接聽器已停用),用戶端的連線會失敗或逾時——請先啟動 ReciPro。
  • 由於全部在 GUI 執行緒上執行,命令會排隊並嚴格逐一處理;請盡量避免在另一個指令碼的超長命令進行中再送出新命令。

另請參閱