v3.0.0

<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

PropertyAttributeTypeDefaultDescription
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

EventDetailDescription
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

MethodSignatureDescription
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

PartDescription
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

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