Skip to content
This repository has been archived by the owner on Jul 31, 2022. It is now read-only.

Commit

Permalink
Support property sets in XKT and metadata #31
Browse files Browse the repository at this point in the history
  • Loading branch information
xeolabs committed Aug 19, 2021
1 parent 7dcfa14 commit c6931be
Show file tree
Hide file tree
Showing 11 changed files with 299 additions and 252 deletions.
126 changes: 55 additions & 71 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

Use **xeokit-xkt-utils** to:

* Convert 3D BIM and AEC models into XKT files for super fast loading into [xeokit](https://xeokit.io)
* Convert BIM and AEC models into XKT files for super fast loading into [xeokit](https://xeokit.io)
* Generate XKT files with JavaScript
<BR><BR>

Expand All @@ -28,7 +28,6 @@ Use **xeokit-xkt-utils** to:
+ [Converting an IFC file into an XKT file on the command line](#converting-an-ifc-file-into-an-xkt-file-on-the-command-line)
+ [Converting an IFC file into an XKT file in Node.js](#converting-an-ifc-file-into-an-xkt-file-in-nodejs)
+ [Converting IFC file data into XKT data in Node.js](#converting-ifc-file-data-into-xkt-data-in-nodejs)
+ [Converting an IFC file into an XKT file, with all element properties](#converting-an-ifc-file-into-an-xkt-file--with-all-element-properties)
- [Using ````XKTModel````](#using-----xktmodel----)
+ [Programmatically Building an XKT File](#programmatically-building-an-xkt-file)
+ [Serializing the XKTModel to an ArrayBuffer](#serializing-the-xktmodel-to-an-arraybuffer)
Expand Down Expand Up @@ -127,7 +126,6 @@ Options:
-f, --format [string] source file format (optional); supported formats are gltf, ifc, laz, las, pcd, ply, stl and cityjson
-m, --metamodel [file] path to source metamodel JSON file (optional)
-o, --output [file] path to target .xkt file; creates directories on path automatically if not existing
-p, --properties [file] path to target directory for object property files; creates directories on path automatically if not existing
-l, --log enable logging
-h, --help output usage information
````
Expand Down Expand Up @@ -224,69 +222,6 @@ convert2xkt({
});
````

### Converting an IFC file into an XKT file, with all element properties

We'll convert our IFC file as before, but this time we'll supply a ````outputObjectProperties```` callback, which will
collect each IFC element's property set.
Via that callback, we'll save each property set to a JSON file.

We could use this feature to store property sets in our own data store.

````javascript
const convert2xkt = require("@xeokit/xeokit-xkt-utils/dist/convert2xkt.cjs.js");
const fs = require('fs');

convert2xkt({
source: "rme_advanced_sample_project.ifc",
output: "rme_advanced_sample_project.ifc.xkt",
outputObjectProperties: async function (objectId, props) {
await fs.writeFileSync(`${objectId}.json`, JSON.stringify(props, null, "\t"));
}
}).then(() => {
console.log("Converted.");
}, (errMsg) => {
console.error("Conversion failed: " + errMsg)
});
````

The output files would be something like:

````bash
rme_advanced_sample_project.ifc.xkt

06uoIsbYr35x9JXU7VZ77u.json
09g7Eo3WDEihdnsYS1YDoI.json
0BTBFw6f90Nfh9rP1dl_39.json
0BTBFw6f90Nfh9rP1dl_3A.json
...
````

Each IFC element's property set file would look something like:
````json
{
"id": "0kF45Qs8L9PAM9kmb1lT2Z",
"type": "IfcFooting",
"name": "Wall Foundation:Bearing Footing - 900 x 300:186656",
"parent": "1xS3BCk291UvhgP2dvNsgp"
}
````
### Converting a glTF file and IFC JSON metadata file into an XKT file
If we have a glTF file and IFC JSON file that were created using
our [standard open source tools](https://www.notion.so/xeokit/Viewing-an-IFC-Model-c373e48bc4094ff5b6e5c5700ff580ee),
then we can use ````convert2xkt```` to convert those into an XKT file:
````javascript
await convert2xkt({
source: "duplex.gltf",
metaModelSource: "metamodel.json",
output: "duplex.xkt"
});
````
# Using ````XKTModel````

````XKTModel```` is a JavaScript class that represents the contents of an XKT file in memory.
Expand Down Expand Up @@ -330,6 +265,50 @@ const xktModel = new XKTModel();

// Create metamodel - this part is optional

// Create property sets to hold info about the model

xktModel.createPropertySet({
propertySetId: "tableTopPropSet",
propertySetType: "Default",
propertySetName: "Table Top",
properties: [
{
id: "tableTopMaterial",
type: "Default",
name: "Table top material",
value: "Marble"
},
{
id: "tableTopDimensions",
type: "Default",
name: "Table top dimensions",
value: "90x90x3 cm"
}
]
});

xktModel.createPropertySet({
propertySetId: "tableLegPropSet",
propertySetType: "Default",
propertySetName: "Table Leg",
properties: [
{
id: "tableLegMaterial",
type: "Default",
name: "Table leg material",
value: "Pine"
},
{
id: "tableLegDimensions",
type: "Default",
name: "Table leg dimensions",
value: "5x5x50 cm"
}
]
});

// Create a hierarchy of metaobjects to describe the structure of the model

xktModel.createMetaObject({ // Root XKTMetaObject, has no XKTEntity
metaObjectId: "table",
metaObjectName: "The Table",
Expand All @@ -340,35 +319,40 @@ xktModel.createMetaObject({
metaObjectId: "redLeg",
metaObjectName: "Red Table Leg",
metaObjectType: "furniturePart",
parentMetaObjectId: "table"
parentMetaObjectId: "table",
propertySetId: "tableLegPropSet"
});

xktModel.createMetaObject({
metaObjectId: "greenLeg",
metaObjectName: "Green Table Leg",
metaObjectType: "furniturePart",
parentMetaObjectId: "table"
parentMetaObjectId: "table",
propertySetId: "tableLegPropSet"
});

xktModel.createMetaObject({
metaObjectId: "blueLeg",
metaObjectName: "Blue Table Leg",
metaObjectType: "furniturePart",
parentMetaObjectId: "table"
parentMetaObjectId: "table",
propertySetId: "tableLegPropSet"
});

xktModel.createMetaObject({
metaObjectId: "yellowLeg",
metaObjectName: "Yellow Table Leg",
metaObjectType: "furniturePart",
parentMetaObjectId: "table"
parentMetaObjectId: "table",
propertySetId: "tableLegPropSet"
});

xktModel.createMetaObject({
metaObjectId: "pinkTop",
metaObjectName: "The Pink Table Top",
metaObjectType: "furniturePart",
parentMetaObjectId: "table"
parentMetaObjectId: "table",
propertySetId: "tableTopPropSet"
});

// Create an XKTGeometry that defines a box shape, as a triangle mesh
Expand Down
19 changes: 6 additions & 13 deletions convert2xkt.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ program
.option('-s, --source [file]', 'path to source file')
.option('-f, --format [string]', 'source file format (optional); supported formats are gltf, ifc, laz, las, pcd, ply, stl and cityjson')
.option('-m, --metamodel [file]', 'path to source metamodel JSON file (optional)')
.option('-i, --include [types]', 'only convert these types (optional)')
.option('-x, --exclude [types]', 'never convert these types (optional)')
.option('-o, --output [file]', 'path to target .xkt file; creates directories on path automatically if not existing')
.option('-p, --properties [file]', 'path to target directory for object property files; creates directories on path automatically if not existing')
.option('-l, --log', 'enable logging');

program.on('--help', () => {
Expand Down Expand Up @@ -47,15 +48,8 @@ async function main() {

if (program.output) {
const outputDir = getBasePath(program.output).trim();
if (outputDir !== "" && !fs.existsSync(outputDir)){
fs.mkdirSync(outputDir, { recursive: true });
}
}

if (program.properties) {
const outputPropertiesDir = getBasePath(program.properties).trim();
if (outputPropertiesDir !== "" && !fs.existsSync(outputPropertiesDir)){
fs.mkdirSync(outputPropertiesDir, { recursive: true });
if (outputDir !== "" && !fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, {recursive: true});
}
}

Expand All @@ -64,9 +58,8 @@ async function main() {
format: program.format,
metaModelSource: program.metamodel,
output: program.output,
outputObjectProperties: program.properties ? async function (objectId, props) {
await fs.writeFileSync(`${program.properties}/${objectId}.json`, JSON.stringify(props, null, "\t"));
} : null,
includeTypes: program.include ? program.include.slice(",") : null,
excludeTypes: program.exclude ? program.exclude.slice(",") : null,
log
});

Expand Down
55 changes: 38 additions & 17 deletions src/convert2xkt.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ const DOMParser = require('xmldom').DOMParser;
* @param {String} [params.sourceFormat] Format of source file/data. Always needed with ````sourceData````, but not normally needed with ````source````, because convert2xkt will determine the format automatically from the file extension of ````source````.
* @param {ArrayBuffer|JSON} [params.metaModelData] Source file data. Overrides metadata from ````metaModelSource````, ````sourceData```` and ````source````.
* @param {String} [params.metaModelSource] Path to source metaModel file. Overrides metadata from ````sourceData```` and ````source````. Overridden by ````metaModelData````.
* @param {Boolean} [params.autoMetaModel=true] Whether to automatically generate a default metamodel in the XKT when no metamodel is given, or parsed in the source.
* @param {String} [params.output] Path to destination XKT file. Directories on this path are automatically created if not existing.
* @param {Function} [params.outputXKTModel] Callback to collect the ````XKTModel```` that is internally build by this method.
* @param {Function} [params.outputXKT] Callback to collect XKT file data.
* @param {Function} [params.outputObjectProperties] Callback to collect each object's property set.
* @param {String[]} [params.includeTypes] Option to only convert objects of these types.
* @param {String[]} [params.excludeTypes] Option to never convert objects of these types.
* @param {Object} [stats] Collects conversion statistics. Statistics are attached to this object if provided.
* @param {Function} [params.outputStats] Callback to collect statistics.
* @param {Boolean} [params.rotateX=true] Whether to rotate the model 90 degrees about the X axis to make the Y axis "up", if necessary. Applies to CityJSON and LAS/LAZ models.
Expand All @@ -41,18 +41,35 @@ function convert2xkt({
sourceFormat,
metaModelSource,
metaModelData,
autoMetaModel,
output,
outputXKTModel,
outputXKT,
outputObjectProperties,
includeTypes,
excludeTypes,
stats = {},
outputStats,
rotateX,
log = (msg) => {
}
}) {

stats.sourceFormat = "";
stats.schemaVersion = "";
stats.title = "";
stats.author = "";
stats.created = "";
stats.numMetaObjects = 0;
stats.numPropertySets = 0;
stats.numTriangles = 0;
stats.numVertices = 0;
stats.numObjects = 0;
stats.numGeometries = 0;
stats.sourceSize = 0;
stats.xktSize = 0;
stats.compressionRatio = 0;
stats.conversionTime = 0;
stats.aabb = null;

return new Promise(function (resolve, reject) {

const _log = log;
Expand Down Expand Up @@ -83,10 +100,6 @@ function convert2xkt({

const ext = sourceFormat || source.split('.').pop();

if (ext === "ifc") {
log("Warning: Here be dragons; IFC conversion is very alpha!");
}

if (!sourceData) {
try {
sourceData = fs.readFileSync(source);
Expand Down Expand Up @@ -134,7 +147,6 @@ function convert2xkt({
convert(parseCityJSONIntoXKTModel, {
data: JSON.parse(sourceData),
xktModel,
outputObjectProperties,
stats,
rotateX,
log
Expand All @@ -159,7 +171,8 @@ function convert2xkt({
data: sourceData,
xktModel,
wasmPath: "./",
outputObjectProperties,
includeTypes,
excludeTypes,
stats,
log
});
Expand Down Expand Up @@ -217,7 +230,6 @@ function convert2xkt({
data: sourceData,
domParser,
xktModel,
outputObjectProperties,
stats,
log
});
Expand All @@ -233,9 +245,7 @@ function convert2xkt({

parser(converterParams).then(() => {

if (autoMetaModel !== false) {
xktModel.createDefaultMetaObjects();
}
xktModel.createDefaultMetaObjects();

xktModel.finalize();

Expand All @@ -249,16 +259,27 @@ function convert2xkt({
stats.compressionRatio = (sourceFileSizeBytes / targetFileSizeBytes).toFixed(2);
stats.conversionTime = ((new Date() - startTime) / 1000.0).toFixed(2);
stats.aabb = xktModel.aabb;

log("Converted to: XKT v9");
if (includeTypes) {
log("Include types: " + (includeTypes ? includeTypes : "(include all)"));
}
if (excludeTypes) {
log("Exclude types: " + (excludeTypes ? excludeTypes : "(exclude none)"));
}
log("XKT size: " + stats.xktSize + " kB");
log("Compression ratio: " + stats.compressionRatio);
log("Conversion time: " + stats.conversionTime + " s");
log("Converted metaobjects: " + stats.numMetaObjects);
log("Converted property sets: " + stats.numPropertySets);
log("Converted drawable objects: " + stats.numObjects);
log("Converted geometries: " + stats.numGeometries);
log("Converted triangles: " + stats.numTriangles);
log("Converted vertices: " + stats.numVertices);

if (output) {
const outputDir = getBasePath(output).trim();
if (outputDir !== "" && !fs.existsSync(outputDir)){
fs.mkdirSync(outputDir, { recursive: true });
if (outputDir !== "" && !fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, {recursive: true});
}
log('Writing XKT file: ' + output);
fs.writeFileSync(output, xktContent);
Expand Down
2 changes: 1 addition & 1 deletion src/parsers/parse3DXMLIntoXKTModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ function parse3DXMLIntoXKTModel({data, domParser, xktModel, autoNormals = false,
};

parseDocument(ctx).then(() => {
ctx.log("Converted objects: " + ctx.stats.numObjects);
ctx.log("Converted drawable objects: " + ctx.stats.numObjects);
ctx.log("Converted geometries: " + ctx.stats.numGeometries);
ctx.log("Converted triangles: " + ctx.stats.numTriangles);
ctx.log("Converted vertices: " + ctx.stats.numVertices);
Expand Down
Loading

0 comments on commit c6931be

Please sign in to comment.