DEV Community

TANCYBOX
TANCYBOX

Posted on

Fitting Three Objects in One Case: Modeling Multi-Item Packaging

The first article in this series modeled a product as a data model. The second one dealt with a curved object that broke the rectangular assumption.

This one is worse: a case that holds three objects — one bottle, two champagne flutes — each with a different shape, a different tolerance, and its own cavity. And they all have to share one outer envelope.

Here is what that does to the schema.

Double-opening leather champagne case, closed

The single-item model stops working immediately

The earlier model had one dimensions block and one interiorProfile. For a multi-item set, "the interior" is not one thing — it is a layout.

The naive fix is to add fields: bottleCavity, fluteCavityLeft, fluteCavityRight. That works for exactly one configuration and collapses the moment a client asks for two glasses instead of one, or a wider bottle.

The better fix: model cavities as a list.

type Cavity = {
  id: string;                 // 'bottle', 'flute-left', 'flute-right'
  holds: 'bottle' | 'flute' | 'accessory';
  inner: Dimensions;
  clearanceMm: number;        // how much room around the object
  position: 'centre' | 'left' | 'right';
  orientation: 'upright' | 'lying';
};

type MultiItemSpec = {
  envelope: Dimensions;
  wallThicknessMm: number;
  cavities: Cavity[];
  sharedToleranceMm: number;
};
Enter fullscreen mode Exit fullscreen mode

Now the configuration is data. Two flutes, one flute, three miniatures — all the same schema.

The constraint that actually matters

With one item, the only check is "does it fit". With multiple items, the constraint is additive:

sum(cavity widths) + dividers + 2 × wallThickness  ≤  envelope width
Enter fullscreen mode Exit fullscreen mode

That sounds obvious, but it is the check that gets skipped, because each cavity is designed independently by a different person. The bottle cavity is specced by the packaging engineer; the flute cradles are specced by the structural designer. Nobody runs the sum until tooling is quoted.

Here it is as a validator:

function validateMultiItem(s: MultiItemSpec): string[] {
  const errors: string[] = [];

  const totalCavityWidth = s.cavities.reduce(
    (sum, c) => sum + c.inner.widthMm + c.clearanceMm * 2,
    0
  );

  // Dividers between adjacent cavities, plus the outer walls.
  const dividers = Math.max(0, s.cavities.length - 1);
  const required =
    totalCavityWidth + dividers * s.wallThicknessMm + s.wallThicknessMm * 2;

  if (required > s.envelope.widthMm) {
    errors.push(
      `interior needs ${required}mm but envelope is ${s.envelope.widthMm}mm`
    );
  }

  // Every cavity must have a distinct position or they overlap.
  const positions = s.cavities.map((c) => c.position);
  if (new Set(positions).size !== positions.length) {
    errors.push('two cavities share the same position');
  }

  return errors;
}
Enter fullscreen mode Exit fullscreen mode

Run it against a real spec and it tells you the case is 8mm too narrow — before anyone cuts a die.

Why the error message matters more than the check

The valuable part is not the boolean. It is the message:

interior needs 268mm but envelope is 260mm

That single line is what turns a two-week email thread into a five-minute fix. A validator that returns false is useless in a supply chain; a validator that returns the missing millimetres gets acted on.

The general pattern, again

Across all three articles the same shape keeps appearing:

  • Keep the shared fields shared: envelope, wall thickness, tolerance, finish, quantity.
  • Push the variable part into a list: profiles for curved products, cavities for multi-item sets.
  • Validate the additive constraint, and return the number, not the boolean.

A packaging spec is not a form. It is a small constraint problem wearing a form's clothes.


The case shown is a double-opening leather champagne case from TANCYBOX — one bottle, two flutes, three cavities, one envelope.

Top comments (0)