# Central Model Repository

The Central Model Repository (CMR) was introduced with Portal and replaces both the model hosting folder and the model folder on the render server with a centrally managed system. UDiTH models are imported into the CMR so that they are available across your organisation.

## Concept

### Model

A model is created when a model version is imported for the first time. It is identified by a model identifier. Each model can have multiple model versions. The model identifier must be unique. Each model also stores a description, tags, title, preview image, and logical path.

The identifier can contain alphanumeric characters and `_`.

### Modelversion

Each model version is identified by a combination of the model identifier and the model version identifier. The latest model version is also available through the alias tag `latest`. It is not possible to assign the model version identifier 'latest' manually. When a model is requested, a specific model version is always required. If no version is specified, the alias tag `latest` is used.

The same model version identifier can be used across multiple models, but it must be unique within a single model. Each model version is only a snapshot in time and contains no delta information.

The identifier can contain alphanumeric characters and `_`.

### Storage

Storage is managed entirely by Portal. It is not possible to intervene manually in the process.

### Import

A model must be imported in order to transfer it into the storage system. During import, the model identifier and model version identifier are specified and other settings must also be defined.

### Attribute Import

It is possible to extract attributes into a tag register, which allows Portal search to find model objects, both 3D and PID, across multiple models.

## Import Process

> ⚠️WARNING⚠️ In the current version, models must be zipped by the administrator before import. This is expected to change in future UDiTH Builder versions, where this process will be automated. Ensure that the `Data` folder is at the top level inside the ZIP archive. Otherwise, the model will be rejected during the import process.


1. Place the ZIP file in the import folder on the server.
1. Navigate to the **CMR -> Model import** section.
1. Select the model to import.
![Model Import Selection](media/Portal_model_import_selection.png)
1. Choose either a new model or an existing model to which a new model version should be added.
![Model Import new or existing model](media/Portal_model_import_select.png)
1. Fill in the form by specifying the name, model ID, folder, version ID, preview image, description, tags, and the number of preloaded and maximum instances for BBV. For the tag register, you can choose which attributes to import. Import can also be disabled either here or globally through configuration. ⚠️ **Do not import all attributes.**
![New Model](media/Portal_model_new_model.png)
1. After the form has been submitted, the model import process starts. The model becomes available for viewing once the transfer to storage has been completed. Attribute import may take longer.
1. If you choose an existing model, the form is prefilled with the contents of the previous import.

## Automated Import

It is possible to automate the process by specifying a `modelDefinition.json` file inside the `Data` folder.

Many attributes can be ignored and may be left empty or removed. In that case, the default behaviour is used, or the previous value if a new model version is being added to an existing model.

During the first import of a model, ensure that the `Model` section is filled in completely. If the model already exists, only `ExternalIdentifier` is required. If other values are specified, they also overwrite the corresponding attributes of the existing model.

If `AutoApprove` is set, an automatic import is attempted. If this fails for any reason, a manual approval can still be attempted.

### Format
```json
{
    "Cmr": {
        "ProcessOptions": {
            "SkipProcessing": false,
            "MaxRetainedModelVersions": null,
            "AutoApprove": false,
            "KeepAllPreloads": null
        },
        "ModelVersion": {
            "ExternalIdentifier": "REQUIRED",
            "Description": null,
            "Tags": null,
            "AttributesPidProcess": null,
            "Attributes3DProcess": null,
            "IsProtected": false,
            "DesiredPreloadInstances": null,
            "MaximumInstances": null
        },
        "Model": {
            "ParentFolderPath": null,
            "Name": "REQUIRED on first import else can be null",
            "ExternalIdentifier": "REQUIRED",
            "ModelAliases": null
        }
    }
}
```

#### ProcessOptions

| Field | Type | Description |
|-------|------|-------------|
| `SkipProcessing` | `bool?` | Skip attribute extraction for this import. When `null`, the global `ModelProcessing__ForcedSkipProcessing` setting is used. When `true`, the model is imported but no attributes are written to the database. |
| `MaxRetainedModelVersions` | `int?` | Maximum number of model versions to keep for this model. When a new version is imported and the limit is exceeded, the oldest unprotected versions are automatically marked for deletion. Leave `null` to keep all versions. |
| `AutoApprove` | `bool?` | When `true`, the import proceeds automatically without manual approval in the web interface. If automatic approval fails for any reason, manual approval can still be attempted. |
| `KeepAllPreloads` | `bool?` | When `true`, all existing model versions retain their `DesiredPreloadInstances` value after a new version is imported. When `false` or `null`, preload instances of all previous versions are set to `0`, so that only the latest version is preloaded for BBV. |

#### ModelVersion

| Field | Type | Description |
|-------|------|-------------|
| `ExternalIdentifier` | `string` | **Required.** Unique identifier for this model version within its model. Can contain alphanumeric characters and `_`. |
| `Description` | `string?` | Optional description of the model version. |
| `Tags` | `string[]?` | Optional list of tags for categorisation and search. |
| `AttributesPidProcess` | `string[]?` | List of PID attribute names to extract into the tag register. ⚠️ Do not import all attributes. |
| `Attributes3DProcess` | `string[]?` | List of 3D attribute names to extract into the tag register. ⚠️ Do not import all attributes. |
| `IsProtected` | `bool` | When `true`, this model version is protected from automatic deletion by retention rules (`MaxRetainedModelVersions`). Protected versions are excluded from retention calculations. Default: `false`. |
| `DesiredPreloadInstances` | `int?` | Number of BBV instances to preload for this version. Preloaded instances reduce initial load time for users. When not set, the value from the previous version is used. |
| `MaximumInstances` | `int?` | Maximum number of concurrent BBV instances allowed for this version. When not set, the value from the previous version is used. |

#### Model

| Field | Type | Description |
|-------|------|-------------|
| `ExternalIdentifier` | `string` | **Required.** Unique identifier for the model. Can contain alphanumeric characters and `_`. Used to match imports to existing models. |
| `Name` | `string` | **Required on first import.** Display name of the model. On subsequent imports, this can be `null` to keep the existing name, or set to overwrite it. |
| `ParentFolderPath` | `string?` | Logical folder path for organising models in the web interface (e.g. `"Demoplants"`). |
| `ModelAliases` | `string[]?` | Alternative identifiers for the model. These aliases can be used to reference the model in addition to the primary `ExternalIdentifier`. |

## Model Aliases and Model Links

### Model Aliases

Model aliases are alternative identifiers for a model. They can be set during import via the `ModelAliases` field in `modelDefinition.json` or edited in the model settings. When a model is requested by identifier, the system first checks for an exact match on the `ExternalIdentifier`. If no match is found, it searches the aliases.

This is useful when a model needs to be referenced by different names, for example, when the external identifier follows an internal naming convention but users should be able to access it by a friendlier name.

### Model Links

Model links allow you to create shareable URLs that open a specific model directly. The URL format is:

```
https://<portal-url>/modellink/<alias-or-identifier>
https://<portal-url>/modellink/<alias-or-identifier>/<version>
```

If no version is specified, the `latest` version is used.

#### Query Parameters

| Parameter | Default | Description |
|-----------|---------|-------------|
| `openLocal` | `true` | When `true`, returns a `upvapi://` link that opens the model in the local UDiTH viewer. When `false`, returns an `https://` link that opens the model in the browser (BBV). |
| `commands` | — | Optional commands to pass to the viewer. Multiple commands are separated by `&`. See below. |

#### Commands

The `commands` parameter accepts all UDiTH deeplink commands. Commands are concatenated with `&` and must be URL-encoded in the query string.

| Syntax | Description |
|--------|-------------|
| `<key>=<value>` | Filter or select objects by a given attribute key and value. |
| `CMD!Select` | Apply selection to the matched object. |
| `CMD!Fit` | Fit the camera to the current selection. |
| `CMD!Highlight` | Highlight the current selection. |

**Example (decoded):**
```
Name=D-240&CMD!Select&CMD!Fit&CMD!Highlight
```
This filters by attribute `Name` with value `D-240`, then applies selection, fits the camera to it, and highlights it.

#### Examples

| URL | Behaviour |
|-----|-----------|
| `/modellink/myplant` | Opens the latest version of model `myplant` in local viewer |
| `/modellink/myplant/2025_10_23` | Opens version `2025_10_23` in local viewer |
| `/modellink/myplant?openLocal=false` | Opens latest version in browser (BBV) |
| `/modellink/demoplant?openLocal=false&commands=Name%3DD-240%26CMD%21Select%26CMD%21Fit%26CMD%21Highlight` | Opens model `demoplant` in browser, selects object `D-240`, fits and highlights it |

> ℹ️ The user must be authenticated to use model links. Unauthenticated users are redirected to the login page first.

### Example First Import
```json
{
    "Cmr": {
        "ProcessOptions": {
            "AutoApprove": true
        },
        "ModelVersion": {
            "ExternalIdentifier": "2025_10_23",
            "Description": "this is optional",
            "Tags": ["this", "is", "also", "optional"],
            "AttributesPidProcess": ["PID", "attributes", "to", "import"],
            "Attributes3DProcess": ["3D", "attributes", "to", "import"],
            "DesiredPreloadInstances": 1,
            "MaximumInstances": 5
        },
        "Model": {
            "ParentFolderPath": "Demoplants",
            "Name": "Sample Demoplant",
            "ExternalIdentifier": "sample"
        }
    }
}
```

### Example Second Import
```json
{
    "Cmr": {
        "ProcessOptions": {
            "AutoApprove": true
        },
        "ModelVersion": {
            "ExternalIdentifier": "2025_10_24"
        },
        "Model": {
            "ExternalIdentifier": "sample"
        }
    }
}
```