# Generic Product 3D parametric foundation

This pass introduces the first production-oriented foundation for product-independent 3D parameters.

The runtime does **not** know what a shelf, shoe, table, frame or handle is. Each Product 3D catalog element supplies its own semantic part map and declarative recipe, while AsukaDesigner executes the same generic operations for every product.

## Implemented in this pass

- Product-level `parametric` definitions on `AsukaElement`.
- Per-instance `parametric.values` persisted in the canonical 3D scene.
- Safe expressions without `eval` or `new Function`.
- Parameter types:
  - `number`
  - `integer`
  - `boolean`
  - `select`
- Parameter groups used to generate the module UI.
- Semantic parts selected by free mesh identifiers/names.
- Computed values and dependency expressions.
- Constraints with error/warning issues.
- Generic rules:
  - `resize`
  - `transform`
  - `visibility`
- Non-cumulative updates: every evaluation starts from the original imported mesh transforms.
- World-space offset conversion, so children remain coherent under normalized product roots.
- Batched parameter updates and a single history commit.
- State import/view restoration re-applies the persisted values after Babylon finishes loading the meshes.
- A generated `product-parameters` module.
- Public API under `designer.util.parametric3d`.

## Catalog definition

```ts
const productElement = {
  id: 'generic-product',
  type: 'product-3d',
  product3d: {
    model: { url: '/models/product.glb', format: 'glb' },
    parts: [],
  },
  parametric: {
    schemaVersion: 1,
    parameters: [
      {
        id: 'widthPercent',
        label: 'Width',
        type: 'number',
        unit: '%',
        min: 70,
        max: 150,
        step: 5,
        default: 100,
      },
      {
        id: 'showAccessory',
        label: 'Show accessory',
        type: 'boolean',
        default: true,
      },
    ],
    groups: [
      {
        id: 'dimensions',
        label: 'Dimensions',
        parameters: ['widthPercent'],
      },
      {
        id: 'options',
        label: 'Options',
        parameters: ['showAccessory'],
      },
    ],
    parts: {
      body: { selectors: ['body'] },
      accessory: { selectors: ['accessory'] },
    },
    computedValues: {
      widthScale: 'widthPercent / 100',
      bodyWidth: 'part.body.width * widthScale',
    },
    rules: [
      {
        type: 'resize',
        target: 'body',
        size: { x: 'bodyWidth' },
      },
      {
        type: 'transform',
        target: 'accessory',
        positionOffset: {
          x: '(bodyWidth - part.body.width) / 2',
        },
      },
      {
        type: 'visibility',
        target: 'accessory',
        visible: 'showAccessory',
      },
    ],
    constraints: [
      {
        expression: 'widthPercent >= 75 || showAccessory == false',
        message: 'Hide the accessory or select a wider product.',
      },
    ],
  },
};
```

Part keys such as `body` and `accessory` are completely free. The runtime assigns no built-in meaning to them.

## Expression context

Expressions can read:

- parameter ids directly, such as `widthPercent`;
- computed ids directly, such as `bodyWidth`;
- `values.<id>`;
- `computed.<id>`;
- baseline product measurements:
  - `product.width`
  - `product.height`
  - `product.depth`
- baseline semantic-part measurements:
  - `part.body.width`
  - `part.body.height`
  - `part.body.depth`
  - `part.body.centerX`, `centerY`, `centerZ`

Supported operators include arithmetic, comparison and boolean operators. Supported numeric functions are `min`, `max`, `abs`, `round`, `floor`, `ceil`, `sqrt`, `pow` and `clamp`.

Parameter and computed ids used in expressions should be identifier-safe: letters, digits, `_` and `$`, without spaces or hyphens.

## Public API

```ts
const descriptor = designer.util.parametric3d.get(productInstanceId);

const preview = designer.util.parametric3d.validate(productInstanceId, {
  widthPercent: 135,
  showAccessory: true,
});

await designer.util.parametric3d.set(
  productInstanceId,
  'widthPercent',
  135,
  { commitHistory: true },
);

await designer.util.parametric3d.setMany(
  productInstanceId,
  {
    widthPercent: 135,
    showAccessory: false,
  },
  {
    source: 'host.product-configurator',
    commitHistory: true,
  },
);
```

Passing `null` as `productInstanceId` targets the currently selected Product 3D instance.

## Persisted state

Only instance values are stored in the canonical scene:

```json
{
  "id": "generic-product-1",
  "catalogId": "generic-product",
  "parametric": {
    "values": {
      "widthPercent": 135,
      "showAccessory": false
    }
  }
}
```

The product recipe remains in the Elements catalog. This keeps history and exported states much smaller than storing rebuilt mesh data.

## Demo

The root `index.html` contains a Product 3D recipe for the bundled demo GLB. The right inline zone includes:

- `Product parameters`
- `Edit variations`

The demo controls expose three presets:

- **Produit large**
- **Produit haut**
- **Réinitialiser paramètres**

They call `setMany()` to modify several dependent parameters atomically. The same functions are exposed through:

```js
globalThis.asukaDemo.getParameters();
globalThis.asukaDemo.setParameters({
  widthPercent: 125,
  heightPercent: 140,
  panelRotation: 15,
});
globalThis.asukaDemo.applyParametricPreset('wide');
```

## Deliberately deferred

This is the generic foundation, not the end of the parametric roadmap. The following operations should be added in later passes without changing the current state contract:

- anchors and attachment graphs;
- repeat/distribution rules;
- segmented stretch with protected ends;
- morph-target drivers;
- mesh-variant selection;
- procedural geometry providers;
- registered custom behaviors for specialist products;
- pricing/material quantity outputs derived from parameters.

Those features should build on the same definition/state/evaluation API introduced here.
