# Physical measurements and print production

AsukaDesigner can calibrate a complete view or one drawing/print area with a real-world size. The same API then measures Fabric and Babylon layers in native renderer units, percentages, and physical units.

## Concrete 12 x 15 cm example

The drawing area below is `1000 x 1200` canvas pixels and represents `30 x 40 cm`. A layer occupying `400 x 450` pixels therefore measures exactly `12 x 15 cm`, or `180 cm2`.

```ts
import { createAsukaDesigner } from 'asuka-designer';

const designer = createAsukaDesigner('#designer', {
  views: [{
    id: 'front',
    name: 'Front 2D',
    engine: 'fabric',
    width: 2000,
    height: 2400,
    physicalSize: {
      width: 50,
      height: 60,
      unit: 'cm',
      activeVariantId: 'M',
      variants: {
        S: { width: 46, height: 56 },
        M: { width: 50, height: 60 },
        L: { width: 54, height: 64 },
      },
    },
  }],
  pricingRules: [{
    handler: 'layerArea',
    scope: 'all',
    conditions: [{ operator: '>=', value: 180, price: 4 }],
  }],
});

const areaResult = await designer.engine.areas?.create({
  id: 'front-print',
    label: 'Front print 30 x 40 cm',
  x: 500,
  y: 400,
  width: 1000,
    height: 1200,
  space: 'canvas',
  unit: 'canvas-px',
  physicalSize: {
    width: 30,
      height: 40,
    unit: 'cm',
    recommendedDpi: 150,
    activeVariantId: 'M',
    variants: {
        S: { width: 28, height: 37 },
        M: { width: 30, height: 40 },
        L: { width: 32, height: 43 },
    },
  },
});

if (!areaResult?.value) throw new Error('The drawing area was not created.');

const imageResult = await designer.api.addImage({
  url: '/assets/logo-1200x1500.png',
  printAreaId: areaResult.value.id,
  center: true,
});
const layerId = imageResult.value.id;

// Force exact production dimensions. Non-uniform resizing is explicit here.
const measurement = await designer.api.resizeLayerPhysical(layerId, {
  width: 12,
  height: 15,
  unit: 'cm',
  preserveAspectRatio: false,
});

console.log(measurement);
```

The important part of the result is renderer-neutral:

```ts
{
  native: {
    width: 400,
    height: 450,
    referenceWidth: 1000,
    referenceHeight: 1200,
    space: 'canvas',
    unit: 'canvas-px'
  },
  relative: {
    width: 40,
    height: 37.5,
    area: 15,
    unit: 'percent'
  },
  physical: {
    width: 12,
    height: 15,
    area: 180,
    unit: 'cm',
    areaUnit: 'cm2',
    calibrated: true,
    source: 'print-area'
  },
  image: {
    pixelWidth: 1200,
    pixelHeight: 1500,
    effectiveDpiX: 254,
    effectiveDpiY: 254,
    minimumDpi: 150,
    sufficient: true
  },
  constraints: {
    fits: true,
    maxWidth: 30,
    maxHeight: 40,
    unit: 'cm'
  }
}
```

## Enforcing the printable limit

`resizeLayerPhysical(...)` rejects dimensions larger than the calibrated area. Existing drawing-area constraints also keep pointer transforms inside the area.

```ts
try {
  await designer.api.resizeLayerPhysical(layerId, {
    width: 31,
    height: 41,
    unit: 'cm',
    preserveAspectRatio: false,
  });
} catch (error) {
  // Requested size exceeds the 30 x 40 cm print area.
}
```

## Production export

The production export contains only serializable, renderer-neutral data. No Fabric or Babylon object crosses the public API.

```ts
const production = designer.api.exportProductionMeasurements();

await fetch('/api/production/jobs', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify(production),
});
```

The payload includes the active view calibration, every print-area calibration, and every measured layer. The regular full-state export keeps the calibration too:

```ts
const editableState = designer.util.state.exportFull();
```

## S, M and L variations

When a product variation is applied through the common variation workflow, AsukaDesigner compares both its id and label with the `physicalSize.variants` keys. Selecting a variation named `L` activates the `L` calibration on every matching 2D and 3D view. Drawing areas with their own `S`, `M`, and `L` values receive the same active key during measurement.

## 3D views

For Babylon views, create or select the mesh print surface in the Drawing Areas module, enter its physical width, height, unit, and minimum DPI, then save it. The remaining API is identical:

```ts
const measurement3d = designer.measureLayer(layerId);
const production3d = designer.api.exportProductionMeasurements();
```

The native coordinates are `surface-tangent/world-unit` instead of `canvas/canvas-px`; the relative and physical sections keep the same shape.

## Built-in UI

- Views edits the whole-view fallback calibration through `Edit view > Physical calibration`.
- Drawing Areas edits physical width, height, unit, and minimum DPI in both 2D and 3D.
- Transform displays exact dimensions and printed area for the selected layer.
- Transform warns when a raster image falls below the configured minimum DPI.
- Pricing Rules exposes `Physical size (cm)` for layer dimensions and `Printed area (cm2)` for area-based pricing.
