Vai al contenuto

Controllo esterno (riga di comando e named pipe)

Le macro di ReciPro possono essere eseguite non solo dall'editor incorporato, ma anche dall'esterno dell'applicazione. Sono disponibili due meccanismi:

Meccanismo Stile Uso tipico
Riga di comando (/m /o /x) Una tantum: avviare ReciPro → eseguire una macro → (facoltativamente) uscire Elaborazioni batch, CI, operazioni pianificate
Named pipe (ReciPro.Macro.v1) Interattivo: inviare macro a un ReciPro in esecuzione e ricevere i risultati Script Python/Jupyter, PC di controllo strumenti, integrazione con altri software

Entrambi eseguono la macro con lo stesso motore dell'editor, quindi è disponibile tutta l'API integrata.


Esecuzione da riga di comando

ReciPro.exe /m <macro.mcr> [/o <result.txt>] [/x]
Opzione Significato
/m Dopo l'avvio esegue il primo file *.mcr esistente trovato tra gli argomenti.
/o <file> Modalità quiet: non viene mostrata alcuna finestra di dialogo; l'output di print() della macro ed eventuali traceback di errore vengono scritti in <file> (UTF-8). In caso di errore il codice di uscita del processo è 1.
/x Chiude ReciPro al termine della macro (consigliato per l'uso batch: il codice di uscita viene restituito al chiamante solo quando il processo termina).

Senza /o gli errori compaiono come normali finestre di dialogo (utile durante lo sviluppo della macro). Con /o l'esecuzione è completamente non presidiata: errori di sintassi, errori di esecuzione, file di macro mancante e persino un fallimento nella scrittura del file dei risultati terminano tutti con codice di uscita 1.

Esempio: file batch

ReciPro.exe /m C:\work\saed_series.mcr /o C:\work\result.txt /x
if errorlevel 1 (
    echo Macro FALLITA:
    type C:\work\result.txt
)

Esempio: 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 }

Listener della named pipe

Mentre ReciPro è in esecuzione può accettare macro da altri programmi tramite la named pipe di Windows \\.\pipe\ReciPro.Macro.v1. Un client scrive una macro, ReciPro la esegue e il client rilegge il risultato: un semplice ciclo richiesta/risposta utilizzabile da Python, PowerShell, C# o da qualunque cosa sappia aprire un file.

Attivazione

Il listener è disattivato per impostazione predefinita. Attivalo dalla finestra principale:

Opzioni → Accept external macro commands (named pipe)

L'impostazione viene ricordata tra le sessioni.

Nota sulla sicurezza

Quando è attivo, qualsiasi processo in esecuzione con lo stesso utente Windows può eseguire codice macro dentro ReciPro. Gli altri utenti (e le altre macchine) vengono rifiutati. Attivalo solo quando usi davvero il controllo esterno.

Protocollo

Voce Specifica
Nome della pipe \\.\pipe\ReciPro.Macro.v1 (macchina locale, solo stesso utente)
Richiesta Codice sorgente della macro in UTF-8, terminato da un singolo byte NUL (0x00). Massimo 1 MiB, deve arrivare entro 30 s.
Risposta JSON UTF-8 {"output":"...","error":"..."}, poi il server chiude la connessione (leggere fino a EOF).
output Tutto ciò che la macro ha scritto su stdout/stderr (print() funziona qui, a differenza dell'editor grafico).
error Stringa vuota in caso di successo; altrimenti il traceback Python, l'errore di sintassi o un messaggio di errore di protocollo.
Connessioni Una connessione = una macro. I comandi vengono eseguiti uno alla volta, in ordine.
Stato Lo scope Python è condiviso con l'editor di macro e persiste tra i comandi: le variabili definite in un comando sono visibili in quello successivo.
Istanze Se sono in esecuzione più processi ReciPro, resta in ascolto solo il primo.

La risposta viene inviata al termine della macro, quindi una simulazione lunga si traduce semplicemente in una lunga attesa lato client: il rilevamento del completamento è automatico.

Python: esempio minimo

Il lato client è normale CPython: numpy, pandas, Jupyter, tutto è possibile. Per la pipe in sé non serve alcun pacchetto:

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: una funzione di supporto riutilizzabile

Tutti gli esempi seguenti usano questa piccola funzione:

import json

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

def recipro(code):
    """Esegue una macro IronPython in ReciPro; restituisce il suo output come 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: caricare un CIF e salvare un pattern 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: elaborazione batch di molti file 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: serie di inclinazioni

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: dati degli spot in pandas

ReciPro.DifSim.SpotInfo() restituisce una stringa CSV; stampala con print() e analizzala lato client:

import io, pandas as pd

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

Python: lo stato persiste tra i comandi

recipro('n = ReciPro.CrystalList.Count')   # definisci una variabile...
print(recipro('print(n * 2)'))             # ...e usala in un comando successivo

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      # output stampato
$r.error       # vuoto in caso di successo

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)"));

Gestione degli errori

Quando la macro fallisce, error contiene il consueto traceback Python e output quanto era stato stampato prima del fallimento:

{"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"}

Le violazioni del protocollo vengono segnalate allo stesso modo (error inizia con Protocol error:): terminatore NUL mancante, richiesta oltre 1 MiB, UTF-8 non valido, oppure una richiesta che ha impiegato più di 30 s ad arrivare.

Note e limitazioni

  • Il limite di 30 secondi vale solo per il trasferimento della richiesta: l'esecuzione della macro può durare quanto necessario; la risposta arriva al termine.
  • I caratteri non ASCII nella risposta JSON sono codificati come \uXXXX (JSON standard); qualsiasi parser JSON li ripristina.
  • Se ReciPro non è in esecuzione (o il listener è disattivato), la connessione del client fallisce o va in timeout: avvia prima ReciPro.
  • Poiché tutto viene eseguito sul thread della GUI, i comandi vengono accodati ed eseguiti rigorosamente uno alla volta; evita di inviarne uno nuovo mentre da un altro script ne è in corso uno molto lungo.

Vedi anche