Table of Contents

How To Organize Include Files

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.


Decide What Kind Of File It Is

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.


Suggested Folder Layout

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.


Name Files By Responsibility

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`.


Prefer One Main Concept Per File

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 Namespace Aliases Consistently

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.


Keep Library Includes Passive

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.


Put Session Management At The Script Boundary

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.


Avoid Circular Includes

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 Relative Paths Deliberately

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.


Keep Configuration Separate From Code

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

Test Include Files With Small Scripts

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:


Checklist

Before adding a new include file, ask:


See Also


How-To index: How-To Guides