Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions packages/core/src/openbim/IDSSpecifications/src/Specification.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as FRAGS from "@thatopen/fragments";
import { Components } from "../../../core/Components";
import { IDSCheckResult, IDSSpecificationData, IfcVersion } from "./types";
import { UUID } from "../../../utils";
import { UUID, ModelIdMapUtils } from "../../../utils";
import { IDSFacet } from "./facets";
import { IDSSpecifications } from "..";
import { ModelIdMap } from "../../../fragments";
Expand Down Expand Up @@ -60,14 +60,19 @@ export class IDSSpecification implements IDSSpecificationData {

if (this.requirements.size === 0) return result;

// Get applicable elements
const entities: ModelIdMap = {};
// Get applicable elements. In IDS an element is applicable only when it
// matches every applicability facet, so each facet collects its own
// matches and the results are intersected.
const facetEntities: ModelIdMap[] = [];
const applicabilityPromises = [];
for (const facet of this.applicability) {
applicabilityPromises.push(facet.getEntities(modelIds, entities));
const facetMatches: ModelIdMap = {};
facetEntities.push(facetMatches);
applicabilityPromises.push(facet.getEntities(modelIds, facetMatches));
}

await Promise.all(applicabilityPromises);
const entities = ModelIdMapUtils.intersect(facetEntities);

// Test applicable elements against requirements
const requirementPromises = [];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import * as FRAGS from "@thatopen/fragments";
import * as fs from "fs";
import * as path from "path";
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
import type { Components } from "../../../index";
import { loadSourceBundle } from "../../../../tests/source-bundle";

// Needs the real `Components`: see `loadSourceBundle`.
const OBC = await loadSourceBundle();

const MODEL_ID = "type-psets";

// `tests/fixtures/type-psets.ifc` (see facets/property.test.ts): six walls,
// four of which have Pset_WallCommon.IsExternal = TRUE, directly or through
// their type.
const WALLS = [40, 41, 42, 43, 45, 46];
const WITH_IS_EXTERNAL_TRUE = [40, 41, 42, 46];

const convert = async () => {
const importer = new FRAGS.IfcImporter();
const root = path.resolve(__dirname, "../../../..");
importer.wasm = {
path: `${path.resolve(root, "../../node_modules/web-ifc")}/`,
absolute: true,
};
const ifc = fs.readFileSync(
path.join(root, "tests", "fixtures", "type-psets.ifc"),
);
const bytes = await importer.process({
bytes: new Uint8Array(ifc),
raw: true,
});
return new FRAGS.SingleThreadedFragmentsModel(MODEL_ID, bytes, true);
};

describe("IDSSpecification applicability", () => {
let components: Components;
let model: FRAGS.SingleThreadedFragmentsModel;

beforeAll(async () => {
model = await convert();
components = new OBC.Components();
const fragments = components.get(OBC.FragmentsManager);
vi.spyOn(fragments, "list", "get").mockReturnValue(
new Map([[MODEL_ID, model]]) as never,
);
}, 60_000);

afterAll(() => {
vi.restoreAllMocks();
components?.dispose();
model?.dispose();
});

const wall = () =>
new OBC.IDSEntity(components, { type: "simple", parameter: "IFCWALL" });

const isExternal = () => {
const property = new OBC.IDSProperty(
components,
{ type: "simple", parameter: "Pset_WallCommon" },
{ type: "simple", parameter: "IsExternal" },
);
property.value = { type: "simple", parameter: true };
return property;
};

const tested = async (
...applicability: InstanceType<typeof OBC.IDSFacet>[]
) => {
const spec = components
.get(OBC.IDSSpecifications)
.create("applicability", ["IFC4"]);
for (const facet of applicability) spec.applicability.add(facet);
spec.requirements.add(wall());
const result = await spec.test([new RegExp(MODEL_ID)]);
return [...(result.get(MODEL_ID)?.keys() ?? [])].sort((a, b) => a - b);
};

it("tests every element a single facet selects", async () => {
expect(await tested(wall())).toEqual(WALLS);
});

it("tests only the elements that match every applicability facet", async () => {
// IDS: applicability facets are combined with AND, so "a wall that is
// external" must not test the two walls that are not.
expect(await tested(wall(), isExternal())).toEqual(WITH_IS_EXTERNAL_TRUE);
});
});
Loading