diff --git a/source/engine/export/exporter.js b/source/engine/export/exporter.js index 2047e7f..d35cd1c 100644 --- a/source/engine/export/exporter.js +++ b/source/engine/export/exporter.js @@ -1,4 +1,5 @@ import { Exporter3dm } from './exporter3dm.js'; +import { ExporterBim } from './exporterbim.js'; import { ExporterGltf } from './exportergltf.js'; import { ExporterModel } from './exportermodel.js'; import { ExporterObj } from './exporterobj.js'; @@ -16,7 +17,8 @@ export class Exporter new ExporterPly (), new ExporterOff (), new ExporterGltf (), - new Exporter3dm () + new Exporter3dm (), + new ExporterBim () ]; } diff --git a/source/engine/export/exporterbim.js b/source/engine/export/exporterbim.js new file mode 100644 index 0000000..14733b0 --- /dev/null +++ b/source/engine/export/exporterbim.js @@ -0,0 +1,77 @@ +import { FileFormat } from '../io/fileutils.js'; +import { ColorComponentFromFloat } from '../model/color.js'; +import { ConvertMeshToMeshBuffer } from '../model/meshbuffer.js'; +import { PropertyToString } from '../model/property.js'; +import { ExportedFile, ExporterBase } from './exporterbase.js'; + +export class ExporterBim extends ExporterBase +{ + constructor () + { + super (); + } + + CanExport (format, extension) + { + return format === FileFormat.Text && extension === 'bim'; + } + + ExportContent (exporterModel, format, files, onFinish) + { + let bimContent = { + schema_version : '1.0.0', + meshes : [], + elements : [] + }; + + this.ExportProperties (exporterModel.GetModel (), bimContent); + + let meshId = 0; + exporterModel.EnumerateTransformedMeshes ((mesh) => { + let meshBuffer = ConvertMeshToMeshBuffer (mesh); + for (let primitiveIndex = 0; primitiveIndex < meshBuffer.PrimitiveCount (); primitiveIndex++) { + let primitive = meshBuffer.GetPrimitive (primitiveIndex); + let material = exporterModel.GetMaterial (primitive.material); + let bimMesh = { + mesh_id : meshId, + coordinates : primitive.vertices, + indices : primitive.indices + }; + let bimElement = { + mesh_id : meshId, + type : 'Other', + color : { + r : material.color.r, + g : material.color.g, + b : material.color.b, + a : ColorComponentFromFloat (material.opacity) + } + }; + this.ExportProperties (mesh, bimElement); + bimContent.meshes.push (bimMesh); + bimContent.elements.push (bimElement); + meshId += 1; + } + }); + + let bimFile = new ExportedFile ('model.bim'); + bimFile.SetTextContent (JSON.stringify (bimContent, null, 4)); + files.push (bimFile); + onFinish (); + } + + ExportProperties (element, targetObject) + { + let info = {}; + for (let groupIndex = 0; groupIndex < element.PropertyGroupCount (); groupIndex++) { + let group = element.GetPropertyGroup (groupIndex); + for (let propertyIndex = 0; propertyIndex < group.PropertyCount (); propertyIndex++) { + let property = group.GetProperty (propertyIndex); + info[property.name] = PropertyToString (property); + } + } + if (Object.keys (info).length !== 0) { + targetObject.info = info; + } + } +} diff --git a/source/engine/export/exportermodel.js b/source/engine/export/exportermodel.js index a0e8b57..e6e70d9 100644 --- a/source/engine/export/exportermodel.js +++ b/source/engine/export/exportermodel.js @@ -23,6 +23,11 @@ export class ExporterModel this.settings = settings || new ExporterSettings (); } + GetModel () + { + return this.model; + } + MaterialCount () { return this.model.MaterialCount (); diff --git a/source/engine/main.js b/source/engine/main.js index e1633c9..c197c81 100644 --- a/source/engine/main.js +++ b/source/engine/main.js @@ -3,6 +3,7 @@ import { TaskRunner, RunTaskAsync, RunTasks, RunTasksBatch, WaitWhile } from './ import { Exporter } from './export/exporter.js'; import { Exporter3dm } from './export/exporter3dm.js'; import { ExportedFile, ExporterBase } from './export/exporterbase.js'; +import { ExporterBim } from './export/exporterbim.js'; import { ExporterGltf } from './export/exportergltf.js'; import { ExporterSettings, ExporterModel } from './export/exportermodel.js'; import { ExporterObj } from './export/exporterobj.js'; @@ -54,7 +55,7 @@ import { FinalizeModel, CheckModel } from './model/modelfinalization.js'; import { IsModelEmpty, GetBoundingBox, GetTopology, IsSolid, HasDefaultMaterial, ReplaceDefaultMaterialColor } from './model/modelutils.js'; import { Node, NodeType } from './model/node.js'; import { Object3D, ModelObject3D } from './model/object.js'; -import { Property, PropertyGroup, PropertyType } from './model/property.js'; +import { Property, PropertyGroup, PropertyToString, PropertyType } from './model/property.js'; import { GetTriangleArea, GetTetrahedronSignedVolume, CalculateVolume, CalculateSurfaceArea } from './model/quantities.js'; import { TopologyVertex, TopologyEdge, TopologyTriangleEdge, TopologyTriangle, Topology } from './model/topology.js'; import { Triangle } from './model/triangle.js'; @@ -82,6 +83,7 @@ export { Exporter3dm, ExportedFile, ExporterBase, + ExporterBim, ExporterGltf, ExporterSettings, ExporterModel, @@ -244,6 +246,7 @@ export { ModelObject3D, Property, PropertyGroup, + PropertyToString, PropertyType, GetTriangleArea, GetTetrahedronSignedVolume, diff --git a/source/engine/model/mesh.js b/source/engine/model/mesh.js index 094ed96..0a7bd3f 100644 --- a/source/engine/model/mesh.js +++ b/source/engine/model/mesh.js @@ -141,6 +141,7 @@ export class Mesh extends ModelObject3D let cloned = new Mesh (); cloned.SetName (this.GetName ()); + this.CloneProperties (cloned); for (let i = 0; i < this.VertexCount (); i++) { let vertex = this.GetVertex (i); diff --git a/source/engine/model/object.js b/source/engine/model/object.js index c37bb7e..10c4daa 100644 --- a/source/engine/model/object.js +++ b/source/engine/model/object.js @@ -80,4 +80,11 @@ export class ModelObject3D extends Object3D { return this.propertyGroups[index]; } + + CloneProperties (target) + { + for (let propertyGroup of this.propertyGroups) { + target.AddPropertyGroup (propertyGroup.Clone ()); + } + } } diff --git a/source/engine/model/property.js b/source/engine/model/property.js index 44d52e0..a0aa560 100644 --- a/source/engine/model/property.js +++ b/source/engine/model/property.js @@ -1,3 +1,5 @@ +import { ColorToHexString } from './color.js'; + export const PropertyType = { Text : 1, @@ -16,6 +18,16 @@ export class Property this.name = name; this.value = value; } + + Clone () + { + const clonable = (this.type === PropertyType.Color); + if (clonable) { + return new Property (this.type, this.name, this.value.Clone ()); + } else { + return new Property (this.type, this.name, this.value); + } + } } export class PropertyGroup @@ -40,4 +52,34 @@ export class PropertyGroup { return this.properties[index]; } + + Clone () + { + let cloned = new PropertyGroup (this.name); + for (let property of this.properties) { + cloned.AddProperty (property.Clone ()); + } + return cloned; + } +} + +export function PropertyToString (property) +{ + if (property.type === PropertyType.Text) { + return property.value; + } else if (property.type === PropertyType.Integer) { + return property.value.toLocaleString (); + } else if (property.type === PropertyType.Number) { + return property.value.toLocaleString (undefined, { + minimumFractionDigits: 2, + maximumFractionDigits: 2 + }); + } else if (property.type === PropertyType.Boolean) { + return property.value ? 'True' : 'False'; + } else if (property.type === PropertyType.Percent) { + return parseInt (property.value * 100, 10).toString () + '%'; + } else if (property.type === PropertyType.Color) { + return '#' + ColorToHexString (property.value); + } + return null; } diff --git a/source/website/sidebardetailspanel.js b/source/website/sidebardetailspanel.js index 29cacbd..226b71a 100644 --- a/source/website/sidebardetailspanel.js +++ b/source/website/sidebardetailspanel.js @@ -2,7 +2,7 @@ import { RunTaskAsync } from '../engine/core/taskrunner.js'; import { SubCoord3D } from '../engine/geometry/coord3d.js'; import { GetBoundingBox, IsSolid } from '../engine/model/modelutils.js'; import { CalculateVolume, CalculateSurfaceArea } from '../engine/model/quantities.js'; -import { Property, PropertyType } from '../engine/model/property.js'; +import { Property, PropertyToString, PropertyType } from '../engine/model/property.js'; import { AddDiv, AddDomElement, ClearDomElement } from '../engine/viewer/domutils.js'; import { SidebarPanel } from './sidebarpanel.js'; import { CreateInlineColorCircle } from './utils.js'; @@ -164,24 +164,15 @@ export class SidebarDetailsPanel extends SidebarPanel valueHtml = '' + property.value + ''; valueTitle = property.value; } else { - valueHtml = property.value; + valueHtml = PropertyToString (property); } - } else if (property.type === PropertyType.Integer) { - valueHtml = property.value.toLocaleString (); - } else if (property.type === PropertyType.Number) { - valueHtml = property.value.toLocaleString (undefined, { - minimumFractionDigits: 2, - maximumFractionDigits: 2 - }); - } else if (property.type === PropertyType.Boolean) { - valueHtml = property.value ? 'True' : 'False'; - } else if (property.type === PropertyType.Percent) { - valueHtml = parseInt (property.value * 100, 10).toString () + '%'; } else if (property.type === PropertyType.Color) { let hexString = '#' + ColorToHexString (property.value); let colorCircle = CreateInlineColorCircle (property.value); targetDiv.appendChild (colorCircle); AddDomElement (targetDiv, 'span', null, hexString); + } else { + valueHtml = PropertyToString (property); } if (valueHtml !== null) { targetDiv.innerHTML = valueHtml; diff --git a/test/tests/exportimport_test.js b/test/tests/exportimport_test.js index 77035a5..4dd5289 100644 --- a/test/tests/exportimport_test.js +++ b/test/tests/exportimport_test.js @@ -133,16 +133,20 @@ function ExportImport (model, format, extension, onReady) }); } +function CheckModelBounds (model, model2) +{ + let modelBounds = OV.GetBoundingBox (model); + let model2Bounds = OV.GetBoundingBox (model2); + assert.ok (OV.CoordIsEqual3D (modelBounds.min, model2Bounds.min)); + assert.ok (OV.CoordIsEqual3D (modelBounds.max, model2Bounds.max)); +} + function CheckSingleMeshModel (model, model2) { assert.strictEqual (model2.MaterialCount (), 1); assert.strictEqual (model2.MeshInstanceCount (), 1); assert.strictEqual (model.TriangleCount (), model2.TriangleCount ()); - - let modelBounds = OV.GetBoundingBox (model); - let model2Bounds = OV.GetBoundingBox (model2); - assert.ok (OV.CoordIsEqual3D (modelBounds.min, model2Bounds.min)); - assert.ok (OV.CoordIsEqual3D (modelBounds.max, model2Bounds.max)); + CheckModelBounds (model, model2); } function CheckModel (model, model2) @@ -150,11 +154,7 @@ function CheckModel (model, model2) assert.strictEqual (model.MaterialCount (), model2.MaterialCount ()); assert.strictEqual (model.MeshInstanceCount (), model2.MeshInstanceCount ()); assert.strictEqual (model.TriangleCount (), model2.TriangleCount ()); - - let modelBounds = OV.GetBoundingBox (model); - let model2Bounds = OV.GetBoundingBox (model2); - assert.ok (OV.CoordIsEqual3D (modelBounds.min, model2Bounds.min)); - assert.ok (OV.CoordIsEqual3D (modelBounds.max, model2Bounds.max)); + CheckModelBounds (model, model2); } describe ('Export-Import Test', function () { @@ -221,6 +221,17 @@ describe ('Export-Import Test', function () { done (); }); }); + + it ('Export-Import Bim', function (done) { + let model = CreateTestModel (); + ExportImport (model, OV.FileFormat.Text, 'bim', (model2) => { + assert.strictEqual (model2.MaterialCount (), 2); + assert.strictEqual (model.MeshInstanceCount (), model2.MeshInstanceCount ()); + assert.strictEqual (model.TriangleCount (), model2.TriangleCount ()); + CheckModelBounds (model, model2); + done (); + }); + }); }); describe ('Export-Import Vertex Colors Test', function () { diff --git a/test/tests/property_test.js b/test/tests/property_test.js index 4d11f7e..bda5c2f 100644 --- a/test/tests/property_test.js +++ b/test/tests/property_test.js @@ -5,7 +5,7 @@ export default function suite () { describe ('Property Test', function () { - it ('Property Group', function() { + it ('Property group', function() { let group = new OV.PropertyGroup ('Group'); group.AddProperty (new OV.Property (OV.PropertyType.Text, 'name 01', 'value 01')); group.AddProperty (new OV.Property (OV.PropertyType.Integer, 'name 02', 2)); @@ -19,7 +19,7 @@ describe ('Property Test', function () { assert.strictEqual (group.GetProperty (2).value, 3.5); }); - it ('Model Properties', function() { + it ('Model properties', function() { let model = new OV.Model (); let group1 = new OV.PropertyGroup ('Group 01'); let group2 = new OV.PropertyGroup ('Group 02'); @@ -41,6 +41,69 @@ describe ('Property Test', function () { assert.strictEqual (model.GetPropertyGroup (2).GetProperty (0).name, 'name 03'); assert.strictEqual (model.GetPropertyGroup (2).GetProperty (0).value, 3.5); }); + + it ('Mesh properties', function() { + let mesh = new OV.Mesh (); + let group1 = new OV.PropertyGroup ('Group 01'); + let group2 = new OV.PropertyGroup ('Group 02'); + let group3 = new OV.PropertyGroup ('Group 03'); + group1.AddProperty (new OV.Property (OV.PropertyType.Text, 'name 01', 'value 01')); + group2.AddProperty (new OV.Property (OV.PropertyType.Integer, 'name 02', 2)); + group3.AddProperty (new OV.Property (OV.PropertyType.Number, 'name 03', 3.5)); + mesh.AddPropertyGroup (group1); + mesh.AddPropertyGroup (group2); + mesh.AddPropertyGroup (group3); + assert.strictEqual (mesh.PropertyGroupCount (), 3); + assert.strictEqual (mesh.GetPropertyGroup (0).name, 'Group 01'); + assert.strictEqual (mesh.GetPropertyGroup (1).name, 'Group 02'); + assert.strictEqual (mesh.GetPropertyGroup (2).name, 'Group 03'); + assert.strictEqual (mesh.GetPropertyGroup (0).GetProperty (0).name, 'name 01'); + assert.strictEqual (mesh.GetPropertyGroup (0).GetProperty (0).value, 'value 01'); + assert.strictEqual (mesh.GetPropertyGroup (1).GetProperty (0).name, 'name 02'); + assert.strictEqual (mesh.GetPropertyGroup (1).GetProperty (0).value, 2); + assert.strictEqual (mesh.GetPropertyGroup (2).GetProperty (0).name, 'name 03'); + assert.strictEqual (mesh.GetPropertyGroup (2).GetProperty (0).value, 3.5); + }); + + it ('Mesh clone test', function() { + let mesh = new OV.Mesh (); + let group1 = new OV.PropertyGroup ('Group 01'); + let group2 = new OV.PropertyGroup ('Group 02'); + let group3 = new OV.PropertyGroup ('Group 03'); + group1.AddProperty (new OV.Property (OV.PropertyType.Text, 'name 01', 'value 01')); + group2.AddProperty (new OV.Property (OV.PropertyType.Integer, 'name 02', 2)); + group3.AddProperty (new OV.Property (OV.PropertyType.Number, 'name 03', 3.5)); + group3.AddProperty (new OV.Property (OV.PropertyType.Color, 'name 04', new OV.Color (10, 20, 30))); + mesh.AddPropertyGroup (group1); + mesh.AddPropertyGroup (group2); + mesh.AddPropertyGroup (group3); + let cloned = mesh.Clone (); + assert.strictEqual (cloned.PropertyGroupCount (), 3); + assert.strictEqual (cloned.GetPropertyGroup (0).name, 'Group 01'); + assert.strictEqual (cloned.GetPropertyGroup (1).name, 'Group 02'); + assert.strictEqual (cloned.GetPropertyGroup (2).name, 'Group 03'); + assert.strictEqual (cloned.GetPropertyGroup (0).GetProperty (0).name, 'name 01'); + assert.strictEqual (cloned.GetPropertyGroup (0).GetProperty (0).value, 'value 01'); + assert.strictEqual (cloned.GetPropertyGroup (1).GetProperty (0).name, 'name 02'); + assert.strictEqual (cloned.GetPropertyGroup (1).GetProperty (0).value, 2); + assert.strictEqual (cloned.GetPropertyGroup (2).GetProperty (0).name, 'name 03'); + assert.strictEqual (cloned.GetPropertyGroup (2).GetProperty (0).value, 3.5); + assert.strictEqual (cloned.GetPropertyGroup (2).GetProperty (1).name, 'name 04'); + assert.strictEqual ( + OV.ColorToHexString (cloned.GetPropertyGroup (2).GetProperty (1).value), + OV.ColorToHexString (new OV.Color (10, 20, 30)) + ); + }); + + it ('Property to string', function() { + assert.strictEqual (OV.PropertyToString (new OV.Property (OV.PropertyType.Text, 'name', 'test')), 'test'); + assert.strictEqual (OV.PropertyToString (new OV.Property (OV.PropertyType.Integer, 'name', 42)), '42'); + assert.strictEqual (OV.PropertyToString (new OV.Property (OV.PropertyType.Number, 'name', 3.14)), '3.14'); + assert.strictEqual (OV.PropertyToString (new OV.Property (OV.PropertyType.Boolean, 'name', true)), 'True'); + assert.strictEqual (OV.PropertyToString (new OV.Property (OV.PropertyType.Boolean, 'name', false)), 'False'); + assert.strictEqual (OV.PropertyToString (new OV.Property (OV.PropertyType.Percent, 'name', 0.2)), '20%'); + assert.strictEqual (OV.PropertyToString (new OV.Property (OV.PropertyType.Color, 'color', new OV.Color (10, 20, 20))), '#0a1414'); + }); }); } diff --git a/website/info/index.html b/website/info/index.html index 7f63d56..d8f7741 100644 --- a/website/info/index.html +++ b/website/info/index.html @@ -151,7 +151,7 @@ bim text ✓ - ✗ + ✓ Native