UNPKG

@itwin/core-backend

Version:
712 lines • 183 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ import { assert, expect } from "chai"; import { BentleyStatus, Id64, IModelStatus } from "@itwin/core-bentley"; import { Angle, AngleSweep, Arc3d, Box, ClipMaskXYZRangePlanes, ClipPlane, ClipPlaneContainment, ClipPrimitive, ClipShape, ClipVector, ConvexClipPlaneSet, CurveCollection, CurvePrimitive, Geometry, IndexedPolyface, InterpolationCurve3d, InterpolationCurve3dOptions, LineSegment3d, LineString3d, Loop, Matrix3d, Plane3dByOriginAndUnitNormal, Point2d, Point3d, Point3dArray, PointString3d, PolyfaceBuilder, Range2d, Range3d, SolidPrimitive, Sphere, StrokeOptions, Transform, Vector3d, YawPitchRollAngles, } from "@itwin/core-geometry"; import { AreaPattern, BackgroundFill, BRepGeometryOperation, Code, ColorByName, ColorDef, ElementGeometry, ElementGeometryOpcode, FillDisplay, GeometryClass, GeometryParams, GeometryStreamBuilder, GeometryStreamFlags, GeometryStreamIterator, Gradient, ImageGraphicCorners, IModel, LinePixels, LineStyle, MassPropertiesOperation, Placement3d, TextString, ThematicGradientMode, ThematicGradientSettings, ViewFlags, } from "@itwin/core-common"; import { _nativeDb, DefinitionModel, deleteElementTree, GeometryPart, LineStyleDefinition, PhysicalObject, SubCategory, Subject } from "../../core-backend"; import { createBRepDataProps } from "../GeometryTestUtil"; import { IModelTestUtils } from "../IModelTestUtils"; import { Timer } from "../TestUtils"; function assertTrue(expr) { assert.isTrue(expr); } function createGeometryPartProps(geom, modelId) { const partProps = { classFullName: GeometryPart.classFullName, model: modelId ?? IModel.dictionaryId, code: Code.createEmpty(), geom, }; return partProps; } function createPhysicalElementProps(seedElement, placement, geom, modelId) { const elementProps = { classFullName: PhysicalObject.classFullName, model: modelId ?? seedElement.model, category: seedElement.category, code: Code.createEmpty(), geom, placement, }; return elementProps; } function createGeometryPart(geom, imodel) { const partProps = createGeometryPartProps(geom); return imodel.elements.insertElement(partProps); } function createGeometricElem(geom, placement, imodel, seedElement) { const elementProps = createPhysicalElementProps(seedElement, placement, geom); const el = imodel.elements.createElement(elementProps); return imodel.elements.insertElement(el.toJSON()); } function createPartElem(partId, origin, angles, imodel, seedElement, isRelative = false) { const builder = new GeometryStreamBuilder(); builder.appendGeometryPart3d(partId, isRelative ? origin : undefined, isRelative ? angles : undefined); return createGeometricElem(builder.geometryStream, isRelative ? { origin: Point3d.createZero(), angles: YawPitchRollAngles.createDegrees(0, 0, 0) } : { origin, angles }, imodel, seedElement); } function createCirclePart(radius, imodel) { const builder = new GeometryStreamBuilder(); builder.appendGeometry(Arc3d.createXY(Point3d.createZero(), radius)); return createGeometryPart(builder.geometryStream, imodel); } function createCircleElem(radius, origin, angles, imodel, seedElement) { const builder = new GeometryStreamBuilder(); builder.appendGeometry(Arc3d.createXY(Point3d.createZero(), radius)); return createGeometricElem(builder.geometryStream, { origin, angles }, imodel, seedElement); } function createSphereElem(radius, origin, angles, imodel, seedElement) { const builder = new GeometryStreamBuilder(); builder.appendGeometry(Sphere.createCenterRadius(Point3d.createZero(), radius)); return createGeometricElem(builder.geometryStream, { origin, angles }, imodel, seedElement); } function createDisjointCirclesElem(radius, origin, angles, imodel, seedElement) { const builder = new GeometryStreamBuilder(); const xOffset = radius * 1.5; builder.appendGeometry(Arc3d.createXY(Point3d.create(-xOffset), radius)); const geomParams = new GeometryParams(seedElement.category); geomParams.geometryClass = GeometryClass.Construction; builder.appendGeometryParamsChange(geomParams); builder.appendGeometry(Arc3d.createXY(Point3d.create(xOffset), radius)); return createGeometricElem(builder.geometryStream, { origin, angles }, imodel, seedElement); } function createIndexedPolyface(radius, origin, angleTol) { const options = StrokeOptions.createForFacets(); options.needParams = true; options.needNormals = true; if (angleTol) options.angleTol = angleTol; // Create indexed polyface for testing by facetting a sphere... const sphere = Sphere.createCenterRadius(undefined !== origin ? origin : Point3d.createZero(), radius); const polyBuilder = PolyfaceBuilder.create(options); polyBuilder.handleSphere(sphere); return polyBuilder.claimPolyface(); } function createStyledLineElem(imodel, seedElement, x, y, length, styleInfo, color) { const builder = new GeometryStreamBuilder(); const params = new GeometryParams(seedElement.category); if (styleInfo) params.styleInfo = styleInfo; if (color) params.lineColor = color; builder.appendGeometryParamsChange(params); builder.appendGeometry(LineSegment3d.create(Point3d.create(x, y, 0), Point3d.create(x + length, y, 0))); const elementProps = createPhysicalElementProps(seedElement, undefined, builder.geometryStream); return imodel.elements.insertElement(elementProps); } function validateElementInfo(info, expected, isWorld) { assert.isTrue(undefined !== info.entryArray && expected.length === info.entryArray.length); const geomParams = (undefined !== info.categoryId ? new GeometryParams(info.categoryId) : undefined); info.entryArray.forEach((entry, i) => { assert.isTrue(expected[i].opcode === entry.opcode); if (ElementGeometry.isGeometryQueryEntry(entry)) { const geom = ElementGeometry.toGeometryQuery(entry); assert.exists(geom); if (undefined !== expected[i].geometryCategory) { assert.isTrue(expected[i].geometryCategory === geom?.geometryCategory); } if (undefined !== expected[i].geometrySubCategory) { switch (expected[i].geometryCategory) { case "curvePrimitive": { assert.isTrue(geom instanceof CurvePrimitive); assert.isTrue(expected[i].geometrySubCategory === geom.curvePrimitiveType); break; } case "curveCollection": { assert.isTrue(geom instanceof CurveCollection); assert.isTrue(expected[i].geometrySubCategory === geom.curveCollectionType); break; } case "solid": { assert.isTrue(geom instanceof SolidPrimitive); assert.isTrue(expected[i].geometrySubCategory === geom.solidPrimitiveType); break; } } } } else if (ElementGeometry.isGeometricEntry(entry)) { switch (entry.opcode) { case ElementGeometryOpcode.BRep: const brep = ElementGeometry.toBRep(entry); assert.exists(brep); if (!isWorld && undefined !== expected[i].originalEntry) { const other = ElementGeometry.toBRep(expected[i].originalEntry); assert.exists(other); // NOTE: Don't compare brep type; set from entity data by backend, ignored if supplied to update... const transform = Transform.fromJSON(brep?.transform); const otherTrans = Transform.fromJSON(other?.transform); assert.isTrue(transform.isAlmostEqual(otherTrans)); const faceSymbLen = (undefined !== brep?.faceSymbology ? brep?.faceSymbology.length : 0); const otherSymbLen = (undefined !== other?.faceSymbology ? other?.faceSymbology.length : 0); assert.isTrue(faceSymbLen === otherSymbLen); } break; case ElementGeometryOpcode.TextString: const text = ElementGeometry.toTextString(entry); assert.exists(text); if (!isWorld && undefined !== expected[i].originalEntry) { const other = ElementGeometry.toTextString(expected[i].originalEntry); assert.exists(other); assert.isTrue(text?.font === other?.font); assert.isTrue(text?.text === other?.text); assert.isTrue(text?.bold === other?.bold); assert.isTrue(text?.italic === other?.italic); assert.isTrue(text?.underline === other?.underline); assert.isTrue(text?.height === other?.height); assert.isTrue(text?.widthFactor === other?.widthFactor); const origin = Point3d.fromJSON(text?.origin); const otherOrigin = Point3d.fromJSON(other?.origin); assert.isTrue(origin.isAlmostEqual(otherOrigin)); const angles = YawPitchRollAngles.fromJSON(text?.rotation); const otherAngles = YawPitchRollAngles.fromJSON(other?.rotation); assert.isTrue(angles.isAlmostEqual(otherAngles)); } break; case ElementGeometryOpcode.Image: const image = ElementGeometry.toImageGraphic(entry); assert.exists(image); if (!isWorld && undefined !== expected[i].originalEntry) { const other = ElementGeometry.toImageGraphic(expected[i].originalEntry); assert.exists(other); assert.isTrue(image?.textureId === other?.textureId); assert.isTrue(image?.hasBorder === other?.hasBorder); const corners = ImageGraphicCorners.fromJSON(image.corners); const otherCorners = ImageGraphicCorners.fromJSON(other.corners); assert.isTrue(corners[0].isAlmostEqual(otherCorners[0])); assert.isTrue(corners[1].isAlmostEqual(otherCorners[1])); assert.isTrue(corners[2].isAlmostEqual(otherCorners[2])); assert.isTrue(corners[3].isAlmostEqual(otherCorners[3])); } break; default: assert.isTrue(false); break; } } else if (ElementGeometryOpcode.SubGraphicRange === entry.opcode) { const subRange = ElementGeometry.toSubGraphicRange(entry); assert.exists(subRange); assert.isFalse(subRange?.isNull); } else if (ElementGeometryOpcode.PartReference === entry.opcode) { const partToElement = Transform.createIdentity(); const part = ElementGeometry.toGeometryPart(entry, partToElement); assert.exists(part); if (!isWorld && undefined !== expected[i].originalEntry) { const otherToElement = Transform.createIdentity(); const other = ElementGeometry.toGeometryPart(expected[i].originalEntry, otherToElement); assert.exists(other); assert.isTrue(partToElement.isAlmostEqual(otherToElement)); } } else if (ElementGeometry.isAppearanceEntry(entry)) { if (undefined !== geomParams) { const updated = ElementGeometry.updateGeometryParams(entry, geomParams); assert.isTrue(updated); if (!isWorld && undefined !== expected[i].geomParams) assert.isTrue(geomParams.isEquivalent(expected[i].geomParams)); } } }); } function validateGeometricElementProps(info, expected) { assert.isFalse(undefined === info.categoryId || undefined === info.sourceToWorld || undefined === info.bbox); assert.isTrue(expected.category === info.categoryId); const placement = Placement3d.fromJSON(expected.placement); const sourceToWorld = ElementGeometry.toTransform(info.sourceToWorld); assert.exists(sourceToWorld); assert.isTrue(sourceToWorld?.isAlmostEqual(placement.transform)); const bbox = ElementGeometry.toElementAlignedBox3d(info.bbox); assert.isFalse(bbox?.isNull); } function doElementGeometryValidate(imodel, elementId, expected, isWorld, elementProps, brepOpt) { const onGeometry = (info) => { if (undefined !== elementProps) validateGeometricElementProps(info, elementProps); if (1 === brepOpt || 2 === brepOpt) assert.isTrue(info.brepsPresent); validateElementInfo(info, expected, isWorld); }; const requestProps = { onGeometry, elementId, }; if (1 === brepOpt) requestProps.replaceBReps = true; else if (2 === brepOpt) requestProps.skipBReps = true; return imodel.elementGeometryRequest(requestProps); } function createGeometricElemFromSeed(imodel, seedId, entryArray, placement) { const seedElement = imodel.elements.getElement(seedId); assert.exists(seedElement); const elementProps = createPhysicalElementProps(seedElement, placement); elementProps.elementGeometryBuilderParams = { entryArray }; const newId = imodel.elements.insertElement(elementProps); assert.isTrue(Id64.isValidId64(newId)); imodel.saveChanges(); return newId; } describe("GeometryStream", () => { let imodel; before(() => { const seedFileName = IModelTestUtils.resolveAssetFile("CompatibilityTestSeed.bim"); const testFileName = IModelTestUtils.prepareOutputFile("GeometryStream", "GeometryStreamTest.bim"); imodel = IModelTestUtils.createSnapshotFromSeed(testFileName, seedFileName); }); after(() => { imodel.close(); }); it("create GeometricElement3d using line codes 1-7", async () => { // Set up element to be placed in iModel const seedElement = imodel.elements.getElement("0x1d"); assert.exists(seedElement); assert.isTrue(seedElement.federationGuid === "18eb4650-b074-414f-b961-d9cfaa6c8746"); // init line code to line pixels array const lsCodes = [LinePixels.Solid, LinePixels.Code1, LinePixels.Code2, LinePixels.Code3, LinePixels.Code4, LinePixels.Code5, LinePixels.Code6, LinePixels.Code7]; // create new line style definitions for line codes 1-7 const lsStyles = []; lsCodes.forEach((linePixels) => { lsStyles.push(LinePixels.Solid === linePixels ? Id64.invalid : LineStyleDefinition.Utils.getOrCreateLinePixelsStyle(imodel, IModel.dictionaryId, linePixels)); }); // get existing line style definitions for line codes 1-7 const lsStylesExist = []; lsCodes.forEach((linePixels) => { lsStylesExist.push(LinePixels.Solid === linePixels ? Id64.invalid : LineStyleDefinition.Utils.getOrCreateLinePixelsStyle(imodel, IModel.dictionaryId, linePixels)); }); // make sure we found existing styles and didn't create a second set assert.isTrue(8 === lsStyles.length && lsStyles.length === lsStylesExist.length); for (let iStyle = 0; iStyle < lsStyles.length; ++iStyle) { assert.isTrue(0 === iStyle || Id64.isValidId64(lsStyles[iStyle])); assert.isTrue(lsStylesExist[iStyle] === (lsStyles[iStyle])); } const builder = new GeometryStreamBuilder(); const params = new GeometryParams(seedElement.category); const pointS = Point3d.createZero(); const pointE = Point3d.create(5, 0, 0); lsStyles.forEach((styleId) => { params.styleInfo = Id64.isValidId64(styleId) ? new LineStyle.Info(styleId) : undefined; builder.appendGeometryParamsChange(params); builder.appendGeometry(LineSegment3d.create(pointS, pointE)); pointS.y += 0.5; pointE.y += 0.5; }); const elementProps = createPhysicalElementProps(seedElement, undefined, builder.geometryStream); const newId = imodel.elements.insertElement(elementProps); assert.isTrue(Id64.isValidId64(newId)); imodel.saveChanges(); // Extract and test value returned... const value = imodel.elements.getElementProps({ id: newId, wantGeometry: true }); assert.isDefined(value.geom); const itNextCheck = new GeometryStreamIterator(value.geom, value.category); assert.isFalse(itNextCheck.next().done); assert.isFalse(itNextCheck.next().done); assert.isFalse(itNextCheck.next().done); assert.isFalse(itNextCheck.next().done); assert.isFalse(itNextCheck.next().done); assert.isFalse(itNextCheck.next().done); assert.isFalse(itNextCheck.next().done); assert.isFalse(itNextCheck.next().done); assert.isTrue(itNextCheck.next().done); const lsStylesUsed = []; const it = new GeometryStreamIterator(value.geom, value.category); for (const entry of it) { assert.equal(entry.primitive.type, "geometryQuery"); lsStylesUsed.push(entry.geomParams.styleInfo ? entry.geomParams.styleInfo.styleId : Id64.invalid); } // Make sure we extracted same style information after round trip... assert.isTrue(lsStyles.length === lsStylesUsed.length); for (let iStyle = 0; iStyle < lsStyles.length; ++iStyle) { assert.isTrue(lsStylesUsed[iStyle] === (lsStyles[iStyle])); } }); it("create GeometricElement3d using continuous style", async () => { // Set up element to be placed in iModel const seedElement = imodel.elements.getElement("0x1d"); assert.exists(seedElement); assert.isTrue(seedElement.federationGuid === "18eb4650-b074-414f-b961-d9cfaa6c8746"); // create special "internal default" continuous style for drawing curves using width overrides const styleId = LineStyleDefinition.Utils.getOrCreateContinuousStyle(imodel, IModel.dictionaryId); assert.isTrue(Id64.isValidId64(styleId)); // make sure we found existing style and didn't create a new one const styleIdExists = LineStyleDefinition.Utils.getOrCreateContinuousStyle(imodel, IModel.dictionaryId); assert.isTrue(Id64.isValidId64(styleIdExists) && styleIdExists === (styleId)); // create continuous style with pre-defined constant width const styleIdWidth = LineStyleDefinition.Utils.getOrCreateContinuousStyle(imodel, IModel.dictionaryId, 0.05); assert.isTrue(Id64.isValidId64(styleIdWidth)); // make sure we found existing style and didn't create a new one const styleIdWidthExists = LineStyleDefinition.Utils.getOrCreateContinuousStyle(imodel, IModel.dictionaryId, 0.05); assert.isTrue(Id64.isValidId64(styleIdWidthExists) && styleIdWidthExists === (styleIdWidth)); const builder = new GeometryStreamBuilder(); const params = new GeometryParams(seedElement.category); const styles = [styleId, styleId, styleIdWidth, styleIdWidth]; const widths = [0.0, 0.025, 0.0, 0.075]; // add line using 0 width continuous style params.styleInfo = new LineStyle.Info(styles[0]); builder.appendGeometryParamsChange(params); builder.appendGeometry(LineSegment3d.create(Point3d.create(0, 0, 0), Point3d.create(0, 5, 0))); // add line with width override, undefined endWidth = startWidth, needed solely for taper params.styleInfo.styleMod = new LineStyle.Modifier({ startWidth: widths[1], physicalWidth: true }); builder.appendGeometryParamsChange(params); builder.appendGeometry(LineSegment3d.create(Point3d.create(0.5, 0, 0), Point3d.create(0.5, 5, 0))); // add line using pre-defined width continuous style params.styleInfo = new LineStyle.Info(styles[2]); builder.appendGeometryParamsChange(params); builder.appendGeometry(LineSegment3d.create(Point3d.create(1.0, 0, 0), Point3d.create(1.0, 5, 0))); // add line with width override, undefined endWidth = startWidth, needed solely for taper params.styleInfo.styleMod = new LineStyle.Modifier({ startWidth: widths[3], physicalWidth: true }); builder.appendGeometryParamsChange(params); builder.appendGeometry(LineSegment3d.create(Point3d.create(1.5, 0, 0), Point3d.create(1.5, 5, 0))); const elementProps = createPhysicalElementProps(seedElement, undefined, builder.geometryStream); const newId = imodel.elements.insertElement(elementProps); assert.isTrue(Id64.isValidId64(newId)); imodel.saveChanges(); // Extract and test value returned... const value = imodel.elements.getElementProps({ id: newId, wantGeometry: true }); assert.isDefined(value.geom); const stylesUsed = []; const widthsUsed = []; const it = new GeometryStreamIterator(value.geom, value.category); for (const entry of it) { assert.equal(entry.primitive.type, "geometryQuery"); assert.isDefined(entry.geomParams.styleInfo); stylesUsed.push(entry.geomParams.styleInfo.styleId); widthsUsed.push(entry.geomParams.styleInfo.styleMod !== undefined ? entry.geomParams.styleInfo.styleMod.startWidth : 0.0); } // Make sure we extracted same style information after round trip... assert.isTrue(styles.length === stylesUsed.length); for (let iStyle = 0; iStyle < styles.length; ++iStyle) { assert.isTrue(stylesUsed[iStyle] === (styles[iStyle])); assert.isTrue(Geometry.isSameCoordinate(widthsUsed[iStyle], widths[iStyle])); } }); function createGeometricElem3dUsingArrowHeadNoStrokePattern(definitionModelId, physicalModelId) { // Set up element to be placed in iModel const seedElement = imodel.elements.getElement("0x1d"); assert.exists(seedElement); assert.isTrue(seedElement.federationGuid === "18eb4650-b074-414f-b961-d9cfaa6c8746"); if (definitionModelId === undefined) definitionModelId = IModel.dictionaryId; if (physicalModelId === undefined) physicalModelId = seedElement.model; const partBuilder = new GeometryStreamBuilder(); const partParams = new GeometryParams(Id64.invalid); // category won't be used partParams.fillDisplay = FillDisplay.Always; partBuilder.appendGeometryParamsChange(partParams); partBuilder.appendGeometry(Loop.create(LineString3d.create(Point3d.create(0.1, 0, 0), Point3d.create(0, -0.05, 0), Point3d.create(0, 0.05, 0), Point3d.create(0.1, 0, 0)))); const partProps = createGeometryPartProps(partBuilder.geometryStream, definitionModelId); const partId = imodel.elements.insertElement(partProps); assert.isTrue(Id64.isValidId64(partId)); const pointSymbolData = LineStyleDefinition.Utils.createPointSymbolComponent(imodel, { geomPartId: partId }); // base and size will be set automatically... assert.isTrue(undefined !== pointSymbolData); // Use internal default instead of creating a stroke component for a solid line const strokePointData = LineStyleDefinition.Utils.createStrokePointComponent(imodel, { descr: "TestArrowHead", lcId: 0, lcType: LineStyleDefinition.ComponentType.Internal, symbols: [{ symId: pointSymbolData.compId, strokeNum: -1, mod1: LineStyleDefinition.SymbolOptions.CurveEnd }] }); assert.isTrue(undefined !== strokePointData); const compoundData = LineStyleDefinition.Utils.createCompoundComponent(imodel, { comps: [{ id: strokePointData.compId, type: strokePointData.compType }, { id: 0, type: LineStyleDefinition.ComponentType.Internal }] }); assert.isTrue(undefined !== compoundData); const styleId = LineStyleDefinition.Utils.createStyle(imodel, definitionModelId, "TestArrowStyle", compoundData); assert.isTrue(Id64.isValidId64(styleId)); const builder = new GeometryStreamBuilder(); const params = new GeometryParams(seedElement.category); params.styleInfo = new LineStyle.Info(styleId); builder.appendGeometryParamsChange(params); builder.appendGeometry(LineSegment3d.create(Point3d.createZero(), Point3d.create(-1, -1, 0))); const elementProps = createPhysicalElementProps(seedElement, undefined, builder.geometryStream, physicalModelId); const newId = imodel.elements.insertElement(elementProps); assert.isTrue(Id64.isValidId64(newId)); imodel.saveChanges(); const usageInfo = imodel[_nativeDb].queryDefinitionElementUsage([partId, styleId]); assert.isTrue(usageInfo.geometryPartIds.includes(partId)); assert.isTrue(usageInfo.lineStyleIds.includes(styleId)); assert.isTrue(usageInfo.usedIds.includes(partId)); assert.isTrue(usageInfo.usedIds.includes(styleId)); } it("create GeometricElement3d using arrow head style w/o using stroke pattern", async () => { createGeometricElem3dUsingArrowHeadNoStrokePattern(); }); it("create GeometricElement3d using arrow head style w/o using stroke pattern - deleteElementTree fails", async () => { const mySubject = Subject.insert(imodel, IModel.rootSubjectId, "My Subject - fails"); const myDefModel = DefinitionModel.insert(imodel, mySubject, "My Definitions - fails"); const myPhysicalModel = IModelTestUtils.createAndInsertPhysicalPartitionAndModel(imodel, Code.createEmpty(), false, mySubject)[0]; createGeometricElem3dUsingArrowHeadNoStrokePattern(myDefModel, myPhysicalModel); deleteElementTree({ iModel: imodel, topElement: mySubject, maxPasses: 1 }); expect(imodel.elements.tryGetElement(mySubject)).not.undefined; }); it("create GeometricElement3d using arrow head style w/o using stroke pattern - deleteElementTree succeeds with 2 passes", async () => { const mySubject = Subject.insert(imodel, IModel.rootSubjectId, "My Subject - success"); const myDefModel = DefinitionModel.insert(imodel, mySubject, "My Definitions - success"); const myPhysicalModel = IModelTestUtils.createAndInsertPhysicalPartitionAndModel(imodel, Code.createEmpty(), false, mySubject)[0]; createGeometricElem3dUsingArrowHeadNoStrokePattern(myDefModel, myPhysicalModel); deleteElementTree({ iModel: imodel, topElement: mySubject, maxPasses: 2 }); expect(imodel.elements.tryGetElement(mySubject)).undefined; }); it("create GeometricElement3d using compound style with dash widths and symbol", async () => { // Set up element to be placed in iModel const seedElement = imodel.elements.getElement("0x1d"); assert.exists(seedElement); assert.isTrue(seedElement.federationGuid === "18eb4650-b074-414f-b961-d9cfaa6c8746"); const lsStrokes = []; lsStrokes.push({ length: 0.25, orgWidth: 0.0, endWidth: 0.025, strokeMode: LineStyleDefinition.StrokeMode.Dash, widthMode: LineStyleDefinition.StrokeWidth.Left }); lsStrokes.push({ length: 0.1 }); lsStrokes.push({ length: 0.1, orgWidth: 0.025, endWidth: 0.025, strokeMode: LineStyleDefinition.StrokeMode.Dash, widthMode: LineStyleDefinition.StrokeWidth.Full }); lsStrokes.push({ length: 0.1 }); lsStrokes.push({ length: 0.25, orgWidth: 0.025, endWidth: 0.0, strokeMode: LineStyleDefinition.StrokeMode.Dash, widthMode: LineStyleDefinition.StrokeWidth.Right }); lsStrokes.push({ length: 0.1 }); const strokePatternData = LineStyleDefinition.Utils.createStrokePatternComponent(imodel, { descr: "TestDashDotDashLineCode", strokes: lsStrokes }); assert.isTrue(undefined !== strokePatternData); const partBuilder = new GeometryStreamBuilder(); partBuilder.appendGeometry(Arc3d.createXY(Point3d.createZero(), 0.05)); const partProps = createGeometryPartProps(partBuilder.geometryStream); const partId = imodel.elements.insertElement(partProps); assert.isTrue(Id64.isValidId64(partId)); const pointSymbolData = LineStyleDefinition.Utils.createPointSymbolComponent(imodel, { geomPartId: partId }); // base and size will be set automatically... assert.isTrue(undefined !== pointSymbolData); const lsSymbols = []; lsSymbols.push({ symId: pointSymbolData.compId, strokeNum: 1, mod1: LineStyleDefinition.SymbolOptions.Center }); lsSymbols.push({ symId: pointSymbolData.compId, strokeNum: 3, mod1: LineStyleDefinition.SymbolOptions.Center }); const strokePointData = LineStyleDefinition.Utils.createStrokePointComponent(imodel, { descr: "TestGapSymbolsLinePoint", lcId: strokePatternData.compId, symbols: lsSymbols }); assert.isTrue(undefined !== strokePointData); const lsComponents = []; lsComponents.push({ id: strokePointData.compId, type: strokePointData.compType }); lsComponents.push({ id: strokePatternData.compId, type: strokePatternData.compType }); const compoundData = LineStyleDefinition.Utils.createCompoundComponent(imodel, { comps: lsComponents }); assert.isTrue(undefined !== compoundData); const styleId = LineStyleDefinition.Utils.createStyle(imodel, IModel.dictionaryId, "TestDashCircleDotCircleDashStyle", compoundData); assert.isTrue(Id64.isValidId64(styleId)); const builder = new GeometryStreamBuilder(); const params = new GeometryParams(seedElement.category); params.styleInfo = new LineStyle.Info(styleId); builder.appendGeometryParamsChange(params); builder.appendGeometry(LineSegment3d.create(Point3d.createZero(), Point3d.create(5, 5, 0))); const elementProps = createPhysicalElementProps(seedElement, undefined, builder.geometryStream); const newId = imodel.elements.insertElement(elementProps); assert.isTrue(Id64.isValidId64(newId)); imodel.saveChanges(); const usageInfo = imodel[_nativeDb].queryDefinitionElementUsage([partId, styleId, seedElement.category]); assert.isTrue(usageInfo.geometryPartIds.includes(partId)); assert.isTrue(usageInfo.lineStyleIds.includes(styleId)); assert.isTrue(usageInfo.spatialCategoryIds.includes(seedElement.category)); assert.isTrue(usageInfo.usedIds.includes(partId)); assert.isTrue(usageInfo.usedIds.includes(styleId)); assert.isTrue(usageInfo.usedIds.includes(seedElement.category)); }); it("create GeometricElement3d with line styles to check bounding box padding", async () => { // Set up element to be placed in iModel const seedElement = imodel.elements.getElement("0x1d"); assert.exists(seedElement); assert.isTrue(seedElement.federationGuid === "18eb4650-b074-414f-b961-d9cfaa6c8746"); // LineStyleDefinition: create special "internal default" continuous style for drawing curves using width overrides let x = 0.0; let y = 0.0; let unitDef = 1.0; let widthDef = 0.0; let name = `Continuous-${unitDef}-${widthDef}`; let styleId = LineStyleDefinition.Utils.createStyle(imodel, IModel.dictionaryId, name, { compId: 0, compType: LineStyleDefinition.ComponentType.Internal, flags: LineStyleDefinition.StyleFlags.Continuous | LineStyleDefinition.StyleFlags.NoSnap }); assert.isTrue(Id64.isValidId64(styleId)); // Expect no range padding... let newId = createStyledLineElem(imodel, seedElement, x++, y, 1.0, new LineStyle.Info(styleId)); assert.isTrue(Id64.isValidId64(newId)); imodel.saveChanges(); assert.isTrue(Geometry.isSameCoordinate(Placement3d.fromJSON(imodel.elements.getElementProps({ id: newId, wantGeometry: true }).placement).bbox.yLength(), 0.0)); // Expect range padded by 0.25... newId = createStyledLineElem(imodel, seedElement, x++, y, 1.0, new LineStyle.Info(styleId, new LineStyle.Modifier({ startWidth: 0.25, physicalWidth: true })), ColorDef.red); assert.isTrue(Id64.isValidId64(newId)); imodel.saveChanges(); assert.isTrue(Geometry.isSameCoordinate(Placement3d.fromJSON(imodel.elements.getElementProps({ id: newId, wantGeometry: true }).placement).bbox.yLength(), 0.25)); // LineStyleDefinition: create continuous style with both width and unit scale specified in definition... x = 0.0; y++; unitDef = 2.0; widthDef = 0.5; name = `Continuous-${unitDef}-${widthDef}`; let strokePatternData = LineStyleDefinition.Utils.createStrokePatternComponent(imodel, { descr: name, strokes: [{ length: 1e37, orgWidth: widthDef, strokeMode: LineStyleDefinition.StrokeMode.Dash, widthMode: LineStyleDefinition.StrokeWidth.Full }] }); styleId = LineStyleDefinition.Utils.createStyle(imodel, IModel.dictionaryId, name, { compId: strokePatternData.compId, compType: strokePatternData.compType, flags: LineStyleDefinition.StyleFlags.Continuous | LineStyleDefinition.StyleFlags.NoSnap, unitDef }); assert.isTrue(Id64.isValidId64(styleId)); // Expect no range padding... newId = createStyledLineElem(imodel, seedElement, x++, y, 1.0, new LineStyle.Info(styleId, new LineStyle.Modifier({ startWidth: 0.0, physicalWidth: true }))); assert.isTrue(Id64.isValidId64(newId)); imodel.saveChanges(); assert.isTrue(Geometry.isSameCoordinate(Placement3d.fromJSON(imodel.elements.getElementProps({ id: newId, wantGeometry: true }).placement).bbox.yLength(), 0.0)); // Expect range padded by 1.0... newId = createStyledLineElem(imodel, seedElement, x++, y, 1.0, new LineStyle.Info(styleId), ColorDef.red); assert.isTrue(Id64.isValidId64(newId)); imodel.saveChanges(); assert.isTrue(Geometry.isSameCoordinate(Placement3d.fromJSON(imodel.elements.getElementProps({ id: newId, wantGeometry: true }).placement).bbox.yLength(), widthDef * unitDef)); // Expect range padded by 0.25... newId = createStyledLineElem(imodel, seedElement, x++, y, 1.0, new LineStyle.Info(styleId, new LineStyle.Modifier({ startWidth: 0.25, physicalWidth: true })), ColorDef.green); assert.isTrue(Id64.isValidId64(newId)); imodel.saveChanges(); assert.isTrue(Geometry.isSameCoordinate(Placement3d.fromJSON(imodel.elements.getElementProps({ id: newId, wantGeometry: true }).placement).bbox.yLength(), 0.25)); // LineStyleDefinition: create stroke pattern with dash widths in definition... x = 0.0; y++; unitDef = 1.0; widthDef = 0.025; name = `StrokePattern-${unitDef}-${widthDef}`; const lsStrokes = []; lsStrokes.push({ length: 0.25, orgWidth: widthDef, endWidth: widthDef, strokeMode: LineStyleDefinition.StrokeMode.Dash, widthMode: LineStyleDefinition.StrokeWidth.Left }); lsStrokes.push({ length: 0.1 }); lsStrokes.push({ length: 0.2, orgWidth: widthDef, endWidth: widthDef, strokeMode: LineStyleDefinition.StrokeMode.Dash, widthMode: LineStyleDefinition.StrokeWidth.Full }); lsStrokes.push({ length: 0.1 }); lsStrokes.push({ length: 0.25, orgWidth: widthDef, endWidth: widthDef, strokeMode: LineStyleDefinition.StrokeMode.Dash, widthMode: LineStyleDefinition.StrokeWidth.Right }); lsStrokes.push({ length: 0.1 }); strokePatternData = LineStyleDefinition.Utils.createStrokePatternComponent(imodel, { descr: name, strokes: lsStrokes }); styleId = LineStyleDefinition.Utils.createStyle(imodel, IModel.dictionaryId, name, { compId: strokePatternData.compId, compType: strokePatternData.compType, flags: LineStyleDefinition.StyleFlags.NoSnap }); assert.isTrue(Id64.isValidId64(styleId)); // Expect range padded by 0.025... newId = createStyledLineElem(imodel, seedElement, x++, y, 1.0, new LineStyle.Info(styleId)); assert.isTrue(Id64.isValidId64(newId)); imodel.saveChanges(); assert.isTrue(Geometry.isSameCoordinate(Placement3d.fromJSON(imodel.elements.getElementProps({ id: newId, wantGeometry: true }).placement).bbox.yLength(), widthDef * unitDef)); // Expect range padded by 0.25... newId = createStyledLineElem(imodel, seedElement, x++, y, 1.0, new LineStyle.Info(styleId, new LineStyle.Modifier({ startWidth: 0.25, physicalWidth: true })), ColorDef.red); assert.isTrue(Id64.isValidId64(newId)); imodel.saveChanges(); assert.isTrue(Geometry.isSameCoordinate(Placement3d.fromJSON(imodel.elements.getElementProps({ id: newId, wantGeometry: true }).placement).bbox.yLength(), 0.25)); // LineStyleDefinition: create stroke pattern with dash widths and unit scale specified in definition... x = 0.0; y++; unitDef = 2.0; widthDef = 0.025; name = `StrokePattern-${unitDef}-${widthDef}`; styleId = LineStyleDefinition.Utils.createStyle(imodel, IModel.dictionaryId, name, { compId: strokePatternData.compId, compType: strokePatternData.compType, flags: LineStyleDefinition.StyleFlags.NoSnap, unitDef }); assert.isTrue(Id64.isValidId64(styleId)); // Expect range padded by 0.05... newId = createStyledLineElem(imodel, seedElement, x++, y, 1.0, new LineStyle.Info(styleId)); assert.isTrue(Id64.isValidId64(newId)); imodel.saveChanges(); assert.isTrue(Geometry.isSameCoordinate(Placement3d.fromJSON(imodel.elements.getElementProps({ id: newId, wantGeometry: true }).placement).bbox.yLength(), widthDef * unitDef)); // Expect range padded by 0.25... newId = createStyledLineElem(imodel, seedElement, x++, y, 1.0, new LineStyle.Info(styleId, new LineStyle.Modifier({ startWidth: 0.25, physicalWidth: true })), ColorDef.red); assert.isTrue(Id64.isValidId64(newId)); imodel.saveChanges(); assert.isTrue(Geometry.isSameCoordinate(Placement3d.fromJSON(imodel.elements.getElementProps({ id: newId, wantGeometry: true }).placement).bbox.yLength(), 0.25)); // Expect range padded by 0.25... newId = createStyledLineElem(imodel, seedElement, x++, y, 1.0, new LineStyle.Info(styleId, new LineStyle.Modifier({ startWidth: 0.25, scale: 0.5, physicalWidth: true })), ColorDef.green); assert.isTrue(Id64.isValidId64(newId)); imodel.saveChanges(); assert.isTrue(Geometry.isSameCoordinate(Placement3d.fromJSON(imodel.elements.getElementProps({ id: newId, wantGeometry: true }).placement).bbox.yLength(), 0.25)); // Expect range padded by 0.025... newId = createStyledLineElem(imodel, seedElement, x++, y, 1.0, new LineStyle.Info(styleId, new LineStyle.Modifier({ scale: 0.5 })), ColorDef.blue); assert.isTrue(Id64.isValidId64(newId)); imodel.saveChanges(); assert.isTrue(Geometry.isSameCoordinate(Placement3d.fromJSON(imodel.elements.getElementProps({ id: newId, wantGeometry: true }).placement).bbox.yLength(), widthDef * unitDef * 0.5)); // LineStyleDefinition: create point symbol with internal default instead of a stroke pattern... x = 0.0; y++; unitDef = 1.0; widthDef = 1.0; name = `PointSymbol-${unitDef}-${widthDef}`; const partId = createCirclePart(0.25, imodel); let pointSymbolData = LineStyleDefinition.Utils.createPointSymbolComponent(imodel, { geomPartId: partId }); let strokePointData = LineStyleDefinition.Utils.createStrokePointComponent(imodel, { descr: name, lcId: 0, lcType: LineStyleDefinition.ComponentType.Internal, symbols: [{ symId: pointSymbolData.compId, strokeNum: -1, mod1: LineStyleDefinition.SymbolOptions.CurveOrigin }] }); let compoundData = LineStyleDefinition.Utils.createCompoundComponent(imodel, { comps: [{ id: strokePointData.compId, type: strokePointData.compType }, { id: 0, type: LineStyleDefinition.ComponentType.Internal }] }); styleId = LineStyleDefinition.Utils.createStyle(imodel, IModel.dictionaryId, name, compoundData); assert.isTrue(Id64.isValidId64(styleId)); // Expect range padded by 0.5 (circle radius)... newId = createStyledLineElem(imodel, seedElement, x++, y, 1.0, new LineStyle.Info(styleId)); assert.isTrue(Id64.isValidId64(newId)); imodel.saveChanges(); assert.isTrue(Geometry.isSameCoordinate(Placement3d.fromJSON(imodel.elements.getElementProps({ id: newId, wantGeometry: true }).placement).bbox.yLength(), 0.5)); // Expect range padded by 0.25 (scaled circle radius)... newId = createStyledLineElem(imodel, seedElement, x++, y, 1.0, new LineStyle.Info(styleId, new LineStyle.Modifier({ scale: 0.5 })), ColorDef.red); assert.isTrue(Id64.isValidId64(newId)); imodel.saveChanges(); assert.isTrue(Geometry.isSameCoordinate(Placement3d.fromJSON(imodel.elements.getElementProps({ id: newId, wantGeometry: true }).placement).bbox.yLength(), 0.25)); // Expect range padded by 1.0 (width override > symbol size)... newId = createStyledLineElem(imodel, seedElement, x++, y, 1.0, new LineStyle.Info(styleId, new LineStyle.Modifier({ startWidth: 1.0, scale: 0.5, physicalWidth: true })), ColorDef.green); assert.isTrue(Id64.isValidId64(newId)); imodel.saveChanges(); assert.isTrue(Geometry.isSameCoordinate(Placement3d.fromJSON(imodel.elements.getElementProps({ id: newId, wantGeometry: true }).placement).bbox.yLength(), 1.0)); // LineStyleDefinition: create scaling point symbol with stroke pattern with width and unit scale specified in definition... x = 0.0; y++; unitDef = 0.5; widthDef = 0.025; // value used for lsStrokes... name = `PointSymbol-${unitDef}-${widthDef}}`; strokePatternData = LineStyleDefinition.Utils.createStrokePatternComponent(imodel, { descr: name, strokes: lsStrokes }); pointSymbolData = LineStyleDefinition.Utils.createPointSymbolComponent(imodel, { geomPartId: partId, scale: 2.0 }); const lsSymbols = []; lsSymbols.push({ symId: pointSymbolData.compId, strokeNum: 1, mod1: LineStyleDefinition.SymbolOptions.Center }); lsSymbols.push({ symId: pointSymbolData.compId, strokeNum: 3, mod1: LineStyleDefinition.SymbolOptions.Center }); strokePointData = LineStyleDefinition.Utils.createStrokePointComponent(imodel, { descr: name, lcId: strokePatternData.compId, symbols: lsSymbols }); const lsComponents = []; lsComponents.push({ id: strokePointData.compId, type: strokePointData.compType }); lsComponents.push({ id: strokePatternData.compId, type: strokePatternData.compType }); compoundData = LineStyleDefinition.Utils.createCompoundComponent(imodel, { comps: lsComponents }); compoundData.unitDef = unitDef; styleId = LineStyleDefinition.Utils.createStyle(imodel, IModel.dictionaryId, name, compoundData); assert.isTrue(Id64.isValidId64(styleId)); // Expect range padded by 0.125 (symbol and unit scaled circle radius)...... newId = createStyledLineElem(imodel, seedElement, x++, y, 1.0, new LineStyle.Info(styleId)); assert.isTrue(Id64.isValidId64(newId)); imodel.saveChanges(); assert.isTrue(Geometry.isSameCoordinate(Placement3d.fromJSON(imodel.elements.getElementProps({ id: newId, wantGeometry: true }).placement).bbox.yLength(), 0.125)); // Expect range padded by 0.0625 (symbol and modifier scaled circle radius)... newId = createStyledLineElem(imodel, seedElement, x++, y, 1.0, new LineStyle.Info(styleId, new LineStyle.Modifier({ scale: 0.5 })), ColorDef.red); assert.isTrue(Id64.isValidId64(newId)); imodel.saveChanges(); assert.isTrue(Geometry.isSameCoordinate(Placement3d.fromJSON(imodel.elements.getElementProps({ id: newId, wantGeometry: true }).placement).bbox.yLength(), 0.0625)); // Expect range padded by 0.75 (width override > symbol and modifier scaled symbol size)... newId = createStyledLineElem(imodel, seedElement, x++, y, 1.0, new LineStyle.Info(styleId, new LineStyle.Modifier({ startWidth: 0.75, scale: 0.5, physicalWidth: true })), ColorDef.green); assert.isTrue(Id64.isValidId64(newId)); imodel.saveChanges(); assert.isTrue(Geometry.isSameCoordinate(Placement3d.fromJSON(imodel.elements.getElementProps({ id: newId, wantGeometry: true }).placement).bbox.yLength(), 0.75)); // LineStyleDefinition: create non-scaling point symbol with stroke pattern with width and unit scale specified in definition... x = 0.0; y++; unitDef = 0.5; widthDef = 0.025; // value used for lsStrokes... name = `NonScalingPointSymbol-${unitDef}-${widthDef}}`; strokePatternData = LineStyleDefinition.Utils.createStrokePatternComponent(imodel, { descr: name, strokes: lsStrokes }); pointSymbolData = LineStyleDefinition.Utils.createPointSymbolComponent(imodel, { geomPartId: partId, scale: 2.0, symFlags: LineStyleDefinition.PointSymbolFlags.NoScale }); lsSymbols.length = 0; lsSymbols.push({ symId: pointSymbolData.compId, strokeNum: 1, mod1: LineStyleDefinition.SymbolOptions.Center }); lsSymbols.push({ symId: pointSymbolData.compId, strokeNum: 3, mod1: LineStyleDefinition.SymbolOptions.Center }); strokePointData = LineStyleDefinition.Utils.createStrokePointComponent(imodel, { descr: name, lcId: strokePatternData.compId, symbols: lsSymbols }); lsComponents.length = 0; lsComponents.push({ id: strokePointData.compId, type: strokePointData.compType }); lsComponents.push({ id: strokePatternData.compId, type: strokePatternData.compType }); compoundData = LineStyleDefinition.Utils.createCompoundComponent(imodel, { comps: lsComponents }); compoundData.unitDef = unitDef; styleId = LineStyleDefinition.Utils.createStyle(imodel, IModel.dictionaryId, name, compoundData); assert.isTrue(Id64.isValidId64(styleId)); // Expect range padded by 0.5 (circle radius)...... newId = createStyledLineElem(imodel, seedElement, x++, y, 1.0, new LineStyle.Info(styleId)); assert.isTrue(Id64.isValidId64(newId)); imodel.saveChanges(); assert.isTrue(Geometry.isSameCoordinate(Placement3d.fromJSON(imodel.elements.getElementProps({ id: newId, wantGeometry: true }).placement).bbox.yLength(), 0.5)); // Expect range padded by 0.5 (unscaled circle radius)... newId = createStyledLineElem(imodel, seedElement, x++, y, 1.0, new LineStyle.Info(styleId, new LineStyle.Modifier({ scale: 0.5 })), ColorDef.red); assert.isTrue(Id64.isValidId64(newId)); imodel.saveChanges(); assert.isTrue(Geometry.isSameCoordinate(Placement3d.fromJSON(imodel.elements.getElementProps({ id: newId, wantGeometry: true }).placement).bbox.yLength(), 0.5)); // Expect range padded by 0.75 (width override > unscaled symbol size)... newId = createStyledLineElem(imodel, seedElement, x++, y, 1.0, new LineStyle.Info(styleId, new LineStyle.Modifier({ startWidth: 0.75, scale: 0.5, physicalWidth: true })), ColorDef.green); assert.isTrue(Id64.isValidId64(newId)); imodel.saveChanges(); assert.isTrue(Geometry.isSameCoordinate(Placement3d.fromJSON(imodel.elements.getElementProps({ id: newId, wantGeometry: true }).placement).bbox.yLength(), 0.75)); // LineStyleDefinition: create scaling point symbol with offsets with stroke pattern with width and unit scale specified in definition... x = 0.0; y++; unitDef = 0.5; widthDef = 0.025; // value used for lsStrokes... name = `OffsetPointSymbol-${unitDef}-${widthDef}}`; strokePatternData = LineStyleDefinition.Utils.createStrokePatternComponent(imodel, { descr: name, strokes: lsStrokes }); pointSymbolData = LineStyleDefinition.Utils.createPointSymbolComponent(imodel, { geomPartId: partId, scale: 2.0 }); lsSymbols.length = 0; lsSymbols.push({ symId: pointSymbolData.compId, strokeNum: 1, mod1: LineStyleDefinition.SymbolOptions.Center, yOffset: -0.1 }); lsSymbols.push({ symId: pointSymbolData.compId, strokeNum: 3, mod1: LineStyleDefinition.SymbolOptions.Center, yOffset: 0.1 }); strokePointData = LineStyleDefinition.Utils.createStrokePointComponent(imodel, { descr: name, lcId: strokePatternData.compId, symbols: lsSymbols }); lsComponents.length = 0; lsComponents.push({ id: strokePointData.compId, type: strokePointData.compType }); lsComponents.push({ id: strokePatternData.compId, type: strokePatternData.compType }); compoundData = LineStyleDefinition.Utils.createCompoundComponent(imodel, { comps: lsComponents }); compoundData.unitDef = unitDef; styleId = LineStyleDefinition.Utils.createStyle(imodel, IModel.dictionaryId, name, compoundData); assert.isTrue(Id64.isValidId64(styleId)); // Expect range padded by 0.225 (offset symbol and unit scaled circle radius)...... newId = createStyledLineElem(imodel, seedElement, x++, y, 1.0, new Lin