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
96 changes: 84 additions & 12 deletions web-ifc-three/src/IFC/components/IFCParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,20 +233,92 @@ export class IFCParser implements ParserAPI {
private ifcGeometryToBuffer(expressID: number, vertexData: Float32Array, indexData: Uint32Array) {
const geometry = new BufferGeometry();

const posFloats = new Float32Array(vertexData.length / 2);
const normFloats = new Float32Array(vertexData.length / 2);
const idAttribute = new Uint32Array(vertexData.length / 6);
const numVertices = vertexData.length / 6;

// First pass: identify valid vertices (no NaN in position) and build index mapping
const validFlags = new Uint8Array(numVertices);
let validCount = 0;
for (let i = 0; i < numVertices; i++) {
const x = vertexData[i * 6];
const y = vertexData[i * 6 + 1];
const z = vertexData[i * 6 + 2];
if (Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z)) {
validFlags[i] = 1;
validCount++;
}
}

// If all vertices are valid, use the fast path (no filtering)
if (validCount === numVertices) {
const posFloats = new Float32Array(vertexData.length / 2);
const normFloats = new Float32Array(vertexData.length / 2);
const idAttribute = new Uint32Array(vertexData.length / 6);

for (let i = 0; i < vertexData.length; i += 6) {
posFloats[i / 2] = vertexData[i];
posFloats[i / 2 + 1] = vertexData[i + 1];
posFloats[i / 2 + 2] = vertexData[i + 2];

normFloats[i / 2] = vertexData[i + 3];
normFloats[i / 2 + 1] = vertexData[i + 4];
normFloats[i / 2 + 2] = vertexData[i + 5];

idAttribute[i / 6] = expressID;
}

geometry.setAttribute(
'position',
new BufferAttribute(posFloats, 3));
geometry.setAttribute(
'normal',
new BufferAttribute(normFloats, 3));
geometry.setAttribute(
'expressID',
new BufferAttribute(idAttribute, 1));

geometry.setIndex(new BufferAttribute(indexData, 1));
return geometry;
}

// Some vertices have NaN positions — filter them out
// Build remapping: old index -> new index
const indexRemap = new Uint32Array(numVertices);
let nextNewIndex = 0;
for (let i = 0; i < numVertices; i++) {
if (validFlags[i]) {
indexRemap[i] = nextNewIndex++;
}
}

for (let i = 0; i < vertexData.length; i += 6) {
posFloats[i / 2] = vertexData[i];
posFloats[i / 2 + 1] = vertexData[i + 1];
posFloats[i / 2 + 2] = vertexData[i + 2];
const posFloats = new Float32Array(validCount * 3);
const normFloats = new Float32Array(validCount * 3);
const idAttribute = new Uint32Array(validCount);

normFloats[i / 2] = vertexData[i + 3];
normFloats[i / 2 + 1] = vertexData[i + 4];
normFloats[i / 2 + 2] = vertexData[i + 5];
let newVertIdx = 0;
for (let i = 0; i < numVertices; i++) {
if (!validFlags[i]) continue;
posFloats[newVertIdx * 3] = vertexData[i * 6];
posFloats[newVertIdx * 3 + 1] = vertexData[i * 6 + 1];
posFloats[newVertIdx * 3 + 2] = vertexData[i * 6 + 2];

idAttribute[i / 6] = expressID;
normFloats[newVertIdx * 3] = vertexData[i * 6 + 3];
normFloats[newVertIdx * 3 + 1] = vertexData[i * 6 + 4];
normFloats[newVertIdx * 3 + 2] = vertexData[i * 6 + 5];

idAttribute[newVertIdx] = expressID;
newVertIdx++;
}

// Remap indices to use new vertex indices, discarding triangles with invalid vertices
const filteredIndices: number[] = [];
for (let i = 0; i < indexData.length; i += 3) {
const a = indexRemap[indexData[i]];
const b = indexRemap[indexData[i + 1]];
const c = indexRemap[indexData[i + 2]];
// Only include the triangle if all three vertices are valid
if (validFlags[indexData[i]] && validFlags[indexData[i + 1]] && validFlags[indexData[i + 2]]) {
filteredIndices.push(a, b, c);
}
}

geometry.setAttribute(
Expand All @@ -259,7 +331,7 @@ export class IFCParser implements ParserAPI {
'expressID',
new BufferAttribute(idAttribute, 1));

geometry.setIndex(new BufferAttribute(indexData, 1));
geometry.setIndex(new BufferAttribute(new Uint32Array(filteredIndices), 1));
return geometry;
}

Expand Down
30 changes: 30 additions & 0 deletions web-ifc-three/src/IFCLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ class IFCLoader extends Loader {
this.ifcManager = new IFCManager();
}

/**
* Loads an IFC file from a given URL.
* @param url URL of the IFC file or blob URL.
* @param onLoad Callback when the IFC model is loaded.
* @param onProgress Optional progress callback.
* @param onError Optional error callback.
*/
load(
url: any,
onLoad: (ifc: IFCModel) => void,
Expand All @@ -19,6 +26,29 @@ class IFCLoader extends Loader {
) {
const scope = this;

// Detect JSON files and provide a helpful error message.
// Users often try to load JSON files (produced by ifc-to-json) directly,
// but web-ifc-three only supports binary IFC files for geometry.
const urlString = typeof url === 'string' ? url : '';
if (urlString.endsWith('.json')) {
const error = new Error(
'Cannot load ".json" files directly with IFCLoader. ' +
'The IFCLoader is designed to load binary IFC files (.ifc) for geometry. ' +
'If you are trying to load a JSON file produced by "ifc-to-json", ' +
'please note that JSON files contain property/metadata only (not geometry) ' +
'and must be loaded alongside an IFC file using addModelJSONData(). ' +
'See the documentation for the correct workflow: ' +
'https://ifcjs.github.io/info/docs/Guide/web-ifc/Introduction'
);
if (onError) {
onError(error);
} else {
console.error(error);
}
scope.manager.itemError(url);
return;
}

const loader = new FileLoader(scope.manager);
this.onProgress = onProgress;
loader.setPath(scope.path);
Expand Down