Controle externo (linha de comando e named pipe)¶
As macros do ReciPro podem ser executadas não apenas pelo editor integrado, mas também de fora do aplicativo. Há dois mecanismos disponíveis:
| Mecanismo | Estilo | Uso típico |
|---|---|---|
Linha de comando (/m /o /x) |
Único: iniciar o ReciPro → executar uma macro → (opcionalmente) sair | Processamento em lote, CI, tarefas agendadas |
Named pipe (ReciPro.Macro.v1) |
Interativo: enviar macros a um ReciPro em execução e receber os resultados | Scripts Python/Jupyter, PCs de controle de instrumentos, integração com outros programas |
Ambos executam a macro no mesmo mecanismo do editor, de modo que toda a API integrada está disponível.
Execução pela linha de comando¶
ReciPro.exe /m <macro.mcr> [/o <result.txt>] [/x]
| Opção | Significado |
|---|---|
/m |
Após a inicialização, executa o primeiro arquivo *.mcr existente encontrado entre os argumentos. |
/o <arquivo> |
Modo silencioso: nenhuma caixa de diálogo é exibida; a saída de print() da macro e qualquer traceback de erro são gravados em <arquivo> (UTF-8). Em caso de falha, o código de saída do processo é 1. |
/x |
Fecha o ReciPro quando a macro termina (recomendado para uso em lote — o código de saída só é devolvido ao chamador quando o processo termina). |
Sem /o, os erros aparecem como caixas de diálogo comuns (útil durante o desenvolvimento da macro). Com /o, a execução é totalmente automática: erros de sintaxe, erros em tempo de execução, arquivo de macro inexistente e até uma falha ao gravar o arquivo de resultado terminam com código de saída 1.
Exemplo: arquivo em lote¶
ReciPro.exe /m C:\work\saed_series.mcr /o C:\work\result.txt /x
if errorlevel 1 (
echo A macro FALHOU:
type C:\work\result.txt
)
Exemplo: 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 }
Ouvinte de named pipe¶
Enquanto o ReciPro está em execução, ele pode aceitar macros de outros programas pelo named pipe do Windows \\.\pipe\ReciPro.Macro.v1. O cliente grava uma macro, o ReciPro a executa e o cliente lê o resultado de volta — um simples ciclo de requisição/resposta que funciona a partir de Python, PowerShell, C# ou de qualquer coisa capaz de abrir um arquivo.
Ativação¶
O ouvinte fica desligado por padrão. Ative-o na janela principal:
Opções → Accept external macro commands (named pipe)
A configuração é mantida entre sessões.
Observação de segurança
Enquanto estiver ativado, qualquer processo executado sob o mesmo usuário do Windows pode executar código de macro dentro do ReciPro. Outros usuários (e outras máquinas) são rejeitados. Ative-o apenas quando realmente usar o controle externo.
Protocolo¶
| Item | Especificação |
|---|---|
| Nome do pipe | \\.\pipe\ReciPro.Macro.v1 (máquina local, apenas o mesmo usuário) |
| Requisição | Código-fonte da macro em UTF-8, terminado por um único byte NUL (0x00). Máximo de 1 MiB, deve chegar em até 30 s. |
| Resposta | JSON UTF-8 {"output":"...","error":"..."}; em seguida o servidor fecha a conexão (leia até o EOF). |
output |
Tudo o que a macro escreveu em stdout/stderr (print() funciona aqui, ao contrário do editor gráfico). |
error |
String vazia em caso de sucesso; caso contrário, o traceback do Python, o erro de sintaxe ou uma mensagem de erro de protocolo. |
| Conexões | Uma conexão = uma macro. Os comandos são executados um de cada vez, em ordem. |
| Estado | O escopo do Python é compartilhado com o editor de macros e persiste entre os comandos — variáveis definidas em um comando ficam visíveis no seguinte. |
| Instâncias | Se houver vários processos do ReciPro em execução, apenas o primeiro escuta. |
A resposta é enviada depois que a macro termina, de modo que uma simulação longa significa apenas uma espera longa no cliente — a detecção de conclusão é automática.
Python: exemplo mínimo¶
O lado cliente é CPython comum — numpy, pandas, Jupyter, o que quiser. Para o pipe em si não é necessário nenhum pacote:
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: uma função auxiliar reutilizável¶
Todos os exemplos a seguir usam esta pequena função:
import json
PIPE = r'\\.\pipe\ReciPro.Macro.v1'
def recipro(code):
"""Executa uma macro IronPython no ReciPro; devolve a saída impressa como 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: carregar um CIF e salvar um padrão 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: processar em lote muitos arquivos 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: série de inclinações¶
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: dados dos pontos para o pandas¶
ReciPro.DifSim.SpotInfo() devolve uma string CSV; imprima-a com print() e analise-a no cliente:
import io, pandas as pd
csv_text = recipro('print(ReciPro.DifSim.SpotInfo())')
df = pd.read_csv(io.StringIO(csv_text))
print(df.head())
Python: o estado persiste entre comandos¶
recipro('n = ReciPro.CrystalList.Count') # define uma variável...
print(recipro('print(n * 2)')) # ...e usa em um comando posterior
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 # saída impressa
$r.error # vazio em caso de sucesso
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)"));
Tratamento de erros¶
Quando a macro falha, error contém o traceback usual do Python e output contém o que foi impresso antes da falha:
{"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"}
Violações de protocolo são relatadas da mesma forma (error começa com Protocol error:): terminador NUL ausente, requisição acima de 1 MiB, UTF-8 inválido ou requisição que levou mais de 30 s para chegar.
Notas e limitações¶
- O prazo de 30 segundos vale apenas para a transferência da requisição — a execução da macro pode demorar o quanto for; a resposta chega quando ela termina.
- Caracteres não ASCII na resposta JSON são escapados como
\uXXXX(JSON padrão); qualquer analisador JSON os restaura. - Se o ReciPro não estiver em execução (ou o ouvinte estiver desativado), a conexão do cliente falha ou expira — inicie o ReciPro primeiro.
- Como tudo roda na thread da interface, os comandos são enfileirados e executados estritamente um de cada vez; evite enviar um novo comando enquanto outro muito longo está em andamento a partir de outro script.