From 4a034f9bdb763f754c4063f043c8d81a1f034a15 Mon Sep 17 00:00:00 2001 From: Matthew Jordan <14913576+mattbjordan@users.noreply.github.com> Date: Thu, 7 Dec 2023 12:50:11 -0500 Subject: [PATCH] translate saved view into view state and display; currently depends on saved views utilities package --- .../src/models/savedViews/View.ts | 22 +- .../translation/SavedViewTranslation.tsx | 240 +++++++ .../translation/clipVectorsExtractor.ts | 84 +++ .../translation/displayStyleExtractor.ts | 665 ++++++++++++++++++ .../translation/extensionExtractor.ts | 121 ++++ .../api/utilities/translation/urlConverter.ts | 67 ++ ...viewExtractorSavedViewToLegacySavedView.ts | 367 ++++++++++ packages/saved-views-react/src/saved-views.ts | 1 + .../src/App/ITwinJsApp/ITwinJsApp.tsx | 45 +- 9 files changed, 1589 insertions(+), 23 deletions(-) create mode 100644 packages/saved-views-react/src/api/utilities/translation/SavedViewTranslation.tsx create mode 100644 packages/saved-views-react/src/api/utilities/translation/clipVectorsExtractor.ts create mode 100644 packages/saved-views-react/src/api/utilities/translation/displayStyleExtractor.ts create mode 100644 packages/saved-views-react/src/api/utilities/translation/extensionExtractor.ts create mode 100644 packages/saved-views-react/src/api/utilities/translation/urlConverter.ts create mode 100644 packages/saved-views-react/src/api/utilities/translation/viewExtractorSavedViewToLegacySavedView.ts diff --git a/packages/saved-views-client/src/models/savedViews/View.ts b/packages/saved-views-client/src/models/savedViews/View.ts index 1e37b7f8..6b9727f9 100644 --- a/packages/saved-views-client/src/models/savedViews/View.ts +++ b/packages/saved-views-client/src/models/savedViews/View.ts @@ -32,7 +32,7 @@ export interface ViewITwinDrawing extends ViewITwin2d { } /** Minimum required information saved for a 2d saved view (Used by Sheet and Drawings). */ -export interface ViewITwin2d extends SavedViewBase { +export interface ViewITwin2d extends SavedViewApiBase { baseModelId: string; origin: [x: number, y: number]; delta: [x: number, y: number]; @@ -40,7 +40,7 @@ export interface ViewITwin2d extends SavedViewBase { displayStyle?: DisplayStyleSettingsProps; } -export interface SavedViewBase { +export interface SavedViewApiBase { /** Origin, represented as an array of x and y coordinates. */ origin: [number, number] | [number, number, number]; /** List of categories that should be displayed or hidden on that view. */ @@ -92,14 +92,24 @@ export interface ClipPlaneProps { } /** Minimum saved view structure including possible legacy data from product setting service. */ -export type ViewWithLegacy = View & { legacyView: unknown; }; +export type ViewWithLegacy = View & { legacyView: unknown; }; // TODO: Replace all usaged with ViewDataWithLegacy /** Minimum Saved View structure so every application can have something to work with. */ export type View = { itwin3dView: ViewITwin3d; } | { itwinSheetView: ViewITwinSheet; } - | { itwinDrawingView: ViewITwinDrawing; }; + | { itwinDrawingView: ViewITwinDrawing; }; // TODO: Replace all usaged with ViewData + +export type ViewDataItwin3d = { itwin3dView: ViewITwin3d; }; +export type ViewDataITwinSheet = { itwinSheetView: ViewITwinSheet; }; +export type ViewDataITwinDrawing = { itwinDrawingView: ViewITwinDrawing; }; + +/** Minimum Saved View structure so every application can have something to work with. */ +export type ViewData = ViewDataItwin3d | ViewDataITwinSheet | ViewDataITwinDrawing; + +/** Minimum saved view structure including possible legacy data from product setting service. */ +export type ViewDataWithLegacy = ViewData & { legacyView: unknown; }; /** Minimum required information saved for a 3D saved view. */ -export interface ViewITwin3d extends SavedViewBase { +export interface ViewITwin3d extends SavedViewApiBase { origin: [x: number, y: number, z: number]; extents: [x: number, y: number, z: number]; angles?: ViewYawPitchRoll; @@ -129,7 +139,7 @@ export interface ViewVisibilityList { } export interface SavedViewWithDataRepresentation extends SavedView { - savedViewData: ViewWithLegacy; + savedViewData: ViewDataWithLegacy; extensions?: Extension[]; } diff --git a/packages/saved-views-react/src/api/utilities/translation/SavedViewTranslation.tsx b/packages/saved-views-react/src/api/utilities/translation/SavedViewTranslation.tsx new file mode 100644 index 00000000..cdd4014b --- /dev/null +++ b/packages/saved-views-react/src/api/utilities/translation/SavedViewTranslation.tsx @@ -0,0 +1,240 @@ +import { IModelReadRpcInterface, ViewQueryParams, ViewStateProps } from "@itwin/core-common"; +import { DrawingViewState, IModelConnection, SheetViewState, SpatialViewState, ViewState } from "@itwin/core-frontend"; +import { Extension, SavedViewWithDataRepresentation, ViewData, ViewDataITwinDrawing, ViewDataITwinSheet, ViewDataItwin3d } from "@itwin/saved-views-client"; +import { cleanLegacyViewModelSelectorPropsModels, savedViewITwin3dToLegacy3dSavedView, savedViewItwinDrawingToLegacyDrawingView, savedViewItwinSheetToLegacySheetSavedView } from "./viewExtractorSavedViewToLegacySavedView.js"; +import { ViewTypes } from "../../../SavedViewTypes.js"; +import { SavedView as LegacySavedView, SavedView2d as LegacySavedView2d, SavedViewBase as LegacySavedViewBase } from "../SavedViewTypes.js"; +import { isDrawingSavedView, isSheetSavedView, isSpatialSavedView } from "../../clients/ISavedViewsClient.js"; + +export async function translateSavedViewIntoITwinJsViewState(savedView: SavedViewWithDataRepresentation, iModelConnection: IModelConnection): Promise { + + // Doesn't have to be done this way, but for now... + // Converts views into legacy views, then converts the legacy view into an iTwin.js-style ViewState + + // Translate into legacy view + const legacySavedView: LegacySavedView | LegacySavedView2d = await translateSavedViewToLegacySavedView(savedView, iModelConnection); + + // Translate into iTwin.js-style ViewState + const viewState = await translateLegacySavedViewToITwinJsViewState(legacySavedView, iModelConnection); + + return viewState; +} + +declare const isSavedViewItwin3d: (savedViewData: ViewData) => savedViewData is ViewDataItwin3d; +declare const isSavedViewItwinSheet: (savedViewData: ViewData) => savedViewData is ViewDataITwinSheet; +declare const isSavedViewItwinDrawing: (savedViewData: ViewData) => savedViewData is ViewDataITwinDrawing; + +/** +* Convert the saved view response recieved from the itwin-saved-views API into a SavedViewBaseSetting +* @param id +* @param savedView +* @returns Promise +*/ +async function translateSavedViewToLegacySavedView( + savedView: SavedViewWithDataRepresentation, + iModelConnection: IModelConnection, + ): Promise { + const savedViewData = savedView.savedViewData; + // If the legacy view already exists, use that; otherwise, use extraction code to get the legacy view + let legacySavedView: LegacySavedView | LegacySavedView2d; + if (savedViewData.legacyView) { + savedView = cleanLegacyViewModelSelectorPropsModels(savedView); + legacySavedView = savedViewData.legacyView as any; + // legacySavedView.id = savedView.id; // Change legacy sv id to comboId + + } else if (isSavedViewItwin3d(savedViewData)) { + const iModelViewData = await fetchIModelViewData( + ViewTypes.ViewDefinition3d, + iModelConnection, + ); + + const actual = savedViewITwin3dToLegacy3dSavedView( + savedView, + iModelViewData as SpatialViewState + ); + legacySavedView = actual; + } else if (isSavedViewItwinDrawing(savedViewData)) { + const iModelViewData = await fetchIModelViewData( + ViewTypes.DrawingViewDefinition, + iModelConnection, + ); + const actual = savedViewItwinDrawingToLegacyDrawingView( + savedView, + iModelViewData as DrawingViewState + ); + legacySavedView = actual; + } else if (isSavedViewItwinSheet(savedViewData)) { + const iModelViewData = await fetchIModelViewData( + ViewTypes.SheetViewDefinition, + iModelConnection, + ); + const actual = savedViewItwinSheetToLegacySheetSavedView( + savedView, + iModelViewData as SheetViewState + ); + legacySavedView = actual; + } else { + throw new Error( + "Could not translate itwin-saved-views API response to a SavedViewBaseSetting" + ); + } + + // Append all extensions to the saved view + legacySavedView.extensions = new Map(); + for (const ext of savedView.extensions as Extension[]) { + if (ext.extensionName && ext.data) { + legacySavedView.extensions.set(ext.extensionName, ext.data); + } + } + + return legacySavedView; + } + + +/** + * Grabs Seeded SavedView From IModel + * @throws if iModel Connection of App is invalid + * @returns iModelViewData + */ +async function fetchIModelViewData(viewClassName: ViewTypes, iModelConnection: IModelConnection) { + // let seedViewState = this._seedViewStates.get(viewClassName); + // if (seedViewState) { + // return seedViewState; + // } + // const iModelConnection = UiFramework.getIModelConnection(); + // if (!iModelConnection) { + // throw new Error("IModel Connection is invalid "); + // } + const viewId = await getDefaultViewIdFromClassName( + iModelConnection, + viewClassName + ); + let seedViewState = await iModelConnection.views.load(viewId); + // this._seedViewStates.set(viewClassName, seedViewState); + return seedViewState; +} + + // code is not D.R.Y but this decision was made to uphold existing contracts + // method shared some implementation with getDefaultViewId +async function getDefaultViewIdFromClassName( + iModelConnection: IModelConnection, + savedViewType: ViewTypes + ) { + let viewFullName = undefined; + switch (savedViewType) { + case ViewTypes.ViewDefinition3d: + viewFullName = SpatialViewState.classFullName; + break; + case ViewTypes.DrawingViewDefinition: + viewFullName = DrawingViewState.classFullName; + break; + case ViewTypes.SheetViewDefinition: + viewFullName = SheetViewState.classFullName; + break; + default: + throw new Error("Unrecognized View Type"); + } + // eslint-disable-next-line deprecation/deprecation + const viewId = await iModelConnection.views.queryDefaultViewId(); + const params: ViewQueryParams = {}; + params.from = viewFullName; + params.where = "ECInstanceId=" + viewId; + + // Check validity of default view + const viewProps = + await IModelReadRpcInterface.getClient().queryElementProps( + iModelConnection.getRpcProps(), + params + ); + if (viewProps.length === 0) { + // Return the first view we can find + const viewList = await iModelConnection.views.getViewList({ + from: viewFullName, + wantPrivate: false, + }); + if (viewList.length === 0) { + return ""; + } + return viewList[0].id; + } + + return viewId; + } + +/** + * Creates a ViewState from a SavedView object returned by the SavedViewsClient + * @param iModelConnection IModelConnection to use for requesting source view states + * @param savedView SavedView object obtained from SavedViewsClient + */ +async function createViewState( + iModelConnection: IModelConnection, + savedView: LegacySavedViewBase, +): Promise { + if (isSpatialSavedView(savedView)) { + return _createSpatialViewState(iModelConnection, savedView as LegacySavedView); + } else if (isDrawingSavedView(savedView)) { + return _createDrawingViewState(iModelConnection, savedView as LegacySavedView2d); + } else if (isSheetSavedView(savedView)) { + return _createSheetViewState(iModelConnection, savedView as LegacySavedView2d); + } + return undefined; +} + +/** Creates a spatial view state from the saved view object props */ +async function _createSpatialViewState( + iModelConnection: IModelConnection, + savedView: LegacySavedView, +): Promise { + const props: ViewStateProps = { + viewDefinitionProps: savedView.viewDefinitionProps, + categorySelectorProps: savedView.categorySelectorProps, + modelSelectorProps: savedView.modelSelectorProps, + displayStyleProps: savedView.displayStyleProps, + }; + const viewState = SpatialViewState.createFromProps(props, iModelConnection); + await viewState.load(); + return viewState; +} + +/** Creates a drawing view state from the data object */ +async function _createDrawingViewState( + iModelConnection: IModelConnection, + savedView: LegacySavedView2d, + ): Promise { + const props: ViewStateProps = { + viewDefinitionProps: savedView.viewDefinitionProps, + categorySelectorProps: savedView.categorySelectorProps, + displayStyleProps: savedView.displayStyleProps, + }; + const viewState = DrawingViewState.createFromProps(props, iModelConnection) as DrawingViewState; + await viewState.load(); + return viewState; + } + +/** Creates a sheet view state from the data object */ +async function _createSheetViewState( + iModelConnection: IModelConnection, + savedView: LegacySavedView2d, + ): Promise { + if ( + savedView.sheetProps === undefined || + savedView.sheetAttachments === undefined + ) { + return undefined; + } + const props: ViewStateProps = { + viewDefinitionProps: savedView.viewDefinitionProps, + categorySelectorProps: savedView.categorySelectorProps, + displayStyleProps: savedView.displayStyleProps, + sheetProps: savedView.sheetProps, + sheetAttachments: savedView.sheetAttachments, + }; + const viewState = SheetViewState.createFromProps(props, iModelConnection); + await viewState.load(); + return viewState; + } + + +async function translateLegacySavedViewToITwinJsViewState(legacySavedView: LegacySavedView | LegacySavedView2d, iModelConnection: IModelConnection): Promise { + return createViewState(iModelConnection, legacySavedView); +} diff --git a/packages/saved-views-react/src/api/utilities/translation/clipVectorsExtractor.ts b/packages/saved-views-react/src/api/utilities/translation/clipVectorsExtractor.ts new file mode 100644 index 00000000..81956bfe --- /dev/null +++ b/packages/saved-views-react/src/api/utilities/translation/clipVectorsExtractor.ts @@ -0,0 +1,84 @@ +// Copyright (c) Bentley Systems, Incorporated. All rights reserved. +import { + type ExtractionFunc, + applyExtraction, + extractArray2d, + extractArrayConditionally, + extractBoolean, + extractNumber, + extractObject, + extractSimpleArray, + simpleTypeOf, +} from "@bentley/itwin-saved-views-utilities"; + +const isPoint = (val: unknown): val is [number, number, number] => + Array.isArray(val) && + val.length === 3 && + val.every((num: unknown) => typeof num === "number"); + +const isTransformRow = ( + value: unknown +): value is [number, number, number, number] => + Array.isArray(value) && + value.length === 4 && + value.every((num: unknown) => typeof num === "number"); + +const clipPrimitiveShapeMappings: ExtractionFunc[] = [ + extractObject( + [ + extractSimpleArray(isPoint, "points"), + extractSimpleArray(isTransformRow, "transform", "trans"), + extractNumber("zLow", "zlow"), + extractNumber("zHigh", "zhigh"), + extractBoolean("mask"), + extractBoolean("invisible"), + ], + "shape" + ), +]; + +const clipPlaneMappings: ExtractionFunc[] = [ + extractSimpleArray(simpleTypeOf("number"), "normal"), + extractNumber("dist", "distance"), + extractBoolean("invisible"), + extractBoolean("interior"), +]; + +const clipPrimitivePlaneMappings: ExtractionFunc[] = [ + extractObject( + [extractArray2d(clipPlaneMappings, "clips"), extractBoolean("invisible")], + "planes" + ), +]; + +const clipVectorMappings: ExtractionFunc[] = [ + extractArrayConditionally( + [ + { + discriminator: "planes", + mappings: [...clipPrimitivePlaneMappings], + }, + { + discriminator: "shape", + mappings: [...clipPrimitiveShapeMappings], + }, + ], + "clip", + "clipVectors" + ), +]; + +/** + * Extracts the clip vectors from a PSS View + * @param input + */ +export const extractClipVectors = (input: object) => { + const viewDetails = input; + if (!("clipVectors" in input)) { + return undefined; + } + + const output: any = {}; + applyExtraction(viewDetails, output, clipVectorMappings); + return output.clipVectors; +}; diff --git a/packages/saved-views-react/src/api/utilities/translation/displayStyleExtractor.ts b/packages/saved-views-react/src/api/utilities/translation/displayStyleExtractor.ts new file mode 100644 index 00000000..335bd7cf --- /dev/null +++ b/packages/saved-views-react/src/api/utilities/translation/displayStyleExtractor.ts @@ -0,0 +1,665 @@ +// Copyright (c) Bentley Systems, Incorporated. All rights reserved. +import { + type ExtractionFunc, + type ViewItwin2d, + type ViewItwin3d, + applyExtraction, + extractArray, + extractArrayConditionally, + extractBoolean, + extractColor, + extractColorLegacy, + extractConditionally, + extractNumber, + extractNumberOrBool, + extractObject, + extractPlainTypedMap, + extractRGB, + extractSimpleArray, + extractString, + extractStringOrArray, + extractStringOrNumber, + extractStringOrNumberArray, + isAnyColorFormat, + simpleTypeOf, +} from "@bentley/itwin-saved-views-utilities"; +import { type ViewState } from "@itwin/core-frontend"; + +import { type SavedView, type SavedView2d } from "../SavedViewTypes"; + +const viewFlagMappings: ExtractionFunc[] = [ + extractNumber("renderMode"), + extractBoolean("noConstructions", "noConstruct"), + extractBoolean("noDimensions", "noDim"), + extractBoolean("noPattern"), + extractBoolean("noWeight"), + extractBoolean("noStyle"), + extractBoolean("noTransparency", "noTransp"), + extractBoolean("noFill"), + extractBoolean("noTexture"), + extractBoolean("noMaterial"), + extractBoolean("visibleEdges", "visEdges"), + extractBoolean("hiddenEdges", "hidEdges"), + extractBoolean("shadows"), + extractBoolean("clipVolume", "clipVol"), + extractBoolean("monochrome"), + extractBoolean("backgroundMap"), + extractBoolean("ambientOcclusion"), +]; + +const viewFlagLegacyMappings: ExtractionFunc[] = [ + extractNumber("renderMode"), + extractBoolean("noConstruct", "noConstructions"), + extractBoolean("noDim", "noDimensions"), + extractBoolean("noPattern"), + extractBoolean("noWeight"), + extractBoolean("noStyle"), + extractBoolean("noTransp", "noTransparency"), + extractBoolean("noFill"), + extractBoolean("noTexture"), + extractBoolean("noMaterial"), + extractBoolean("visEdges", "visibleEdges"), + extractBoolean("hidEdges", "hiddenEdges"), + extractBoolean("shadows"), + extractBoolean("clipVol", "clipVolume"), + extractBoolean("monochrome"), + extractBoolean("backgroundMap"), + extractBoolean("ambientOcclusion"), +]; + +const planarClipMaskMappings: ExtractionFunc[] = [ + extractNumber("mode"), + extractString("modelIds"), + extractString("subCategoryOrElementIds"), + extractNumber("priority"), + extractNumber("transparency"), + extractBoolean("invert"), +]; + +const displayStylePlanarClipMaskMappings: ExtractionFunc[] = [ + ...planarClipMaskMappings, + extractString("modelId"), +]; + +const backgroundMapMappings: ExtractionFunc[] = [ + extractNumber("groundBias"), + extractNumberOrBool("transparency"), + extractBoolean("useDepthBuffer"), + extractBoolean("applyTerrain"), + extractObject( + [ + extractString("providerName"), + extractNumber("exaggeration"), + extractBoolean("applyLighting"), + extractNumber("heightOrigin"), + extractNumber("heightOriginMode"), + ], + "terrainSettings" + ), + extractNumber("globeMode"), + extractBoolean("nonLocatable"), + extractObject(planarClipMaskMappings, "planarClipMask"), +]; + +const displayStyleSubCategoryMappings: ExtractionFunc[] = [ + extractString("subCategory"), + extractColor("color"), + extractColor("fill"), + extractBoolean("invisible"), + extractNumber("weight"), + extractString("style"), + extractNumber("priority"), + extractString("material"), + extractNumber("transp", "transparency"), + extractNumber("transpFill", "transparencyFill"), +]; + +export const featureAppearanceMappings: ExtractionFunc[] = [ + extractRGB("rgb"), + extractNumber("weight"), + extractNumber("transparency"), + extractNumber("linePixels"), + extractBoolean("ignoresMaterial"), + extractBoolean("nonLocatable"), + extractBoolean("emphasized"), +]; + +export const featureAppearanceLegacyMappings: ExtractionFunc[] = [ + extractColorLegacy("rgb"), + extractNumber("weight"), + extractNumber("transparency"), + extractNumber("linePixels"), + extractBoolean("ignoresMaterial"), + extractBoolean("nonLocatable"), + extractBoolean("emphasized"), +]; + +const displayStyleModelAppearanceMappings: ExtractionFunc[] = [ + ...featureAppearanceMappings, + extractString("modelId"), +]; + +const displayStyleModelAppearanceLegacyMappings: ExtractionFunc[] = + [...featureAppearanceLegacyMappings, extractString("modelId")]; +const contextRealityModelsMappings: ExtractionFunc[] = [ + extractObject( + [ + extractString("provider"), + extractString("format"), + extractString("id"), + extractString("iTwinId"), + ], + "rdSourceKey", + "realityDataSourceKey" + ), + extractString("tilesetUrl"), + extractString("realityDataId"), + extractString("name"), + extractString("description"), + extractArray( + [ + extractString("modelId"), + extractNumber("expand"), + extractObject( + [ + extractNumber("inside"), + extractNumber("outside"), + extractBoolean("isVolumeClassifier"), + ], + "flags" + ), + extractString("name"), + extractBoolean("isActive"), + ], + "classifiers" + ), + extractObject(planarClipMaskMappings, "planarClipMask"), + extractObject(featureAppearanceMappings, "appearanceOverrides"), +]; + +const contextRealityModelsLegacyMappings: ExtractionFunc[] = [ + extractObject( + [ + extractString("provider"), + extractString("format"), + extractString("id"), + extractString("iTwinId"), + ], + "rdSourceKey", + "realityDataSourceKey" + ), + extractString("tilesetUrl"), + extractString("realityDataId"), + extractString("name"), + extractString("description"), + extractArray( + [ + extractString("modelId"), + extractNumber("expand"), + extractObject( + [ + extractNumber("inside"), + extractNumber("outside"), + extractBoolean("isVolumeClassifier"), + ], + "flags" + ), + extractString("name"), + extractBoolean("isActive"), + ], + "classifiers" + ), + extractObject(planarClipMaskMappings, "planarClipMask"), + extractObject(featureAppearanceLegacyMappings, "appearanceOverrides"), +]; + +const commonMapLayerPropsMapping: ExtractionFunc[] = [ + extractBoolean("visible"), + extractString("name"), + extractNumber("transparency"), + extractBoolean("transparentBackground"), +]; + +const mapSubLayerMappings: ExtractionFunc[] = [ + extractString("name"), + extractString("title"), + extractBoolean("visible"), + extractStringOrNumber("id"), + extractStringOrNumber("parent"), + extractStringOrNumberArray("children"), +]; + +const imageMapLayerPropsMapping: ExtractionFunc[] = [ + ...commonMapLayerPropsMapping, + extractString("url"), + extractString("formatId"), + extractArray(mapSubLayerMappings, "subLayers"), +]; + +const baseMapLayerPropsMapping: ExtractionFunc[] = [ + ...imageMapLayerPropsMapping, + extractObject([extractString("name"), extractNumber("type")], "provider"), +]; + +const modelMapLayerPropsMapping: ExtractionFunc[] = [ + ...commonMapLayerPropsMapping, + extractString("modelId"), +]; + +const mapImageryMapping: ExtractionFunc[] = [ + extractConditionally( + [ + { + discriminator: isAnyColorFormat, + mappings: extractColor, + }, + { + discriminator: "url", + mappings: baseMapLayerPropsMapping, + }, + ], + "backgroundBase" + ), + extractArrayConditionally( + [ + { + discriminator: "modelId", + mappings: modelMapLayerPropsMapping, + }, + { + discriminator: "url", + mappings: imageMapLayerPropsMapping, + }, + ], + "backgroundLayers" + ), + extractArrayConditionally( + [ + { + discriminator: "modelId", + mappings: modelMapLayerPropsMapping, + }, + { + discriminator: "url", + mappings: imageMapLayerPropsMapping, + }, + ], + "overlayLayers" + ), +]; + +const viewFlagOverridesMapping: ExtractionFunc[] = [ + extractNumber("renderMode"), + extractBoolean("dimensions"), + extractBoolean("patterns"), + extractBoolean("weights"), + extractBoolean("styles"), + extractBoolean("transparency"), + extractBoolean("fill"), + extractBoolean("textures"), + extractBoolean("materials"), + extractBoolean("acs"), + extractBoolean("grid"), + extractBoolean("visibleEdges"), + extractBoolean("hiddenEdges"), + extractBoolean("shadows"), + extractBoolean("clipVolume"), + extractBoolean("constructions"), + extractBoolean("constructions"), + extractBoolean("backgroundMap"), + extractBoolean("ambientOcclusion"), + extractBoolean("wiremesh"), + extractBoolean("lighting"), +]; + +const hiddenLineStyleMappings: ExtractionFunc[] = [ + extractBoolean("overrideColor", "ovrColor"), + extractColor("color"), + extractNumber("pattern"), + extractNumber("width"), +]; + +const hiddenLineStyleLegacyMappings: ExtractionFunc[] = [ + extractBoolean("ovrColor", "overrideColor"), + extractColorLegacy("color"), + extractNumber("pattern"), + extractNumber("width"), +]; + +const hiddenLineSettingsMappings: ExtractionFunc[] = [ + extractObject(hiddenLineStyleMappings, "visible"), + extractObject(hiddenLineStyleMappings, "hidden"), + extractNumber("transparencyThreshold", "transThreshold"), +]; + +const hiddenLineSettingsLegacyMappings: ExtractionFunc[] = [ + extractObject(hiddenLineStyleLegacyMappings, "visible"), + extractObject(hiddenLineStyleLegacyMappings, "hidden"), + extractNumber("transThreshold", "transparencyThreshold"), +]; + +const cutStyleMappings: ExtractionFunc[] = [ + extractObject(viewFlagOverridesMapping, "viewflags"), + extractObject(hiddenLineSettingsMappings, "hiddenLine"), + extractObject(featureAppearanceMappings, "appearance"), +]; + +const cutStyleLegacyMappings: ExtractionFunc[] = [ + extractObject(viewFlagOverridesMapping, "viewflags"), + extractObject(hiddenLineSettingsLegacyMappings, "hiddenLine"), + extractObject(featureAppearanceLegacyMappings, "appearance"), +]; + +const clipStyleIntersectionMappings: ExtractionFunc[] = [ + extractRGB("color"), + extractNumber("width"), +]; + +const clipStyleIntersectionLegacyMappings: ExtractionFunc[] = [ + extractColorLegacy("color"), + extractNumber("width"), +]; + +const clipStyleMappings: ExtractionFunc[] = [ + extractBoolean("produceCutGeometry"), + extractObject(cutStyleMappings, "cutStyle"), + extractRGB("insideColor"), + extractRGB("outsideColor"), + extractBoolean("colorizeIntersection"), + extractObject(clipStyleIntersectionMappings, "intersectionStyle"), +]; + +const clipStyleLegacyMappings: ExtractionFunc[] = [ + extractBoolean("produceCutGeometry"), + extractObject(cutStyleLegacyMappings, "cutStyle"), + extractColorLegacy("insideColor"), + extractColorLegacy("outsideColor"), + extractBoolean("colorizeIntersection"), + extractObject(clipStyleIntersectionLegacyMappings, "intersectionStyle"), +]; + +const displayStylesMapping: ExtractionFunc[] = [ + extractObject(viewFlagMappings, "viewflags"), + extractColor("backgroundColor"), + extractColor("monochromeColor"), + extractNumber("monochromeMode"), + extractString("renderTimeline"), + extractNumber("timePoint"), + extractArray( + displayStyleSubCategoryMappings, + "subCategoryOverrides", + "subCategoryOvr" + ), + extractObject(backgroundMapMappings, "backgroundMap"), + extractArray(contextRealityModelsMappings, "contextRealityModels"), + extractStringOrArray("excludedElements"), + extractObject(mapImageryMapping, "mapImagery"), + extractArray( + displayStyleModelAppearanceMappings, + "modelOverrides", + "modelOvr" + ), + extractObject(clipStyleMappings, "clipStyle"), + extractArray( + displayStylePlanarClipMaskMappings, + "planarClipOverrides", + "planarClipOvr" + ), +]; + +const displayStylesLegacyMapping: ExtractionFunc[] = [ + extractObject(viewFlagLegacyMappings, "viewflags"), + extractColorLegacy("backgroundColor"), + extractColorLegacy("monochromeColor"), + extractNumber("monochromeMode"), + extractString("renderTimeline"), + extractNumber("timePoint"), + extractArray( + displayStyleSubCategoryMappings, + "subCategoryOvr", + "subCategoryOverrides" + ), + extractObject(backgroundMapMappings, "backgroundMap"), + extractArray(contextRealityModelsLegacyMappings, "contextRealityModels"), + extractStringOrArray("excludedElements"), + extractObject(mapImageryMapping, "mapImagery"), + extractArray( + displayStyleModelAppearanceLegacyMappings, + "modelOvr", + "modelOverrides" + ), + extractObject(clipStyleLegacyMappings, "clipStyle"), + extractArray( + displayStylePlanarClipMaskMappings, + "planarClipOvr", + "planarClipOverrides" + ), +]; + +const environmentMappings: ExtractionFunc[] = [ + extractObject( + [ + extractBoolean("display"), + extractNumber("elevation"), + extractColor("aboveColor"), + extractColor("belowColor"), + ], + "ground" + ), + extractObject( + [ + extractBoolean("display"), + extractBoolean("twoColor"), + extractColor("skyColor"), + extractColor("groundColor"), + extractColor("zenithColor"), + extractColor("nadirColor"), + extractNumber("skyExponent"), + extractNumber("groundExponent"), + extractObject( + [ + extractNumber("type"), + extractString("texture"), + extractObject( + [ + extractString("front"), + extractString("back"), + extractString("top"), + extractString("bottom"), + extractString("right"), + extractString("left"), + ], + "textures" + ), + ], + "image" + ), + ], + "sky" + ), +]; + +const environmentLegacyMappings: ExtractionFunc[] = [ + extractObject( + [ + extractBoolean("display"), + extractNumber("elevation"), + extractColorLegacy("aboveColor"), + extractColorLegacy("belowColor"), + ], + "ground" + ), + extractObject( + [ + extractBoolean("display"), + extractBoolean("twoColor"), + extractColorLegacy("skyColor"), + extractColorLegacy("groundColor"), + extractColorLegacy("zenithColor"), + extractColorLegacy("nadirColor"), + extractNumber("skyExponent"), + extractNumber("groundExponent"), + extractObject( + [ + extractNumber("type"), + extractString("texture"), + extractObject( + [ + extractString("front"), + extractString("back"), + extractString("top"), + extractString("bottom"), + extractString("right"), + extractString("left"), + ], + "textures" + ), + ], + "image" + ), + ], + "sky" + ), +]; + +const ambientOcclusionMappings: ExtractionFunc[] = [ + extractNumber("bias"), + extractNumber("zLengthCap"), + extractNumber("maxDistance"), + extractNumber("intensity"), + extractNumber("texelStepSize"), + extractNumber("blurDelta"), + extractNumber("blurSigma"), + extractNumber("blurTexelStepSize"), +]; + +const solarShadowMappings: ExtractionFunc[] = [ + extractColor("color"), +]; + +const lightsMappings: ExtractionFunc[] = [ + extractObject([extractNumber("intensity")], "portrait"), + extractObject( + [ + extractNumber("intensity"), + extractSimpleArray(simpleTypeOf("number"), "direction"), + extractBoolean("alwaysEnabled"), + extractNumber("timePoint"), + ], + "solar" + ), + extractObject( + [ + extractColor("upperColor"), + extractColor("lowerColor"), + extractNumber("intensity"), + ], + "hemisphere" + ), + extractObject([extractColor("color"), extractNumber("intensity")], "ambient"), + extractNumber("specularIntensity"), + extractNumber("numCels"), + extractObject( + [extractNumber("intensity"), extractBoolean("invert")], + "fresnel" + ), +]; + +const planProjectionSettingsMappings: ExtractionFunc[] = [ + extractNumber("elevation"), + extractNumber("transparency"), + extractBoolean("overlay"), + extractBoolean("enforceDisplayPriority"), +]; + +const displayStyle3dMapping: ExtractionFunc[] = [ + ...displayStylesMapping, + extractObject(environmentMappings, "environment"), + extractObject(ambientOcclusionMappings, "ambientOcclusion", "ao"), + extractObject(solarShadowMappings, "solarShadows"), + extractObject(lightsMappings, "lights"), + extractPlainTypedMap( + planProjectionSettingsMappings, + simpleTypeOf("string"), + "planProjections" + ), +]; + +const displayStyle3dLegacyMapping: ExtractionFunc[] = [ + ...displayStylesLegacyMapping, + extractObject(environmentLegacyMappings, "environment"), + extractObject(ambientOcclusionMappings, "ambientOcclusion", "ao"), + extractObject(solarShadowMappings, "solarShadows"), + extractObject(lightsMappings, "lights"), + extractPlainTypedMap( + planProjectionSettingsMappings, + simpleTypeOf("string"), + "planProjections" + ), +]; + +/** + * Extracts the display style from a PSS view displayStyle field + * And transforms it into our schema + * @param data + * @param viewState + */ +export const extractDisplayStyle = (data: object, viewState?: ViewState) => { + let styles; + const output: any = {}; + if ("displayStyle" in data) { + styles = (data as ViewItwin2d).displayStyle; + applyExtraction(styles, output, displayStylesMapping); + } + if ("displayStyleProps" in data) { + styles = (data as SavedView2d).displayStyleProps.jsonProperties?.styles; + applyExtraction(styles, output, displayStylesLegacyMapping); + } + if (styles === undefined) { + return undefined; + } + if (viewState) { + appendAcsAndGridViewFlagsToOutput(viewState, output); + } + return output; +}; + +/** + * Extracts the display style 3d from a PSS view displayStyle field + * And transforms it into our schema + * @param data + */ +export const extractDisplayStyle3d = (data: object) => { + let styles; + const output: any = {}; + if ("displayStyle" in data) { + styles = (data as ViewItwin3d).displayStyle; + applyExtraction(styles, output, displayStyle3dMapping); + } + if ("displayStyleProps" in data) { + styles = (data as SavedView).displayStyleProps.jsonProperties?.styles; + applyExtraction(styles, output, displayStyle3dLegacyMapping); + } + if (styles === undefined) { + return undefined; + } + + return output; +}; + +function appendAcsAndGridViewFlagsToOutput( + drawingViewState: ViewState, + output: any +) { + output.viewflags.acs = false; + output.viewflags.grid = true; + if (drawingViewState.auxiliaryCoordinateSystem) { + output.viewflags.acs = true; + } + const gridOrient = drawingViewState.getGridOrientation(); + const gridSpacing = drawingViewState.getGridOrientation(); + const gridPerRef = drawingViewState.getGridsPerRef(); + if (!gridOrient || !gridSpacing || !gridPerRef) { + output.viewflags.grid = false; + } +} diff --git a/packages/saved-views-react/src/api/utilities/translation/extensionExtractor.ts b/packages/saved-views-react/src/api/utilities/translation/extensionExtractor.ts new file mode 100644 index 00000000..b8f4dc45 --- /dev/null +++ b/packages/saved-views-react/src/api/utilities/translation/extensionExtractor.ts @@ -0,0 +1,121 @@ +// Copyright (c) Bentley Systems, Incorporated. All rights reserved. +import { + type ExtractionFunc, + applyExtraction, + extractArray, + extractBoolean, + extractColor, + extractNumber, + extractObject, + extractSimpleArray, + extractString, + simpleTypeOf, +} from "@bentley/itwin-saved-views-utilities"; +import { type ModelCategoryOverrideProviderProps } from "@bentley/itwin-tools"; +import { type EmphasizeElementsProps } from "@itwin/core-common"; + +import { type PerModelCategoryVisibilityProps } from "../SavedViewTypes"; +import { featureAppearanceMappings } from "./displayStyleExtractor"; + +/** Appearance Override type for EmphasizeElements */ +const appearanceOverrideEmphElemMappings: ExtractionFunc[] = [ + extractNumber("overrideType"), // an enum + extractColor("color"), + extractSimpleArray(simpleTypeOf("string"), "ids"), +]; + +/** Appearance Override type for VisibilityOverrides (ie ModelCategoryOverrideProviderProps) */ +const appearanceOverrideVisibOvrMappings: ExtractionFunc[] = [ + extractSimpleArray(simpleTypeOf("string"), "ids"), + extractObject(featureAppearanceMappings, "app"), +]; + +const emphasizeElementsMapping: ExtractionFunc[] = [ + extractSimpleArray(simpleTypeOf("string"), "neverDrawn"), + extractSimpleArray(simpleTypeOf("string"), "alwaysDrawn"), + extractBoolean("isAlwaysDrawnExclusive"), + extractSimpleArray(simpleTypeOf("string"), "alwaysDrawnExclusiveEmphasized"), + extractObject(featureAppearanceMappings, "defaultAppearance"), + extractArray(appearanceOverrideEmphElemMappings, "appearanceOverride"), + extractBoolean("wantEmphasis"), + extractObject(featureAppearanceMappings, "unanimatedAppearance"), +]; + +const perModelCategoryVisibilityMapping: ExtractionFunc[] = [ + extractString("modelId"), + extractString("categoryId"), + extractBoolean("visible"), +]; + +const visibilityOverrideMapping: ExtractionFunc[] = [ + extractArray(appearanceOverrideVisibOvrMappings, "subCategoryOverrides"), + extractArray(appearanceOverrideVisibOvrMappings, "modelOverrides"), + extractObject(appearanceOverrideVisibOvrMappings, "catEmphasizeOverride"), + extractObject(appearanceOverrideVisibOvrMappings, "modelEmphasizeOverride"), +]; + +/** + * Extracts the EmphasizeElementsProps from string data in an extension + * @param extensionData + */ +export const extractEmphasizeElements = ( + extensionData: string +): EmphasizeElementsProps | undefined => { + const dataObj = JSON.parse(extensionData); + if (dataObj === undefined || dataObj.emphasizeElementsProps === undefined) { + return undefined; + } + + const output: EmphasizeElementsProps = {}; + applyExtraction( + dataObj.emphasizeElementsProps, + output, + emphasizeElementsMapping + ); + return output; +}; + +/** + * Extracts array of PerModelCategoryVisibilityProps from string data in an extension + * @param extensionData + */ +export const extractPerModelCategoryVisibility = ( + extensionData: string +): PerModelCategoryVisibilityProps[] => { + const dataObjArray = JSON.parse(extensionData); + if ( + dataObjArray === undefined || + dataObjArray.perModelCategoryVisibilityProps === undefined + ) { + return []; + } + + const outputArray: PerModelCategoryVisibilityProps[] = []; + for (const dataObj of dataObjArray.perModelCategoryVisibilityProps) { + const output: any = {}; + applyExtraction(dataObj, output, perModelCategoryVisibilityMapping); + outputArray.push(output); + } + return outputArray; +}; + +/** + * Extracts the VisibilityOverrideProps (ie ModelCategoryOverrideProviderProps) from string data in an extension + * @param extensionData + */ +export const extractVisibilityOverride = ( + extensionData: string +): ModelCategoryOverrideProviderProps | undefined => { + const dataObj = JSON.parse(extensionData); + if (dataObj === undefined || dataObj.visibilityOverrideProps === undefined) { + return undefined; + } + + const output: ModelCategoryOverrideProviderProps = {}; + applyExtraction( + dataObj.visibilityOverrideProps, + output, + visibilityOverrideMapping + ); + return output; +}; diff --git a/packages/saved-views-react/src/api/utilities/translation/urlConverter.ts b/packages/saved-views-react/src/api/utilities/translation/urlConverter.ts new file mode 100644 index 00000000..27f4db4a --- /dev/null +++ b/packages/saved-views-react/src/api/utilities/translation/urlConverter.ts @@ -0,0 +1,67 @@ +// Copyright (c) Bentley Systems, Incorporated. All rights reserved. +import { + type BaseMapLayerProps, + type ImageMapLayerProps, + type View, +} from "@bentley/itwin-saved-views-utilities"; + +/** + * Convert url that potentially contains restricted characters ('&' or '.') to use unrestricated substitute characters ('++and++' or '++dot++') + * @param extensionData + */ +export const legacyUrlToUrl = (unrestrictedUrl: string): string => { + const restrictedUrl = unrestrictedUrl + .replaceAll("&", "++and++") + .replaceAll(".", "++dot++"); + return restrictedUrl; +}; + +/** + * Convert url that potentially contains subtituted characters ('++and++' or '++dot++') to use restricated characters ('&' or '.') + * @param extensionData + */ +export const urlToLegacyUrl = (restrictedUrl: string): string => { + const unrestrictedUrl = restrictedUrl + .replaceAll("++and++", "&") + .replaceAll("++dot++", "."); + return unrestrictedUrl; +}; + +export const convertAllLegacyUrlsToUrls = ( + // savedViewCreate: SavedViewCreate, + savedViewData: View, + convert: (url: string) => string +): void => { + const displayStyle = + savedViewData.itwin3dView?.displayStyle ?? + savedViewData.itwinDrawingView?.displayStyle ?? + savedViewData.itwinSheetView?.displayStyle; + if (displayStyle === undefined) { + return; + } + + // Convert legacy urls to restricted urls + const baseUrl = (displayStyle.mapImagery?.backgroundBase as BaseMapLayerProps) + ?.url; + if (displayStyle.mapImagery && baseUrl) { + (displayStyle.mapImagery.backgroundBase as BaseMapLayerProps).url = + convert(baseUrl); + } + for (const layer of (displayStyle.mapImagery?.overlayLayers ?? + []) as ImageMapLayerProps[]) { + if (layer.url) { + layer.url = convert(layer.url); + } + } + for (const layer of (displayStyle.mapImagery?.backgroundLayers ?? + []) as ImageMapLayerProps[]) { + if (layer.url) { + layer.url = convert(layer.url); + } + } + for (const model of displayStyle.contextRealityModels ?? []) { + if (model.tilesetUrl) { + model.tilesetUrl = convert(model.tilesetUrl); + } + } +}; diff --git a/packages/saved-views-react/src/api/utilities/translation/viewExtractorSavedViewToLegacySavedView.ts b/packages/saved-views-react/src/api/utilities/translation/viewExtractorSavedViewToLegacySavedView.ts new file mode 100644 index 00000000..d6544dfa --- /dev/null +++ b/packages/saved-views-react/src/api/utilities/translation/viewExtractorSavedViewToLegacySavedView.ts @@ -0,0 +1,367 @@ +// Copyright (c) Bentley Systems, Incorporated. All rights reserved. +// import { +// type SavedViewBase as SavedViewItwinBase, +// type SavedViewItwin3d, +// type SavedViewItwinDrawing, +// type SavedViewItwinSheet, +// type SavedViewTag, +// type SavedViewWithData, +// type ViewItwin3d, +// } from "@bentley/itwin-saved-views-utilities"; +import { type Id64Array } from "@itwin/core-bentley"; +import { + type DrawingViewState, + type SpatialViewState, + SheetViewState, +} from "@itwin/core-frontend"; +import _ from "lodash"; + +// import { SavedViewsManager } from "../../SavedViewsManager"; +// import { +// type SavedView, +// type SavedView2d, +// type SavedViewBase, +// type Tag, +// } from "../SavedViewTypes"; +import { extractClipVectors } from "./clipVectorsExtractor"; +import { + extractDisplayStyle, + extractDisplayStyle3d, +} from "./displayStyleExtractor"; +import { convertAllLegacyUrlsToUrls, urlToLegacyUrl } from "./urlConverter"; +import { SavedViewApiBase, SavedViewTag, SavedViewWithDataRepresentation, ViewDataITwinDrawing, ViewDataITwinSheet, ViewDataItwin3d, ViewITwin3d } from "@itwin/saved-views-client"; +import { Tag as LegacyTag } from "../../types"; +import { SavedView as LegacySavedView, SavedView2d as LegacySavedView2d } from "../SavedViewTypes"; +// import { SavedView, SavedView2d, SavedViewBase, Tag } from "./SavedViewTypes.js"; + +const UNGROUPED_ID = "-1"; + +/** + * Extracts id from href + * @param href + */ +export const extractIdFromHref = (href: string) => { + return href.split("/").pop(); +}; + +/** + * Extract all the tags + * @param creator href for the creator + * @param tags the list of tags in the saved view + * @returns + */ +const extractTags = (creator: string, tags?: SavedViewTag[]) => { + return tags?.map((tag) => { + const legacyTag: LegacyTag = { + name: tag.displayName, + createdByUserId: extractIdFromHref(creator) ?? "", + }; + return legacyTag; + }); +}; + +/** + * Transform a ViewItwinDrawing into a PSS SavedView if possible + * @param savedViewRsp + * @param iModelViewData + * @returns SavedView2d + */ +export function savedViewItwinDrawingToLegacyDrawingView( +// savedViewRsp: SavedViewItwinDrawing, + savedViewRsp: SavedViewWithDataRepresentation, + seedDrawingViewState: DrawingViewState +): LegacySavedView2d { + convertAllLegacyUrlsToUrls(savedViewRsp.savedViewData, urlToLegacyUrl); + const iTwinDrawingView = (savedViewRsp.savedViewData as ViewDataITwinDrawing).itwinDrawingView; + seedDrawingViewState.displayStyle; + const legacyView: LegacySavedView2d = { + id: savedViewRsp.id, + is2d: true, + groupId: savedViewRsp._links.group + ? extractIdFromHref(savedViewRsp._links.group.href) + : UNGROUPED_ID ?? "", + tags: extractTags(savedViewRsp._links.creator?.href ?? "", savedViewRsp.tags), + name: savedViewRsp.displayName, + userId: extractIdFromHref(savedViewRsp._links.creator?.href ?? "") ?? "", + shared: savedViewRsp.shared, + thumbnailId: savedViewRsp.id ?? "", + categorySelectorProps: { + classFullName: seedDrawingViewState.categorySelector.classFullName, + categories: (iTwinDrawingView.categories?.enabled ?? []) as Id64Array, + code: { + scope: seedDrawingViewState.categorySelector.code.scope, + spec: seedDrawingViewState.categorySelector.code.spec, + value: seedDrawingViewState.categorySelector.code.value, + }, + model: seedDrawingViewState.categorySelector.model, + federationGuid: + seedDrawingViewState.categorySelector.federationGuid ?? "", + id: seedDrawingViewState.categorySelector.id, + }, + viewDefinitionProps: { + classFullName: seedDrawingViewState.classFullName, + id: seedDrawingViewState.id, + jsonProperties: { + viewDetails: { + gridOrient: seedDrawingViewState.getGridOrientation() ?? undefined, + }, + }, + code: { + scope: seedDrawingViewState.code.scope, + spec: seedDrawingViewState.code.spec, + value: seedDrawingViewState.code.value, + }, + model: seedDrawingViewState.model, + federationGuid: seedDrawingViewState.federationGuid ?? "", + categorySelectorId: seedDrawingViewState.categorySelector.id, + displayStyleId: seedDrawingViewState.displayStyle.id, + isPrivate: seedDrawingViewState.isPrivate ?? false, + description: seedDrawingViewState.description ?? "", + origin: iTwinDrawingView.origin, + delta: iTwinDrawingView.delta, + angle: iTwinDrawingView.angle, + baseModelId: iTwinDrawingView.baseModelId, + }, + displayStyleProps: { + classFullName: seedDrawingViewState.displayStyle.classFullName, + id: seedDrawingViewState.displayStyle.id, + jsonProperties: { + styles: extractDisplayStyle(iTwinDrawingView, seedDrawingViewState), + }, + code: { + spec: seedDrawingViewState.displayStyle.code.spec, + scope: seedDrawingViewState.displayStyle.code.scope, + value: seedDrawingViewState.displayStyle.code.value, + }, + model: seedDrawingViewState.displayStyle.model, + federationGuid: seedDrawingViewState.displayStyle.federationGuid ?? "", + }, + }; + appendHiddenCategoriesToLegacyView(iTwinDrawingView, legacyView); + return legacyView; +} + +/** + * Transform a ViewItwinSheet into a PSS SavedView if possible + * @param savedViewRsp + * @param seedSheetViewState + * @returns SavedView2d + */ +export function savedViewItwinSheetToLegacySheetSavedView( +// savedViewRsp: SavedViewItwinSheet, + savedViewRsp: SavedViewWithDataRepresentation, + seedSheetViewState: SheetViewState +): LegacySavedView2d { + convertAllLegacyUrlsToUrls(savedViewRsp.savedViewData, urlToLegacyUrl); +// const itwinSheetView = savedViewRsp.savedViewData.itwinSheetView; + const itwinSheetView = (savedViewRsp.savedViewData as ViewDataITwinSheet).itwinSheetView; + const legacyView: LegacySavedView2d = { + id: savedViewRsp.id, + is2d: true, + groupId: savedViewRsp._links.group + ? extractIdFromHref(savedViewRsp._links.group.href) + : UNGROUPED_ID, + tags: extractTags(savedViewRsp._links.creator?.href ?? "", savedViewRsp.tags), + name: savedViewRsp.displayName, + userId: extractIdFromHref(savedViewRsp._links.creator?.href ?? "") ?? "", + shared: savedViewRsp.shared, + thumbnailId: savedViewRsp.id ?? "", + categorySelectorProps: { + classFullName: seedSheetViewState.categorySelector.classFullName, + categories: (itwinSheetView.categories?.enabled ?? []) as Id64Array, + code: { + scope: seedSheetViewState.categorySelector.code.scope, + spec: seedSheetViewState.categorySelector.code.spec, + value: seedSheetViewState.categorySelector.code.value, + }, + model: seedSheetViewState.categorySelector.model, + federationGuid: seedSheetViewState.categorySelector.federationGuid, + id: seedSheetViewState.categorySelector.id, + }, + viewDefinitionProps: { + classFullName: seedSheetViewState.classFullName, + id: seedSheetViewState.id, + jsonProperties: { + viewDetails: { + gridOrient: seedSheetViewState.getGridOrientation() ?? undefined, + }, + }, + code: { + scope: seedSheetViewState.code.scope, + spec: seedSheetViewState.code.spec, + value: seedSheetViewState.code.value, + }, + model: seedSheetViewState.model, + federationGuid: seedSheetViewState.federationGuid ?? "", + categorySelectorId: seedSheetViewState.categorySelector.id, + displayStyleId: seedSheetViewState.displayStyle.id, + isPrivate: seedSheetViewState.isPrivate ?? false, + description: seedSheetViewState.description ?? "", + origin: itwinSheetView.origin, + delta: itwinSheetView.delta, + angle: itwinSheetView.angle, + baseModelId: itwinSheetView.baseModelId, + }, + displayStyleProps: { + classFullName: seedSheetViewState.displayStyle.classFullName, + id: seedSheetViewState.displayStyle.id, + jsonProperties: { + styles: extractDisplayStyle(itwinSheetView, seedSheetViewState), + }, + code: { + spec: seedSheetViewState.displayStyle.code.spec, + scope: seedSheetViewState.displayStyle.code.scope, + value: seedSheetViewState.displayStyle.code.value, + }, + model: seedSheetViewState.displayStyle.model, + federationGuid: seedSheetViewState.displayStyle.federationGuid ?? "", + }, + sheetProps: { + width: itwinSheetView.width ?? -1, + height: itwinSheetView.height ?? -1, + model: seedSheetViewState.displayStyle.model, + classFullName: SheetViewState.classFullName, + code: { + spec: seedSheetViewState.displayStyle.code.spec, + scope: seedSheetViewState.displayStyle.code.scope, + value: "", + }, + }, + sheetAttachments: itwinSheetView.sheetAttachments ?? [], + }; + appendHiddenCategoriesToLegacyView(itwinSheetView, legacyView); + return legacyView; +} + +/** + * Transform a ViewItwin3d into a PSS SavedView if possible + * @param savedViewRsp + * @param seedSpatialViewState + * @returns SavedView + */ +export function savedViewITwin3dToLegacy3dSavedView( + // savedViewRsp: SavedViewItwin3d, + savedViewRsp: SavedViewWithDataRepresentation, + seedSpatialViewState: SpatialViewState + // ): SavedView { + ): LegacySavedView { + convertAllLegacyUrlsToUrls(savedViewRsp.savedViewData, urlToLegacyUrl); + const modelSelector = seedSpatialViewState.modelSelector; + const itwin3dView = (savedViewRsp.savedViewData as ViewDataItwin3d).itwin3dView; + const legacyView: LegacySavedView = { + id: savedViewRsp.id, + is2d: false, + groupId: savedViewRsp._links.group + ? extractIdFromHref(savedViewRsp._links.group.href) + : UNGROUPED_ID, + tags: extractTags(savedViewRsp._links.creator?.href ?? "", savedViewRsp.tags), + name: savedViewRsp.displayName, + userId: extractIdFromHref(savedViewRsp._links.creator?.href ?? "") ?? "", + shared: savedViewRsp.shared, + thumbnailId: savedViewRsp.id ?? "", + viewDefinitionProps: { + origin: itwin3dView.origin, + extents: itwin3dView.extents, + angles: itwin3dView.angles ?? {}, + camera: itwin3dView.camera!, + jsonProperties: { + viewDetails: extractClipVectors(itwin3dView) ?? {}, + }, + classFullName: seedSpatialViewState.classFullName, + code: seedSpatialViewState.code, + model: seedSpatialViewState.model, + categorySelectorId: seedSpatialViewState.categorySelector.id, + displayStyleId: seedSpatialViewState.displayStyle.id, + cameraOn: itwin3dView.camera !== undefined, + modelSelectorId: seedSpatialViewState.modelSelector.id, + }, + modelSelectorProps: { + classFullName: modelSelector.classFullName, + code: { + spec: modelSelector.code.spec, + scope: modelSelector.code.scope, + value: modelSelector.code.value, + }, + model: modelSelector.model, + models: (itwin3dView.models?.enabled ?? []) as Id64Array, + }, + categorySelectorProps: { + classFullName: seedSpatialViewState.categorySelector.classFullName, + categories: (itwin3dView.categories?.enabled ?? []) as Id64Array, + code: { + scope: seedSpatialViewState.categorySelector.code.scope, + spec: seedSpatialViewState.categorySelector.code.spec, + value: seedSpatialViewState.categorySelector.code.value, + }, + model: seedSpatialViewState.categorySelector.model, + }, + displayStyleProps: { + id: seedSpatialViewState.displayStyle.id, + classFullName: seedSpatialViewState.displayStyle.classFullName, + code: seedSpatialViewState.displayStyle.code, + model: seedSpatialViewState.displayStyle.model, + jsonProperties: { + styles: extractDisplayStyle3d((savedViewRsp.savedViewData as ViewDataItwin3d).itwin3dView), + }, + }, + }; + appendHiddenCategoriesToLegacyView(itwin3dView, legacyView); + appendHiddenModelsTo3dLegacySavedView(itwin3dView, legacyView); + return legacyView; + } + + +/** + * append Hidden Categories Or Models To Legacy Saved View + * @param iTwinView new schema + * @param legacyView + * @returns iModelViewData + */ +function appendHiddenCategoriesToLegacyView( +// iTwinView: SavedViewItwinBase, + iTwinView: SavedViewApiBase, +// legacyView: SavedViewBase +// legacyView: LegacySavedView + legacyView: LegacySavedView | LegacySavedView2d +) { + if (iTwinView.categories && iTwinView.categories.disabled) { + legacyView.hiddenCategories = iTwinView.categories.disabled as Id64Array; + } +} + +/** + * append Hidden Categories Or Models To Legacy Saved View + * @param view new schema + * @param legacyView + * @returns iModelViewData + */ +function appendHiddenModelsTo3dLegacySavedView( +// view: ViewItwin3d, + view: ViewITwin3d, + legacyView: LegacySavedView +) { + if (view.models && view.models.disabled) { + legacyView.hiddenModels = view.models?.disabled as Id64Array; + } +} + +/** + * removes null and undefined from legacy view model selectors props models + * @param savedView + * @returns SavedViewWithData + */ +export const cleanLegacyViewModelSelectorPropsModels = ( +// savedView: SavedViewWithData + savedView: SavedViewWithDataRepresentation +) => { +// if (savedView.savedViewData.legacyView?.modelSelectorProps) { + if ((savedView.savedViewData.legacyView as LegacySavedView)?.modelSelectorProps) { + const savedViewCopy = _.cloneDeep(savedView); + const legacyView = (savedViewCopy.savedViewData.legacyView as LegacySavedView); + legacyView.modelSelectorProps.models = + legacyView.modelSelectorProps.models.filter((model) => !!model); + savedViewCopy.savedViewData.legacyView = legacyView; + return savedViewCopy; + } + return savedView; +}; diff --git a/packages/saved-views-react/src/saved-views.ts b/packages/saved-views-react/src/saved-views.ts index 2776b383..79402451 100644 --- a/packages/saved-views-react/src/saved-views.ts +++ b/packages/saved-views-react/src/saved-views.ts @@ -12,6 +12,7 @@ export * from "./api/clients/DefaultViewIdClient"; export * from "./api/clients/IGroupClient"; export * from "./api/clients/ISavedViewsClient"; export * from "./api/clients/ITagClient"; +export * from "./api/utilities/translation/SavedViewTranslation"; export * from "./api/utilities/SavedViewTypes"; export * from "./api/utilities/SavedViewUtil"; export * from "./api/utilities/ViewCreator"; diff --git a/packages/test-app-frontend/src/App/ITwinJsApp/ITwinJsApp.tsx b/packages/test-app-frontend/src/App/ITwinJsApp/ITwinJsApp.tsx index 278d7f5f..cb741316 100644 --- a/packages/test-app-frontend/src/App/ITwinJsApp/ITwinJsApp.tsx +++ b/packages/test-app-frontend/src/App/ITwinJsApp/ITwinJsApp.tsx @@ -15,13 +15,14 @@ import { IModelsClient } from "@itwin/imodels-client-management"; import { PageLayout } from "@itwin/itwinui-layouts-react"; import { Button, MenuItem, toaster } from "@itwin/itwinui-react"; import { - ITwinSavedViewsClient, SavedViewOptions, SavedViewsFolderWidget, useSavedViews, + ITwinSavedViewsClient, SavedViewOptions, SavedViewsClient, SavedViewsFolderWidget, translateSavedViewIntoITwinJsViewState, useSavedViews, } from "@itwin/saved-views-react"; import { ReactElement, useEffect, useMemo, useState } from "react"; import { applyUrlPrefix } from "../../environment"; import { LoadingScreen } from "../common/LoadingScreen"; -import { useNavigate } from "react-router-dom"; +import { SavedViewWithDataRepresentation } from "@itwin/saved-views-client"; +import { ViewportComponent } from "@itwin/imodel-components-react"; export interface ITwinJsAppProps { iTwinId: string; @@ -32,8 +33,8 @@ export interface ITwinJsAppProps { export function ITwinJsApp(props: ITwinJsAppProps): ReactElement | null { type LoadingState = "opening-imodel" | "opening-viewstate" | "creating-viewstate" | "loaded" | "rendering-imodel" | "rendered"; const [loadingState, setLoadingState] = useState("opening-imodel"); - // const [selectedViewState, setSelectedViewState] = useState(); const [selectedViewId, setSelectedViewId] = useState(); + const [selectedViewState, setSelectedViewState] = useState(); const iModel = useIModel(props.iTwinId, props.iModelId, props.authorizationClient); useEffect( () => { @@ -68,18 +69,31 @@ export function ITwinJsApp(props: ITwinJsAppProps): ReactElement | null { useEffect( () => { + + async function renderSavedView(iModel: IModelConnection, savedViewId: string, client: SavedViewsClient) { + + const savedViewResponse: SavedViewWithDataRepresentation = await client.getSingularSavedView({savedViewId}); + // console.log({savedViewResponse}); + + const savedViewState = await translateSavedViewIntoITwinJsViewState(savedViewResponse, iModel); + + setLoadingState("rendered"); + + setSelectedViewState(savedViewState); + // console.log({savedViewState}); + + return savedViewState; + } + if (!iModel) { return; } - // if (selectedViewState) { if (selectedViewId && selectedViewId !== "") { setLoadingState("rendering-imodel"); - // renderSavedView(iModel, selectedViewState, props.iTwinId, props.iModelId); - renderSavedView(iModel, selectedViewId, props.iTwinId, props.iModelId); + renderSavedView(iModel, selectedViewId, client); } }, - // [selectedViewState], [selectedViewId], ); @@ -112,6 +126,13 @@ export function ITwinJsApp(props: ITwinJsAppProps): ReactElement | null { return Loading saved views...; } + if (selectedViewState) { + return + } + const groups = [...savedViews.groups.values()]; const tags = [...savedViews.tags.values()]; @@ -150,16 +171,6 @@ export function ITwinJsApp(props: ITwinJsAppProps): ReactElement | null { ); } -async function renderSavedView(iModel: IModelConnection, savedViewId: string, iTwinId: string, iModelId: string) { -// function renderSavedView(iModel: IModelConnection, selectedViewState: string, iTwinId: string, iModelId: string) { - - const viewState = await iModel.views.load(savedViewId); - - const navigate = useNavigate(); - // return navigate(`/itwinjs/open-imodel/${iTwinId}/${iModelId}/view`, {state: {iModel: iModel, savedViewId: savedViewId}}) - return navigate(`/itwinjs/open-imodel/${iTwinId}/${iModelId}/view`, {state: {iModel: iModel, viewState: viewState}}) -} - export async function initializeITwinJsApp(_authorizationClient: AuthorizationClient): Promise { if (IModelApp.initialized) { return;