ThatOpen / engine_web-ifc

Reading and writing IFC files with Javascript, at native speeds.
https://thatopen.github.io/engine_web-ifc/demo
Mozilla Public License 2.0
617 stars 190 forks source link

Space boundaries not created - IFCCURVEBOUNDEDPLANE not supported #208

Open MadsHolten opened 2 years ago

MadsHolten commented 2 years ago

When I try to access the space boundaries' geometries I get an error "unexpected mesh type". I have tested the same model in ifcopenshell for Python where I am able to get the space boundaries, export them to STL and view them in ThreeJS.

const spaceBoundaries = ifcAPi.GetLineIDsWithType(modelID, IFCRELSPACEBOUNDARY);
for (let i = 0; i < spaceBoundaries.size(); i++)
{
    const expressID = spaceBoundaries.get(i);

    // Get the element's geometry
    const flatMesh: FlatMesh = ifcAPi.GetFlatMesh(modelID, expressID);

    const geometry: IfcGeometry = ifcAPi.GetGeometry(modelID, flatMesh.expressID);
    // Returns the geometry object but also prints "unexpected mesh type"

    const vertices = geometry.GetVertexData();
    console.log(vertices); // Returns 0

}

Tested with web-ifc version 0.0.35

As I traverse through the relations I can see that the boundaries are represented by IfcSurfaceOfLinearExtrusion and IfcCurveBoundedPlane

vojtatom commented 1 year ago

Hello, I also came across "IFCCURVEBOUNDEDPLANE" geometry not being handled properly. I am parsing the geometry in a simple dedicated worker:

import { IfcAPI, Vector } from 'web-ifc/web-ifc-api';

interface UserInputModel {
    name: string;
    buffer: ArrayBuffer;
}

class IFCLoader {
    readonly data: { [item: number]: any } = {};
    private api = new IfcAPI();
    constructor() {
        this.api.SetWasmPath('/', true);
    }

    async loadIFC(name: string, buffer: ArrayBuffer) {
        await this.api.Init();
        console.log(this.api);

        const bufferui8 = new Uint8Array(buffer);
        console.log(`Loading IFC model ${name} with size ${bufferui8.length}`);
        const modelID = this.api.OpenModel(bufferui8);
        console.log(`Loading model ID: ${modelID}`);
        const lines = this.api.GetAllLines(modelID);
        this.getAllItemsFromLines(modelID, lines);
    }

    private getAllItemsFromLines(modelID: number, lines: Vector<number>) {
        for (let i = 0; i < lines.size(); i++) {
            this.saveProperties(modelID, lines, i);
        }
    }

    private saveProperties(modelID: number, lines: Vector<number>, index: number) {
        const itemID = lines.get(index);
        console.log(`Loading item ID: ${itemID}`);
        const props = this.api.GetLine(modelID, itemID);
        props.type = props.__proto__.constructor.name;
        this.data[itemID] = props;
    }
}

export function parseIFC(models: UserInputModel[]) {
    console.log(`Loading ${models.length} models`);
    models.forEach(async (model) => {
        if (model.name.endsWith('.ifc')) {
            console.log(`Loading IFC model ${model.name}`);
            const ifcLoader = new IFCLoader();
            await ifcLoader.loadIFC(model.name, model.buffer);
            console.log(ifcLoader.data);
        }
    });
}

self.onmessage = (e) => {
    parseIFC(e.data);
};

The error I get follows:

Uncaught (in promise) TypeError: Cannot read properties of null (reading 'map')
    at 2827736869 (web-ifc-api.js:11318:115)
    at IfcAPI2.GetLine (web-ifc-api.js:62076:83)
    at IFCLoader.saveProperties (IFC.worker.ts?type=module&worker_file:33:28)
    at IFCLoader.getAllItemsFromLines (IFC.worker.ts?type=module&worker_file:26:9)
    at IFCLoader.loadIFC (IFC.worker.ts?type=module&worker_file:21:5)
    at async IFC.worker.ts?type=module&worker_file:45:13

Any way I could help fixing this?

vojtatom commented 1 year ago

To elaborate: the typical line in .ifc file that fails to load looks like this:

#1929=IFCCURVEBOUNDEDPLANE(#1928,#1926,$);

as the last parameter is $, it doesn't translate to anything with the .map attribute.

beachtom commented 1 year ago

Yes - this is for some reason being processed as a mandatory field here - not optional so something is wrong. I will take a look

beachtom commented 1 year ago

This should be fixed now! Please let me know if it is not

MadsHolten commented 1 year ago

Just to understand correctly. Does this mean that Space boundary geometries (IFCRELSPACEBOUNDARY) are now being processed? If so, that's great news!

beachtom commented 1 year ago

So my understanding is reading in IFCRELSPACEBOUNDARY was broken (which it was) so you should be able to read it in without errors

vojtatom commented 1 year ago

✅ Successfully pulled 7d06a61069be6033e06c7e6114daa9c335047051 and built it locally, after parsing a file that was previously causing problems, no errors occurred, thank you.

Would be nice to update the package on NPM so we can just update the dependency, until then we'll be using the local build.

MadsHolten commented 1 year ago

Unfortunately I am still having problems with surface geometries of type IFCCURVEBOUNDEDPLANE and IFCSURFACEOFLINEAREXTRUSION. You can test with the below example that will fetch my demo model that contains space boundaries.

const { IfcAPI, IFCCURVEBOUNDEDPLANE, IFCSURFACEOFLINEAREXTRUSION} = require("../../dist/web-ifc-api-node.js");

console.log("Hello surface geometries!");

const ifcapi = new IfcAPI();

async function loadFile(fileURL) {
    // load model data as a string
    const res = await fetch(fileURL);
    const arrayBuffer = await res.arrayBuffer();
    const ifcData = Buffer.from( new Uint8Array(arrayBuffer) );

    await ifcapi.Init();

    const modelID = ifcapi.OpenModel(ifcData);

    console.log(`Loaded modelID ${modelID}`);

    // Get geometries of type IFCCURVEBOUNDEDPLANE
    const cbps = ifcapi.GetLineIDsWithType(modelID, IFCCURVEBOUNDEDPLANE);
    console.log(`Found ${cbps.size()} IFCCURVEBOUNDEDPLANEs`);
    const cbpsGeometries = getFlatMeshes(modelID, cbps);

    // Get geometries of type IFCSURFACEOFLINEAREXTRUSION
    const soles = ifcapi.GetLineIDsWithType(modelID, IFCSURFACEOFLINEAREXTRUSION);
    console.log(`Found ${soles.size()} IFCSURFACEOFLINEAREXTRUSIONs`);
    const solesGeometries = getFlatMeshes(modelID, soles);

    console.log(cbpsGeometries);
    console.log(solesGeometries);

    ifcapi.CloseModel(modelID);
    console.log(`Closed model`);

}

function getFlatMeshes(modelID, items) {
    let geometries = {};
    for (let i = 0; i < items.size(); i++) {
        const expressId = items.get(i);

        const flatMesh = ifcapi.GetFlatMesh(modelID, expressId);
        geometries[expressId] = [];
        for (let j = 0; j < flatMesh.geometries.size(); j++) {
            const geometry = ifcapi.GetGeometry(modelID, flatMesh.geometries.get(j).geometryExpressID);
            const geometryVertexArray = ifcapi.GetVertexArray(geometry.GetVertexData(), geometry.GetVertexDataSize());
            const geometryIndexData = ifcapi.GetIndexArray(geometry.GetIndexData(), geometry.GetIndexDataSize());
            geometries[expressId].push({geometryVertexArray, geometryIndexData});
        }

    }
    return geometries;
}

loadFile("https://raw.githubusercontent.com/MadsHolten/BOT-Duplex-house/master/Model%20files/IFC/Duplex.ifc");

I ran it from examples/nodejs both in the current main and in https://github.com/IFCjs/web-ifc/commit/7d06a61069be6033e06c7e6114daa9c335047051. Both throw "ERROR: unexpected mesh type"

beachtom commented 1 year ago

Can you give me the exact error? It may be we can now parse that IFC entity but do not have the geometric decoding for it yet

MadsHolten commented 1 year ago

I don't get more than ERROR: unexpected mesh type. Although I get it for every single element. How can I get more detailed logs?

beachtom commented 1 year ago

Sorry I found it now it is actually IFCCURVEBOUNDEDPLANE that we do not support right now. I'll re-open this

agonzalezesteve commented 12 months ago

I'm getting the same error with IfcRelSpaceBoundaries but with IfcFaceSurface, that is another IfcRepresentatonItem subclass, as geometric representation.