Retrieve Validation Model Data
Introduction
Model validation functions are used to check a model you’ve created to make sure everything is as expected. In the case of property validation, this means making sure element properties fit specific criteria. In the case of clash detection, this means certain elements are not touching or are within a certain range of each other.
1. (Optional) Create a sample iModel for testing
Follow the steps listed here
You will be redirected to the "My sample iModels"
page. Wait a few minutes while the create process completes. Once it is, you can click "Show"
in the IDs column to easily access the iTwin ID (also referred to as the project ID) and the iModel ID, both which will be frequently used throughout validation API calls.
2. Get a token
To make API requests, a user token is needed. There are several ways to get it.
Follow this article to implement Authorization code workflow in your application. You will need to include the scopes clashdetection:read and clashdetection:modify.
- Go here
- Click
"Try it out"
button. - On Authorization section select
"AuthorizationCode"
. - After popup closes Authorization header with your user token value should be visible.
- Save user token value for this tutorial.
3. Retrieving Models and Categories
For creating any Clash Detection test, you will need a list of the models or categories you wish to check for clashing elements.
There are two ways to retrieve these:
First, you can send an HTTP request to POST https://api.bentley.com/clashdetection/modelsAndCategories/imodels/{iModelId}
using your iModel ID in place of {iModelId}. If model information has not previously been extracted for the iModel, an agent will be started to process the request. A status of 202 is returned if the agent was successfully started.
Next, you can repeat calls to GET https://api.bentley.com//clashdetection/modelsAndCategories/imodels{iModelId}
until a status of available
is returned with the response.
The response will include a complete list of models and categories, with their respective ids and display names.
Extraction Request Syntax
POST https://api.bentley.com/clashdetection/modelsAndCategories/imodels/00000000-0000-0000-0000-000000000000 HTTP/1.1
Request Headers
Accept: application/vnd.bentley.itwin-platform.v1+json Authorization: Bearer JWT_TOKEN
Request Body
{ "projectId": "00000000-0000-0000-0000-000000000000" }
Retrieval Request Syntax
GET https://api.bentley.com/clashdetection/modelsAndCategories/imodels/00000000-0000-0000-0000-000000000000?projectId=00000000-0000-0000-0000-000000000000 HTTP/1.1
Request Headers
Accept: application/vnd.bentley.itwin-platform.v1+json Authorization: Bearer JWT_TOKEN
Response Body
{ "status":"available", "models":[ { "id":"0x21", "displayName":"ProcessPhysicalModel" } ], "categories":[ { "id":"0x20000000002", "displayName":"Uncategorized" }, { "id":"0x30000000048", "displayName":"PID LineStyle Default" }, { "id":"0x3000000004a", "displayName":"Border" }, { "id":"0x4000000000d", "displayName":"Tag-Category" }, { "id":"0x40000000e71", "displayName":"Structure" } ] }
For applications with access to an iModel, you can directly query for the list of models and categories.
To assist you with implementing this, we have included two sample widgets on the right which can be dropped into an iTwin viewer app.
Once you have a sample app ready to test with, you can create a couple new .tsx files called CategoryListWidget.tsx
and ModelListWidget.tsx
and copy the sample code into them.
Sample Category List widget and provider
import { useCallback, useEffect, useMemo, useState } from "react"; import { useActiveIModelConnection } from "@itwin/appui-react"; import { AbstractWidgetProps, StagePanelLocation, StagePanelSection, UiItemsProvider, WidgetState } from "@itwin/appui-abstract"; import { QueryRowFormat } from "@itwin/core-common"; import { Table } from "@itwin/itwinui-react"; export interface CategoryInfo { id: string; name: string; }; export type CreateTypeFromInterface<Interface> = { [Property in keyof Interface]: Interface[Property]; }; export type CategoryInfoType = CreateTypeFromInterface<CategoryInfo>; const CategoryListWidget = () => { const [availableCategories, setAvailableCategories] = useState<CategoryInfoType[]>([]); const iModel = useActiveIModelConnection(); // Example for querying list of all categories in the iModel const queryCategories = useCallback(async (): Promise<CategoryInfo[]> => { const categories: CategoryInfo[] = []; // Query for list of unique category ids in iModel const selectUsedSpatialCategoryIds = "SELECT DISTINCT Category.Id as id from BisCore.GeometricElement3d WHERE Category.Id IN (SELECT ECInstanceId from BisCore.SpatialCategory)"; // Use category ids to query for label and code values const ecsql = "SELECT ECInstanceId as id, UserLabel as label, CodeValue as code FROM BisCore.SpatialCategory WHERE ECInstanceId IN (" + selectUsedSpatialCategoryIds + ")"; const rowIterator = iModel?.query(ecsql, undefined, { rowFormat: QueryRowFormat.UseJsPropertyNames }); if (rowIterator) { for await (const row of rowIterator) { categories.push({ id: row.id, name: row.label ?? row.code }); } } return categories; }, [iModel]); useEffect(() => { queryCategories() .then((categoryInfos: CategoryInfo[]) => { setAvailableCategories(categoryInfos); }) .catch((_e) => { setAvailableCategories([]); }); }, [queryCategories]); const columnDefinition = useMemo(() => [ { Header: "Table", columns: [ { id: "categoryId", Header: "Id", accessor: "id", }, { id: "categoryName", Header: "Name", accessor: "name", }, ], }, ], []); return ( <Table data={availableCategories} columns={columnDefinition} emptyTableContent={"No data"} density="extra-condensed" style={{ height: "100%" }} /> ); }; export class CategoryListWidgetProvider implements UiItemsProvider { public readonly id: string = "CategoryListWidgetProvider"; public provideWidgets(_stageId: string, _stageUsage: string, location: StagePanelLocation, _section?: StagePanelSection): ReadonlyArray<AbstractWidgetProps> { const widgets: AbstractWidgetProps[] = []; if (location === StagePanelLocation.Bottom && _section === StagePanelSection.Start) { widgets.push( { id: "CategorListWidget", label: "Category List", defaultState: WidgetState.Open, getWidgetContent: () => <CategoryListWidget />, } ); } return widgets; } }
Sample Model List widget and provider
import { useCallback, useEffect, useMemo, useState } from "react"; import { AbstractWidgetProps, StagePanelLocation, StagePanelSection, UiItemsProvider, WidgetState } from "@itwin/appui-abstract"; import { useActiveIModelConnection } from "@itwin/appui-react"; import { Table } from "@itwin/itwinui-react"; import type { GeometricModel3dProps, ModelQueryParams } from "@itwin/core-common"; export type CreateTypeFromInterface<Interface> = { [Property in keyof Interface]: Interface[Property]; }; export interface ModelInfo { id: string; name: string; } export type ModelInfoType = CreateTypeFromInterface<ModelInfo>; const ModelListWidget = () => { const [availableModels, setAvailableModels] = useState<ModelInfoType[]>([]); const iModel = useActiveIModelConnection(); // Example for querying list of all 3D models in the iModel const queryModels = useCallback(async (): Promise<ModelInfo[]> => { const queryParams: ModelQueryParams = { from: "BisCore.GeometricModel3d", wantPrivate: false, }; const modelProps = await iModel?.models.queryProps(queryParams) ?? []; return modelProps .map(({ id, name }: GeometricModel3dProps) => ({ id, name })) .filter(({ id }) => id) as ModelInfo[]; }, [iModel]); useEffect(() => { queryModels() .then((modelInfos: ModelInfo[]) => { setAvailableModels(modelInfos); }) .catch((_e) => { setAvailableModels([]); }); }, [queryModels]); const columnDefinition = useMemo(() => [ { Header: "Table", columns: [ { id: "id", Header: "Id", accessor: "id", }, { id: "modelName", Header: "Name", accessor: "name", }, ], }, ], []); return ( <Table data={availableModels} columns={columnDefinition} emptyTableContent={"No data"} density="extra-condensed" style={{ height: "100%" }} /> ); }; export class ModelListWidgetProvider implements UiItemsProvider { public readonly id: string = "ModelListWidgetProvider"; public provideWidgets(_stageId: string, _stageUsage: string, location: StagePanelLocation, _section?: StagePanelSection): ReadonlyArray<AbstractWidgetProps> { const widgets: AbstractWidgetProps[] = []; if (location === StagePanelLocation.Bottom && _section === StagePanelSection.Start) { widgets.push( { id: "ModelListWidget", label: "Model List", defaultState: WidgetState.Open, getWidgetContent: () => <ModelListWidget />, } ); } return widgets; } }
Once you have the viewer app ready with the sample widgets copied in, you can get them hooked up by adding the provider names to the uiProviders
property of the Viewer
react component (as shown on the right).
Sample imports for widget providers
import { CategoryListWidgetProvider } from "./CategoryListWidget"; import { ModelListWidgetProvider } from "./ModelListWidget";
Sample for adding the CategoryListWidgetProvider and ModelListWidgetProvider to your iTwin viewer app.
<Viewer iTwinId={iTwinId ?? ""} iModelId={iModelId ?? ""} authClient={authClient} onIModelAppInit={onIModelAppInit} uiProviders={[ new CategoryListWidgetProvider(), new ModelListWidgetProvider(), ]} />
4. Get Schema Info
For creating Property Validation rules you will need to get all the class/property names in the iModel schemas.
First, you can send an HTTP request to POST https://api.bentley.com/clashdetection/schemas/imodels/{iModelId}
using your iModel ID in place of {iModelId}. If schema information has not previously been extracted for the iModel, an agent will be started to process the request. A status of 202 is returned if the agent was successfully started.
Next, you can repeat calls to GET https://api.bentley.com//clashdetection/schemas/imodels{iModelId}
until a status of available
is returned with the response.
The response will include a complete tree of schemas, classes and associated properties. For creating property validation rules, you will be using the name
values. However, the label
values may help you identify the correct classes and properties.
Extraction Request Syntax
POST https://api.bentley.com/clashdetection/schemas/imodels/00000000-0000-0000-0000-000000000000 HTTP/1.1
Request Headers
Accept: application/vnd.bentley.itwin-platform.v1+json Authorization: Bearer JWT_TOKEN
Request Body
{ "projectId": "00000000-0000-0000-0000-000000000000" }
Retrieval Request Syntax
GET https://api.bentley.com/clashdetection/schemas/imodels/00000000-0000-0000-0000-000000000000?projectId=00000000-0000-0000-0000-000000000000 HTTP/1.1
Request Headers
Accept: application/vnd.bentley.itwin-platform.v1+json Authorization: Bearer JWT_TOKEN
Response Body
{ "schema":[ { "name":"ProcessPhysical", "label":null, "entityClass":[ { "name":"PIPING_COMPONENT", "label":"Piping Component", "properties":[ { "name":"COMPONENT_NAME", "label":"Component Name" }, { "name":"STATE", "label":"Component State" }, { "name":"LENGTH_EFFECTIVE", "label":"Length Effective" }, { "name":"DESIGN_LENGTH_CENTER_TO_BRANCH_END_EFFECTIVE", "label":"Design Length Center To Branch End Effective" }, { "name":"DESIGN_LENGTH_CENTER_TO_OUTLET_END_EFFECTIVE", "label":"Design Length Center To Outlet End Effective" }, { "name":"DESIGN_LENGTH_CENTER_TO_RUN_END_EFFECTIVE", "label":"Design Length Center To Run End Effective" }, { "name":"WALL_THICKNESS", "label":"Wall Thickness" }, { "name":"UPDATE_GRAPHICS", "label":"Update Graphics" }, { "name":"NOMINAL_DIAMETER_RUN_END", "label":"Nominal Diameter Run End" }, { "name":"Geometry", "label":"Element Geometry" }, { "name":"SPOOL_ID", "label":"Spool Id" }, { "name":"SPOOL_NUMBER", "label":"Spool Number" }, { "name":"OPTION_CODE", "label":"Option Code" }, { "name":"NOTES", "label":"Notes" }, { "name":"CATALOG_NAME", "label":"Catalog Name" }, { "name":"EC_CLASS_NAME", "label":"EC Class Name" }, { "name":"UNIT_OF_MEASURE", "label":"Unit Of Measure" }, { "name":"PIECE_MARK", "label":"Piece Mark" }, { "name":"FABRICATION_CATEGORY", "label":"Fabrication Category" }, { "name":"LINENUMBER", "label":"Line Number" }, { "name":"INSULATION_THICKNESS", "label":"Insulation Thickness" }, { "name":"INSULATION", "label":"Insulation Material" }, { "name":"LENGTH", "label":"Length" }, { "name":"INSIDE_DIAMETER", "label":"Inside Diameter" }, { "name":"NORMAL_OPERATING_PRESSURE", "label":"Normal Operating Pressure" }, { "name":"OUTSIDE_DIAMETER", "label":"Outside Diameter" }, { "name":"PIPE_FLANGE_TYPE", "label":"Pipe Flange Type" }, { "name":"GRADE", "label":"Grade" }, { "name":"SHOP_FIELD", "label":"Shop Field" }, { "name":"HUB_DEPTH", "label":"Hub Depth" }, { "name":"HUB_WIDTH", "label":"Hub Width" }, { "name":"TRACING", "label":"Tracing" } ], "aspects":[], "typeDefinitions":[] } ] } ] }
4. Conclusion
You should be able to successfully retrieve the related IDs and schema info for your model. While this information is of limited value on its own, it is useful and necessary for creating clash detection and property validation tests, so check out the continue learning section to put your new knowledge to use.