Skip to content

Commit

Permalink
Allow to export glTF by level and/or category
Browse files Browse the repository at this point in the history
  • Loading branch information
agviegas committed Mar 30, 2022
1 parent b39d198 commit 2dcc508
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 39 deletions.
101 changes: 87 additions & 14 deletions example/build/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -113977,6 +113977,8 @@
this.loader = new GLTFLoader();
this.exporter = new GLTFExporter();
this.tempIfcLoader = null;
this.allFloors = 'allFloors';
this.allCategories = 'allCategories';
this.options = {
trs: false,
onlyVisible: false,
Expand Down Expand Up @@ -114026,11 +114028,12 @@
* @fileURL The URL of the IFC file to convert to glTF
* @ids (optional) The ids of the items to export. If not defined, the full model is exported
*/
async exportIfcFileAsGltf(ifcFileUrl, getProperties = false, categories, maxJSONSize, onProgress) {
async exportIfcFileAsGltf(config) {
const loader = new IFCLoader();
this.tempIfcLoader = loader;
const state = this.IFC.loader.ifcManager.state;
const manager = loader.ifcManager;
const { ifcFileUrl, getProperties, categories, splitByFloors, maxJSONSize, onProgress } = config;
if (state.wasmPath)
await manager.setWasmPath(state.wasmPath);
if (state.worker.active)
Expand All @@ -114041,10 +114044,10 @@
if (onProgress)
onProgress(event.loaded, event.total, 'IFC');
});
// If there are no geometry of a group of categories, it adds "null" to the result
// If there are no geometry of a group of categories, it returns "null" as result
// because it makes no sense to create an empty gltf file
const result = {
gltf: [],
gltf: {},
json: [],
id: ''
};
Expand All @@ -114055,31 +114058,65 @@
if (!GUID)
throw new Error('The found IfcProject does not have a GUID');
result.id = GUID.value;
let allIdsByFloor = {};
let floorNames = [];
if (splitByFloors) {
allIdsByFloor = await this.getIDsByFloor(loader);
floorNames = Object.keys(allIdsByFloor);
}
if (categories) {
const items = [];
for (let i = 0; i < categories.length; i++) {
const currentCategories = categories[i];
const categoryNames = Object.keys(categories);
for (let i = 0; i < categoryNames.length; i++) {
const categoryName = categoryNames[i];
const currentCategories = categories[categoryName];
if (!result.gltf[categoryName])
result.gltf[categoryName] = {};
for (let j = 0; j < currentCategories.length; j++) {
// eslint-disable-next-line no-await-in-loop
const foundItems = await manager.getAllItemsOfType(0, currentCategories[j], false);
items.push(...foundItems);
}
if (items.length) {
// eslint-disable-next-line no-await-in-loop
const gltf = await this.exportModelPartToGltf(model, items, true);
result.gltf.push(new File([new Blob([gltf])], 'model-part.gltf'));
const groupedIDs = {};
if (splitByFloors) {
floorNames.forEach((floorName) => {
const floorIDs = allIdsByFloor[floorName];
groupedIDs[floorName] = items.filter((id) => floorIDs.has(id));
});
}
else {
result.gltf.push(null);
groupedIDs[this.allFloors] = items;
}
const idsByFloor = Object.keys(groupedIDs);
for (let j = 0; j < idsByFloor.length; j++) {
const floorName = idsByFloor[j];
const items = groupedIDs[floorName];
if (items.length) {
const gltf = await this.exportModelPartToGltf(model, items, true);
result.gltf[categoryName][floorName] = this.glTFToFile(gltf, 'model-part.gltf');
}
else {
result.gltf[categoryName][floorName] = null;
}
}
if (onProgress)
onProgress(i, categories === null || categories === void 0 ? void 0 : categories.length, 'GLTF');
onProgress(i, categoryNames === null || categoryNames === void 0 ? void 0 : categoryNames.length, 'GLTF');
items.length = 0;
}
}
else {
const gltf = await this.exportMeshToGltf(model);
result.gltf.push(new File([new Blob([gltf])], 'full-model.gltf'));
result.gltf[this.allCategories] = {};
if (splitByFloors) {
for (let i = 0; i < floorNames.length; i++) {
const floorName = floorNames[i];
const floorIDs = Array.from(allIdsByFloor[floorName]);
const gltf = await this.exportModelPartToGltf(model, floorIDs, true);
result.gltf[this.allCategories][floorName] = this.glTFToFile(gltf);
}
}
else {
const gltf = await this.exportMeshToGltf(model);
result.gltf[this.allCategories][this.allFloors] = this.glTFToFile(gltf);
}
}
if (getProperties) {
const previousLoader = this.IFC.properties.loader;
Expand Down Expand Up @@ -114168,6 +114205,42 @@
const mesh = new Mesh(geometryToExport, newMaterials);
return this.exportMeshToGltf(mesh);
}
glTFToFile(gltf, name = 'model.gltf') {
return new File([new Blob([gltf])], name);
}
async getIDsByFloor(loader) {
const ifcProject = await loader.ifcManager.getSpatialStructure(0);
console.log(ifcProject);
const idsByFloor = {};
const storeys = ifcProject.children[0].children[0].children;
const storeysIDs = storeys.map((storey) => storey.expressID);
for (let i = 0; i < storeysIDs.length; i++) {
const storey = storeys[i];
const ids = [];
this.getChildrenRecursively(storey, ids);
const storeyID = storeysIDs[i];
const properties = await loader.ifcManager.getItemProperties(0, storeyID);
const name = this.getStoreyName(properties);
idsByFloor[name] = new Set(ids);
}
return idsByFloor;
}
getStoreyName(storey) {
if (storey.Name)
return storey.Name.value;
if (storey.LongName)
return storey.LongName.value;
return storey.GlobalId;
}
getChildrenRecursively(spatialNode, result) {
const ids = spatialNode.children.map((child) => child.expressID);
result.push(...ids);
spatialNode.children.forEach((child) => {
if (child.children.length) {
this.getChildrenRecursively(child, result);
}
});
}
getModelID() {
const models = this.context.items.ifcModels;
if (!models.length)
Expand Down
4 changes: 2 additions & 2 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "web-ifc-viewer-basic-example",
"private": true,
"type": "module",
"version": "1.0.166",
"version": "1.0.167",
"description": "A basic html example for web-ifc-viewer",
"main": "main.js",
"scripts": {
Expand All @@ -29,6 +29,6 @@
"stats.js": "0.17.0",
"three": "0.135",
"web-ifc": "0.0.33",
"web-ifc-viewer": "1.0.166"
"web-ifc-viewer": "1.0.167"
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "web-ifc-viewer-root",
"description": "IFC viewer",
"version": "1.0.166",
"version": "1.0.167",
"private": true,
"main": "viewer/src/ifc-viewer-api.ts",
"author": "agviegas",
Expand Down
1 change: 1 addition & 0 deletions viewer/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ module.exports = {
'eol-last': 'off',
'no-unused-vars': 'off',
'class-methods-use-this': 'off',
'no-await-in-loop': 'off',
'import/extensions': [
'error',
'ignorePackages',
Expand Down
2 changes: 1 addition & 1 deletion viewer/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "web-ifc-viewer",
"version": "1.0.166",
"version": "1.0.167",
"description": "IFC viewer",
"main": "dist/index.js",
"scripts": {
Expand Down
138 changes: 117 additions & 21 deletions viewer/src/components/import-export/glTF.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,23 @@ import { IfcContext } from '../context';
import { IfcManager } from '../ifc';
import { disposeMeshRecursively } from '../../utils/ThreeUtils';

export interface ExportConfig {
ifcFileUrl: string;
getProperties?: boolean;
categories?: { [categoryName: string]: number[] };
splitByFloors?: boolean;
maxJSONSize?: number;
onProgress?: (progress: number, total: number, process: string) => void;
}

export class GLTFManager extends IfcComponent {
GLTFModels: { [modelID: number]: Group } = {};

private loader = new GLTFLoader();
private exporter = new GLTFExporter();
private tempIfcLoader: IFCLoader | null = null;
private allFloors = 'allFloors';
private allCategories = 'allCategories';

options = {
trs: false,
Expand Down Expand Up @@ -80,18 +91,15 @@ export class GLTFManager extends IfcComponent {
* @fileURL The URL of the IFC file to convert to glTF
* @ids (optional) The ids of the items to export. If not defined, the full model is exported
*/
async exportIfcFileAsGltf(
ifcFileUrl: string,
getProperties = false,
categories?: number[][],
maxJSONSize?: number,
onProgress?: (progress: number, total: number, process: string) => void
) {
async exportIfcFileAsGltf(config: ExportConfig) {
const loader = new IFCLoader();
this.tempIfcLoader = loader;
const state = this.IFC.loader.ifcManager.state;
const manager = loader.ifcManager;

const { ifcFileUrl, getProperties, categories, splitByFloors, maxJSONSize, onProgress } =
config;

if (state.wasmPath) await manager.setWasmPath(state.wasmPath);
if (state.worker.active) await manager.useWebWorkers(true, state.worker.path);
if (state.webIfcSettings) await manager.applyWebIfcConfig(state.webIfcSettings);
Expand All @@ -100,10 +108,18 @@ export class GLTFManager extends IfcComponent {
if (onProgress) onProgress(event.loaded, event.total, 'IFC');
});

// If there are no geometry of a group of categories, it adds "null" to the result
// If there are no geometry of a group of categories, it returns "null" as result
// because it makes no sense to create an empty gltf file
const result: { gltf: (File | null)[]; json: File[]; id: string } = {
gltf: [],
const result: {
gltf: {
[categoryName: string]: {
[floorName: string]: File | null;
};
};
json: File[];
id: string;
} = {
gltf: {},
json: [],
id: ''
};
Expand All @@ -114,31 +130,67 @@ export class GLTFManager extends IfcComponent {
if (!GUID) throw new Error('The found IfcProject does not have a GUID');
result.id = GUID.value;

let allIdsByFloor: { [p: string]: Set<number> } = {};
let floorNames: string[] = [];
if (splitByFloors) {
allIdsByFloor = await this.getIDsByFloor(loader);
floorNames = Object.keys(allIdsByFloor);
}

if (categories) {
const items: number[] = [];

for (let i = 0; i < categories.length; i++) {
const currentCategories = categories[i];
const categoryNames = Object.keys(categories);

for (let i = 0; i < categoryNames.length; i++) {
const categoryName = categoryNames[i];
const currentCategories = categories[categoryName];
if (!result.gltf[categoryName]) result.gltf[categoryName] = {};

for (let j = 0; j < currentCategories.length; j++) {
// eslint-disable-next-line no-await-in-loop
const foundItems = await manager.getAllItemsOfType(0, currentCategories[j], false);
items.push(...foundItems);
}

if (items.length) {
// eslint-disable-next-line no-await-in-loop
const gltf = await this.exportModelPartToGltf(model, items, true);
result.gltf.push(new File([new Blob([gltf])], 'model-part.gltf'));
const groupedIDs: { [floorName: string]: number[] } = {};
if (splitByFloors) {
floorNames.forEach((floorName) => {
const floorIDs = allIdsByFloor[floorName];
groupedIDs[floorName] = items.filter((id) => floorIDs.has(id));
});
} else {
result.gltf.push(null);
groupedIDs[this.allFloors] = items;
}

if (onProgress) onProgress(i, categories?.length, 'GLTF');
const idsByFloor = Object.keys(groupedIDs);
for (let j = 0; j < idsByFloor.length; j++) {
const floorName = idsByFloor[j];
const items = groupedIDs[floorName];

if (items.length) {
const gltf = await this.exportModelPartToGltf(model, items, true);
result.gltf[categoryName][floorName] = this.glTFToFile(gltf, 'model-part.gltf');
} else {
result.gltf[categoryName][floorName] = null;
}
}

if (onProgress) onProgress(i, categoryNames?.length, 'GLTF');
items.length = 0;
}
} else {
const gltf = await this.exportMeshToGltf(model);
result.gltf.push(new File([new Blob([gltf])], 'full-model.gltf'));
result.gltf[this.allCategories] = {};
if (splitByFloors) {
for (let i = 0; i < floorNames.length; i++) {
const floorName = floorNames[i];
const floorIDs = Array.from(allIdsByFloor[floorName]);
const gltf = await this.exportModelPartToGltf(model, floorIDs, true);
result.gltf[this.allCategories][floorName] = this.glTFToFile(gltf);
}
} else {
const gltf = await this.exportMeshToGltf(model);
result.gltf[this.allCategories][this.allFloors] = this.glTFToFile(gltf);
}
}

if (getProperties) {
Expand Down Expand Up @@ -249,6 +301,50 @@ export class GLTFManager extends IfcComponent {
return this.exportMeshToGltf(mesh);
}

private glTFToFile(gltf: any, name = 'model.gltf') {
return new File([new Blob([gltf])], name);
}

private async getIDsByFloor(loader: IFCLoader) {
const ifcProject = await loader.ifcManager.getSpatialStructure(0);
console.log(ifcProject);

const idsByFloor: { [floorName: string]: Set<number> } = {};

const storeys = ifcProject.children[0].children[0].children as any[];
const storeysIDs = storeys.map((storey: any) => storey.expressID);

for (let i = 0; i < storeysIDs.length; i++) {
const storey = storeys[i];
const ids: number[] = [];
this.getChildrenRecursively(storey, ids);

const storeyID = storeysIDs[i];
const properties = await loader.ifcManager.getItemProperties(0, storeyID);
const name = this.getStoreyName(properties);

idsByFloor[name] = new Set<number>(ids);
}

return idsByFloor;
}

private getStoreyName(storey: any) {
if (storey.Name) return storey.Name.value;
if (storey.LongName) return storey.LongName.value;
return storey.GlobalId;
}

private getChildrenRecursively(spatialNode: any, result: number[]) {
const ids = spatialNode.children.map((child: any) => child.expressID) as number[];
result.push(...ids);
spatialNode.children.forEach((child: any) => {
if (child.children.length) {
this.getChildrenRecursively(child, result);
}
});
}

private getModelID() {
const models = this.context.items.ifcModels;
if (!models.length) return 0;
Expand Down

0 comments on commit 2dcc508

Please sign in to comment.