From 2826753549831e6d5951e042c0bc2af2fdd2eded Mon Sep 17 00:00:00 2001 From: nickcastel50 Date: Wed, 21 Jun 2023 13:54:24 -0400 Subject: [PATCH 1/3] update conway-geom --- dependencies/conway-geom | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies/conway-geom b/dependencies/conway-geom index 89800a14..9b8e2021 160000 --- a/dependencies/conway-geom +++ b/dependencies/conway-geom @@ -1 +1 @@ -Subproject commit 89800a14c5d618a7e757ac7bad47341441e57d4e +Subproject commit 9b8e20218a76989028d974d4b727bde6a034e829 From 03d097612d0155efc109eaf0be400b0877e0b6ae Mon Sep 17 00:00:00 2001 From: nickcastel50 Date: Wed, 21 Jun 2023 14:04:01 -0400 Subject: [PATCH 2/3] * Update conway-geom --- dependencies/conway-geom | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies/conway-geom b/dependencies/conway-geom index 9b8e2021..9a0ddec9 160000 --- a/dependencies/conway-geom +++ b/dependencies/conway-geom @@ -1 +1 @@ -Subproject commit 9b8e20218a76989028d974d4b727bde6a034e829 +Subproject commit 9a0ddec998873a4a230d9d792148c46913e37ef0 From c8e28d1db73d7a01212f445335de2886b6bf93c3 Mon Sep 17 00:00:00 2001 From: nickcastel50 Date: Thu, 29 Jun 2023 17:53:07 -0400 Subject: [PATCH 3/3] * Basic property extraction support - refactor --- dependencies/conway-geom | 2 +- package.json | 2 +- src/core/canonical_mesh.ts | 42 ++ src/core/model.ts | 13 + src/core/readonly_typed_array.ts | 41 ++ src/core/scene.ts | 10 + src/core/scene_node.ts | 57 +++ src/ifc/ifc_command_line_main.ts | 201 +++++---- src/ifc/ifc_geometry_extraction.test.ts | 50 ++- src/ifc/ifc_geometry_extraction.ts | 561 +++++++++--------------- src/ifc/ifc_model_geometry.ts | 44 ++ src/ifc/ifc_property_extraction.test.ts | 39 ++ src/ifc/ifc_property_extraction.ts | 205 +++++++++ src/ifc/ifc_scene_builder.ts | 293 +++++++++++++ src/ifc/ifc_step_model.test.ts | 4 +- src/ifc/ifc_step_model.ts | 3 + src/step/step_model_base.ts | 3 +- 17 files changed, 1121 insertions(+), 449 deletions(-) create mode 100644 src/core/canonical_mesh.ts create mode 100644 src/core/readonly_typed_array.ts create mode 100644 src/core/scene.ts create mode 100644 src/core/scene_node.ts create mode 100644 src/ifc/ifc_model_geometry.ts create mode 100644 src/ifc/ifc_property_extraction.test.ts create mode 100644 src/ifc/ifc_property_extraction.ts create mode 100644 src/ifc/ifc_scene_builder.ts diff --git a/dependencies/conway-geom b/dependencies/conway-geom index 9a0ddec9..020bc328 160000 --- a/dependencies/conway-geom +++ b/dependencies/conway-geom @@ -1 +1 @@ -Subproject commit 9a0ddec998873a4a230d9d792148c46913e37ef0 +Subproject commit 020bc32868adee9d22df5df9dbf3ad85f4c8f519 diff --git a/package.json b/package.json index 3df2cd0f..ea3a8473 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "scripts": { "build-conway_geom": "run-script-os", "build-conway_geom:win32": "cd .\\dependencies\\conway-geom\\ && call .\\build_win.bat release && (if not exist Dist (mkdir Dist)) && xcopy .\\bin\\release\\* Dist\\ /Y/r && copy /Y ConwayGeomWasm.d.ts Dist\\ && (if not exist ..\\..\\compiled\\dependencies\\conway-geom\\Dist (mkdir ..\\..\\compiled\\dependencies\\conway-geom\\Dist)) && xcopy .\\bin\\release\\* ..\\..\\compiled\\dependencies\\conway-geom\\Dist\\ /Y/r", - "build-conway_geom:darwin": "cd ./dependencies/conway-geom/ && ./build_osx.sh release && mkdir -p Dist && cp ./bin/release/* Dist/ && cp ConwayGeomWasm.d.ts Dist/ && mkdir -p ../../compiled/dependencies/conway-geom/Dist && cp ./bin/release/* ../../compiled/dependencies/conway-geom/Dist", + "build-conway_geom:darwin": "cd ./dependencies/conway-geom/ && ./build_osx.sh release wasm && mkdir -p Dist && cp ./bin/release/* Dist/ && cp ConwayGeomWasm.d.ts Dist/ && mkdir -p ../../compiled/dependencies/conway-geom/Dist && cp ./bin/release/* ../../compiled/dependencies/conway-geom/Dist", "build-clean-conway_geom": "run-script-os", "build-clean-conway_geom:win32": "cd ./dependencies/conway-geom/ && build_win.bat clean", "build-clean-conway_geom:darwin": "cd ./dependencies/conway-geom/ && ./build_osx.sh clean", diff --git a/src/core/canonical_mesh.ts b/src/core/canonical_mesh.ts new file mode 100644 index 00000000..55c309b6 --- /dev/null +++ b/src/core/canonical_mesh.ts @@ -0,0 +1,42 @@ +import { GeometryObject } from '../../dependencies/conway-geom/conway_geometry' +import { Model } from './model' + + +/* eslint-disable no-shadow,no-unused-vars,no-magic-numbers */ +export enum CanonicalMeshType { + BUFFER_GEOMETRY = 0, + GLTF_URL = 1, + GLB_BUFFER = 2 +} + + +export interface CanonicalMeshBase { + readonly type: CanonicalMeshType + readonly geometry: GeometryObject | string | ArrayBuffer + readonly model: Model + readonly localID: number + /** + * This is true if this is not final geometry, some geometry is only kept for intermediate + * calculation purposes, and is removed, if it is not final. + */ + readonly temporary?: boolean +} + +export interface CanonicalMeshBuffer extends CanonicalMeshBase { + readonly type: CanonicalMeshType.BUFFER_GEOMETRY + readonly geometry: GeometryObject +} + +export interface CanonicalMeshUrl extends CanonicalMeshBase { + readonly type: CanonicalMeshType.GLTF_URL + readonly geometry: string + readonly temporary: undefined +} + +export interface CanonicalMeshGLBBuffer extends CanonicalMeshBase { + readonly type: CanonicalMeshType.GLB_BUFFER + readonly geometry: ArrayBuffer + readonly temporary: undefined +} + +export type CanonicalMesh = CanonicalMeshBuffer | CanonicalMeshUrl | CanonicalMeshGLBBuffer diff --git a/src/core/model.ts b/src/core/model.ts index 0f2f2595..fc75e8d4 100644 --- a/src/core/model.ts +++ b/src/core/model.ts @@ -1,6 +1,17 @@ +import { CanonicalMesh } from './canonical_mesh' import { Entity } from './entity' import { IIndexSetCursor } from './i_index_set_cursor' +/** + * Geometry capability, this model has geometry. + */ +export interface ModelGeometry extends Iterable< CanonicalMesh > { + + length: number + + getByLocalID(localID: number): CanonicalMesh | undefined + +} export interface Model extends Iterable { @@ -10,5 +21,7 @@ export interface Model extends Iterable { from(cursor: IIndexSetCursor, freeCursor: boolean): IterableIterator + readonly geometry?: ModelGeometry + readonly size: number } diff --git a/src/core/readonly_typed_array.ts b/src/core/readonly_typed_array.ts new file mode 100644 index 00000000..ab5e76c8 --- /dev/null +++ b/src/core/readonly_typed_array.ts @@ -0,0 +1,41 @@ +export type TypedArrayMutableProperties = 'copyWithin' | 'fill' | 'reverse' | 'set' | 'sort' + +export interface ReadonlyUint8Array extends + Omit< Uint8Array, TypedArrayMutableProperties > { + readonly [n: number ]: number +} + +export interface ReadonlyUint16Array extends + Omit< Uint16Array, TypedArrayMutableProperties > { + readonly [n: number ]: number +} + +export interface ReadonlyUint32Array extends + Omit< Uint32Array, TypedArrayMutableProperties > { + readonly [n: number ]: number +} + +export interface ReadonlyBigUint64Array extends + Omit< BigUint64Array, TypedArrayMutableProperties > { + readonly [n: number ]: bigint +} + +export interface ReadonlyInt8Array extends + Omit< Int8Array, TypedArrayMutableProperties > { + readonly [n: number ]: number +} + +export interface ReadonlyInt16Array extends + Omit< Int16Array, TypedArrayMutableProperties > { + readonly [n: number ]: number +} + +export interface ReadonlyInt32Array extends + Omit< Int32Array, TypedArrayMutableProperties > { + readonly [n: number ]: number +} + +export interface ReadonlyBigInt64Array extends + Omit< BigInt64Array, TypedArrayMutableProperties > { + readonly [n: number ]: bigint +} diff --git a/src/core/scene.ts b/src/core/scene.ts new file mode 100644 index 00000000..bcab06eb --- /dev/null +++ b/src/core/scene.ts @@ -0,0 +1,10 @@ +import { ReadonlyUint32Array } from './readonly_typed_array' +import { SceneNode } from './scene_node' + + +export interface Scene { + + getByNodeIndex( nodeIndex: number ): SceneNode | undefined + + readonly roots: ReadonlyUint32Array | ReadonlyArray< number > +} diff --git a/src/core/scene_node.ts b/src/core/scene_node.ts new file mode 100644 index 00000000..c6b6e18e --- /dev/null +++ b/src/core/scene_node.ts @@ -0,0 +1,57 @@ +import { Model } from './model' +import { ReadonlyUint32Array } from './readonly_typed_array' + + +/* eslint-disable no-shadow,no-unused-vars,no-magic-numbers */ +export enum SceneNodeModelType { + GEOMETRY = 0, + TRANSFORM = 1, + URL = 2 +} + + +export interface SceneNodeModelBase { + readonly type: SceneNodeModelType + + readonly model: string | Model + + readonly localID: number + + readonly parentIndex?: number + + readonly index: number +} + +export interface SceneNodeTransform extends SceneNodeModelBase { + + readonly type: SceneNodeModelType.TRANSFORM + + readonly model: Model + + /** + * 4x4 transform matrix, stored in OpenGL Convention. + * + * If none is found, the identity transform is assumeer + */ + readonly transform: Float64Array | Float32Array | ReadonlyArray< number > + + readonly children: ReadonlyUint32Array | ReadonlyArray< number > +} + +export interface SceneNodeGeometry extends SceneNodeModelBase { + + readonly type: SceneNodeModelType.GEOMETRY + + readonly model: Model +} + +export interface SceneNodeUrl extends SceneNodeModelBase { + + readonly type: SceneNodeModelType.URL + + readonly model: string + + resolve(): Promise< void > + } + +export type SceneNode = SceneNodeUrl | SceneNodeGeometry | SceneNodeTransform diff --git a/src/ifc/ifc_command_line_main.ts b/src/ifc/ifc_command_line_main.ts index fc030a27..7aa81573 100644 --- a/src/ifc/ifc_command_line_main.ts +++ b/src/ifc/ifc_command_line_main.ts @@ -9,6 +9,9 @@ import fs from 'fs' import StepEntityBase from '../step/step_entity_base' import IfcStepModel from './ifc_step_model' import { ExtractResult, IfcGeometryExtraction } from './ifc_geometry_extraction' +import { IfcPropertyExtraction } from './ifc_property_extraction' +import { ConwayGeometry, GeometryObject } from '../../dependencies/conway-geom/conway_geometry' +import { CanonicalMeshType } from '../core/canonical_mesh' const SKIP_PARAMS = 2 @@ -38,6 +41,12 @@ const args = // eslint-disable-line no-unused-vars type: 'boolean', alias: 'g', }) + yargs2.option('properties', { + describe: 'Output PropertySets', + type: 'boolean', + alias: 'p', + }) + yargs2.positional('filename', { describe: 'IFC File Paths', type: 'string' }) }, (argv) => { const ifcFile = argv['filename'] as string @@ -52,6 +61,8 @@ const args = // eslint-disable-line no-unused-vars ['expressID', 'type', 'localID'] const geometry = (argv['geometry'] as boolean | undefined) + const outputProperties = (argv['properties'] as boolean | undefined) + try { indexIfcBuffer = fs.readFileSync(ifcFile) } catch (ex) { @@ -111,62 +122,6 @@ const args = // eslint-disable-line no-unused-vars return } - console.log('\n') - - console.log(fields.reduce((previous, current, currentIndex) => { - return `${previous}${(currentIndex === 0) ? '|' : ''}${current}|` - }, '')) - - console.log(fields.reduce((previous, current, currentIndex) => { - return `${previous}${(currentIndex === 0) ? '|' : ''}---|` - }, '')) - - let rowCount = 0 - - const elements = - (expressIDs?.map((value) => model?.getElementByExpressID(value))?.filter( - (value) => value !== void 0 && (types === void 0 || - types.includes(value.type))) ?? - (types !== void 0 ? model.typeIDs(...types) : void 0) ?? - model) as StepEntityBase[] | - IterableIterator> - - for (const element of elements) { - const elementTypeID = EntityTypesIfc[element.type] - - console.log( - fields.reduce((previous, current, currentIndex) => { - let result - - try { - if (current === 'type') { - result = elementTypeID - } else { - result = ((element as { [key: string]: any })[current]) - - if (result === null) { - result = 'null' - } else if (result === void 0) { - result = ' ' - } else if (current === 'expressID') { - result = `#${result}` - } - } - } catch (ex) { - result = 'err' - } - - return `${previous}${(currentIndex === 0) ? '|' : ''}${result}|` - }, '')) - - ++rowCount - } - - console.log('\n') - console.log(`Row Count: ${rowCount}`) - console.log(`Header parse time ${headerDataTimeEnd - headerDataTimeStart} ms`) - console.log(`Data parse time ${parseDataTimeEnd - parseDataTimeStart} ms`) - if (geometry) { // Get the filename with extension const fileNameWithExtension = ifcFile.split('/').pop()! @@ -175,21 +130,97 @@ const args = // eslint-disable-line no-unused-vars // Add space between camel-cased words const fileNameNoExtension = fileName.split(/(?=[A-Z])/).join(' ') geometryExtraction(model, fileNameNoExtension) + } else { + + console.log('\n') + + console.log(fields.reduce((previous, current, currentIndex) => { + return `${previous}${(currentIndex === 0) ? '|' : ''}${current}|` + }, '')) + + console.log(fields.reduce((previous, current, currentIndex) => { + return `${previous}${(currentIndex === 0) ? '|' : ''}---|` + }, '')) + + let rowCount = 0 + + const elements = + (expressIDs?.map((value) => model?.getElementByExpressID(value))?.filter( + (value) => value !== void 0 && (types === void 0 || + types.includes(value.type))) ?? + (types !== void 0 ? model.typeIDs(...types) : void 0) ?? + model) as StepEntityBase[] | + IterableIterator> + + for (const element of elements) { + const elementTypeID = EntityTypesIfc[element.type] + + console.log( + fields.reduce((previous, current, currentIndex) => { + let result + + try { + if (current === 'type') { + result = elementTypeID + } else { + result = ((element as { [key: string]: any })[current]) + + if (result === null) { + result = 'null' + } else if (result === void 0) { + result = ' ' + } else if (current === 'expressID') { + result = `#${result}` + } + } + } catch (ex) { + result = 'err' + } + + return `${previous}${(currentIndex === 0) ? '|' : ''}${result}|` + }, '')) + + ++rowCount + } + + console.log('\n') + console.log(`Row Count: ${rowCount}`) + console.log(`Header parse time ${headerDataTimeEnd - headerDataTimeStart} ms`) + console.log(`Data parse time ${parseDataTimeEnd - parseDataTimeStart} ms`) + } + + if (outputProperties) { + propertyExtraction(model) } }) .help().argv +/** + * Function to extract PropertySets from an IfcStepModel + */ +function propertyExtraction(model: IfcStepModel) { + + IfcPropertyExtraction.extractIFCProperties(model, true) +} + /** * Function to extract Geometry from an IfcStepModel */ async function geometryExtraction(model: IfcStepModel, fileNameNoExtension: string) { - // get a model Id - const modelId = await IfcGeometryExtraction.create() + const conwaywasm = new ConwayGeometry() + const initializationStatus = await conwaywasm.initialize() + + if (!initializationStatus) { + return + } + + const conwayModel = new IfcGeometryExtraction(conwaywasm, model) + // parse + extract data model + geometry data - const [extractionResult, meshArray] = - IfcGeometryExtraction.extractIFCGeometryData(model, true, modelId) + const [extractionResult, scene] = + conwayModel.extractIFCGeometryData(true) if (extractionResult !== ExtractResult.COMPLETE) { console.error('Could not extract geometry, exiting...') @@ -198,17 +229,33 @@ async function geometryExtraction(model: IfcStepModel, fileNameNoExtension: stri // we can assign the first GeometryObject to another variable here to combine them all. // TODO(nickcastel50): rework this - const fullGeometry = meshArray[0] - for (let i = 0; i < meshArray.length; i++) { - if (i > 0) { - fullGeometry.geometry.appendGeometry(meshArray[i].geometry) + let fullGeometry: GeometryObject | undefined + + // eslint-disable-next-line no-unused-vars + for (const [_, nativeTransform, geometry] of scene.walk()) { + if (geometry.type === CanonicalMeshType.BUFFER_GEOMETRY) { + + const clonedGeometry = geometry.geometry.clone() + + clonedGeometry.applyTransform(nativeTransform) + + if (fullGeometry === void 0) { + fullGeometry = clonedGeometry + } else { + fullGeometry.appendGeometry(clonedGeometry) + } } } + if (fullGeometry === void 0) { + console.log('No Geometry Found') + return + } + // returns a string containing a full obj const startTimeObj = Date.now() - const objResult = IfcGeometryExtraction.toObj(fullGeometry.geometry) + const objResult = conwayModel.toObj(fullGeometry) const endTimeObj = Date.now() const executionTimeInMsObj = endTimeObj - startTimeObj @@ -224,7 +271,7 @@ async function geometryExtraction(model: IfcStepModel, fileNameNoExtension: stri const startTimeGlb = Date.now() const glbResult = - IfcGeometryExtraction.toGltf(fullGeometry.geometry, true, false, `${fileNameNoExtension}_test`) + conwayModel.toGltf(fullGeometry, true, false, `${fileNameNoExtension}_test`) const endTimeGlb = Date.now() const executionTimeInMsGlb = endTimeGlb - startTimeGlb @@ -240,7 +287,7 @@ async function geometryExtraction(model: IfcStepModel, fileNameNoExtension: stri // Create a (zero copy!) memory view from the native vector const managedBuffer: Uint8Array = - IfcGeometryExtraction.getWasmModule().getUint8Array(glbResult.buffers.get(uriIndex)) + conwayModel.getWasmModule().getUint8Array(glbResult.buffers.get(uriIndex)) fs.writeFile(uri, managedBuffer, function(err) { if (err) { console.error('Error writing to file: ', err) @@ -253,7 +300,7 @@ async function geometryExtraction(model: IfcStepModel, fileNameNoExtension: stri const startTimeGlbDraco = Date.now() const glbDracoResult = - IfcGeometryExtraction.toGltf(fullGeometry.geometry, + conwayModel.toGltf(fullGeometry, true, true, `${fileNameNoExtension}_test_draco`) @@ -272,7 +319,7 @@ async function geometryExtraction(model: IfcStepModel, fileNameNoExtension: stri // Create a memory view from the native vector const managedBuffer: Uint8Array = - IfcGeometryExtraction.getWasmModule().getUint8Array(glbDracoResult.buffers.get(uriIndex)) + conwayModel.getWasmModule().getUint8Array(glbDracoResult.buffers.get(uriIndex)) fs.writeFile(uri, managedBuffer, function(err) { if (err) { console.error('Error writing to file: ', err) @@ -285,10 +332,10 @@ async function geometryExtraction(model: IfcStepModel, fileNameNoExtension: stri const startTimeGltf = Date.now() const gltfResult = - IfcGeometryExtraction.toGltf(fullGeometry.geometry, - false, - false, - `${fileNameNoExtension}_test`) + conwayModel.toGltf(fullGeometry, + false, + false, + `${fileNameNoExtension}_test`) const endTimeGltf = Date.now() const executionTimeInMsGltf = endTimeGltf - startTimeGltf @@ -304,7 +351,7 @@ async function geometryExtraction(model: IfcStepModel, fileNameNoExtension: stri // Create a memory view from the native vector const managedBuffer: Uint8Array = - IfcGeometryExtraction.getWasmModule(). + conwayModel.getWasmModule(). getUint8Array(gltfResult.buffers.get(uriIndex)) fs.writeFile(uri, managedBuffer, function(err) { @@ -319,8 +366,8 @@ async function geometryExtraction(model: IfcStepModel, fileNameNoExtension: stri const startTimeGltfDraco = Date.now() const gltfDracoResult = - IfcGeometryExtraction - .toGltf(fullGeometry.geometry, false, true, `${fileNameNoExtension}_test_draco`) + conwayModel + .toGltf(fullGeometry, false, true, `${fileNameNoExtension}_test_draco`) const endTimeGltfDraco = Date.now() const executionTimeInMsGltfDraco = endTimeGltfDraco - startTimeGltfDraco @@ -343,7 +390,7 @@ async function geometryExtraction(model: IfcStepModel, fileNameNoExtension: stri // Create a memory view from the native vector const managedBuffer: Uint8Array = - IfcGeometryExtraction.getWasmModule() + conwayModel.getWasmModule() .getUint8Array(gltfDracoResult.buffers.get(uriIndex)) fs.writeFile(uri, managedBuffer, function(err) { diff --git a/src/ifc/ifc_geometry_extraction.test.ts b/src/ifc/ifc_geometry_extraction.test.ts index ffe9ea53..a668cc41 100644 --- a/src/ifc/ifc_geometry_extraction.test.ts +++ b/src/ifc/ifc_geometry_extraction.test.ts @@ -4,27 +4,15 @@ import { ExtractResult, IfcGeometryExtraction } from './ifc_geometry_extraction' import { ParseResult } from '../step/parsing/step_parser' import IfcStepParser from './ifc_step_parser' import ParsingBuffer from '../parsing/parsing_buffer' +import { ConwayGeometry } from '../../dependencies/conway-geom/conway_geometry' -/** - * - */ -async function initializeGeometryExtractor() { - await IfcGeometryExtraction.create() - return IfcGeometryExtraction.isInitialized() -} +let conwayModel:IfcGeometryExtraction /** - * @return {boolean} indicating whether the wasm module is initialized. - */ -function isInitialized(): Boolean { - return IfcGeometryExtraction.isInitialized() -} - -/** - * @return {ExtractResult} indicating whether the geometry extraction was successful. + * */ -function extractGeometry(): ExtractResult { +async function initializeGeometryExtractor() { const parser = IfcStepParser.Instance const indexIfcBuffer: Buffer = fs.readFileSync('index.ifc') const bufferInput = new ParsingBuffer(indexIfcBuffer) @@ -39,23 +27,45 @@ function extractGeometry(): ExtractResult { if (model === void 0) { return ExtractResult.INCOMPLETE } + const conwayGeometry: ConwayGeometry = new ConwayGeometry() + const initializationStatus = await conwayGeometry.initialize() - return IfcGeometryExtraction.extractIFCGeometryData(model, true)[0] + if (!initializationStatus) { + return + } + + conwayModel = new IfcGeometryExtraction(conwayGeometry, model) + + return conwayModel.isInitialized() +} + +/** + * @return {boolean} indicating whether the wasm module is initialized. + */ +function isInitialized(): Boolean { + return conwayModel.isInitialized() +} + +/** + * @return {ExtractResult} indicating whether the geometry extraction was successful. + */ +function extractGeometry(): ExtractResult { + return conwayModel.extractIFCGeometryData(true)[0] } /** * @return {number} indicating number of meshes */ function getMeshSize(): Number { - return IfcGeometryExtraction.getMeshes().size + return conwayModel.getMeshes().size } /** * @return {boolean} indicating if the geometry extraction module is still initialized or not */ function destroy(): Boolean { - IfcGeometryExtraction.destroy() - return IfcGeometryExtraction.isInitialized() + conwayModel.destroy() + return conwayModel.isInitialized() } beforeAll(async () => { diff --git a/src/ifc/ifc_geometry_extraction.ts b/src/ifc/ifc_geometry_extraction.ts index 2a01c1d7..c766b771 100644 --- a/src/ifc/ifc_geometry_extraction.ts +++ b/src/ifc/ifc_geometry_extraction.ts @@ -1,14 +1,14 @@ -import { ConwayGeometry, ParamsPolygonalFaceSet, GeometryObject, ResultsGltf, IndexedPolygonalFace, ParamsAxis2Placement3D, ParamsLocalPlacement } +import { ConwayGeometry, ParamsPolygonalFaceSet, GeometryObject, + ResultsGltf, IndexedPolygonalFace, ParamsAxis2Placement3D } from '../../dependencies/conway-geom/conway_geometry' +import { CanonicalMesh, CanonicalMeshType } from '../core/canonical_mesh' import { - IfcArbitraryClosedProfileDef, IfcAxis2Placement3D, IfcBSplineCurveWithKnots, - IfcBooleanResult, IfcBoundaryCurve, IfcBuildingElementProxy, IfcCircle, - IfcCircleProfileDef, IfcCompositeCurve, IfcCompositeProfileDef, IfcEllipse, - IfcExtrudedAreaSolid, IfcGridPlacement, IfcIndexedPolyCurve, IfcIndexedPolygonalFaceWithVoids, - IfcIntersectionCurve, IfcLine, IfcLocalPlacement, IfcOffsetCurve2D, IfcOffsetCurve3D, - IfcOuterBoundaryCurve, IfcPcurve, IfcPolygonalFaceSet, IfcProduct, IfcProductDefinitionShape, - IfcRationalBSplineCurveWithKnots, IfcSeamCurve, IfcSurfaceCurve, IfcTrimmedCurve, + IfcAxis2Placement3D, IfcCartesianTransformationOperator3D, IfcGridPlacement, + IfcIndexedPolygonalFaceWithVoids, IfcLocalPlacement, IfcMappedItem, + IfcObjectPlacement, IfcOpeningElement, IfcOpeningStandardCase, + IfcPolygonalFaceSet, IfcProduct, IfcRepresentationItem, IfcSpace, } from './ifc4_gen' +import { IfcSceneBuilder } from './ifc_scene_builder' import IfcStepModel from './ifc_step_model' @@ -45,52 +45,38 @@ export interface ConwayMesh { */ export class IfcGeometryExtraction { - // Define the map - private static conwayGeomMap = new Map() - - // Map> - private static geometryMap: Map> = new Map() - - private static wasmModule: WasmModule + private geometryMap: Map = new Map() + private wasmModule: WasmModule + private scene:IfcSceneBuilder /** - * NOTE* Must be called before any other functions in this class + * + * @param conwayModel + * @param model */ - static async create(): Promise { - - const localIDMap: Map = new Map() - // Check if the map is empty - if (this.conwayGeomMap.size === 0) { - const temp: ConwayGeometry = new ConwayGeometry() - const modelId = await temp.initialize() - this.conwayGeomMap.set(modelId, temp) - this.geometryMap.set(modelId, localIDMap) - // set wasm module - this.wasmModule = temp.wasmModule - return modelId - } else { - // initialize new ConwayGeometry module passing in the wasm module - const temp: ConwayGeometry = new ConwayGeometry(this.wasmModule) - const modelId = await temp.initialize() - this.conwayGeomMap.set(modelId, temp) - this.geometryMap.set(modelId, localIDMap) - return modelId - } + constructor( + private readonly conwayModel: ConwayGeometry, + public readonly model: IfcStepModel ) { + console.log('WTF4') + this.scene = new IfcSceneBuilder(model, conwayModel) + + console.log(`wasmModule: ${ conwayModel.wasmModule}`) + this.wasmModule = conwayModel.wasmModule } /** * * @return { Map} - Map containing all geometry data that was extracted */ - static getMeshes(modelId: number = 0): Map { - return this.geometryMap.get(modelId)! + getMeshes(): Map { + return this.geometryMap } /** * * @return {WasmModule} - A handle to the loaded wasm module */ - static getWasmModule(): WasmModule { + getWasmModule(): WasmModule { return this.wasmModule } @@ -99,7 +85,7 @@ export class IfcGeometryExtraction { * @param initialSize number - initial size of the vector (optional) * @return {NativeVectorGlmVec3} - a native std::vector from the wasm module */ - static nativeVectorGlmVec3(initialSize?: number): NativeVectorGlmVec3 { + nativeVectorGlmVec3(initialSize?: number): NativeVectorGlmVec3 { const nativeVectorGlmVec3_ = new (this.wasmModule.glmVec3Array as NativeVectorGlmVec3)() if (initialSize) { @@ -115,7 +101,7 @@ export class IfcGeometryExtraction { * @param initialSize number - initial size of the vector (optional) * @return {NativeUintVector} - a native std::vector from the wasm module */ - static nativeUintVector(initialize?: number): NativeUintVector { + nativeUintVector(initialize?: number): NativeUintVector { const nativeUintVector_ = new (this.wasmModule.UintVector as NativeUintVector)() if (initialize) { @@ -131,7 +117,7 @@ export class IfcGeometryExtraction { * @param initialSize number - initial size of the vector (optional) * @return {NativeULongVector} - a native std::vector from the wasm module */ - static nativeULongVector(initialize?: number): NativeULongVector { + nativeULongVector(initialize?: number): NativeULongVector { const nativeULongVector_ = new (this.wasmModule.ULongVector as NativeULongVector)() if (initialize) { @@ -147,7 +133,7 @@ export class IfcGeometryExtraction { * @param initialSize number - initial size of the vector (optional) * @return {NativeVectorIndexedPolygonalFace} - a native object from the wasm module */ - static nativeIndexedPolygonalFaceVector(initialize?: number): NativeVectorIndexedPolygonalFace { + nativeIndexedPolygonalFaceVector(initialize?: number): NativeVectorIndexedPolygonalFace { const nativeVectorIndexedPolygonalFace = new (this.wasmModule.VectorIndexedPolygonalFace as NativeVectorIndexedPolygonalFace)() @@ -161,12 +147,11 @@ export class IfcGeometryExtraction { /** - * @param modelId - model ID * @return {boolean} indicating if the wasm module has been initialized */ - static isInitialized(modelId: number = 0): Boolean { - if (this.conwayGeomMap.get(modelId)) { - return this.conwayGeomMap.get(modelId)!.initialized + isInitialized(): Boolean { + if (this.conwayModel !== void 0) { + return this.conwayModel.initialized } return false @@ -176,11 +161,8 @@ export class IfcGeometryExtraction { * * @param geometry ConwayMesh to add to the ConwayMesh array */ - static addMesh(mesh: ConwayMesh, modelId: number = 0) { - - if (this.geometryMap.get(modelId)) { - this.geometryMap.get(modelId)!.set(mesh.localID, mesh) - } + addMesh(mesh: ConwayMesh) { + this.geometryMap.set(mesh.localID, mesh) } /** @@ -188,9 +170,9 @@ export class IfcGeometryExtraction { * @param geometry - GeometryObject to convert to OBJ * @return {string} - Obj string or blank string */ - static toObj(geometry: GeometryObject, modelId: number = 0): string { - if (this.conwayGeomMap.get(modelId)) { - return this.conwayGeomMap.get(modelId)!.toObj(geometry) + toObj(geometry: GeometryObject, modelId: number = 0): string { + if (this.conwayModel !== void 0) { + return this.conwayModel.toObj(geometry) } return '' @@ -204,12 +186,13 @@ export class IfcGeometryExtraction { * @param fileUri string - base filenames for GLTF / GLB files * @return {ResultsGltf} - Structure containing GLTF / GLB filenames + data vectors */ - static toGltf(geometry: GeometryObject, isGlb: boolean, + toGltf(geometry: GeometryObject, isGlb: boolean, outputDraco: boolean, fileUri: string, modelId: number = 0): ResultsGltf { const noResults: ResultsGltf = { success: false, bufferUris: undefined, buffers: undefined } noResults.success = false - if (this.conwayGeomMap.get(modelId)) { - return this.conwayGeomMap.get(modelId)!.toGltf(geometry, isGlb, outputDraco, fileUri) + if (this.conwayModel !== void 0) { + + return this.conwayModel.toGltf(geometry, isGlb, outputDraco, fileUri) } return noResults @@ -218,10 +201,10 @@ export class IfcGeometryExtraction { /** * Destroy geometry processor and deinitialize */ - static destroy(modelId: number = 0) { - if (this.conwayGeomMap.get(modelId)) { - this.conwayGeomMap.get(modelId)!.destroy() - this.conwayGeomMap.get(modelId)!.initialized = false + destroy(modelId: number = 0) { + if (this.conwayModel !== void 0) { + this.conwayModel.destroy() + this.conwayModel.initialized = false } } @@ -230,7 +213,7 @@ export class IfcGeometryExtraction { * @param arr - a 2D number array * @return {number} - total length of all 2D array elements */ - private static getTotalLength(arr: number[][]): number { + private getTotalLength(arr: number[][]): number { let totalLength = 0 for (const innerArray of arr) { totalLength += innerArray.length @@ -242,7 +225,7 @@ export class IfcGeometryExtraction { * * @param geometry - GeometryObject to print information from */ - static printGeometryInfo(geometry: GeometryObject) { + printGeometryInfo(geometry: GeometryObject) { const vertexDataPtr = geometry.getVertexData() const vertexDataSize = geometry.getVertexDataSize() const indexDataPtr = geometry.getIndexData() @@ -275,9 +258,8 @@ export class IfcGeometryExtraction { * @param modelId - current modelId * @return {ExtractResult} - Extraction status result */ - private static extractPolygonalFaceSet(entity: IfcPolygonalFaceSet, - polygonalFaceStartIndices: NativeULongVector, - modelId: number = 0): ExtractResult { + private extractPolygonalFaceSet(entity: IfcPolygonalFaceSet, + polygonalFaceStartIndices: NativeULongVector): ExtractResult { const result: ExtractResult = ExtractResult.COMPLETE // map points @@ -373,15 +355,17 @@ export class IfcGeometryExtraction { faces: polygonalFaceVector, } - const geometry: GeometryObject = this.conwayGeomMap.get(modelId)!.getGeometry(parameters) - const conwayMesh: ConwayMesh = { + const geometry: GeometryObject = this.conwayModel.getGeometry(parameters) + + const canonicalMesh:CanonicalMesh = { + type: CanonicalMeshType.BUFFER_GEOMETRY, geometry: geometry, localID: entity.localID, - transform: undefined, + model: this.model, } - // add mesh to the list of mesh objects returned by wasm module - this.addMesh(conwayMesh, modelId) + // add mesh to the list of mesh objects + this.model.geometry.add(canonicalMesh) // free allocated wasm vectors pointsArray.delete() @@ -404,8 +388,7 @@ export class IfcGeometryExtraction { * @param modelId - the modelId * @return {ExtractResult} - Extraction status result */ - private static extractPolygonalFaceSets(entities: IfcPolygonalFaceSet[], - modelId: number = 0): ExtractResult { + private extractPolygonalFaceSets(entities: IfcPolygonalFaceSet[]): ExtractResult { let result: ExtractResult = ExtractResult.COMPLETE let faceSetResult: ExtractResult = ExtractResult.INCOMPLETE @@ -416,7 +399,7 @@ export class IfcGeometryExtraction { for (const entity of entities) { - faceSetResult = this.extractPolygonalFaceSet(entity, polygonalFaceStartIndices, modelId) + faceSetResult = this.extractPolygonalFaceSet(entity, polygonalFaceStartIndices) if (faceSetResult !== ExtractResult.COMPLETE) { console.log(`Warning, face set express ID: ${entity.expressID} extraction incomplete.`) @@ -431,319 +414,203 @@ export class IfcGeometryExtraction { /** * - * @param model - Input IfcStepModel to extract geometry data from - * @param logTime boolean - print execution time (default no) - * @return {[ExtractResult, ConwayMesh[]]} - Enum indicating extraction result - * + Geometry array + * @param from */ - static extractIFCGeometryData(model: IfcStepModel, logTime: boolean = false, modelId: number = 0): - [ExtractResult, ConwayMesh[]] { - let result: ExtractResult = ExtractResult.INCOMPLETE + extractCartesianTransformOperator3D(from: IfcCartesianTransformationOperator3D) { - const startTime = Date.now() + // apply transform to scene + } + /** + * + * @param from + */ + extractMappedItem(from: IfcMappedItem) { - const products = model.types(IfcProduct) - const productEntities = Array.from(products) + const representationMap = from.MappingSource - for (const product of productEntities) { - if (product.ObjectPlacement instanceof IfcLocalPlacement) { - - const localGeometryIDs: number[] = [] - - if (product.Representation instanceof IfcProductDefinitionShape) { - for (const representation of product.Representation.Representations) { - for (const item of representation.Items) { - if (item instanceof IfcPolygonalFaceSet) { - localGeometryIDs.push(item.localID) - this.conwayGeomMap.get(modelId)!.graph.addEdge(product.localID, item.localID) - } else if (item instanceof IfcBooleanResult) { - let recursiveElement: any = item - this.conwayGeomMap.get(modelId)!.graph.addEdge(product.localID, item.localID) - while (recursiveElement.FirstOperand !== undefined) { - this.conwayGeomMap.get(modelId)!.graph - .addEdge(recursiveElement.localID, recursiveElement.FirstOperand.localID) - this.conwayGeomMap.get(modelId)!.graph - .addEdge(recursiveElement.localID, recursiveElement.SecondOperand.localID) - recursiveElement = recursiveElement.FirstOperand - } - - recursiveElement = item - - while (recursiveElement.SecondOperand !== undefined) { - this.conwayGeomMap.get(modelId)!.graph - .addEdge(recursiveElement.localID, recursiveElement.FirstOperand.localID) - this.conwayGeomMap.get(modelId)!.graph - .addEdge(recursiveElement.localID, recursiveElement.SecondOperand.localID) - recursiveElement = recursiveElement.SecondOperand - } - } - } - } + for (const representationItem of representationMap.MappedRepresentation.Items) { - // add to transform mapping - this.conwayGeomMap.get(modelId)!.transformMapping - .set(product.ObjectPlacement.localID, localGeometryIDs) - } else { - console.log('product.representation == null') - } + this.extractRepresentationItem(representationItem) + } + } + + /** + * + * @param from + */ + extractRepresentationItem(from: IfcRepresentationItem) { + + const foundGeometry = this.model.geometry.getByLocalID(from.localID) + + if (foundGeometry !== void 0) { + + this.scene.addGeometry(from.localID) + return + } - } else if (product.ObjectPlacement instanceof IfcGridPlacement) { - // TODO(nickcastel50): Handle IfcGridPlacement - console.log('TODO: Handle IfcGridPlacement') + if (from instanceof IfcPolygonalFaceSet) { + + // initialize new native indices array (free memory with delete()) + const polygonalFaceStartIndices: NativeULongVector = this.nativeULongVector(1) + + polygonalFaceStartIndices.set(0, 0) + + const faceSetResult: ExtractResult = + this.extractPolygonalFaceSet(from, polygonalFaceStartIndices) + + if (faceSetResult !== ExtractResult.COMPLETE) { + console.log(`Warning, face set express ID: ${from.expressID} extraction incomplete.`) } + polygonalFaceStartIndices.delete() + this.scene.addGeometry(from.localID) + + + } else if (from instanceof IfcMappedItem) { + + this.extractMappedItem(from) } - const sortedIfcElements = this.conwayGeomMap.get(modelId)! - .topologicalSort(this.conwayGeomMap.get(modelId)!.graph) as number[] - - // Extract IfcPolygonalFaceSet geometries - const polygonalFaceSets = model.types(IfcPolygonalFaceSet) - const entities = Array.from(polygonalFaceSets) - result = this.extractPolygonalFaceSets(entities, modelId) - - let polygonalFaceSetCount = 0 - let ifcBuildingElementProxyCount = 0 - let ifcBooleanResultCount = 0 - let ifcExtrudedAreaSolidCount = 0 - - let previousElementsCount = 0 - for (let elementIndex = sortedIfcElements.length - 1; elementIndex >= 0; elementIndex--) { - const element = model.getElementByLocalID(sortedIfcElements[elementIndex])! - // console.log("(" + element + "):" + element.localID + " - " + EntityTypesIfc[element.type]) - if (element instanceof IfcPolygonalFaceSet) { - polygonalFaceSetCount++ - } else if (element instanceof IfcBuildingElementProxy) { - ifcBuildingElementProxyCount++ - - const relativeTransformArray: any[] = [] - // map the transform from BuildingElementProxy to our geometry - let recursiveElement: any = element.ObjectPlacement - - while (recursiveElement instanceof IfcLocalPlacement) { - const relativePlacement = recursiveElement.RelativePlacement - let normalizeZ: boolean = false - let normalizeX: boolean = false - if (relativePlacement instanceof IfcAxis2Placement3D) { - if (relativePlacement.Axis !== null) { - normalizeZ = true - } - - if (relativePlacement.RefDirection !== null) { - normalizeX = true - } - - const position = { - x: relativePlacement.Location.Coordinates[0], - y: relativePlacement.Location.Coordinates[1], - z: relativePlacement.Location.Coordinates[2], - } - - const zAxisRef = { - x: relativePlacement.Axis?.DirectionRatios[0], - y: relativePlacement.Axis?.DirectionRatios[1], - z: relativePlacement.Axis?.DirectionRatios[2], - } - - const xAxisRef = { - x: relativePlacement.RefDirection?.DirectionRatios[0], - y: relativePlacement.RefDirection?.DirectionRatios[1], - z: relativePlacement.RefDirection?.DirectionRatios[2], - } - - const axis2Placement3DParameters: ParamsAxis2Placement3D = { - position: position, - zAxisRef: zAxisRef, - xAxisRef: xAxisRef, - normalizeZ: normalizeZ, - normalizeX: normalizeX, - } - - const axis2PlacementTransform = this.conwayGeomMap.get(modelId)! - .getAxis2Placement3D(axis2Placement3DParameters) - // use axis2PlacementTransform.getValues() to get transforms in TypeScript - - relativeTransformArray.push(axis2PlacementTransform) - - recursiveElement = recursiveElement.PlacementRelTo - } + } + + /** + * + * @param from + */ + extractPlacement(from: IfcObjectPlacement) { + + const result = this.scene.getTransform(from.localID) + + if (result !== void 0) { + + this.scene.pushTransform(result) + + return + } + + if (from instanceof IfcLocalPlacement) { + + const relativeTo = from.PlacementRelTo + + if (relativeTo !== null) { + + this.extractPlacement(relativeTo) + } + + const relativePlacement = from.RelativePlacement + + let normalizeZ: boolean = false + let normalizeX: boolean = false + + if (relativePlacement instanceof IfcAxis2Placement3D) { + + if (relativePlacement.Axis !== null) { + normalizeZ = true } - // now we should have all our relative transform matrices, create the absolute transforms - const absoluteTransformArray: any[] = [] - for (let index = relativeTransformArray.length - 1; index >= 0; index--) { - const currentTransform = relativeTransformArray[index] - - if (index === relativeTransformArray.length - 1) { - const localPlacementParameters: ParamsLocalPlacement = { - useRelPlacement: false, - axis2Placement: currentTransform, - // TODO(nickcastel50): figure out how to pass null - relPlacement: currentTransform, - } - - const localPlacement = this.conwayGeomMap.get(modelId)! - .getLocalPlacement(localPlacementParameters) - absoluteTransformArray.push(localPlacement) - } else { - const localPlacementParameters: ParamsLocalPlacement = { - useRelPlacement: true, - axis2Placement: currentTransform, - relPlacement: absoluteTransformArray[absoluteTransformArray.length - 1], - } - - const localPlacement = this.conwayGeomMap.get(modelId)! - .getLocalPlacement(localPlacementParameters) - absoluteTransformArray.push(localPlacement) - } + if (relativePlacement.RefDirection !== null) { + normalizeX = true } - // now we have the absolute transforms, lets assign them to our ConwayMeshes - const absoluteTransform = absoluteTransformArray[absoluteTransformArray.length - 1] + const position = { + x: relativePlacement.Location.Coordinates[0], + y: relativePlacement.Location.Coordinates[1], + z: relativePlacement.Location.Coordinates[2], + } - for (let currentIndex = 1; currentIndex < previousElementsCount + 1; currentIndex++) { - const currentElement = model - .getElementByLocalID(sortedIfcElements[elementIndex + currentIndex])! + const zAxisRef = { + x: relativePlacement.Axis?.DirectionRatios[0], + y: relativePlacement.Axis?.DirectionRatios[1], + z: relativePlacement.Axis?.DirectionRatios[2], + } - if (currentElement instanceof IfcPolygonalFaceSet) { - // use localID as map into ConwayMeshes - const currentMesh = this.geometryMap.get(modelId)!.get(currentElement.localID) + const xAxisRef = { + x: relativePlacement.RefDirection?.DirectionRatios[0], + y: relativePlacement.RefDirection?.DirectionRatios[1], + z: relativePlacement.RefDirection?.DirectionRatios[2], + } - if (currentMesh !== undefined) { - currentMesh.transform = absoluteTransform - } - } + const axis2Placement3DParameters: ParamsAxis2Placement3D = { + position: position, + zAxisRef: zAxisRef, + xAxisRef: xAxisRef, + normalizeZ: normalizeZ, + normalizeX: normalizeX, } - } else if (element instanceof IfcBooleanResult) { - // TODO(nickcastel50): Implement IfcBooleanResult - ifcBooleanResultCount++ - console.log('Found IfcBooleanResult:') - } else if (element instanceof IfcExtrudedAreaSolid) { - ifcExtrudedAreaSolidCount++ - // TODO(nickcastel50): Implement IfcExtrudedAreaSolid + Curves - console.log('Found IfcExtrudedAreaSolid') - - if (element.Position !== null) { - let normalizeZ: boolean = false - let normalizeX: boolean = false - if (element.Position.Axis !== null) { - normalizeZ = true - } + const axis2PlacementTransform = this.conwayModel + .getAxis2Placement3D(axis2Placement3DParameters) - if (element.Position.RefDirection !== null) { - normalizeX = true - } + this.scene.addTransform( + from.localID, + axis2PlacementTransform.getValues(), + axis2PlacementTransform) + } - const position = { - x: element.Position.Location.Coordinates[0], - y: element.Position.Location.Coordinates[1], - z: element.Position.Location.Coordinates[2], - } + } else if (from instanceof IfcGridPlacement) { + // TODO(nickcastel50) Implement IfcGridPlacement + console.log('IfcGridPlacement: unimplemented.') + } + } - const zAxisRef = { - x: element.Position.Axis?.DirectionRatios[0], - y: element.Position.Axis?.DirectionRatios[1], - z: element.Position.Axis?.DirectionRatios[2], - } + /** + * + * @param model - Input IfcStepModel to extract geometry data from + * @param logTime boolean - print execution time (default no) + * @return {[ExtractResult, IfcSceneBuilder]} - Enum indicating extraction result + * + Geometry array + */ + extractIFCGeometryData(logTime: boolean = false): + [ExtractResult, IfcSceneBuilder] { + let result: ExtractResult = ExtractResult.INCOMPLETE - const xAxisRef = { - x: element.Position.RefDirection?.DirectionRatios[0], - y: element.Position.RefDirection?.DirectionRatios[1], - z: element.Position.RefDirection?.DirectionRatios[2], - } + const startTime = Date.now() - const axis2Placement3DParameters: ParamsAxis2Placement3D = { - position: position, - zAxisRef: zAxisRef, - xAxisRef: xAxisRef, - normalizeZ: normalizeZ, - normalizeX: normalizeX, - } - const axis2PlacementTransform = this.conwayGeomMap.get(modelId)! - .getAxis2Placement3D(axis2Placement3DParameters) - axis2PlacementTransform.getValues() - // scaffolding for profile extraction - if (element.SweptArea instanceof IfcArbitraryClosedProfileDef) { - if (element.SweptArea.OuterCurve instanceof IfcLine) { - ; - } else if (element.SweptArea.OuterCurve instanceof IfcOffsetCurve2D) { - ; - } else if (element.SweptArea.OuterCurve instanceof IfcOffsetCurve3D) { - ; - } else if (element.SweptArea.OuterCurve instanceof IfcPcurve) { - ; - } else if (element.SweptArea.OuterCurve instanceof IfcSurfaceCurve) { - ; - } else if (element.SweptArea.OuterCurve instanceof IfcCompositeCurve) { - ; - } else if (element.SweptArea.OuterCurve instanceof IfcIndexedPolyCurve) { - ; - } else if (element.SweptArea.OuterCurve instanceof IfcTrimmedCurve) { - ; - } else if (element.SweptArea.OuterCurve instanceof IfcBSplineCurveWithKnots) { - ; - } else if (element.SweptArea.OuterCurve instanceof IfcRationalBSplineCurveWithKnots) { - ; - } else if (element.SweptArea.OuterCurve instanceof IfcBoundaryCurve) { - ; - } else if (element.SweptArea.OuterCurve instanceof IfcOuterBoundaryCurve) { - ; - } else if (element.SweptArea.OuterCurve instanceof IfcCircle) { - ; - } else if (element.SweptArea.OuterCurve instanceof IfcEllipse) { - ; - } else if (element.SweptArea.OuterCurve instanceof IfcIntersectionCurve) { - ; - } else if (element.SweptArea.OuterCurve instanceof IfcSeamCurve) { - ; - } - } else if (element.SweptArea instanceof IfcCompositeProfileDef) { - ; - } else if (element.SweptArea instanceof IfcCircleProfileDef) { - ; - } - } else { - console.log('Element.position === null!') - } + const products = this.model.types(IfcProduct) + const productEntities = Array.from(products) + + for (const product of productEntities) { + + this.scene.clearParentStack() + + if ( product instanceof IfcOpeningElement || + product instanceof IfcSpace || + product instanceof IfcOpeningStandardCase ) { + continue } - if (element instanceof IfcBuildingElementProxy) { - previousElementsCount = 0 - } else { - previousElementsCount++ + const objectPlacement = product.ObjectPlacement + + if (objectPlacement !== null) { + + this.extractPlacement(objectPlacement) } - } + const representations = product.Representation - console.log(`Number of polygonalFaceSet: ${polygonalFaceSetCount}`) - console.log(`Number of ifcBuildingElementProxys: ${ifcBuildingElementProxyCount}`) - console.log(`Number of ifcBooleanResult: ${ifcBooleanResultCount}`) - console.log(`Number of ifcExtrudedAreaSolid: ${ifcExtrudedAreaSolidCount}`) + if (representations !== null) { - // TODO(nickcastel50): Not sure when we actually want to apply these transforms - // apply transformations to the geometry - const meshes = Array.from(this.getMeshes(modelId).values()) + for (const representation of representations.Representations) { - for (let index = 0; index < meshes.length; index++) { - if (meshes[index].transform !== undefined) { - meshes[index].geometry.applyTransform(meshes[index].transform) - } else { - meshes.splice(index--, 1) - console.log(`mesh.transform undefined at localID: - ${meshes[index].localID} removing from mesh array...`) + for (const item of representation.Items) { + + this.extractRepresentationItem(item) + } + } } } + result = ExtractResult.COMPLETE + const endTime = Date.now() const executionTimeInMs = endTime - startTime - if (logTime) { console.log(`Geometry Extraction took ${executionTimeInMs} milliseconds to execute.`) } - return [result, meshes] + + return [result, this.scene] } } diff --git a/src/ifc/ifc_model_geometry.ts b/src/ifc/ifc_model_geometry.ts new file mode 100644 index 00000000..714cc9c1 --- /dev/null +++ b/src/ifc/ifc_model_geometry.ts @@ -0,0 +1,44 @@ +import { CanonicalMesh } from '../core/canonical_mesh' +import {ModelGeometry} from '../core/model' + + +/** + * + */ +export class IfcModelGeometry implements ModelGeometry { + + private readonly meshes_ = new Map< number, CanonicalMesh >() + + /** + * @return {number} + */ + get length(): number { + return this.meshes_.size + } + + /** + * + * @param mesh + */ + public add( mesh: CanonicalMesh ) { + this.meshes_.set( mesh.localID, mesh ) + } + + /** + * + * @param localID + * @return {CanonicalMesh | undefined} + */ + public getByLocalID(localID: number): CanonicalMesh | undefined { + return this.meshes_.get(localID) + } + + /** + * + * @return {IterableIterator} + */ + public [Symbol.iterator](): IterableIterator { + return this.meshes_.values() + } + +} diff --git a/src/ifc/ifc_property_extraction.test.ts b/src/ifc/ifc_property_extraction.test.ts new file mode 100644 index 00000000..45ce4738 --- /dev/null +++ b/src/ifc/ifc_property_extraction.test.ts @@ -0,0 +1,39 @@ +import fs from 'fs' +import { describe, expect, test } from '@jest/globals' +import { IfcPropertyExtraction, PropertyExtractResult } from './ifc_property_extraction' +import { ParseResult } from '../step/parsing/step_parser' +import IfcStepParser from './ifc_step_parser' +import ParsingBuffer from '../parsing/parsing_buffer' + + +/** + * @return {PropertyExtractResult} indicating whether the IFC properties extraction was successful. + */ +function parseProperties(): PropertyExtractResult { + const parser = IfcStepParser.Instance + const indexIfcBuffer: Buffer = fs.readFileSync('index.ifc') + const bufferInput = new ParsingBuffer(indexIfcBuffer) + const result0 = parser.parseHeader(bufferInput)[1] + + if (result0 !== ParseResult.COMPLETE) { + return PropertyExtractResult.INCOMPLETE + } + + const [, model] = parser.parseDataToModel(bufferInput) + + if (model === void 0) { + return PropertyExtractResult.INCOMPLETE + } + + return IfcPropertyExtraction.extractIFCProperties(model, true) +} + + +describe('Ifc Properties Extraction', () => { + + test('parseProperties()', () => { + + expect(parseProperties()).toBe(PropertyExtractResult.COMPLETE) + + }) +}) diff --git a/src/ifc/ifc_property_extraction.ts b/src/ifc/ifc_property_extraction.ts new file mode 100644 index 00000000..dfc3da02 --- /dev/null +++ b/src/ifc/ifc_property_extraction.ts @@ -0,0 +1,205 @@ +import { + IfcComplexProperty, + IfcProperty, + IfcPropertyBoundedValue, + IfcPropertyEnumeratedValue, + IfcPropertyListValue, + IfcPropertyReferenceValue, + IfcPropertySet, + IfcPropertySingleValue, + IfcPropertyTableValue, +} from './ifc4_gen' +import IfcStepModel from './ifc_step_model' + + +/** + * + */ +/* eslint-disable no-shadow, no-unused-vars, no-magic-numbers */ +// -- eslint doesn't understand enums properly. +export enum PropertyExtractResult { + + COMPLETE = 0, + INCOMPLETE = 1, + SYNTAX_ERROR = 2, + MISSING_TYPE = 3, + INVALID_STEP = 4 +} + +/** + * Handles IFC Property extraction from a populated IfcStepModel + */ +export class IfcPropertyExtraction { + + /** + * + * @param model - IfcStepModel to extract properties from + * @param logTime - optional parameter to print execution time + * @return {PropertyExtractResult} indicating the status of the parsing + */ + static extractIFCProperties(model: IfcStepModel, logTime: boolean = false): + PropertyExtractResult { + let result: PropertyExtractResult = PropertyExtractResult.COMPLETE + + const startTime = Date.now() + + // extract all IfcPropertySets from the model + const propertySets = model.types(IfcPropertySet) + + + for (const propertySet of propertySets) { + console.log('\n\nProperty Set: %s', propertySet.Name) + + // if PropertySet has properties, print them + const tempResult = this.processIfcProperties(propertySet.HasProperties) + + if (result !== PropertyExtractResult.COMPLETE) { + if (tempResult !== PropertyExtractResult.COMPLETE) { + result = tempResult + } + } else { + result = tempResult + } + } + + const endTime = Date.now() + const executionTimeInMs = endTime - startTime + + if (logTime) { + console.log(`Property Extraction took ${executionTimeInMs} milliseconds to execute.`) + } + + return result + } + + /** + * + * @param properties + * @return {PropertyExtractResult} + */ + static processIfcProperties(properties: IfcProperty[]): PropertyExtractResult { + let result: PropertyExtractResult = PropertyExtractResult.COMPLETE + for (const property of properties) { + switch (property.constructor) { + case IfcComplexProperty: + this.processIfcComplexProperty(property as IfcComplexProperty) + break + case IfcPropertyBoundedValue: + this.processIfcPropertyBoundedValue(property as IfcPropertyBoundedValue) + break + case IfcPropertyEnumeratedValue: + this.processIfcPropertyEnumeratedValue(property as IfcPropertyEnumeratedValue) + break + case IfcPropertyListValue: + this.processIfcPropertyListValue(property as IfcPropertyListValue) + break + case IfcPropertyReferenceValue: + this.processIfcPropertyReferenceValue(property as IfcPropertyReferenceValue) + break + case IfcPropertySingleValue: + this.processIfcPropertySingleValue(property as IfcPropertySingleValue) + break + case IfcPropertyTableValue: + this.processIfcPropertyTableValue(property as IfcPropertyTableValue) + break + default: + console.log('Invalid property type.') + result = PropertyExtractResult.INCOMPLETE + break + } + } + + return result + } + + /** + * + * @param property + */ + static processIfcComplexProperty(property: IfcComplexProperty) { + console.log('Name: %s\nUsage Name: %s\nDescription: %s\nProperties Count: ', + property.Name, + property.UsageName, + property.Description, + property.HasProperties.length) + + this.processIfcProperties(property.HasProperties) + } + + /** + * + * @param property + */ + static processIfcPropertyBoundedValue(property: IfcPropertyBoundedValue) { + console.log('Name: %s\nDescription: %s\nUnit: %s\nLower Bound: %s, ' + + 'Upper Bound: %s, Set Point: %s', + property.Name, + property.Description, + property.Unit, + property.LowerBoundValue?.Value, + property.UpperBoundValue?.Value, + property.SetPointValue?.Value) + } + + /** + * + * @param property + */ + static processIfcPropertyEnumeratedValue(property: IfcPropertyEnumeratedValue) { + console.log('Name: %s\nDescription: %s\nEnumerationReference: %s\nEnumerationValues: %s', + property.Name, + property.Description, + property.EnumerationReference, + property.EnumerationValues) + } + + /** + * + * @param property + */ + static processIfcPropertyListValue(property: IfcPropertyListValue) { + console.log('Name: %s\nDescription: %s\nUnit: %s\nListValues: %s', + property.Name, property.Description, property.Unit, property.ListValues) + } + + /** + * + * @param property + */ + static processIfcPropertyReferenceValue(property: IfcPropertyReferenceValue) { + console.log('Name: %s\nDescription: %s\nUsageName: %s\nPropertyReference: %s', + property.Name, + property.Description, + property.UsageName, + property.PropertyReference) + } + + /** + * + * @param property + */ + static processIfcPropertySingleValue(property: IfcPropertySingleValue) { + console.log('Name: %s\nDescription: %s\nUnit: %s\nNominalValue: %s', + property.Name, + property.Description, + property.Unit, + property.NominalValue?.Value) + } + + /** + * + * @param property + */ + static processIfcPropertyTableValue(property: IfcPropertyTableValue) { + console.log('Name: %s\nDescription: %s\nDefinedUnit: %s\nCurveInterpolation: %s' + + '\nDefinedValues: %s\nDefiningUnit: %s\nDefiningValues: %s\nExpression: %s', + property.Name, + property.Description, + property.DefinedUnit, + property.CurveInterpolation?.toString, + property.DefinedValues, + property.DefiningUnit, + property.DefiningValues, + property.Expression) + } +} diff --git a/src/ifc/ifc_scene_builder.ts b/src/ifc/ifc_scene_builder.ts new file mode 100644 index 00000000..725f7c53 --- /dev/null +++ b/src/ifc/ifc_scene_builder.ts @@ -0,0 +1,293 @@ +import { ConwayGeometry, ParamsLocalPlacement } from + '../../dependencies/conway-geom/conway_geometry' +import { CanonicalMesh } from '../core/canonical_mesh' +import { Model } from '../core/model' +import { Scene } from '../core/scene' +import { + SceneNodeModelType, + SceneNodeGeometry, + SceneNodeTransform, +} + from '../core/scene_node' +import IfcStepModel from './ifc_step_model' + + +export type IfcNativeTransform = { getValues(): Readonly } + +/** + * + */ +export class IfcSceneTransform implements SceneNodeTransform { + + readonly type = SceneNodeModelType.TRANSFORM + + + /* eslint-disable no-useless-constructor, no-empty-function */ + /** + * + * @param model + * @param transform + * @param absoluteTransform + * @param localID + * @param index + * @param nativeTransform + * @param absoluteNativeTransform + * @param parentIndex + */ + constructor( + public readonly model: Model, + public readonly transform: ReadonlyArray, + public readonly absoluteTransform: ReadonlyArray, + public readonly localID: number, + public readonly index: number, + public readonly nativeTransform: IfcNativeTransform, + public readonly absoluteNativeTransform: IfcNativeTransform, + public readonly parentIndex?: number) { } + /* eslint-enable no-useless-constructor, no-empty-function */ + public children: number[] = [] +} + +/** + * + */ +export class IfcSceneGeometry implements SceneNodeGeometry { + + readonly type = SceneNodeModelType.GEOMETRY + + /* eslint-disable no-useless-constructor, no-empty-function */ + /** + * + * @param model + * @param localID + * @param index + * @param parentIndex + */ + constructor( + public readonly model: Model, + public readonly localID: number, + public readonly index: number, + public readonly parentIndex?: number) { } + /* eslint-enable no-useless-constructor, no-empty-function */ +} + +export type IfcSceneNode = IfcSceneTransform | IfcSceneGeometry + +/** + * + */ +export class IfcSceneBuilder implements Scene { + + public roots: number[] = [] + + private scene_: IfcSceneNode[] = [] + private sceneLocalIdMap_ = new Map() + + private sceneStack_: IfcSceneTransform[] = [] + private currentParent_?: IfcSceneTransform + + /* eslint-disable no-useless-constructor, no-empty-function */ + /** + * + * @param model + * @param conwayGeometry + */ + public constructor( + public readonly model: IfcStepModel, + public readonly conwayGeometry: ConwayGeometry) { + + } + /* eslint-enable no-useless-constructor, no-empty-function */ + + /** + * + * @param nodeIndex + * @return {IfcSceneNode | undefined} + */ + public getByNodeIndex(nodeIndex: number): IfcSceneNode | undefined { + return this.scene_[nodeIndex] + } + + /** + * + * @param localID + * @return {IfcSceneNode | undefined} + */ + private get(localID: number): IfcSceneNode | undefined { + + const sceneID = this.sceneLocalIdMap_.get(localID) + + return sceneID !== void 0 ? this.scene_[sceneID] : void 0 + } + + /** + * + */ + public clearParentStack(): void { + + this.sceneStack_.length = 0 + + delete this.currentParent_ + } + + /** + * + * @param localID + * @return {IfcSceneTransform | undefined} + */ + public getTransform(localID: number): IfcSceneTransform | undefined { + + const result = this.get(localID) + + if (result instanceof IfcSceneTransform) { + + return result + } + + return void 0 + } + + /** + * @yields + * @param walkTemporary + */ + public* walk(walkTemporary: boolean = false): + IterableIterator<[readonly number[] | undefined, + IfcNativeTransform | undefined, CanonicalMesh]> { + + for (const node of this.scene_) { + + if (node instanceof IfcSceneGeometry) { + + const parentIndex = node.parentIndex + + const geometry = node.model.geometry?.getByLocalID(node.localID) + + if (geometry === void 0) { + continue + } + + let parentNode: IfcSceneTransform | undefined + + if (parentIndex !== void 0) { + parentNode = this.scene_[parentIndex] as IfcSceneTransform + } + + yield [ + parentNode?.absoluteTransform, + parentNode?.absoluteNativeTransform, + geometry, + ] + } + } + } + + /** + * + */ + public popTransform(): void { + + this.currentParent_ = this.sceneStack_.pop() + } + + /** + * + * @param transform + */ + public pushTransform(transform: IfcSceneTransform) { + + if (this.currentParent_ !== void 0) { + this.sceneStack_.push(this.currentParent_) + } + + this.currentParent_ = transform + } + + /** + * + * @param localID + * @return {IfcSceneGeometry} + */ + public addGeometry(localID: number): IfcSceneGeometry { + + const nodeIndex = this.scene_.length + + let parentIndex: number | undefined + + if (this.currentParent_ !== void 0) { + + parentIndex = this.currentParent_.index + this.currentParent_.children.push(nodeIndex) + + } else { + + this.roots.push(nodeIndex) + } + + const result = new IfcSceneGeometry(this.model, localID, nodeIndex, parentIndex) + + this.scene_.push(result) + + return result + } + + /** + * + * @param localID + * @param transform + * @param nativeTransform + * @return {IfcSceneTransform} + */ + public addTransform( + localID: number, + transform: ReadonlyArray, + nativeTransform: IfcNativeTransform): IfcSceneTransform { + + if (this.sceneLocalIdMap_.has(localID)) { + throw Error('Scene already has transform node') + } + + const nodeIndex = this.scene_.length + let parentIndex: number | undefined + + let absoluteNativeTransform: IfcNativeTransform + + if (this.currentParent_ !== void 0) { + + const localPlacementParameters: ParamsLocalPlacement = { + useRelPlacement: true, + axis2Placement: nativeTransform, + relPlacement: this.currentParent_.absoluteNativeTransform, + } + + absoluteNativeTransform = this.conwayGeometry + .getLocalPlacement(localPlacementParameters) + + parentIndex = this.currentParent_.index + this.currentParent_.children.push(nodeIndex) + + } else { + + absoluteNativeTransform = nativeTransform + this.roots.push(nodeIndex) + } + + const result = + new IfcSceneTransform( + this.model, + transform, + absoluteNativeTransform.getValues(), + localID, + nodeIndex, + nativeTransform, + absoluteNativeTransform, + parentIndex) + + this.scene_.push(result) + + this.sceneLocalIdMap_.set(localID, nodeIndex) + + this.pushTransform(result) + + return result + } +} diff --git a/src/ifc/ifc_step_model.test.ts b/src/ifc/ifc_step_model.test.ts index e7f11bb2..0bbeaf69 100644 --- a/src/ifc/ifc_step_model.test.ts +++ b/src/ifc/ifc_step_model.test.ts @@ -98,8 +98,8 @@ const PROPERTY_SINGLE_VALUE_ID = 181096 /** * Test extracting an invalid unicode endpoint with graceful failure. - * - * @returns {boolean} True of the test succeeds + * + * @return {boolean} True of the test succeeds */ function extractInvalidUnicodePoint() { const bufferInput = new ParsingBuffer( ifcUFCCodePointStringBuffer ) diff --git a/src/ifc/ifc_step_model.ts b/src/ifc/ifc_step_model.ts index 2b7ebe0e..bb075e19 100644 --- a/src/ifc/ifc_step_model.ts +++ b/src/ifc/ifc_step_model.ts @@ -4,6 +4,7 @@ import SchemaIfc from './ifc4_gen/schema_ifc.gen' import {StepIndexEntry} from '../step/parsing/step_parser' import {StepTypeIndexer} from '../step/indexing/step_type_indexer' import {MultiIndexSet} from '../indexing/multi_index_set' +import { IfcModelGeometry } from './ifc_model_geometry' const indexerInstance = new StepTypeIndexer< EntityTypesIfc >( EntityTypesIfcCount ) @@ -14,6 +15,8 @@ const indexerInstance = new StepTypeIndexer< EntityTypesIfc >( EntityTypesIfcCou export default class IfcStepModel extends StepModelBase< EntityTypesIfc > { public readonly typeIndex: MultiIndexSet< EntityTypesIfc > + public readonly geometry = new IfcModelGeometry() + /** * Construct this model given a buffer containing the data and the parsed data index on that, * adding the typeIndex on top of that. diff --git a/src/step/step_model_base.ts b/src/step/step_model_base.ts index 6c962816..ee760f46 100644 --- a/src/step/step_model_base.ts +++ b/src/step/step_model_base.ts @@ -7,6 +7,7 @@ import { IIndexSetCursor } from '../core/i_index_set_cursor' import { extractOneHotLow } from '../indexing/bit_operations' import { MultiIndexSet } from '../indexing/multi_index_set' import { StepEntityConstructorAbstract } from './step_entity_constructor' +import { Model } from '../core/model' /** * The base for models parsed from STEP. @@ -14,7 +15,7 @@ import { StepEntityConstructorAbstract } from './step_entity_constructor' export default abstract class StepModelBase< EntityTypeIDs extends number, BaseEntity extends StepEntityBase = StepEntityBase > -implements Iterable { +implements Iterable, Model { public readonly abstract typeIndex: MultiIndexSet; private readonly vtableBuilder_: StepVtableBuilder = new StepVtableBuilder()