From c43b57d120c7072bd54fa2f21ea3ec5244104457 Mon Sep 17 00:00:00 2001 From: "Brien.Bastings" <65233531+bbastings@users.noreply.github.com> Date: Thu, 23 Jul 2026 09:39:56 -0400 Subject: [PATCH 1/9] Support for AccuDraw shortcuts to apply and create named ACS. Port commented off C++ code related to named ACS elements. To be useful, getACS and writeACS need to be async. Because writeACS would require an EditTxn it only returns the props for insert/update, the shortcut tools for both get and write will be implemented in another package for editing. --- common/api/core-frontend.api.md | 8 +- ...named-acs-support_2026-07-23-13-45-55.json | 10 ++ core/frontend/src/AuxCoordSys.ts | 20 ++- core/frontend/src/EntityState.ts | 2 +- core/frontend/src/tools/AccuDrawTool.ts | 163 +++++++++++------- 5 files changed, 124 insertions(+), 79 deletions(-) create mode 100644 common/changes/@itwin/core-frontend/accudraw-named-acs-support_2026-07-23-13-45-55.json diff --git a/common/api/core-frontend.api.md b/common/api/core-frontend.api.md index 493ccc53e9f6..d0b79aaa6d2d 100644 --- a/common/api/core-frontend.api.md +++ b/common/api/core-frontend.api.md @@ -884,8 +884,7 @@ export class AccuDrawShortcuts { static defineACSByElement(): Promise; // (undocumented) static defineACSByPoints(): Promise; - // (undocumented) - static getACS(acsName: string | undefined, useOrigin: boolean, useRotation: boolean): BentleyStatus; + static getACS(acsName: string | undefined, useOrigin: boolean, useRotation: boolean): Promise; // (undocumented) static itemFieldAcceptInput(index: ItemField, str: string): Promise; // (undocumented) @@ -939,8 +938,7 @@ export class AccuDrawShortcuts { static suspendToggle(): void; // (undocumented) static updateACSByPoints(acs: AuxCoordSystemState, vp: Viewport, points: Point3d[], isDynamics: boolean): boolean; - // (undocumented) - static writeACS(_acsName: string): BentleyStatus; + static writeACS(acsName: string | undefined): Promise; } // @beta @@ -1416,7 +1414,7 @@ export abstract class AuxCoordSystemState extends ElementState implements AuxCoo constructor(props: AuxCoordSystemProps, iModel: IModelConnection); // (undocumented) static get className(): string; - static createNew(acsName: string, iModel: IModelConnection): AuxCoordSystemState; + static createNew(_acsName: string, iModel: IModelConnection): AuxCoordSystemState; // (undocumented) description?: string; // (undocumented) diff --git a/common/changes/@itwin/core-frontend/accudraw-named-acs-support_2026-07-23-13-45-55.json b/common/changes/@itwin/core-frontend/accudraw-named-acs-support_2026-07-23-13-45-55.json new file mode 100644 index 000000000000..ac11a63efe7e --- /dev/null +++ b/common/changes/@itwin/core-frontend/accudraw-named-acs-support_2026-07-23-13-45-55.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@itwin/core-frontend", + "comment": "", + "type": "none" + } + ], + "packageName": "@itwin/core-frontend" +} \ No newline at end of file diff --git a/core/frontend/src/AuxCoordSys.ts b/core/frontend/src/AuxCoordSys.ts index e555467f0654..35ee4a69004b 100644 --- a/core/frontend/src/AuxCoordSys.ts +++ b/core/frontend/src/AuxCoordSys.ts @@ -9,7 +9,7 @@ import { JsonUtils } from "@itwin/core-bentley"; import { Angle, AngleSweep, Arc3d, Matrix3d, Point2d, Point3d, Transform, Vector3d, XAndY, XYAndZ, YawPitchRollAngles } from "@itwin/core-geometry"; import { - AuxCoordSystem2dProps, AuxCoordSystem3dProps, AuxCoordSystemProps, BisCodeSpec, Code, ColorDef, IModel, LinePixels, Npc, + AuxCoordSystem2dProps, AuxCoordSystem3dProps, AuxCoordSystemProps, Code, ColorDef, IModel, LinePixels, Npc, } from "@itwin/core-common"; import { ElementState } from "./EntityState"; import { IModelConnection } from "./IModelConnection"; @@ -79,13 +79,19 @@ export abstract class AuxCoordSystemState extends ElementState implements AuxCoo } /** Create a new AuxCoordSystemState. - * @param acsName the name for the new AuxCoordSystem - * @param iModel the iModel for which the ACS applies. - * @note call this method with the appropriate subclass (e.g. AuxCoordSystemSpatialState, AuxCoordSystem2dState, etc), not on AuxCoordSystemState directly + * @param _acsName unused, this method creates an empty code as it lacks sufficient information to do otherwise. + * @param iModel the iModel for which the ACS applies + * @note Inserting a new named ACS element requires creating a CodeProps for the type of view and the view's definition model. + * This method doesn't have the information to do this so it creates and empty code. + * @example + * ```typescript + * const codeSpecName = vp.view.isSpatialView() ? BisCodeSpec.auxCoordSystemSpatial : (vp.view.is3d() ? BisCodeSpec.auxCoordSystem3d : BisCodeSpec.auxCoordSystem2d); + * const codeSpec = await vp.iModel.codeSpecs.getByName(codeSpecName); + * const code = new Code({ spec: codeSpec.id, scope: vp.view.model, value: acsName }); + * ``` */ - public static createNew(acsName: string, iModel: IModelConnection): AuxCoordSystemState { - const myCode = new Code({ spec: BisCodeSpec.auxCoordSystemSpatial, scope: IModel.dictionaryId.toString(), value: acsName }); - return new AuxCoordSystemSpatialState({ model: IModel.dictionaryId, code: myCode, classFullName: this.classFullName }, iModel); + public static createNew(_acsName: string, iModel: IModelConnection): AuxCoordSystemState { + return new AuxCoordSystemSpatialState({ model: IModel.dictionaryId, code: Code.createEmpty(), classFullName: this.classFullName }, iModel); } public constructor(props: AuxCoordSystemProps, iModel: IModelConnection) { diff --git a/core/frontend/src/EntityState.ts b/core/frontend/src/EntityState.ts index eee2fe8b7036..fd46df9288d4 100644 --- a/core/frontend/src/EntityState.ts +++ b/core/frontend/src/EntityState.ts @@ -109,7 +109,7 @@ export class ElementState extends EntityState implements ElementProps { public override toJSON(): ElementProps { const val = super.toJSON() as ElementProps; if (Id64.isValid(this.code.spec)) - val.code = this.code; + val.code = this.code.toJSON(); // Ensure _value with get accessor is serialized... val.model = this.model; val.parent = this.parent; diff --git a/core/frontend/src/tools/AccuDrawTool.ts b/core/frontend/src/tools/AccuDrawTool.ts index c8645cd3b0db..8f883e4c40c1 100644 --- a/core/frontend/src/tools/AccuDrawTool.ts +++ b/core/frontend/src/tools/AccuDrawTool.ts @@ -6,11 +6,11 @@ * @module Tools */ -import { BentleyStatus } from "@itwin/core-bentley"; +import { AuxCoordSystemProps, BisCodeSpec, Code } from "@itwin/core-common"; import { AxisOrder, Geometry, Matrix3d, Plane3dByOriginAndUnitNormal, Point3d, Transform, Vector3d } from "@itwin/core-geometry"; import { AccuDraw, AccuDrawFlags, CompassMode, ContextMode, ItemField, KeyinStatus, LockedStates, RotationMode, ThreeAxes } from "../AccuDraw"; import { TentativeOrAccuSnap } from "../AccuSnap"; -import { ACSDisplayOptions, AuxCoordSystemState } from "../AuxCoordSys"; +import { ACSDisplayOptions, ACSType, AuxCoordSystemState } from "../AuxCoordSys"; import { SnapDetail } from "../HitDetail"; import { IModelApp } from "../IModelApp"; import { DecorateContext } from "../ViewContext"; @@ -871,56 +871,89 @@ export class AccuDrawShortcuts { return IModelApp.tools.run("AccuDraw.DefineACSByPoints"); } - public static getACS(acsName: string | undefined, useOrigin: boolean, useRotation: boolean): BentleyStatus { - const accudraw = IModelApp.accuDraw; - if (!accudraw.isEnabled) - return BentleyStatus.ERROR; + private static async getACSCode(vp: Viewport, acsName: string): Promise { + try { + const codeSpecName = vp.view.isSpatialView() ? BisCodeSpec.auxCoordSystemSpatial : (vp.view.is3d() ? BisCodeSpec.auxCoordSystem3d : BisCodeSpec.auxCoordSystem2d); + const codeSpec = await vp.iModel.codeSpecs.getByName(codeSpecName); + return new Code({ spec: codeSpec.id, scope: vp.view.model, value: acsName }); + } catch { + return undefined; + } + } - const vp = accudraw.currentView; - if (!vp) - return BentleyStatus.ERROR; + private static async getNamedACS(vp: Viewport, acsName: string): Promise { + try { + const acsCode = await this.getACSCode(vp, acsName); + if (!acsCode) + return undefined; - let currRotation = 0, currBaseRotation = 0; - const axes = new ThreeAxes(); + const acsProps = await vp.iModel.elements.loadProps(acsCode); + if (!acsProps) + return undefined; - if (!useRotation) { - // Save current rotation, event listener on ACS change will orient AccuDraw to ACS... - currRotation = accudraw.rotationMode; - currBaseRotation = accudraw.flags.baseRotation; - axes.setFrom(accudraw.axes); + const namedAcs = AuxCoordSystemState.fromProps(acsProps, vp.iModel); + return namedAcs.isValidForView(vp.view) ? namedAcs : undefined; + } catch { + return undefined; } + } - if (acsName && "" !== acsName) { - // // See if this ACS already exists... - // DgnCode acsCode = AuxCoordSystem:: CreateCode(vp -> GetViewControllerR().GetViewDefinition(), acsName); - // DgnElementId acsId = vp -> GetViewController().GetDgnDb().Elements().QueryElementIdByCode(acsCode); + private static async createNewACS(vp: Viewport, acsName: string): Promise { + // Want a CodeProps to support insert so ViewState.createAuxCoordSystem(acsName) is not called. + try { + const acsCode = await this.getACSCode(vp, acsName); + if (!acsCode) + return undefined; - // if (!acsId.IsValid()) - // return ERROR; + // Determine the class name based on view type + let classFullName: string; + if (vp.view.isSpatialView()) + classFullName = "BisCore:AuxCoordSystemSpatial"; + else if (vp.view.is3d()) + classFullName = "BisCore:AuxCoordSystem3d"; + else + classFullName = "BisCore:AuxCoordSystem2d"; - // AuxCoordSystemCPtr auxElm = vp -> GetViewController().GetDgnDb().Elements().Get(acsId); + const acsProps: AuxCoordSystemProps = { model: acsCode.scope, code: acsCode, classFullName, type: ACSType.None }; + const acs = AuxCoordSystemState.fromProps(acsProps, vp.iModel); + return acs; + } catch { + return undefined; + } + } - // if (!auxElm.IsValid()) - // return ERROR; + /** + * Set the AuxiliaryCoordinateSystem for the current AccuDraw viewport by name. + * Optionally updates the AccuDraw origin and rotation to match the ACS. + * @note When no name is specified, AccuDraw is updated using the view's current ACS. + * @returns AuxCoordSystemState or undefined if name does not exist or is invalid for the view. + */ + public static async getACS(acsName: string | undefined, useOrigin: boolean, useRotation: boolean): Promise { + const accudraw = IModelApp.accuDraw; + if (!accudraw.isEnabled) + return undefined; - // AuxCoordSystemPtr acsPtr = auxElm -> MakeCopy(); + const vp = accudraw.currentView; + if (!vp) + return undefined; - // if (!acsPtr.IsValid()) - // return ERROR; + let currentACS = vp.view.auxiliaryCoordinateSystem; - // AuxCoordSystemCR oldACS = vp -> GetViewController().GetAuxCoordinateSystem(); + if (acsName && "" !== acsName) { + const namedAcs = await this.getNamedACS(vp, acsName); + if (!namedAcs) + return undefined; - // if (!useOrigin) - // acsPtr -> SetOrigin(oldACS.GetOrigin()); + if (!useOrigin) + namedAcs.setOrigin(currentACS.getOrigin()); - // if (!useRotation) - // acsPtr -> SetRotation(oldACS.GetRotation()); + if (!useRotation) + namedAcs.setRotation(currentACS.getRotation()); - // AccuDraw:: UpdateAuxCoordinateSystem(* acsPtr, * vp); + AccuDraw.updateAuxCoordinateSystem(namedAcs, vp); // Sets AccuDrawFlags.OrientACS hint to orient AccuDraw to ACS... + currentACS = namedAcs; } - const currentACS = vp.view.auxiliaryCoordinateSystem; - if (useOrigin) { accudraw.origin.setFrom(currentACS.getOrigin()); accudraw.point.setFrom(accudraw.origin); @@ -933,57 +966,55 @@ export class AccuDrawShortcuts { } else { this.itemFieldUnlockAll(); - accudraw.setRotationMode(currRotation); - accudraw.flags.baseRotation = currBaseRotation; - accudraw.axes.setFrom(axes); - if (RotationMode.ACS === accudraw.flags.baseRotation) { const acs = currentACS.clone(); - const rMatrix = accudraw.getRotation(); - acs.setRotation(rMatrix); - + acs.setRotation(accudraw.getRotation()); AccuDraw.updateAuxCoordinateSystem(acs, vp); } accudraw.published.flags &= ~AccuDrawFlags.OrientACS; } - return BentleyStatus.SUCCESS; + return currentACS; } - public static writeACS(_acsName: string): BentleyStatus { + /** + * Create a new AuxiliaryCoordinateSystem for the current AccuDraw viewport using the compass origin, orientation, and mode. + * @note When no name is specified, the view's current ACS is updated from AccuDraw. Caller can choose to create + * an EditCommand to insert/update as a named ACS element using the returned AuxCoordSystemState. + * @returns AuxCoordSystemState or undefined if ACS could not be created. + */ + public static async writeACS(acsName: string | undefined): Promise { const accudraw = IModelApp.accuDraw; if (!accudraw.isEnabled) - return BentleyStatus.ERROR; + return undefined; const vp = accudraw.currentView; if (!vp) - return BentleyStatus.ERROR; - - // const origin = accudraw.origin; - // const rMatrix = accudraw.getRotation(); - // AuxCoordSystemPtr acsPtr = AuxCoordSystem:: CreateFrom(vp -> GetViewController().GetAuxCoordinateSystem()); - // acsPtr -> SetOrigin(origin); - // acsPtr -> SetRotation(rMatrix); - // acsPtr -> SetType(CompassMode.Polar == accudraw.getCompassMode() ? ACSType :: Cylindrical : ACSType:: Rectangular); - // acsPtr -> SetCode(AuxCoordSystem:: CreateCode(vp -> GetViewControllerR().GetViewDefinition(), nullptr != acsName ? acsName : "")); - // acsPtr -> SetDescription(""); + return undefined; - // if (acsName && '\0' != acsName[0]) { - // DgnDbStatus status; - // acsPtr -> Insert(& status); + let acs; + if (acsName && "" !== acsName) { + const namedAcs = await this.getNamedACS(vp, acsName); + acs = namedAcs ?? await this.createNewACS(vp, acsName); + } else { + acs = vp.view.auxiliaryCoordinateSystem.clone(); + acs.description = ""; + } - // if (DgnDbStatus:: Success != status) - // return BentleyStatus.ERROR; - // } + if (undefined === acs) + return undefined; - // AccuDraw:: UpdateAuxCoordinateSystem(* acsPtr, * vp); + acs.setOrigin(accudraw.origin); + acs.setRotation(accudraw.getRotation()); + acs.type = CompassMode.Polar === accudraw.compassMode ? ACSType.Cylindrical : ACSType.Rectangular; - // accudraw.flags.baseRotation = RotationMode.ACS; - // accudraw.SetRotationMode(RotationMode.ACS); + AccuDraw.updateAuxCoordinateSystem(acs, vp); + accudraw.flags.baseRotation = RotationMode.ACS; + accudraw.setRotationMode(RotationMode.ACS); - return BentleyStatus.SUCCESS; + return acs; } public static itemFieldUnlockAll(): void { From 06c84fe896b77418e2ad2f4b7a1a9d74eaa11c54 Mon Sep 17 00:00:00 2001 From: "Brien.Bastings" <65233531+bbastings@users.noreply.github.com> Date: Thu, 23 Jul 2026 10:19:04 -0400 Subject: [PATCH 2/9] Remove ACS name comparison from invalid Code in test. --- full-stack-tests/core/src/frontend/standalone/ViewState.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/full-stack-tests/core/src/frontend/standalone/ViewState.test.ts b/full-stack-tests/core/src/frontend/standalone/ViewState.test.ts index 027613022f04..657c7d996bdd 100644 --- a/full-stack-tests/core/src/frontend/standalone/ViewState.test.ts +++ b/full-stack-tests/core/src/frontend/standalone/ViewState.test.ts @@ -87,7 +87,6 @@ describe("ViewState", () => { assert.notDeepEqual(v2, viewState); const acs = v2.createAuxCoordSystem("test"); - assert.equal(acs.code.value, "test"); assert.instanceOf(acs, AuxCoordSystemSpatialState); acs.setOrigin({ x: 1, y: 1 }); assert.isTrue(acs.getOrigin().isExactEqual({ x: 1, y: 1, z: 0 })); From ac4f34e62147612664d8bfd957152626a2376a65 Mon Sep 17 00:00:00 2001 From: bbastings <65233531+bbastings@users.noreply.github.com> Date: Thu, 23 Jul 2026 10:29:57 -0400 Subject: [PATCH 3/9] Supply CodeProps instead of Code Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- core/frontend/src/tools/AccuDrawTool.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/frontend/src/tools/AccuDrawTool.ts b/core/frontend/src/tools/AccuDrawTool.ts index 8f883e4c40c1..d000b6f55377 100644 --- a/core/frontend/src/tools/AccuDrawTool.ts +++ b/core/frontend/src/tools/AccuDrawTool.ts @@ -914,7 +914,7 @@ export class AccuDrawShortcuts { else classFullName = "BisCore:AuxCoordSystem2d"; - const acsProps: AuxCoordSystemProps = { model: acsCode.scope, code: acsCode, classFullName, type: ACSType.None }; + const acsProps: AuxCoordSystemProps = { model: acsCode.scope, code: acsCode.toJSON(), classFullName, type: ACSType.None }; const acs = AuxCoordSystemState.fromProps(acsProps, vp.iModel); return acs; } catch { From 6c2ed46fa034635ee98ccb708c49988fcd7b1615 Mon Sep 17 00:00:00 2001 From: bbastings <65233531+bbastings@users.noreply.github.com> Date: Thu, 23 Jul 2026 10:31:53 -0400 Subject: [PATCH 4/9] Fix comment Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- core/frontend/src/AuxCoordSys.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/frontend/src/AuxCoordSys.ts b/core/frontend/src/AuxCoordSys.ts index 35ee4a69004b..a1319815707c 100644 --- a/core/frontend/src/AuxCoordSys.ts +++ b/core/frontend/src/AuxCoordSys.ts @@ -82,7 +82,7 @@ export abstract class AuxCoordSystemState extends ElementState implements AuxCoo * @param _acsName unused, this method creates an empty code as it lacks sufficient information to do otherwise. * @param iModel the iModel for which the ACS applies * @note Inserting a new named ACS element requires creating a CodeProps for the type of view and the view's definition model. - * This method doesn't have the information to do this so it creates and empty code. + * This method doesn't have the information to do this so it creates an empty code. * @example * ```typescript * const codeSpecName = vp.view.isSpatialView() ? BisCodeSpec.auxCoordSystemSpatial : (vp.view.is3d() ? BisCodeSpec.auxCoordSystem3d : BisCodeSpec.auxCoordSystem2d); From 65b0445397d6affcdf2f69cc5af08336a29da9f6 Mon Sep 17 00:00:00 2001 From: bbastings <65233531+bbastings@users.noreply.github.com> Date: Thu, 23 Jul 2026 10:36:20 -0400 Subject: [PATCH 5/9] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- core/frontend/src/tools/AccuDrawTool.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/frontend/src/tools/AccuDrawTool.ts b/core/frontend/src/tools/AccuDrawTool.ts index d000b6f55377..ce65d9d8063e 100644 --- a/core/frontend/src/tools/AccuDrawTool.ts +++ b/core/frontend/src/tools/AccuDrawTool.ts @@ -887,7 +887,7 @@ export class AccuDrawShortcuts { if (!acsCode) return undefined; - const acsProps = await vp.iModel.elements.loadProps(acsCode); + const acsProps = await vp.iModel.elements.loadProps(acsCode.toJSON()); if (!acsProps) return undefined; From 213d5a5e27b628615ea78dc31ab6c8060e49b5e0 Mon Sep 17 00:00:00 2001 From: "Brien.Bastings" <65233531+bbastings@users.noreply.github.com> Date: Thu, 23 Jul 2026 12:13:54 -0400 Subject: [PATCH 6/9] Allow exceptions to propagate. Distinguish between acs not found and found but invalid for view. Validate viewport/accudraw after await. --- core/frontend/src/tools/AccuDrawTool.ts | 109 ++++++++++++++---------- 1 file changed, 63 insertions(+), 46 deletions(-) diff --git a/core/frontend/src/tools/AccuDrawTool.ts b/core/frontend/src/tools/AccuDrawTool.ts index ce65d9d8063e..bee1a8c304d6 100644 --- a/core/frontend/src/tools/AccuDrawTool.ts +++ b/core/frontend/src/tools/AccuDrawTool.ts @@ -35,6 +35,9 @@ function normalizedCrossProduct(vec1: Vector3d, vec2: Vector3d, out: Vector3d): * @beta */ export class AccuDrawShortcuts { + // Monotonic token used to ignore stale async ACS operations. + private static _acsRequestGeneration = 0; + /** Disable/Enable AccuDraw for the session */ public static sessionToggle(): void { const accudraw = IModelApp.accuDraw; @@ -871,55 +874,40 @@ export class AccuDrawShortcuts { return IModelApp.tools.run("AccuDraw.DefineACSByPoints"); } - private static async getACSCode(vp: Viewport, acsName: string): Promise { - try { - const codeSpecName = vp.view.isSpatialView() ? BisCodeSpec.auxCoordSystemSpatial : (vp.view.is3d() ? BisCodeSpec.auxCoordSystem3d : BisCodeSpec.auxCoordSystem2d); - const codeSpec = await vp.iModel.codeSpecs.getByName(codeSpecName); - return new Code({ spec: codeSpec.id, scope: vp.view.model, value: acsName }); - } catch { - return undefined; - } + private static async getACSCode(vp: Viewport, acsName: string): Promise { + const codeSpecName = vp.view.isSpatialView() ? BisCodeSpec.auxCoordSystemSpatial : (vp.view.is3d() ? BisCodeSpec.auxCoordSystem3d : BisCodeSpec.auxCoordSystem2d); + const codeSpec = await vp.iModel.codeSpecs.getByName(codeSpecName); + return new Code({ spec: codeSpec.id, scope: vp.view.model, value: acsName }); } - private static async getNamedACS(vp: Viewport, acsName: string): Promise { - try { - const acsCode = await this.getACSCode(vp, acsName); - if (!acsCode) - return undefined; + private static async getNamedACS(vp: Viewport, acsName: string): Promise<{ validForView: boolean, acs?: AuxCoordSystemState }> { + const acsCode = await this.getACSCode(vp, acsName); + const acsProps = await vp.iModel.elements.loadProps(acsCode.toJSON()); + if (!acsProps) + return { validForView: false }; - const acsProps = await vp.iModel.elements.loadProps(acsCode.toJSON()); - if (!acsProps) - return undefined; + const namedAcs = AuxCoordSystemState.fromProps(acsProps, vp.iModel); + const validForView = namedAcs.isValidForView(vp.view); - const namedAcs = AuxCoordSystemState.fromProps(acsProps, vp.iModel); - return namedAcs.isValidForView(vp.view) ? namedAcs : undefined; - } catch { - return undefined; - } + return { validForView, acs: namedAcs }; } - private static async createNewACS(vp: Viewport, acsName: string): Promise { + private static async createNewACS(vp: Viewport, acsName: string): Promise { // Want a CodeProps to support insert so ViewState.createAuxCoordSystem(acsName) is not called. - try { - const acsCode = await this.getACSCode(vp, acsName); - if (!acsCode) - return undefined; - - // Determine the class name based on view type - let classFullName: string; - if (vp.view.isSpatialView()) - classFullName = "BisCore:AuxCoordSystemSpatial"; - else if (vp.view.is3d()) - classFullName = "BisCore:AuxCoordSystem3d"; - else - classFullName = "BisCore:AuxCoordSystem2d"; + const acsCode = await this.getACSCode(vp, acsName); + + // Determine the class name based on view type + let classFullName: string; + if (vp.view.isSpatialView()) + classFullName = "BisCore:AuxCoordSystemSpatial"; + else if (vp.view.is3d()) + classFullName = "BisCore:AuxCoordSystem3d"; + else + classFullName = "BisCore:AuxCoordSystem2d"; - const acsProps: AuxCoordSystemProps = { model: acsCode.scope, code: acsCode.toJSON(), classFullName, type: ACSType.None }; - const acs = AuxCoordSystemState.fromProps(acsProps, vp.iModel); - return acs; - } catch { - return undefined; - } + const acsProps: AuxCoordSystemProps = { model: acsCode.scope, code: acsCode.toJSON(), classFullName, type: ACSType.None }; + const acs = AuxCoordSystemState.fromProps(acsProps, vp.iModel); + return acs; } /** @@ -937,10 +925,20 @@ export class AccuDrawShortcuts { if (!vp) return undefined; + const requestGeneration = ++this._acsRequestGeneration; + const isRequestCurrent = () => accudraw.isEnabled && accudraw.currentView === vp && requestGeneration === this._acsRequestGeneration; + let currentACS = vp.view.auxiliaryCoordinateSystem; if (acsName && "" !== acsName) { - const namedAcs = await this.getNamedACS(vp, acsName); + const namedAcsResult = await this.getNamedACS(vp, acsName); + if (!isRequestCurrent()) + return undefined; + + if (!namedAcsResult.validForView) + return undefined; + + const namedAcs = namedAcsResult.acs; if (!namedAcs) return undefined; @@ -954,6 +952,9 @@ export class AccuDrawShortcuts { currentACS = namedAcs; } + if (!isRequestCurrent()) + return undefined; + if (useOrigin) { accudraw.origin.setFrom(currentACS.getOrigin()); accudraw.point.setFrom(accudraw.origin); @@ -994,16 +995,32 @@ export class AccuDrawShortcuts { if (!vp) return undefined; - let acs; + const requestGeneration = ++this._acsRequestGeneration; + const isRequestCurrent = () => accudraw.isEnabled && accudraw.currentView === vp && requestGeneration === this._acsRequestGeneration; + + let acs: AuxCoordSystemState | undefined; if (acsName && "" !== acsName) { - const namedAcs = await this.getNamedACS(vp, acsName); - acs = namedAcs ?? await this.createNewACS(vp, acsName); + const namedAcsResult = await this.getNamedACS(vp, acsName); + if (!isRequestCurrent()) + return undefined; + + acs = namedAcsResult.acs; + if (!acs) { + acs = await this.createNewACS(vp, acsName); + if (!isRequestCurrent()) + return undefined; + } else if (!namedAcsResult.validForView) { + return undefined; + } } else { acs = vp.view.auxiliaryCoordinateSystem.clone(); acs.description = ""; } - if (undefined === acs) + if (!isRequestCurrent()) + return undefined; + + if (!acs) return undefined; acs.setOrigin(accudraw.origin); From fdc3261e97327ac109d161412094c0960161c630 Mon Sep 17 00:00:00 2001 From: "Brien.Bastings" <65233531+bbastings@users.noreply.github.com> Date: Thu, 23 Jul 2026 13:12:33 -0400 Subject: [PATCH 7/9] Make sure ViewState isn't changed --- core/frontend/src/tools/AccuDrawTool.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/core/frontend/src/tools/AccuDrawTool.ts b/core/frontend/src/tools/AccuDrawTool.ts index bee1a8c304d6..d2cc38092ac4 100644 --- a/core/frontend/src/tools/AccuDrawTool.ts +++ b/core/frontend/src/tools/AccuDrawTool.ts @@ -925,10 +925,11 @@ export class AccuDrawShortcuts { if (!vp) return undefined; + const view = vp.view; const requestGeneration = ++this._acsRequestGeneration; - const isRequestCurrent = () => accudraw.isEnabled && accudraw.currentView === vp && requestGeneration === this._acsRequestGeneration; + const isRequestCurrent = () => accudraw.isEnabled && accudraw.currentView === vp && vp.view === view && requestGeneration === this._acsRequestGeneration; - let currentACS = vp.view.auxiliaryCoordinateSystem; + let currentACS = view.auxiliaryCoordinateSystem; if (acsName && "" !== acsName) { const namedAcsResult = await this.getNamedACS(vp, acsName); @@ -995,8 +996,9 @@ export class AccuDrawShortcuts { if (!vp) return undefined; + const view = vp.view; const requestGeneration = ++this._acsRequestGeneration; - const isRequestCurrent = () => accudraw.isEnabled && accudraw.currentView === vp && requestGeneration === this._acsRequestGeneration; + const isRequestCurrent = () => accudraw.isEnabled && accudraw.currentView === vp && vp.view === view && requestGeneration === this._acsRequestGeneration; let acs: AuxCoordSystemState | undefined; if (acsName && "" !== acsName) { @@ -1013,7 +1015,7 @@ export class AccuDrawShortcuts { return undefined; } } else { - acs = vp.view.auxiliaryCoordinateSystem.clone(); + acs = view.auxiliaryCoordinateSystem.clone(); acs.description = ""; } From 03006b52ed27ebca7d8de2dd696f680c9051283e Mon Sep 17 00:00:00 2001 From: "Brien.Bastings" <65233531+bbastings@users.noreply.github.com> Date: Thu, 23 Jul 2026 13:29:46 -0400 Subject: [PATCH 8/9] Update doc comments to mention when error will be thrown --- core/frontend/src/tools/AccuDrawTool.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/frontend/src/tools/AccuDrawTool.ts b/core/frontend/src/tools/AccuDrawTool.ts index d2cc38092ac4..99a250392b4a 100644 --- a/core/frontend/src/tools/AccuDrawTool.ts +++ b/core/frontend/src/tools/AccuDrawTool.ts @@ -915,6 +915,7 @@ export class AccuDrawShortcuts { * Optionally updates the AccuDraw origin and rotation to match the ACS. * @note When no name is specified, AccuDraw is updated using the view's current ACS. * @returns AuxCoordSystemState or undefined if name does not exist or is invalid for the view. + * @throws Error if resolving the ACS CodeSpec or loading ACS element properties fails. */ public static async getACS(acsName: string | undefined, useOrigin: boolean, useRotation: boolean): Promise { const accudraw = IModelApp.accuDraw; @@ -986,6 +987,7 @@ export class AccuDrawShortcuts { * @note When no name is specified, the view's current ACS is updated from AccuDraw. Caller can choose to create * an EditCommand to insert/update as a named ACS element using the returned AuxCoordSystemState. * @returns AuxCoordSystemState or undefined if ACS could not be created. + * @throws Error if resolving the ACS CodeSpec or loading ACS element properties fails. */ public static async writeACS(acsName: string | undefined): Promise { const accudraw = IModelApp.accuDraw; From c085df68e42635932135114d4a08470a6a265492 Mon Sep 17 00:00:00 2001 From: "Brien.Bastings" <65233531+bbastings@users.noreply.github.com> Date: Thu, 23 Jul 2026 15:28:33 -0400 Subject: [PATCH 9/9] Pass ViewState instead of Viewport to helper methods --- core/frontend/src/tools/AccuDrawTool.ts | 35 +++++++++++++------------ 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/core/frontend/src/tools/AccuDrawTool.ts b/core/frontend/src/tools/AccuDrawTool.ts index 99a250392b4a..7c3e67dc5541 100644 --- a/core/frontend/src/tools/AccuDrawTool.ts +++ b/core/frontend/src/tools/AccuDrawTool.ts @@ -14,6 +14,7 @@ import { ACSDisplayOptions, ACSType, AuxCoordSystemState } from "../AuxCoordSys" import { SnapDetail } from "../HitDetail"; import { IModelApp } from "../IModelApp"; import { DecorateContext } from "../ViewContext"; +import { ViewState } from "../ViewState"; import { ScreenViewport, Viewport } from "../Viewport"; import { BeButtonEvent, CoreTools, Tool } from "./Tool"; import { AccuDrawShortcutImplementation, AccuDrawShortcutTool } from "./AccuDrawShortcutTool"; @@ -874,39 +875,39 @@ export class AccuDrawShortcuts { return IModelApp.tools.run("AccuDraw.DefineACSByPoints"); } - private static async getACSCode(vp: Viewport, acsName: string): Promise { - const codeSpecName = vp.view.isSpatialView() ? BisCodeSpec.auxCoordSystemSpatial : (vp.view.is3d() ? BisCodeSpec.auxCoordSystem3d : BisCodeSpec.auxCoordSystem2d); - const codeSpec = await vp.iModel.codeSpecs.getByName(codeSpecName); - return new Code({ spec: codeSpec.id, scope: vp.view.model, value: acsName }); + private static async getACSCode(view: ViewState, acsName: string): Promise { + const codeSpecName = view.isSpatialView() ? BisCodeSpec.auxCoordSystemSpatial : (view.is3d() ? BisCodeSpec.auxCoordSystem3d : BisCodeSpec.auxCoordSystem2d); + const codeSpec = await view.iModel.codeSpecs.getByName(codeSpecName); + return new Code({ spec: codeSpec.id, scope: view.model, value: acsName }); } - private static async getNamedACS(vp: Viewport, acsName: string): Promise<{ validForView: boolean, acs?: AuxCoordSystemState }> { - const acsCode = await this.getACSCode(vp, acsName); - const acsProps = await vp.iModel.elements.loadProps(acsCode.toJSON()); + private static async getNamedACS(view: ViewState, acsName: string): Promise<{ validForView: boolean, acs?: AuxCoordSystemState }> { + const acsCode = await this.getACSCode(view, acsName); + const acsProps = await view.iModel.elements.loadProps(acsCode.toJSON()); if (!acsProps) return { validForView: false }; - const namedAcs = AuxCoordSystemState.fromProps(acsProps, vp.iModel); - const validForView = namedAcs.isValidForView(vp.view); + const namedAcs = AuxCoordSystemState.fromProps(acsProps, view.iModel); + const validForView = namedAcs.isValidForView(view); return { validForView, acs: namedAcs }; } - private static async createNewACS(vp: Viewport, acsName: string): Promise { + private static async createNewACS(view: ViewState, acsName: string): Promise { // Want a CodeProps to support insert so ViewState.createAuxCoordSystem(acsName) is not called. - const acsCode = await this.getACSCode(vp, acsName); + const acsCode = await this.getACSCode(view, acsName); // Determine the class name based on view type let classFullName: string; - if (vp.view.isSpatialView()) + if (view.isSpatialView()) classFullName = "BisCore:AuxCoordSystemSpatial"; - else if (vp.view.is3d()) + else if (view.is3d()) classFullName = "BisCore:AuxCoordSystem3d"; else classFullName = "BisCore:AuxCoordSystem2d"; const acsProps: AuxCoordSystemProps = { model: acsCode.scope, code: acsCode.toJSON(), classFullName, type: ACSType.None }; - const acs = AuxCoordSystemState.fromProps(acsProps, vp.iModel); + const acs = AuxCoordSystemState.fromProps(acsProps, view.iModel); return acs; } @@ -933,7 +934,7 @@ export class AccuDrawShortcuts { let currentACS = view.auxiliaryCoordinateSystem; if (acsName && "" !== acsName) { - const namedAcsResult = await this.getNamedACS(vp, acsName); + const namedAcsResult = await this.getNamedACS(view, acsName); if (!isRequestCurrent()) return undefined; @@ -1004,13 +1005,13 @@ export class AccuDrawShortcuts { let acs: AuxCoordSystemState | undefined; if (acsName && "" !== acsName) { - const namedAcsResult = await this.getNamedACS(vp, acsName); + const namedAcsResult = await this.getNamedACS(view, acsName); if (!isRequestCurrent()) return undefined; acs = namedAcsResult.acs; if (!acs) { - acs = await this.createNewACS(vp, acsName); + acs = await this.createNewACS(view, acsName); if (!isRequestCurrent()) return undefined; } else if (!namedAcsResult.validForView) {