@itwin/core-backend
Version:
iTwin.js backend components
712 lines • 183 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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