UNPKG

@itwin/core-backend

Version:
417 lines • 18.2 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ import { expect } from "chai"; import { Guid, Id64 } from "@itwin/core-bentley"; import { LineString3d, Loop, Point3d } from "@itwin/core-geometry"; import { AreaPattern, Code, ColorDef, GeometryParams, GeometryStreamBuilder, GeometryStreamIterator, IModel, } from "@itwin/core-common"; import { _nativeDb, GenericSchema, GeometricElement3d, GeometryPart, PhysicalModel, PhysicalObject, PhysicalPartition, RenderMaterialElement, SnapshotDb, SpatialCategory, SubCategory, SubjectOwnsPartitionElements, } from "../../core-backend"; import { IModelTestUtils } from "../IModelTestUtils"; function makeGeomParams(symb) { const params = new GeometryParams(symb.categoryId ?? Id64.invalid, symb.subCategoryId); params.lineColor = symb.color; params.materialId = symb.materialId; if (symb.patternOrigin) params.pattern = AreaPattern.Params.fromJSON({ origin: [symb.patternOrigin, 0, 0] }); return params; } class GeomWriter { builder; constructor(symbology) { this.builder = new GeometryStreamBuilder(); if (symbology) this.append(symbology); } append(entry) { if (entry.partId) this.builder.appendGeometryPart3d(entry.partId, new Point3d(entry.origin, 0, 0)); else if (entry.subCategoryId || entry.categoryId || entry.color || entry.materialId || entry.patternOrigin) this.builder.appendGeometryParamsChange(makeGeomParams(entry)); else if (undefined !== entry.pos) this.builder.appendGeometry(Loop.createPolygon([new Point3d(entry.pos, 0, 0), new Point3d(entry.pos + 1, 0, 0), new Point3d(entry.pos + 1, 1, 0), new Point3d(entry.pos, 1, 0)])); else if (undefined !== entry.appendSubRanges) this.builder.appendGeometryRanges(); } } function readGeomStream(iter) { const result = []; for (const entry of iter) { const symb = { categoryId: entry.geomParams.categoryId, subCategoryId: entry.geomParams.subCategoryId }; if (undefined !== entry.geomParams.lineColor) symb.color = entry.geomParams.lineColor; if (undefined !== entry.geomParams.materialId) symb.materialId = entry.geomParams.materialId; if (entry.geomParams.pattern) { expect(entry.geomParams.pattern.origin).not.to.be.undefined; symb.patternOrigin = entry.geomParams.pattern.origin.x; } result.push(symb); if (entry.localRange) { expect(entry.localRange.low.y).to.equal(0); expect(entry.localRange.low.z).to.equal(0); expect(entry.localRange.high.y).to.equal(1); expect(entry.localRange.high.z).to.equal(0); expect(entry.localRange.high.x - entry.localRange.low.x).to.equal(1); result.push({ low: entry.localRange.low.x }); } if (entry.primitive.type === "geometryQuery") { expect(entry.primitive.geometry.geometryCategory).to.equal("curveCollection"); if (entry.primitive.geometry.geometryCategory === "curveCollection") { expect(entry.primitive.geometry.children.length).to.equal(1); expect(entry.primitive.geometry.children[0]).instanceOf(LineString3d); const pts = entry.primitive.geometry.children[0].points; expect(pts.length).to.equal(5); expect(pts[1].x).to.equal(pts[0].x + 1); result.push({ pos: pts[0].x }); } } else { expect(entry.primitive.type).to.equal("partReference"); if (entry.primitive.type === "partReference") { const partRef = { partId: entry.primitive.part.id }; if (entry.primitive.part.toLocal) partRef.origin = entry.primitive.part.toLocal.origin.x; result.push(partRef); } } } result.viewIndependent = iter.isViewIndependent; return result; } describe("DgnDb.inlineGeometryPartReferences", () => { let imodel; let modelId; let categoryId; let blueSubCategoryId; let redSubCategoryId; let materialId; beforeEach(() => { imodel = SnapshotDb.createEmpty(IModelTestUtils.prepareOutputFile("InlineGeomParts", `${Guid.createValue()}.bim`), { rootSubject: { name: "InlineGeomParts", description: "InlineGeomParts" }, }); GenericSchema.registerSchema(); const partitionId = imodel.elements.insertElement({ classFullName: PhysicalPartition.classFullName, model: IModel.repositoryModelId, parent: new SubjectOwnsPartitionElements(IModel.rootSubjectId), code: PhysicalPartition.createCode(imodel, IModel.rootSubjectId, `PhysicalPartition_${Guid.createValue()}`), }); expect(Id64.isValidId64(partitionId)).to.be.true; const model = imodel.models.createModel({ classFullName: PhysicalModel.classFullName, modeledElement: { id: partitionId }, }); expect(model).instanceOf(PhysicalModel); modelId = imodel.models.insertModel(model.toJSON()); expect(Id64.isValidId64(modelId)).to.be.true; categoryId = SpatialCategory.insert(imodel, IModel.dictionaryId, "ctgry", { color: ColorDef.blue.toJSON() }); expect(Id64.isValidId64(categoryId)).to.be.true; blueSubCategoryId = IModel.getDefaultSubCategoryId(categoryId); redSubCategoryId = SubCategory.insert(imodel, categoryId, "red", { color: ColorDef.red.toJSON() }); expect(Id64.isValidId64(redSubCategoryId)).to.be.true; materialId = RenderMaterialElement.insert(imodel, IModel.dictionaryId, "mat", { paletteName: "pal" }); expect(Id64.isValidId64(materialId)).to.be.true; }); afterEach(() => { imodel.close(); }); function insertGeometryPart(geom) { const writer = new GeomWriter(); for (const entry of geom) writer.append(entry); const props = { classFullName: GeometryPart.classFullName, model: IModel.dictionaryId, code: GeometryPart.createCode(imodel, IModel.dictionaryId, Guid.createValue()), geom: writer.builder.geometryStream, }; const partId = imodel.elements.insertElement(props); expect(Id64.isValidId64(partId)).to.be.true; return partId; } function insertElement(geom, viewIndependent = false) { const writer = new GeomWriter({ categoryId }); if (viewIndependent) writer.builder.isViewIndependent = true; for (const entry of geom) writer.append(entry); const props = { classFullName: PhysicalObject.classFullName, model: modelId, code: Code.createEmpty(), category: categoryId, geom: writer.builder.geometryStream, placement: { origin: [0, 0, 0], angles: {}, }, }; const elemId = imodel.elements.insertElement(props); expect(Id64.isValidId64(elemId)).to.be.true; return elemId; } function readElementGeom(id) { let iter; const elem = imodel.elements.getElement({ id, wantGeometry: true }); if (elem instanceof GeometryPart) { iter = GeometryStreamIterator.fromGeometryPart(elem); } else { expect(elem).instanceOf(GeometricElement3d); iter = GeometryStreamIterator.fromGeometricElement3d(elem); } return readGeomStream(iter); } function expectGeom(actual, expected) { expect(actual).to.deep.equal(expected); } function inlinePartRefs() { const result = imodel[_nativeDb].inlineGeometryPartReferences(); expect(result.numCandidateParts).to.equal(result.numPartsDeleted); expect(result.numRefsInlined).to.equal(result.numCandidateParts); return result.numRefsInlined; } it("inlines and deletes a simple unique part reference", () => { // Create single reference to a part and perform sanity checks on our geometry validation code. const partId = insertGeometryPart([{ pos: 123 }]); expectGeom(readElementGeom(partId), [ { categoryId: "0", subCategoryId: "0" }, { pos: 123 }, ]); const elemId = insertElement([{ partId }]); expectGeom(readElementGeom(elemId), [ { categoryId, subCategoryId: blueSubCategoryId }, { partId }, ]); // Inline and delete the part. expect(inlinePartRefs()).to.equal(1); expect(imodel.elements.tryGetElement(partId)).to.be.undefined; const geom = readElementGeom(elemId); expect(geom.viewIndependent).to.be.false; expectGeom(geom, [ { categoryId, subCategoryId: blueSubCategoryId }, { low: 123 }, { pos: 123 }, ]); }); it("inlines and deletes unique parts, ignoring non-unique parts", () => { const part1 = insertGeometryPart([{ pos: 1 }]); const part2 = insertGeometryPart([{ pos: 2 }]); const part3 = insertGeometryPart([{ pos: 3 }]); const part4 = insertGeometryPart([{ pos: 4 }]); const elem1 = insertElement([{ partId: part1 }]); const elem2 = insertElement([{ partId: part2 }, { partId: part3 }]); const elem3 = insertElement([{ partId: part3 }]); const elem4 = insertElement([{ partId: part4 }]); const elem5 = insertElement([{ partId: part4 }]); expect(inlinePartRefs()).to.equal(2); const symb = { categoryId, subCategoryId: blueSubCategoryId }; expectGeom(readElementGeom(elem1), [symb, { low: 1 }, { pos: 1 }]); expectGeom(readElementGeom(elem2), [symb, { low: 2 }, { pos: 2 }, symb, { partId: part3 }]); expectGeom(readElementGeom(elem3), [symb, { partId: part3 }]); expectGeom(readElementGeom(elem4), [symb, { partId: part4 }]); expectGeom(readElementGeom(elem5), [symb, { partId: part4 }]); }); it("applies part transform", () => { const partId = insertGeometryPart([{ pos: -8 }]); const elemId = insertElement([{ partId, origin: 50 }]); expect(inlinePartRefs()).to.equal(1); expectGeom(readElementGeom(elemId), [ { categoryId, subCategoryId: blueSubCategoryId }, { low: 42 }, { pos: 42 }, ]); }); it("applies element symbology to part and resets element symbology after embedding part", () => { const part1 = insertGeometryPart([ { pos: 1 }, { color: ColorDef.green }, { pos: 1.5 }, ]); const part2 = insertGeometryPart([ { pos: 2 }, { materialId }, { pos: 2.5 }, ]); // Sanity check. expectGeom(readElementGeom(part2), [ { categoryId: "0", subCategoryId: "0" }, { pos: 2 }, { categoryId: "0", subCategoryId: "0", materialId }, { pos: 2.5 }, ]); const part3 = insertGeometryPart([ { pos: 3 }, { materialId: "0" }, { pos: 3.5 }, ]); const elemId = insertElement([ { pos: -1 }, { subCategoryId: redSubCategoryId }, { partId: part1 }, { pos: -2 }, { color: ColorDef.black }, { partId: part2 }, { pos: -3 }, { materialId, color: ColorDef.white }, { partId: part3 }, { pos: -4 }, ]); expect(inlinePartRefs()).to.equal(3); expectGeom(readElementGeom(elemId), [ { categoryId, subCategoryId: blueSubCategoryId }, { pos: -1 }, { categoryId, subCategoryId: redSubCategoryId }, { low: 1 }, { pos: 1 }, { categoryId, subCategoryId: redSubCategoryId, color: ColorDef.green }, { low: 1.5 }, { pos: 1.5 }, { categoryId, subCategoryId: redSubCategoryId }, { low: -2 }, { pos: -2 }, { categoryId, subCategoryId: redSubCategoryId, color: ColorDef.black }, { low: 2 }, { pos: 2 }, { categoryId, subCategoryId: redSubCategoryId, materialId }, { low: 2.5 }, { pos: 2.5 }, { categoryId, subCategoryId: redSubCategoryId, color: ColorDef.black }, { low: -3 }, { pos: -3 }, { categoryId, subCategoryId: redSubCategoryId, color: ColorDef.white, materialId }, { low: 3 }, { pos: 3 }, { categoryId, subCategoryId: redSubCategoryId, materialId: "0" }, { low: 3.5 }, { pos: 3.5 }, { categoryId, subCategoryId: redSubCategoryId, color: ColorDef.white, materialId }, { low: -4 }, { pos: -4 }, ]); }); it("inserts subgraphic ranges for parts", () => { const part1 = insertGeometryPart([{ pos: 1 }]); const part2 = insertGeometryPart([{ pos: 2 }]); const elem = insertElement([ { pos: -1 }, { partId: part1 }, { partId: part2, origin: 5 }, ]); expect(inlinePartRefs()).to.equal(2); expectGeom(readElementGeom(elem), [ { categoryId, subCategoryId: blueSubCategoryId }, { pos: -1 }, { categoryId, subCategoryId: blueSubCategoryId }, { low: 1 }, { pos: 1 }, { categoryId, subCategoryId: blueSubCategoryId }, { low: 7 }, { pos: 7 }, ]); }); it("preserves existing subgraphic ranges", () => { const partId = insertGeometryPart([{ pos: 0 }]); const elem = insertElement([ { appendSubRanges: true }, { pos: -1 }, { partId }, { pos: 1 }, ]); expectGeom(readElementGeom(elem), [ { categoryId, subCategoryId: blueSubCategoryId }, { low: -1 }, { pos: -1 }, { categoryId, subCategoryId: blueSubCategoryId }, { partId }, { categoryId, subCategoryId: blueSubCategoryId }, { low: 1 }, { pos: 1 }, ]); expect(inlinePartRefs()).to.equal(1); expectGeom(readElementGeom(elem), [ { categoryId, subCategoryId: blueSubCategoryId }, { low: -1 }, { pos: -1 }, { categoryId, subCategoryId: blueSubCategoryId }, { low: 0 }, { pos: 0 }, { categoryId, subCategoryId: blueSubCategoryId }, { low: 1 }, { pos: 1 }, ]); }); it("inserts subgraphic ranges geometry following part", () => { const partId = insertGeometryPart([{ pos: 1 }]); const elem = insertElement([ { pos: 0 }, { partId }, { pos: 2 }, ]); expect(inlinePartRefs()).to.equal(1); expectGeom(readElementGeom(elem), [ { categoryId, subCategoryId: blueSubCategoryId }, { pos: 0 }, { categoryId, subCategoryId: blueSubCategoryId }, { low: 1 }, { pos: 1 }, { categoryId, subCategoryId: blueSubCategoryId }, { low: 2 }, { pos: 2 }, ]); }); it("applies transform to patterns", () => { const part1 = insertGeometryPart([{ pos: 1 }]); const part2 = insertGeometryPart([{ patternOrigin: 123 }, { pos: 2 }]); expectGeom(readElementGeom(part2), [ { categoryId: "0", subCategoryId: "0", patternOrigin: 123 }, { pos: 2 }, ]); const elemId = insertElement([ { patternOrigin: 456 }, { pos: -1 }, { partId: part1, origin: 8 }, { pos: -2 }, { partId: part2, origin: 12 }, { pos: -3 }, ]); expectGeom(readElementGeom(elemId), [ { categoryId, subCategoryId: blueSubCategoryId, patternOrigin: 456 }, { pos: -1 }, { categoryId, subCategoryId: blueSubCategoryId, patternOrigin: 456 }, { partId: part1, origin: 8 }, { categoryId, subCategoryId: blueSubCategoryId, patternOrigin: 456 }, { pos: -2 }, { categoryId, subCategoryId: blueSubCategoryId, patternOrigin: 456 }, { partId: part2, origin: 12 }, { categoryId, subCategoryId: blueSubCategoryId, patternOrigin: 456 }, { pos: -3 }, ]); expect(inlinePartRefs()).to.equal(2); expectGeom(readElementGeom(elemId), [ { categoryId, subCategoryId: blueSubCategoryId, patternOrigin: 456 }, { pos: -1 }, { categoryId, subCategoryId: blueSubCategoryId, patternOrigin: 456 }, { low: 1 + 8 }, { pos: 1 + 8 }, { categoryId, subCategoryId: blueSubCategoryId, patternOrigin: 456 }, { low: -2 }, { pos: -2 }, { categoryId, subCategoryId: blueSubCategoryId, patternOrigin: 123 + 12 }, { low: 2 + 12 }, { pos: 2 + 12 }, { categoryId, subCategoryId: blueSubCategoryId, patternOrigin: 456 }, { low: -3 }, { pos: -3 }, ]); }); it("preserves element header flags", () => { const partId = insertGeometryPart([{ pos: 1 }]); const elemId = insertElement([{ partId }], true); expect(readElementGeom(elemId).viewIndependent).to.be.true; expect(inlinePartRefs()).to.equal(1); expect(readElementGeom(elemId).viewIndependent).to.be.true; }); }); //# sourceMappingURL=InlineGeometryPartReferences.test.js.map