Include files let you share Expr functions, classes, and workflow code between scripts. Good organization keeps includes easy to reuse and prevents hidden session state, duplicate names, and tangled dependencies.
Use this guide when a project grows beyond one script file.
Separate reusable library files from runnable script files.
Library files define things:
// lib/probing/probe_job.expr class ProbeJob() { safe_z = 5; feed = 100; function SetFeed(value) { feed = value; } function GetFeed() { return feed; } function Run() { return safe_z; } }
Script files do things:
// jobs/probe_plate.expr include "./lib/probing/probe_job.expr" as Probe job = Probe::ProbeJob(); job.SetFeed(120); job.Run();
This distinction makes it clear whether including a file should only define code or should perform a workflow.
A practical layout is:
expr/
lib/
probing/
probe_job.expr
probe_math.expr
atc/
tool_table.expr
tool_change.expr
dialogs/
tool_dialog.expr
jobs/
probe_plate.expr
change_tool.expr
tests/
probe_job_test.expr
assets/
icons/
images/
Use `lib` for reusable code and `jobs` for scripts that are meant to be run directly.
Keep shared assets under the profile `assets` folder. Keep job-only data beside the script that uses it.
Use names that describe the main class or task in the file.
Good names:
probe_job.expr tool_change.expr tool_table.expr height_map.expr serial_monitor.expr
Avoid names that are too broad:
utils.expr common.expr main.expr test.expr
A broad name is acceptable only when the folder already provides the missing context, for example `probing/math.expr`.
A library file should usually have one main class or one small group of closely related functions.
Good:
// lib/probing/probe_job.expr class ProbeJob() { // probing workflow state and methods }
Also good:
// lib/probing/probe_math.expr function ClearanceZ(surface_z, clearance) { return surface_z + clearance; } function ProbeFeed(base_feed, material_factor) { return base_feed * material_factor; }
Avoid one large include that contains unrelated probing, ATC, dialogs, serial, and file import code.
Use aliases for reusable include files.
include "./lib/probing/probe_job.expr" as Probe include "./lib/atc/tool_change.expr" as ATC include "./lib/dialogs/tool_dialog.expr" as ToolDlg probe = Probe::ProbeJob(); atc = ATC::ToolChange(); dlg = ToolDlg::ToolDialog();
Aliases make it clear where a class or function comes from, and they reduce name collisions.
For aliases, prefer short names that identify the domain:
Avoid aliases such as `A`, `B`, `Lib`, or `Common` in larger projects.
A reusable library include should not normally:
Prefer definitions only:
// good library include class ToolChangeJob() { tool = 0; function SetTool(t) { tool = t; } }
Then let the runnable script decide what to do:
include "./lib/atc/tool_change.expr" as ATC job = ATC::ToolChangeJob(); job.SetTool(3);
This keeps library files reusable from MDI, custom dialogs, callbacks, and larger scripts.
If a script needs a job-specific session, do it in the runnable script, not in the library file.
include "./lib/probing/probe_job.expr" as Probe old_session = session.current; job_session = 'probe_job_001'; session.create(job_session); session.set_variable(job_session, 'old_session', old_session); session.join(job_session); job = Probe::ProbeJob(); job.Run(); session.join(old_session); session.delete(job_session);
The include file remains reusable. The script owns the session lifecycle.
Circular includes make code hard to reason about.
Avoid this:
probe_job.expr includes probe_dialog.expr probe_dialog.expr includes probe_job.expr
Prefer one direction:
probe_job.expr defines ProbeJob probe_dialog.expr defines ProbeDialog probe_plate.expr includes both and connects them
The top-level script should wire independent parts together.
Use `./` for shared profile libraries and assets. Use paths without a leading dot for files that live beside the current source script.
include "./lib/probing/probe_job.expr" as Probe include "./lib/dialogs/probe_dialog.expr" as ProbeDlg
When a library needs an asset, prefer passing a profile-relative asset path from the caller.
include "./lib/dialogs/icon_dialog.expr" as IconDlg dlg = IconDlg::IconDialog(); dlg.SetIconPath("./assets/icons/probe.png");
Avoid hard-coded machine-specific absolute paths in reusable include files. See How relative paths work.
If a value changes per machine, job, material, or operator, avoid burying it inside a shared library.
Instead, pass it to the object:
include "./lib/probing/probe_job.expr" as Probe job = Probe::ProbeJob(); job.SetSafeZ(5); job.SetFeed(100); job.SetMaterial('aluminum');
Or use a small configuration object:
class ProbeConfig() { safe_z = 5; feed = 100; samples = 3; } config = ProbeConfig(); job = Probe::ProbeJob(config);
For important libraries, keep small test scripts that include the file and exercise expected behavior.
// tests/probe_job_test.expr include "./lib/probing/probe_job.expr" as Probe job = Probe::ProbeJob(); job.SetFeed(120); if (job.GetFeed() != 120) { error('feed was not updated'); };
Tests are especially useful for code used by ATC, probing, dialogs, serial communication, or file import workflows.
For a reusable include library:
Before adding a new include file, ask: