2. Automate PRIMER Startup and Model Load w
Why a wrapper instead of a direct command line
Oasys PRIMER is a GUI-driven pre-processor. It accepts a model file as a command-line argument, but it does not provide a native "load, do nothing, exit" switch. If you simply call primer.exe model.key from a scheduler or CI job, the process stays alive waiting for a user to close the window, and the job hangs until a timeout kills it. A batch wrapper solves this by launching PRIMER with the model, waiting for the model to finish loading, then issuing a clean shutdown through PRIMER's own scripting interface rather than terminating the process.
The goal of this case is narrow and deliberately so: prove that an automation harness can start PRIMER, load a named model, and terminate with a predictable exit code — with zero mouse clicks. Everything in later cases (free-edge extraction, duplicate detection, report export) depends on this harness being reliable, so it is worth building carefully and verifying explicitly.
Choosing the automation channel
PRIMER exposes two automation surfaces that matter here:
- Command-line model argument — passing the model path on the process command line. This is the most stable way to get a specific file loaded at startup, and it avoids depending on any "recent files" or preference state.
- PRIMER's scripting interface — a script file executed inside the running session, which can query model state and request application exit. This is what lets the wrapper close PRIMER deterministically instead of killing it.
The wrapper's job is to sequence these two surfaces and to gate on a real readiness signal, not a fixed sleep. A fixed sleep is the most common cause of flaky harnesses: too short and the script runs before the model is loaded; too long and every job pays the penalty.
Wrapper structure
The reference implementation below is a Python wrapper. It is intentionally small: launch, wait for readiness, request exit, verify exit code. It assumes PRIMER is installed and that a script-capable interface is available; the exact executable name and script-invocation mechanism should be confirmed against your installed PRIMER version before first use.
import subprocess, time, sys, pathlib
PRIMER_EXE = r"C:\Program Files\Oasys\PRIMER\primer.exe" # confirm on your install
MODEL = pathlib.Path(r"D:\models\frame_baseline.key")
READY_SCRIPT = pathlib.Path(r"D:\automation\primer_ready_and_exit.py")
TIMEOUT_S = 180
POLL_S = 2
def run_wrapper():
if not MODEL.exists():
print(f"FAIL: model not found: {MODEL}")
return 2
if not READY_SCRIPT.exists():
print(f"FAIL: control script not found: {READY_SCRIPT}")
return 2
cmd = [PRIMER_EXE, str(MODEL), "-script", str(READY_SCRIPT)]
print("LAUNCH:", " ".join(cmd))
proc = subprocess.Popen(cmd)
deadline = time.time() + TIMEOUT_S
while time.time() < deadline:
rc = proc.poll()
if rc is not None:
# PRIMER exited on its own (script requested exit, or crash)
print(f"PRIMER exited with code {rc}")
return 0 if rc == 0 else 1
time.sleep(POLL_S)
print("FAIL: timeout waiting for PRIMER to exit")
proc.terminate()
return 3
if __name__ == "__main__":
sys.exit(run_wrapper())
Two design points are worth calling out. First, the wrapper does not call proc.kill() on the happy path — it lets the in-session script request a normal application exit, so PRIMER can flush any state it needs to. Second, the timeout is a safety net, not the primary control: if the timeout fires, that is a genuine failure to investigate, not a normal outcome.
What the in-session control script must do
The control script runs inside PRIMER after the model argument has been processed. Its responsibilities are: confirm the model is actually loaded, record a small readiness marker (so the wrapper or a log can prove it), and request exit. A minimal, version-tolerant pattern is:
# primer_ready_and_exit.py (runs inside PRIMER's scripting context)
# 1. Confirm a model is present and has content.
model = GetCurrentModel() # confirm exact API name for your version
if model is None:
LogError("No model loaded at script start")
RequestExit(1)
else:
node_count = model.GetNodeCount()
LogInfo(f"Model loaded: {model.GetTitle()} nodes={node_count}")
if node_count == 0:
LogError("Model loaded but contains zero nodes")
RequestExit(1)
else:
RequestExit(0)
The exact function names (GetCurrentModel, GetNodeCount, RequestExit) are placeholders for the scripting API your PRIMER build exposes; substitute the real identifiers from the installed scripting reference. The structure — check model, check content, log, exit with a code — is what matters and is stable across versions.
Readiness and exit-code contract
The wrapper and the control script together define a small contract. Making it explicit is what turns a one-off script into a harness you can trust in a pipeline.
| Exit code | Meaning | Typical cause |
|---|---|---|
| 0 | Model loaded, non-empty, clean exit | Normal success |
| 1 | PRIMER ran but reported a problem | Zero-node model, load error, script error |
| 2 | Wrapper precondition failed | Model or control script path missing |
| 3 | Timeout — PRIMER did not exit | Script not invoked, modal dialog blocking exit |
RequestExit and surface as exit code 3. If you see repeated timeouts, check for dialogs before assuming the script is wrong.Verifying the harness without manual interaction
Run the wrapper from a plain terminal with no PRIMER window already open. A successful run should show the launch line, a PRIMER window appearing and closing on its own, and a final PRIMER exited with code 0. Confirm the log contains the model title and a non-zero node count — that is your proof the model actually loaded rather than PRIMER starting empty and exiting immediately.
To prove the harness is genuinely driving PRIMER and not just returning success, run two negative checks: point MODEL at a non-existent path (expect exit 2), and point it at a deliberately empty or corrupt model (expect exit 1 with a logged error). If both negatives behave as specified, the positive path is trustworthy.
Common failure symptoms and what they point to
- PRIMER window opens but never closes, wrapper hits timeout (3). The control script was not invoked — check the
-scriptargument spelling against your build, or a modal dialog is blockingRequestExit. - Wrapper returns 0 but the log has no model title. PRIMER started empty and the script exited before load completed; the readiness check is being skipped or the model argument is being ignored.
- Exit 1 with "zero nodes". The model path resolved but the file is empty or failed to parse; verify the file opens interactively before blaming the harness.
- Intermittent exit 3 on a fast machine. Usually a race between the script starting and the model finishing load — move the readiness poll into the script itself rather than relying on the wrapper's fixed poll interval.
Once this wrapper returns 0 on a known-good model and returns 1 and 2 on the two negative checks, the harness is proven. Later cases can then attach real work — free-edge extraction, duplicate detection, report export — to the same launch/readiness/exit skeleton without re-solving the startup problem.