외부 제어 (명령줄 및 명명된 파이프)¶
ReciPro 매크로는 내장 편집기뿐 아니라 애플리케이션 외부에서도 실행할 수 있습니다. 두 가지 방식이 있습니다.
| 방식 | 형태 | 주요 용도 |
|---|---|---|
명령줄 (/m /o /x) |
일회성: ReciPro 시작 → 매크로 실행 → (필요하면) 종료 | 일괄 처리, CI, 작업 스케줄러 |
명명된 파이프 (ReciPro.Macro.v1) |
대화형: 실행 중인 ReciPro에 매크로를 보내고 결과를 받음 | Python/Jupyter 스크립팅, 장비 제어 PC, 다른 소프트웨어와의 연동 |
둘 다 편집기와 동일한 엔진으로 매크로를 실행하므로 내장 API 전체를 사용할 수 있습니다.
명령줄 실행¶
ReciPro.exe /m <macro.mcr> [/o <result.txt>] [/x]
| 스위치 | 의미 |
|---|---|
/m |
시작 후, 인수 중에서 처음 발견된 실제 *.mcr 파일을 실행합니다. |
/o <파일> |
quiet 모드: 대화 상자를 전혀 표시하지 않고, 매크로의 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 트레이스백·구문 오류·프로토콜 오류 메시지. |
| 연결 | 연결 1개 = 매크로 1개. 명령은 도착 순서대로 하나씩 실행됩니다. |
| 상태 | Python 스코프는 매크로 편집기와 공유되며 명령 사이에 유지됩니다 — 한 명령에서 정의한 변수를 다음 명령에서 볼 수 있습니다. |
| 인스턴스 | ReciPro 프로세스가 여러 개 실행 중이면 첫 번째 프로세스만 대기합니다. |
응답은 매크로가 끝난 뒤 전송되므로, 오래 걸리는 시뮬레이션은 클라이언트에서 그만큼 기다리기만 하면 됩니다 — 완료 감지는 자동입니다.
Python: 최소 예제¶
클라이언트 쪽은 평범한 CPython입니다 — numpy, pandas, 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 매크로를 실행하고 출력 문자열을 반환합니다."""
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 # print 출력
$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"}
프로토콜 위반도 같은 형식으로 보고됩니다(error 가 Protocol error: 로 시작). NUL 종료 없음, 요청이 1 MiB 초과, UTF-8이 아님, 요청 도착에 30초 이상 걸림 등입니다.
주의점 및 제한¶
- 30초 제한은 요청 전송에만 적용됩니다 — 매크로 실행 은 얼마든지 오래 걸려도 되며, 응답은 완료 시점에 돌아옵니다.
- JSON 응답의 비 ASCII 문자는
\uXXXX로 이스케이프됩니다(JSON 표준). JSON 파서를 거치면 원래대로 복원됩니다. - ReciPro가 실행 중이 아니거나 리스너가 꺼져 있으면 클라이언트의 연결이 실패하거나 시간 초과됩니다 — 먼저 ReciPro를 실행하세요.
- 모두 GUI 스레드에서 실행되므로 명령은 엄격히 하나씩 순서대로 처리됩니다. 다른 스크립트에서 아주 긴 명령이 진행 중일 때 새 명령을 보내면 대기하게 됩니다.