<bmx-load-planner>
Plans how cargo goes into shipping containers, trailers and trucks, vans, pallets and air cargo units: each item's size, weight and how many; whether it may be turned (any way, this way up, or not at all), stacked on and how much it bears, must stand on the floor, and the delivery stop it comes off at. The optimiser - on a background thread - places every piece it can: walls built from the front wall, nothing overlapping or hanging over, enough of each base resting on what is below, no piece crushed or put on what must not be stacked on, the weight limit kept, and pieces for later stops loaded deeper so each stop's cargo comes off first. When there is more cargo than space, more spaces like the last are added if allowed.
12 properties · 4 events · 21 methods · 5 parts
Example
Try it with your own cargo lists in confidence: they are opened by your browser, on your device. Nothing is uploaded, no copy reaches us or anyone else, and nothing is kept once you close this tab.
Packing...
Packing...
Show markup
<bmx-load-planner id="ex-load" label="Order 1042 - 40' high cube"></bmx-load-planner>
<p class="note" id="ex-load-out" role="status" style="margin: 12px 0 0">Packing...</p>
<bmx-load-planner id="ex-truck" label="Rigid truck - three drops" view="side" style="margin-block-start: 24px"></bmx-load-planner>
<p class="note" id="ex-truck-out" role="status" style="margin: 12px 0 0">Packing...</p>
<script type="module">
await customElements.whenDefined('bmx-load-planner');
const planner = document.getElementById('ex-load');
const out = document.getElementById('ex-load-out');
// Two drops: what comes off first (stop 1) is loaded last, nearest the doors.
planner.plan = {
name: 'Order 1042',
spaces: [{ id: 'c1', name: "40' high cube", kind: 'container', length: 12032, width: 2352, height: 2698, maxWeight: 26500 }],
items: [
{ name: 'Pallet - tinned food', length: 1200, width: 800, height: 1100, weight: 520, quantity: 12, maxLoadOnTop: 600, stop: 2 },
{ name: 'Pallet - bottled water', length: 1200, width: 1000, height: 1200, weight: 780, quantity: 6, stackable: false, stop: 2 },
{ name: 'Crate - machine parts', length: 1500, width: 1000, height: 900, weight: 420, quantity: 4, floorOnly: true, stop: 2 },
{ name: 'Carton - electronics', length: 600, width: 400, height: 400, weight: 14, quantity: 120, maxLoadOnTop: 60, stop: 1 },
{ name: 'Drum - lubricant', length: 600, width: 600, height: 900, weight: 210, quantity: 10, orientation: 'fixed', stackable: false, stop: 1 },
{ name: 'Roll - flooring', length: 2000, width: 300, height: 300, weight: 40, quantity: 16, orientation: 'any', stop: 1 },
],
};
const report = async () => {
const stats = await planner.getStats();
const space = stats.spaces[0];
out.textContent = space
? `${stats.loaded} of ${stats.pieces} pieces loaded, ${(space.volumeShare * 100).toFixed(1)}% of the volume, ${Math.round(space.weight).toLocaleString()} kg, ${(space.usedLength / 1000).toFixed(2)} m of the length used.`
: '';
};
planner.addEventListener('bmxChange', report);
await planner.pack();
await report();
// A rigid truck with its axles given, so the summary shows the load on each. Select a piece and drag it, or use
// Alt with the arrow keys: it is locked where you leave it, and Pack places the rest around it.
const truck = document.getElementById('ex-truck');
const truckOut = document.getElementById('ex-truck-out');
truck.plan = {
name: 'Round 7',
spaces: [
{
id: 't1',
name: '18 t rigid truck',
kind: 'truck',
length: 7300,
width: 2480,
height: 2600,
maxWeight: 9000,
axles: { front: { name: 'Front axle', position: -1200, max: 7100, tare: 4300 }, rear: { name: 'Rear axle', position: 4600, max: 11500, tare: 3200 } },
cogLength: 0.15,
},
],
items: [
{ name: 'Pallet - drinks', length: 1200, width: 800, height: 1400, weight: 650, quantity: 4, stackable: false, stop: 3 },
{ name: 'Roll cage - groceries', length: 800, width: 700, height: 1800, weight: 250, quantity: 6, stackable: false, stop: 2 },
{ name: 'Carton - household', length: 600, width: 400, height: 450, weight: 18, quantity: 40, maxLoadOnTop: 90, stop: 1 },
],
};
const truckReport = async () => {
const stats = await truck.getStats();
const space = stats.spaces[0];
truckOut.textContent = space?.axles
? `${stats.loaded} of ${stats.pieces} pieces, ${Math.round(space.weight).toLocaleString()} kg; front axle ${Math.round(space.axles.front).toLocaleString()} kg, rear axle ${Math.round(space.axles.rear).toLocaleString()} kg (truck included).`
: '';
};
truck.addEventListener('bmxChange', truckReport);
await truck.pack();
await truckReport();
</script>
<bmx-load-planner src="/loads/order-1042.json"></bmx-load-planner>
The load shows in 3D (WebGL 2) and from the top, the side and the doors; step through the loading order, or play it. Pieces can be moved by hand
- dragged in any view, or moved, raised and turned from the keyboard - with what is wrong shown as they move; moved pieces are locked, and packing again fills the rest around them. The summary gives each space's volume and weight used, length used, loading metres, centre of gravity and - for a vehicle whose axles are set in the Spaces tab - the load on each axle; the issues list everything wrong. Cargo lists come in from CSV; the plan goes out as JSON, the cargo and the placements as CSV, the view as a picture, and loading instructions as a PDF for the loading bay. Standard sizes (ISO containers, trailers, pallets, air cargo units) are included as starting points. Everything runs in the page; nothing is uploaded.
Properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
label |
label |
string |
— | A heading for the planner (the plan's name when not given). |
lengthUnit |
length-unit |
BmxLoadLengthUnit |
'mm' |
The unit lengths are shown and typed in. |
packTime |
pack-time |
number |
2000 |
How long packing may take to improve the plan, milliseconds. |
panel |
panel |
boolean |
true |
Show the side panel. Two-way. |
plan |
plan |
BmxLoadPlan | string | null |
null |
The plan: items, spaces, placements and rules, as an object or JSON. Two-way: bmxChange reports every edit. |
readonly |
readonly |
boolean |
false |
Show the plan without changing it. |
seed |
seed |
number |
1 |
Seed for packing's changes; the same seed gives the same plan. |
src |
src |
string |
— | An address to read the plan from (the page's own server): JSON, or a cargo list as CSV. |
strings |
strings |
Partial<BmxLoadPlannerStrings> | string |
— | Words for translation: an object or JSON. |
toolbar |
toolbar |
boolean |
true |
Show the toolbar. |
view |
view |
BmxLoadPlannerView |
'iso' |
The view: iso (3D), top, side or end. Two-way. |
weightUnit |
weight-unit |
BmxLoadWeightUnit |
'kg' |
The unit weights are shown and typed in. |
Events
| Event | Detail | Description |
|---|---|---|
bmxChange |
{ readonly plan: BmxLoadPlan; } |
Fired after every change, with the plan. |
bmxError |
{ readonly message: string; } |
Fired when a plan cannot be read. |
bmxPack |
{ readonly loaded: number; readonly pieces: number; readonly spaces: number; readonly elapsed: number; } |
Fired when packing ends, with how much was loaded. |
bmxSelect |
{ readonly placement: BmxLoadPlacement | null; readonly index: number | null; } |
Fired when a piece is selected or the selection cleared. |
Methods
| Method | Signature | Description |
|---|---|---|
cancelPack |
cancelPack() => Promise<void> |
Stops packing; the plan stays as it was. |
check |
check() => Promise<BmxLoadIssue[]> |
Everything wrong with the plan. |
export |
export(kind: BmxLoadPlannerExport) => Promise<Blob> |
A file of the plan: JSON, the cargo list or the placements as CSV, a picture of the view (PNG), or loading instructions (PDF). |
fit |
fit() => Promise<void> |
Puts the camera back to show the whole space. |
getPlan |
getPlan() => Promise<BmxLoadPlan> |
The plan as it stands (a copy). |
getStats |
getStats() => Promise<BmxLoadStats> |
Each space's figures: pieces, volume and weight used, length used, centre of gravity, axle loads; and the pieces not loaded. |
goToStep |
goToStep(n: number | null) => Promise<void> |
Shows the current space's load up to a step (from 1; 0 for none, null or anything past the last for all). |
importItems |
importItems(csv: string, options?: { replace?: boolean; }) => Promise<{ added: number; skipped: number; }> |
Adds the items of a cargo list (CSV; a header row, then one item a row). Sizes and weights are in the planner's units unless a header says (Length (cm)). |
load |
load(data: string | BmxLoadPlan | Blob | Record<string, unknown>) => Promise<void> |
Opens a plan (JSON text, an object, or a file), or adds the items a cargo list (CSV) holds. |
lockPiece |
lockPiece(index: number, locked?: boolean) => Promise<void> |
Locks a placement in place (or unlocks it): packing again leaves locked pieces where they are. |
movePiece |
movePiece(index: number, to: { x?: number; y?: number; z?: number; space?: string; }) => Promise<BmxLoadIssue[]> |
Moves a placement (by index) to a corner position, millimetres; it is locked so packing again leaves it there. Resolves with what is wrong with it there. |
pack |
pack(options?: { timeLimit?: number; seed?: number; }) => Promise<{ loaded: number; pieces: number; spaces: number; } | null> |
Packs the cargo into the spaces (in a worker when it can). Resolves with what was loaded, or null when stopped. |
redo |
redo() => Promise<boolean> |
|
save |
save(kind?: BmxLoadPlannerExport, fileName?: string) => Promise<void> |
Downloads a file of the plan (see export). |
selectPlacement |
selectPlacement(index: number | null) => Promise<void> |
Selects a placement (by index in the plan's list), or clears the selection with null. |
setFocus |
setFocus() => Promise<void> |
|
setPlan |
setPlan(plan: BmxLoadPlan | string) => Promise<void> |
Replaces the plan (an object or JSON); undo history is cleared. |
showSpace |
showSpace(space: number | string) => Promise<void> |
Shows a space (by index from 0, or id). |
takeOut |
takeOut(index: number) => Promise<void> |
Takes a placement out of the load (the piece is then not loaded). |
turnPiece |
turnPiece(index: number) => Promise<boolean> |
Turns a placement to the next way its item may stand (corner kept, then lowered onto what is below). |
undo |
undo() => Promise<boolean> |
CSS shadow parts
| Part | Description |
|---|---|
panel |
The side panel (cargo, spaces, summary, issues, steps). |
stage |
The 3D or flat view. |
status |
The line under the view. |
steps |
The loading-order bar under the view. |
toolbar |
The bar of commands. |
CSS custom properties
| Property | Description |
|---|---|
--bmx-load-planner-background |
The view's background. |
--bmx-load-planner-deck |
A pallet's deck. |
--bmx-load-planner-edge |
The edges of the pieces in 3D. |
--bmx-load-planner-floor |
The floor of the space in 3D. |
--bmx-load-planner-height |
The height of the view. |
--bmx-load-planner-highlight |
The selected piece. |
--bmx-load-planner-outline |
The space's outline. |
--bmx-load-planner-panel-width |
The side panel's width. |
--bmx-load-planner-radius |
Corner radius of the frame. |