Skip to content
Merged
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
59 changes: 51 additions & 8 deletions packages/fragments/src/Utils/ifc-splitter/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,12 +323,55 @@ test("split ifc", async () => {
ids: fileIds,
});
}
}, 60_000);

// Regression: extract used to resolve dependencies (including resolveStyles)
// before rewriting relationship lines. Entities pulled into the output only by
// the relations pass therefore never got their presentation styles resolved:
// their IfcStyledItem bindings — and the style chain behind them (surface
// style, rendering, colour) — were silently dropped. Nothing references a
// styled item, so the output has no dangling refs and no warning fires; the
// geometry just loses its render style. The relations pass must run before
// dependency resolution so resolveStyles covers relation-discovered geometry.
test("extract keeps the styled items of every included geometry item", async () => {
const splitter = new IfcSplitterNode();
const inputPath = path.resolve(assetDir, "resources/ifc/school_str.ifc");
const outputPath = path.resolve(__dirname, ".tmp", "styled.ifc");

const extractedIds = await splitter.extract(inputPath, [501], outputPath);

const source = await readFile(inputPath, "utf8");

// Every styled item in the source whose target geometry was extracted must
// be extracted too, along with the styles it binds.
const missingStyledItems: number[] = [];
const missingStyles: number[] = [];
for (const m of source.matchAll(/^#(\d+)\s*=\s*IFCSTYLEDITEM\((.*)$/gm)) {
const styledItemId = Number(m[1]);
const [target, ...styleRefs] = [...m[2].matchAll(/#(\d+)/g)].map((r) =>
Number(r[1]),
);
if (!extractedIds.has(target)) continue;
if (!extractedIds.has(styledItemId)) {
missingStyledItems.push(styledItemId);
continue;
}
for (const styleId of styleRefs) {
if (!extractedIds.has(styleId)) missingStyles.push(styleId);
}
}

expect(missingStyledItems, "styled items of included geometry").toHaveLength(
0,
);
expect(missingStyles, "styles bound by included styled items").toHaveLength(
0,
);
});

test("extract ifc", async () => {
const splitter = new IfcSplitterNode();
const inputPath = path.resolve(assetDir, "resources/ifc/school_str.ifc");
const inputFrag = path.resolve(assetDir, "resources/frags/school_str.frag");
const outputPath = path.resolve(__dirname, ".tmp", "extracted.ifc");
const onProgress = vi.fn<(event: IfcSplitterProgressEvent) => unknown>();
const onSplitsResolved = vi.fn<(event: IfcSplitterGroupsEvent) => unknown>();
Expand Down Expand Up @@ -369,17 +412,17 @@ test("extract ifc", async () => {
importer.addAllRelations();
importer.wasm = { path: webIfcDir + path.sep, absolute: true };
importer.webIfcSettings.COORDINATE_TO_ORIGIN = false;
const fixtureFrag = await importer.process({
bytes: await readFile(inputPath),
});
const fixtureModel = new SingleThreadedFragmentsModel("fixture", fixtureFrag);
const extractedFrag = await importer.process({
bytes: await readFile(outputPath),
});
const extractedModel = new SingleThreadedFragmentsModel(
"extracted",
extractedFrag,
);
const fixtureModel = new SingleThreadedFragmentsModel(
"fixture",
await readFile(inputFrag),
);
expect(extractedModel.getItemsGeometry(idsToExtract)).toEqual([
[
expect.objectContaining({
Expand All @@ -393,8 +436,8 @@ test("extract ifc", async () => {
[
expect.objectContaining({
localId: 501,
sampleId: 99301,
representationId: 98690,
sampleId: 99277,
representationId: 98663,
}),
],
]);
Expand Down Expand Up @@ -446,4 +489,4 @@ test("extract ifc", async () => {
comparisons.map(({ message, actual, expected }) =>
expect.soft(actual, message).toEqual(expected),
);
});
}, 60_000);