diff --git a/src/Spectacles.RevitExporter/Command.cs b/src/Spectacles.RevitExporter/Command.cs index 0b945c7..ff49e1a 100644 --- a/src/Spectacles.RevitExporter/Command.cs +++ b/src/Spectacles.RevitExporter/Command.cs @@ -415,12 +415,12 @@ public Result Execute( { try { - if (camera.IsPerspective) + if ((camera.IsTemplate == false) && (camera.IsPerspective)) { ViewOrientation3D vo = camera.GetOrientation(); cameraNames.Add(camera.Name); cameraPositions.Add((-vo.EyePosition.X * 304.8).ToString() + "," + (vo.EyePosition.Z * 304.8).ToString() + "," + (vo.EyePosition.Y* 304.8).ToString()); - cameraTargets.Add((-vo.ForwardDirection.X * 304.8).ToString() + "," + (vo.ForwardDirection.Z * 304.8).ToString() + "," + (vo.ForwardDirection.Y* 304.8).ToString()); + cameraTargets.Add((-vo.ForwardDirection.X).ToString() + "," + (vo.ForwardDirection.Z).ToString() + "," + (vo.ForwardDirection.Y).ToString()); } } catch { } diff --git a/src/Spectacles.RevitExporter/Properties/AssemblyInfo.cs b/src/Spectacles.RevitExporter/Properties/AssemblyInfo.cs index 25ef92d..38424e2 100644 --- a/src/Spectacles.RevitExporter/Properties/AssemblyInfo.cs +++ b/src/Spectacles.RevitExporter/Properties/AssemblyInfo.cs @@ -40,7 +40,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Thornton Tomasetti")] [assembly: AssemblyProduct("Spectacles.RevitExporter")] -[assembly: AssemblyCopyright("Copyright 2015 © Thornton Tomasetti")] +[assembly: AssemblyCopyright("Copyright 2015-2017 © Thornton Tomasetti, et al.")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -75,5 +75,5 @@ // 2014-11-25 2015.0.0.28 skip elements with null category in OnElementEnd as well // 2015-02-15 2015.0.0.29 incremented copyright year // -[assembly: AssemblyVersion("0.1.0.3")] -[assembly: AssemblyFileVersion("0.1.0.3")] \ No newline at end of file +[assembly: AssemblyVersion("0.1.0.4")] +[assembly: AssemblyFileVersion("0.1.0.4")] \ No newline at end of file diff --git a/src/Spectacles.RevitExporter/Spectacles.RevitExporter.csproj b/src/Spectacles.RevitExporter/Spectacles.RevitExporter.csproj index 9d0b0dc..eda0a88 100644 --- a/src/Spectacles.RevitExporter/Spectacles.RevitExporter.csproj +++ b/src/Spectacles.RevitExporter/Spectacles.RevitExporter.csproj @@ -1,149 +1,150 @@ - - - + + + None - - - - - Debug - AnyCPU - - - - - {3F865D22-8849-4DFA-9E7C-3533A8A159EA} - Library - Properties - Spectacles.RevitExporter - v4.5 - 512 - - - true - full - false - bin\ - DEBUG;TRACE - prompt - 4 - Program - $(ProgramW6432)\Autodesk\Revit 2015\Revit.exe - false - - - pdbonly - true - bin\ - TRACE - prompt - 4 - Program - $(ProgramW6432)\Autodesk\Revit 2015\Revit.exe - false - - - Spectacles.RevitExporter - - - - - packages\Newtonsoft.Json.dll - - - - - ..\..\..\..\..\..\..\Program Files\Autodesk\Revit 2016\RevitAPI.dll - False - - - ..\..\..\..\..\..\..\Program Files\Autodesk\Revit 2016\RevitAPIUI.dll - False - - - - - - - - - - - - - - - - - Form - - - ExportOptions.cs - - - Form - - - ParameterFilter.cs - - - - True - True - Resources.resx - - - - - - Form - - - Success.cs - - - - - - - - - ExportOptions.cs - - - ParameterFilter.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - Success.cs - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + Debug + AnyCPU + + + + + {3F865D22-8849-4DFA-9E7C-3533A8A159EA} + Library + Properties + Spectacles.RevitExporter + v4.5 + 512 + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + Program + $(ProgramW6432)\Autodesk\Revit 2015\Revit.exe + false + + + pdbonly + true + bin\ + TRACE + prompt + 4 + Program + $(ProgramW6432)\Autodesk\Revit 2015\Revit.exe + false + + + Spectacles.RevitExporter + + + + + ..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + + + + ..\..\..\..\..\..\..\Program Files\Autodesk\Revit 2016\RevitAPI.dll + False + + + ..\..\..\..\..\..\..\Program Files\Autodesk\Revit 2016\RevitAPIUI.dll + False + + + + + + + + + + + + + + + + + Form + + + ExportOptions.cs + + + Form + + + ParameterFilter.cs + + + + True + True + Resources.resx + + + + + + Form + + + Success.cs + + + + + + ExportOptions.cs + + + ParameterFilter.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + Success.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Spectacles.RevitExporter/SpectaclesExportContext.cs b/src/Spectacles.RevitExporter/SpectaclesExportContext.cs index fd8c9a4..34c08b0 100644 --- a/src/Spectacles.RevitExporter/SpectaclesExportContext.cs +++ b/src/Spectacles.RevitExporter/SpectaclesExportContext.cs @@ -1,903 +1,919 @@ -//The MIT License (MIT) - -//Those portions created by va3c authors are provided with the following copyright: - -//Copyright (c) 2014 va3c - -//Those portions created by Thornton Tomasetti employees are provided with the following copyright: - -//Copyright (c) 2015 Thornton Tomasetti - -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: - -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. - -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. - -#region Namespaces -using System; -using System.Collections.Generic; -using System.Linq; -using System.Diagnostics; -using System.IO; -using Autodesk.Revit.DB; -using Autodesk.Revit.Utility; -using Newtonsoft.Json; -#endregion // Namespaces - -namespace Spectacles.RevitExporter -{ - // Done: - // Check instance transformation - // Support transparency - // Add scaling for Theo [(0,0),(20000,20000)] - // Implement the external application button - // Implement element properties - // Eliminate multiple materials - // Prompt user for output file name and location - // Eliminate null element properties, i.e. useless - // JSON userData entries - // Todo: - // Check for file size - // Instance/type reuse - - public class SpectaclesExportContext : IExportContext - { - /// - /// Scale entire top level BIM object nove in JSON - /// output. A scale of 1.0 will output the model in - /// millimetres. Currently we scale it to decimetres - /// so that a typical model has a chance of fitting - /// into a cube with side length 100, i.e. 10 metres. - /// - double _scale_bim = 1.0; - - /// - /// Scale applied to each vertex in each individual - /// BIM element. This can be used to scale the model - /// down from millimetres to metres, e.g. - /// Currently we stick with millimetres after all - /// at this level. - /// - double _scale_vertex = 1.0; - - /// - /// If true, switch Y and Z coordinate - /// and flip X to negative to convert from - /// Revit coordinate system to standard 3d - /// computer graphics coordinate system with - /// Z pointing out of screen, X towards right, - /// Y up. - /// - bool _switch_coordinates = true; - - #region VertexLookupXyz - /// - /// A vertex lookup class to eliminate - /// duplicate vertex definitions. - /// - class VertexLookupXyz : Dictionary - { - #region XyzEqualityComparer - /// - /// Define equality for Revit XYZ points. - /// Very rough tolerance, as used by Revit itself. - /// - class XyzEqualityComparer : IEqualityComparer - { - const double _sixteenthInchInFeet - = 1.0 / (16.0 * 12.0); - - public bool Equals(XYZ p, XYZ q) - { - return p.IsAlmostEqualTo(q, - _sixteenthInchInFeet); - } - - public int GetHashCode(XYZ p) - { - return Util.PointString(p).GetHashCode(); - } - } - #endregion // XyzEqualityComparer - - public VertexLookupXyz() - : base(new XyzEqualityComparer()) - { - } - - /// - /// Return the index of the given vertex, - /// adding a new entry if required. - /// - public int AddVertex(XYZ p) - { - return ContainsKey(p) - ? this[p] - : this[p] = Count; - } - } - #endregion // VertexLookupXyz - - #region VertexLookupInt - /// - /// An integer-based 3D point class. - /// - class PointInt : IComparable - { - public long X { get; set; } - public long Y { get; set; } - public long Z { get; set; } - - //public PointInt( int x, int y, int z ) - //{ - // X = x; - // Y = y; - // Z = z; - //} - - /// - /// Consider a Revit length zero - /// if is smaller than this. - /// - const double _eps = 1.0e-9; - - /// - /// Conversion factor from feet to millimetres. - /// - const double _feet_to_mm = 25.4 * 12; - - /// - /// Conversion a given length value - /// from feet to millimetre. - /// - static long ConvertFeetToMillimetres(double d) - { - if (0 < d) - { - return _eps > d - ? 0 - : (long)(_feet_to_mm * d + 0.5); - - } - else - { - return _eps > -d - ? 0 - : (long)(_feet_to_mm * d - 0.5); - - } - } - - public PointInt(XYZ p, bool switch_coordinates) - { - X = ConvertFeetToMillimetres(p.X); - Y = ConvertFeetToMillimetres(p.Y); - Z = ConvertFeetToMillimetres(p.Z); - - if (switch_coordinates) - { - X = -X; - long tmp = Y; - Y = Z; - Z = tmp; - } - } - - public int CompareTo(PointInt a) - { - long d = X - a.X; - - if (0 == d) - { - d = Y - a.Y; - - if (0 == d) - { - d = Z - a.Z; - } - } - return (0 == d) ? 0 : ((0 < d) ? 1 : -1); - } - } - - /// - /// A vertex lookup class to eliminate - /// duplicate vertex definitions. - /// - class VertexLookupInt : Dictionary - { - #region PointIntEqualityComparer - /// - /// Define equality for integer-based PointInt. - /// - class PointIntEqualityComparer : IEqualityComparer - { - public bool Equals(PointInt p, PointInt q) - { - return 0 == p.CompareTo(q); - } - - public int GetHashCode(PointInt p) - { - return (p.X.ToString() - + "," + p.Y.ToString() - + "," + p.Z.ToString()) - .GetHashCode(); - } - } - #endregion // PointIntEqualityComparer - - public VertexLookupInt() - : base(new PointIntEqualityComparer()) - { - } - - /// - /// Return the index of the given vertex, - /// adding a new entry if required. - /// - public int AddVertex(PointInt p) - { - return ContainsKey(p) - ? this[p] - : this[p] = Count; - } - } - #endregion // VertexLookupInt - - Document _doc; - string _filename; - SpectaclesContainer _container; - Dictionary _materials; - Dictionary _objects; - Dictionary _geometries; - Dictionary _viewsAndLayersDict; - List layerList; - - SpectaclesContainer.SpectaclesObject _currentElement; - - // Keyed on material uid to handle several materials per element: - - Dictionary _currentObject; - Dictionary _currentGeometry; - Dictionary _vertices; - - Stack _elementStack = new Stack(); - Stack _transformationStack = new Stack(); - - string _currentMaterialUid; - - public string myjs = null; - - SpectaclesContainer.SpectaclesObject CurrentObjectPerMaterial - { - get - { - return _currentObject[_currentMaterialUid]; - } - } - - SpectaclesContainer.SpectaclesGeometry CurrentGeometryPerMaterial - { - get - { - return _currentGeometry[_currentMaterialUid]; - } - } - - VertexLookupInt CurrentVerticesPerMaterial - { - get - { - return _vertices[_currentMaterialUid]; - } - } - - Transform CurrentTransform - { - get - { - return _transformationStack.Peek(); - } - } - - public override string ToString() - { - return myjs; - } - /// - /// Set the current material - /// - void SetCurrentMaterial(string uidMaterial) - { - if (!_materials.ContainsKey(uidMaterial)) - { - Material material = _doc.GetElement( - uidMaterial) as Material; - - SpectaclesContainer.SpectaclesMaterial m - = new SpectaclesContainer.SpectaclesMaterial(); - - //m.metadata = new SpectaclesContainer.SpectaclesMaterialMetadata(); - //m.metadata.type = "material"; - //m.metadata.version = 4.2; - //m.metadata.generator = "Spectacles.RevitExporter 2015.0.0.0"; - - m.uuid = uidMaterial; - m.name = material.Name; - m.type = "MeshLambertMaterial"; - m.color = Util.ColorToInt(material.Color); - m.ambient = m.color; - m.emissive = 0; - m.opacity = 0.01 * (double)(100 - material.Transparency); // Revit has material.Transparency in [0,100], three.js expects opacity in [0.0,1.0] - m.transparent = 0 < material.Transparency; - m.shading = 1; - m.wireframe = false; - - _materials.Add(uidMaterial, m); - } - _currentMaterialUid = uidMaterial; - - string uid_per_material = _currentElement.uuid + "-" + uidMaterial; - - if (!_currentObject.ContainsKey(uidMaterial)) - { - Debug.Assert(!_currentGeometry.ContainsKey(uidMaterial), "expected same keys in both"); - - _currentObject.Add(uidMaterial, new SpectaclesContainer.SpectaclesObject()); - CurrentObjectPerMaterial.name = _currentElement.name; - CurrentObjectPerMaterial.geometry = uid_per_material; - CurrentObjectPerMaterial.material = _currentMaterialUid; - CurrentObjectPerMaterial.matrix = new double[] { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 }; - CurrentObjectPerMaterial.type = "Mesh"; - CurrentObjectPerMaterial.uuid = uid_per_material; - } - - if (!_currentGeometry.ContainsKey(uidMaterial)) - { - _currentGeometry.Add(uidMaterial, new SpectaclesContainer.SpectaclesGeometry()); - CurrentGeometryPerMaterial.uuid = uid_per_material; - CurrentGeometryPerMaterial.type = "Geometry"; - CurrentGeometryPerMaterial.data = new SpectaclesContainer.SpectaclesGeometryData(); - CurrentGeometryPerMaterial.data.faces = new List(); - CurrentGeometryPerMaterial.data.vertices = new List(); - CurrentGeometryPerMaterial.data.normals = new List(); - CurrentGeometryPerMaterial.data.uvs = new List(); - CurrentGeometryPerMaterial.data.visible = true; - CurrentGeometryPerMaterial.data.castShadow = true; - CurrentGeometryPerMaterial.data.receiveShadow = false; - CurrentGeometryPerMaterial.data.doubleSided = true; - CurrentGeometryPerMaterial.data.scale = 1.0; - } - - if (!_vertices.ContainsKey(uidMaterial)) - { - _vertices.Add(uidMaterial, new VertexLookupInt()); - } - } - - public SpectaclesExportContext(Document document, string filename) - { - _doc = document; - _filename = filename; - } - - public bool Start() - { - _materials = new Dictionary(); - _geometries = new Dictionary(); - _objects = new Dictionary(); - - _viewsAndLayersDict = new Dictionary(); - layerList = new List(); - - _transformationStack.Push(Transform.Identity); - - _container = new SpectaclesContainer(); - - _container.metadata = new SpectaclesContainer.Metadata(); - _container.metadata.type = "Object"; - _container.metadata.version = 4.3; - _container.metadata.generator = "Spectacles.RevitExporter Revit Spectacles exporter"; - _container.geometries = new List(); - - _container.obj = new SpectaclesContainer.SpectaclesObject(); - _container.obj.uuid = _doc.ActiveView.UniqueId; - _container.obj.name = "BIM " + _doc.Title; - _container.obj.type = "Scene"; - - // Scale entire BIM from millimetres to metres. - - _container.obj.matrix = new double[] { - _scale_bim, 0, 0, 0, - 0, _scale_bim, 0, 0, - 0, 0, _scale_bim, 0, - 0, 0, 0, _scale_bim }; - - return true; - } - - public void Finish() - { - // Finish populating scene - - _container.materials = _materials.Values.ToList(); - - _container.geometries = _geometries.Values.ToList(); - - _container.obj.children = _objects.Values.ToList(); - - if (ExportOptions.includeViews && Command.cameraNames.Count>0) - { - //create an empty string to append the list of views - string viewList = Command.cameraNames[0] + "," + Command.cameraPositions[0] + "," + Command.cameraTargets[0]; - for (int i = 1; i < Command.cameraPositions.Count; i++) - { - viewList += "," + Command.cameraNames[i] + "," + Command.cameraPositions[i] + "," + Command.cameraTargets[i]; - } - _viewsAndLayersDict.Add("views", viewList); - } - - _container.obj.userData = _viewsAndLayersDict; - - // Serialise scene - - //using( FileStream stream - // = File.OpenWrite( filename ) ) - //{ - // DataContractJsonSerializer serialiser - // = new DataContractJsonSerializer( - // typeof( SpectaclesContainer ) ); - // serialiser.WriteObject( stream, _container ); - //} - - JsonSerializerSettings settings - = new JsonSerializerSettings(); - - settings.NullValueHandling - = NullValueHandling.Ignore; - - Formatting formatting = Formatting.Indented; - - myjs = JsonConvert.SerializeObject( - _container, formatting, settings); - - - File.WriteAllText(_filename, myjs); - -#if USE_DYNAMIC_JSON - // This saves the whole hassle of explicitly - // defining a whole hierarchy of C# classes - // to serialise to JSON - do it all on the - // fly instead. - - // https://github.com/Spectacles/GHSpectacles/blob/master/GHSpectacles/GHSpectacles/Spectacles_geometry.cs - - dynamic jason = new ExpandoObject(); - - //populate object properties - - jason.geometry = new ExpandoObject(); - jason.groups = new object[0]; - jason.material = matName; - jason.position = new object[3]; - jason.position[0] = 0; jason.position[1] = 0; jason.position[2] = 0; - jason.rotation = new object[3]; - jason.rotation[0] = 0; jason.rotation[1] = 0; jason.rotation[2] = 0; - jason.quaternion = new object[4]; - jason.quaternion[0] = 0; jason.quaternion[1] = 0; jason.quaternion[2] = 0; jason.quaternion[3] = 0; - jason.scale = new object[3]; - jason.scale[0] = 1; jason.scale[1] = 1; jason.scale[2] = 1; - jason.visible = true; - jason.castShadow = true; - jason.receiveShadow = false; - jason.doubleSided = true; - - - //populate geometry object - jason.geometry.metadata = new ExpandoObject(); - jason.geometry.metadata.version = 3.1; - jason.geometry.metadata.generatedBy = "Spectacles.RevitExporter Revit Spectacles exporter"; - jason.geometry.metadata.vertices = mesh.Vertices.Count; - jason.geometry.metadata.faces = mesh.Faces.Count; - jason.geometry.metadata.normals = 0; - jason.geometry.metadata.colors = 0; - jason.geometry.metadata.uvs = 0; - jason.geometry.metadata.materials = 0; - jason.geometry.metadata.morphTargets = 0; - jason.geometry.metadata.bones = 0; - - jason.geometry.scale = 1.000; - jason.geometry.materials = new object[0]; - jason.geometry.vertices = new object[mesh.Vertices.Count * 3]; - jason.geometry.morphTargets = new object[0]; - jason.geometry.normals = new object[0]; - jason.geometry.colors = new object[0]; - jason.geometry.uvs = new object[0]; - jason.geometry.faces = new object[mesh.Faces.Count * 3]; - jason.geometry.bones = new object[0]; - jason.geometry.skinIndices = new object[0]; - jason.geometry.skinWeights = new object[0]; - jason.geometry.animation = new ExpandoObject(); - - //populate vertices - int counter = 0; - int i = 0; - foreach( var v in mesh.Vertices ) - { - jason.geometry.vertices[counter++] = mesh.Vertices[i].X; - jason.geometry.vertices[counter++] = mesh.Vertices[i].Y; - jason.geometry.vertices[counter++] = mesh.Vertices[i].Z; - i++; - } - - //populate faces - counter = 0; - i = 0; - foreach( var f in mesh.Faces ) - { - jason.geometry.faces[counter++] = mesh.Faces[i].A; - jason.geometry.faces[counter++] = mesh.Faces[i].B; - jason.geometry.faces[counter++] = mesh.Faces[i].C; - i++; - } - - return JsonConvert.SerializeObject( jason ); -#endif // USE_DYNAMIC_JSON - } - - public void OnPolymesh(PolymeshTopology polymesh) - { - //Debug.WriteLine( string.Format( - // " OnPolymesh: {0} points, {1} facets, {2} normals {3}", - // polymesh.NumberOfPoints, - // polymesh.NumberOfFacets, - // polymesh.NumberOfNormals, - // polymesh.DistributionOfNormals ) ); - - IList pts = polymesh.GetPoints(); - - Transform t = CurrentTransform; - - pts = pts.Select(p => t.OfPoint(p)).ToList(); - - int v1, v2, v3; - - foreach (PolymeshFacet facet - in polymesh.GetFacets()) - { - //Debug.WriteLine( string.Format( - // " {0}: {1} {2} {3}", i++, - // facet.V1, facet.V2, facet.V3 ) ); - - v1 = CurrentVerticesPerMaterial.AddVertex(new PointInt( - pts[facet.V1], _switch_coordinates)); - - v2 = CurrentVerticesPerMaterial.AddVertex(new PointInt( - pts[facet.V2], _switch_coordinates)); - - v3 = CurrentVerticesPerMaterial.AddVertex(new PointInt( - pts[facet.V3], _switch_coordinates)); - - CurrentGeometryPerMaterial.data.faces.Add(0); - CurrentGeometryPerMaterial.data.faces.Add(v1); - CurrentGeometryPerMaterial.data.faces.Add(v2); - CurrentGeometryPerMaterial.data.faces.Add(v3); - } - } - - public void OnMaterial(MaterialNode node) - { - //Debug.WriteLine( " --> On Material: " - // + node.MaterialId + ": " + node.NodeName ); - - // OnMaterial method can be invoked for every - // single out-coming mesh even when the material - // has not actually changed. Thus it is usually - // beneficial to store the current material and - // only get its attributes when the material - // actually changes. - - ElementId id = node.MaterialId; - - if (ElementId.InvalidElementId != id) - { - Element m = _doc.GetElement(node.MaterialId); - SetCurrentMaterial(m.UniqueId); - } - else - { - //string uid = Guid.NewGuid().ToString(); - - // Generate a GUID based on colour, - // transparency, etc. to avoid duplicating - // non-element material definitions. - - int iColor = Util.ColorToInt(node.Color); - - string uid = string.Format("MaterialNode_{0}_{1}", - iColor, Util.RealString(node.Transparency * 100)); - - if (!_materials.ContainsKey(uid)) - { - SpectaclesContainer.SpectaclesMaterial m - = new SpectaclesContainer.SpectaclesMaterial(); - - m.uuid = uid; - m.type = "MeshLambertMaterial"; - m.color = iColor; - m.ambient = m.color; - m.emissive = 0; - m.shading = 1; - m.opacity = 1; // 128 - material.Transparency; - m.opacity = 1.0 - node.Transparency; // Revit MaterialNode has double Transparency in ?range?, three.js expects opacity in [0.0,1.0] - m.transparent = 0.0 < node.Transparency; - m.wireframe = false; - - _materials.Add(uid, m); - } - SetCurrentMaterial(uid); - } - } - - public bool IsCanceled() - { - // This method is invoked many - // times during the export process. - - return false; - } - - public void OnDaylightPortal(DaylightPortalNode node) - { - Debug.WriteLine("OnDaylightPortal: " + node.NodeName); - Asset asset = node.GetAsset(); - Debug.WriteLine("OnDaylightPortal: Asset:" - + ((asset != null) ? asset.Name : "Null")); - } - - public void OnRPC(RPCNode node) - { - Debug.WriteLine("OnRPC: " + node.NodeName); - Asset asset = node.GetAsset(); - Debug.WriteLine("OnRPC: Asset:" - + ((asset != null) ? asset.Name : "Null")); - } - - public RenderNodeAction OnViewBegin(ViewNode node) - { - Debug.WriteLine("OnViewBegin: " - + node.NodeName + "(" + node.ViewId.IntegerValue - + "): LOD: " + node.LevelOfDetail); - - return RenderNodeAction.Proceed; - } - - public void OnViewEnd(ElementId elementId) - { - Debug.WriteLine("OnViewEnd: Id: " + elementId.IntegerValue); - // Note: This method is invoked even for a view that was skipped. - } - - public RenderNodeAction OnElementBegin( - ElementId elementId) - { - Element e = _doc.GetElement(elementId); - string uid = e.UniqueId; - - Debug.WriteLine(string.Format( - "OnElementBegin: id {0} category {1} name {2}", - elementId.IntegerValue, e.Category.Name, e.Name)); - - if (_objects.ContainsKey(uid)) - { - Debug.WriteLine("\r\n*** Duplicate element!\r\n"); - return RenderNodeAction.Skip; - } - - if (null == e.Category) - { - Debug.WriteLine("\r\n*** Non-category element!\r\n"); - return RenderNodeAction.Skip; - } - - _elementStack.Push(elementId); - - ICollection idsMaterialGeometry = e.GetMaterialIds(false); - ICollection idsMaterialPaint = e.GetMaterialIds(true); - - int n = idsMaterialGeometry.Count; - - if (1 < n) - { - Debug.Print("{0} has {1} materials: {2}", - Util.ElementDescription(e), n, - string.Join(", ", idsMaterialGeometry.Select( - id => _doc.GetElement(id).Name))); - } - - // We handle a current element, which may either - // be identical to the current object and have - // one single current geometry or have - // multiple current child objects each with a - // separate current geometry. - - _currentElement = new SpectaclesContainer.SpectaclesObject(); - - _currentElement.name = Util.ElementDescription(e); - _currentElement.material = _currentMaterialUid; - _currentElement.matrix = new double[] { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 }; - _currentElement.type = "RevitElement"; - _currentElement.uuid = uid; - - _currentObject = new Dictionary(); - _currentGeometry = new Dictionary(); - _vertices = new Dictionary(); - - if (null != e.Category - && null != e.Category.Material) - { - SetCurrentMaterial(e.Category.Material.UniqueId); - } - - return RenderNodeAction.Proceed; - } - - public void OnElementEnd( - ElementId id) - { - // Note: this method is invoked even for - // elements that were skipped. - - Element e = _doc.GetElement(id); - string uid = e.UniqueId; - - Debug.WriteLine(string.Format( - "OnElementEnd: id {0} category {1} name {2}", - id.IntegerValue, e.Category.Name, e.Name)); - - if (_objects.ContainsKey(uid)) - { - Debug.WriteLine("\r\n*** Duplicate element!\r\n"); - return; - } - - if (null == e.Category) - { - Debug.WriteLine("\r\n*** Non-category element!\r\n"); - return; - } - - List materials = _vertices.Keys.ToList(); - - int n = materials.Count; - - _currentElement.children = new List(n); - - foreach (string material in materials) - { - SpectaclesContainer.SpectaclesObject obj = _currentObject[material]; - SpectaclesContainer.SpectaclesGeometry geo = _currentGeometry[material]; - - foreach (KeyValuePair p in _vertices[material]) - { - geo.data.vertices.Add(_scale_vertex * p.Key.X); - geo.data.vertices.Add(_scale_vertex * p.Key.Y); - geo.data.vertices.Add(_scale_vertex * p.Key.Z); - } - obj.geometry = geo.uuid; - _geometries.Add(geo.uuid, geo); - _currentElement.children.Add(obj); - } - - //Dictionary d = Util.GetElementProperties(e, true); - Dictionary d = new Dictionary(); - if (Command._filterParameters) - { - d = Util.GetElementFilteredProperties(e, true); - } - else - { - d = Util.GetElementProperties(e, true); - } - - string layerName = e.Category.Name; - - //add a property for layer - d.Add("layer", layerName); - - - - if (!_viewsAndLayersDict.ContainsKey("layers")) _viewsAndLayersDict.Add("layers", layerName); - else - { - if (!layerList.Contains(layerName)) - { - _viewsAndLayersDict["layers"] += "," + layerName; - } - } - - if (!layerList.Contains(layerName)) layerList.Add(layerName); - - _currentElement.userData = d; - - //also add guid to user data dict - _currentElement.userData.Add("revit_id", uid); - - _objects.Add(_currentElement.uuid, _currentElement); - - _elementStack.Pop(); - } - - public RenderNodeAction OnFaceBegin(FaceNode node) - { - // This method is invoked only if the - // custom exporter was set to include faces. - - Debug.Assert(false, "we set exporter.IncludeFaces false"); - Debug.WriteLine(" OnFaceBegin: " + node.NodeName); - return RenderNodeAction.Proceed; - } - - public void OnFaceEnd(FaceNode node) - { - // This method is invoked only if the - // custom exporter was set to include faces. - - Debug.Assert(false, "we set exporter.IncludeFaces false"); - Debug.WriteLine(" OnFaceEnd: " + node.NodeName); - // Note: This method is invoked even for faces that were skipped. - } - - public RenderNodeAction OnInstanceBegin(InstanceNode node) - { - Debug.WriteLine(" OnInstanceBegin: " + node.NodeName + " symbol: " + node.GetSymbolId().IntegerValue); - // This method marks the start of processing a family instance - _transformationStack.Push(CurrentTransform.Multiply(node.GetTransform())); - - // We can either skip this instance or proceed with rendering it. - return RenderNodeAction.Proceed; - } - - public void OnInstanceEnd(InstanceNode node) - { - Debug.WriteLine(" OnInstanceEnd: " + node.NodeName); - // Note: This method is invoked even for instances that were skipped. - _transformationStack.Pop(); - } - - public RenderNodeAction OnLinkBegin(LinkNode node) - { - Debug.WriteLine(" OnLinkBegin: " + node.NodeName + " Document: " + node.GetDocument().Title + ": Id: " + node.GetSymbolId().IntegerValue); - _transformationStack.Push(CurrentTransform.Multiply(node.GetTransform())); - return RenderNodeAction.Proceed; - } - - public void OnLinkEnd(LinkNode node) - { - Debug.WriteLine(" OnLinkEnd: " + node.NodeName); - // Note: This method is invoked even for instances that were skipped. - _transformationStack.Pop(); - } - - public void OnLight(LightNode node) - { - Debug.WriteLine("OnLight: " + node.NodeName); - Asset asset = node.GetAsset(); - Debug.WriteLine("OnLight: Asset:" + ((asset != null) ? asset.Name : "Null")); - } - } -} +//The MIT License (MIT) + +//Those portions created by va3c authors are provided with the following copyright: + +//Copyright (c) 2014 va3c + +//Those portions created by Thornton Tomasetti employees are provided with the following copyright: + +//Copyright (c) 2015 Thornton Tomasetti + +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: + +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. + +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. + +#region Namespaces +using System; +using System.Collections.Generic; +using System.Linq; +using System.Diagnostics; +using System.IO; +using Autodesk.Revit.DB; +using Autodesk.Revit.Utility; +using Newtonsoft.Json; +#endregion // Namespaces + +namespace Spectacles.RevitExporter +{ + // Done: + // Check instance transformation + // Support transparency + // Add scaling for Theo [(0,0),(20000,20000)] + // Implement the external application button + // Implement element properties + // Eliminate multiple materials + // Prompt user for output file name and location + // Eliminate null element properties, i.e. useless + // JSON userData entries + // Todo: + // Check for file size + // Instance/type reuse + + public class SpectaclesExportContext : IExportContext + { + /// + /// Scale entire top level BIM object nove in JSON + /// output. A scale of 1.0 will output the model in + /// millimetres. Currently we scale it to decimetres + /// so that a typical model has a chance of fitting + /// into a cube with side length 100, i.e. 10 metres. + /// + double _scale_bim = 1.0; + + /// + /// Scale applied to each vertex in each individual + /// BIM element. This can be used to scale the model + /// down from millimetres to metres, e.g. + /// Currently we stick with millimetres after all + /// at this level. + /// + double _scale_vertex = 1.0; + + /// + /// If true, switch Y and Z coordinate + /// and flip X to negative to convert from + /// Revit coordinate system to standard 3d + /// computer graphics coordinate system with + /// Z pointing out of screen, X towards right, + /// Y up. + /// + bool _switch_coordinates = true; + + #region VertexLookupXyz + /// + /// A vertex lookup class to eliminate + /// duplicate vertex definitions. + /// + class VertexLookupXyz : Dictionary + { + #region XyzEqualityComparer + /// + /// Define equality for Revit XYZ points. + /// Very rough tolerance, as used by Revit itself. + /// + class XyzEqualityComparer : IEqualityComparer + { + const double _sixteenthInchInFeet + = 1.0 / (16.0 * 12.0); + + public bool Equals(XYZ p, XYZ q) + { + return p.IsAlmostEqualTo(q, + _sixteenthInchInFeet); + } + + public int GetHashCode(XYZ p) + { + return Util.PointString(p).GetHashCode(); + } + } + #endregion // XyzEqualityComparer + + public VertexLookupXyz() + : base(new XyzEqualityComparer()) + { + } + + /// + /// Return the index of the given vertex, + /// adding a new entry if required. + /// + public int AddVertex(XYZ p) + { + return ContainsKey(p) + ? this[p] + : this[p] = Count; + } + } + #endregion // VertexLookupXyz + + #region VertexLookupInt + /// + /// An integer-based 3D point class. + /// + class PointInt : IComparable + { + public long X { get; set; } + public long Y { get; set; } + public long Z { get; set; } + + //public PointInt( int x, int y, int z ) + //{ + // X = x; + // Y = y; + // Z = z; + //} + + /// + /// Consider a Revit length zero + /// if is smaller than this. + /// + const double _eps = 1.0e-9; + + /// + /// Conversion factor from feet to millimetres. + /// + const double _feet_to_mm = 25.4 * 12; + + /// + /// Conversion a given length value + /// from feet to millimetre. + /// + static long ConvertFeetToMillimetres(double d) + { + if (0 < d) + { + return _eps > d + ? 0 + : (long)(_feet_to_mm * d + 0.5); + + } + else + { + return _eps > -d + ? 0 + : (long)(_feet_to_mm * d - 0.5); + + } + } + + public PointInt(XYZ p, bool switch_coordinates) + { + X = ConvertFeetToMillimetres(p.X); + Y = ConvertFeetToMillimetres(p.Y); + Z = ConvertFeetToMillimetres(p.Z); + + if (switch_coordinates) + { + X = -X; + long tmp = Y; + Y = Z; + Z = tmp; + } + } + + public int CompareTo(PointInt a) + { + long d = X - a.X; + + if (0 == d) + { + d = Y - a.Y; + + if (0 == d) + { + d = Z - a.Z; + } + } + return (0 == d) ? 0 : ((0 < d) ? 1 : -1); + } + } + + /// + /// A vertex lookup class to eliminate + /// duplicate vertex definitions. + /// + class VertexLookupInt : Dictionary + { + #region PointIntEqualityComparer + /// + /// Define equality for integer-based PointInt. + /// + class PointIntEqualityComparer : IEqualityComparer + { + public bool Equals(PointInt p, PointInt q) + { + return 0 == p.CompareTo(q); + } + + public int GetHashCode(PointInt p) + { + return (p.X.ToString() + + "," + p.Y.ToString() + + "," + p.Z.ToString()) + .GetHashCode(); + } + } + #endregion // PointIntEqualityComparer + + public VertexLookupInt() + : base(new PointIntEqualityComparer()) + { + } + + /// + /// Return the index of the given vertex, + /// adding a new entry if required. + /// + public int AddVertex(PointInt p) + { + return ContainsKey(p) + ? this[p] + : this[p] = Count; + } + } + #endregion // VertexLookupInt + + Document _doc; + Document _currentDoc; + string _filename; + SpectaclesContainer _container; + Dictionary _materials; + Dictionary _objects; + Dictionary _geometries; + Dictionary _viewsAndLayersDict; + List layerList; + + SpectaclesContainer.SpectaclesObject _currentElement; + + // Keyed on material uid to handle several materials per element: + + Dictionary _currentObject; + Dictionary _currentGeometry; + Dictionary _vertices; + + Stack _elementStack = new Stack(); + Stack _transformationStack = new Stack(); + + string _currentMaterialUid; + + public string myjs = null; + + SpectaclesContainer.SpectaclesObject CurrentObjectPerMaterial + { + get + { + return _currentObject[_currentMaterialUid]; + } + } + + SpectaclesContainer.SpectaclesGeometry CurrentGeometryPerMaterial + { + get + { + return _currentGeometry[_currentMaterialUid]; + } + } + + VertexLookupInt CurrentVerticesPerMaterial + { + get + { + return _vertices[_currentMaterialUid]; + } + } + + Transform CurrentTransform + { + get + { + return _transformationStack.Peek(); + } + } + + public override string ToString() + { + return myjs; + } + /// + /// Set the current material + /// + void SetCurrentMaterial(string uidMaterial) + { + if (!_materials.ContainsKey(uidMaterial)) + { + Material material = _currentDoc.GetElement( + uidMaterial) as Material; + + SpectaclesContainer.SpectaclesMaterial m + = new SpectaclesContainer.SpectaclesMaterial(); + + //m.metadata = new SpectaclesContainer.SpectaclesMaterialMetadata(); + //m.metadata.type = "material"; + //m.metadata.version = 4.2; + //m.metadata.generator = "Spectacles.RevitExporter 2015.0.0.0"; + + m.uuid = uidMaterial; + m.name = material.Name; + m.type = "MeshLambertMaterial"; + m.color = Util.ColorToInt(material.Color); + m.ambient = m.color; + m.emissive = 0; + m.opacity = 0.01 * (double)(100 - material.Transparency); // Revit has material.Transparency in [0,100], three.js expects opacity in [0.0,1.0] + m.transparent = 0 < material.Transparency; + m.shading = 1; + m.wireframe = false; + + _materials.Add(uidMaterial, m); + } + _currentMaterialUid = uidMaterial; + + string uid_per_material = _currentElement.uuid + "-" + uidMaterial; + + if (!_currentObject.ContainsKey(uidMaterial)) + { + Debug.Assert(!_currentGeometry.ContainsKey(uidMaterial), "expected same keys in both"); + + _currentObject.Add(uidMaterial, new SpectaclesContainer.SpectaclesObject()); + CurrentObjectPerMaterial.name = _currentElement.name; + CurrentObjectPerMaterial.geometry = uid_per_material; + CurrentObjectPerMaterial.material = _currentMaterialUid; + CurrentObjectPerMaterial.matrix = new double[] { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 }; + CurrentObjectPerMaterial.type = "Mesh"; + CurrentObjectPerMaterial.uuid = uid_per_material; + } + + if (!_currentGeometry.ContainsKey(uidMaterial)) + { + _currentGeometry.Add(uidMaterial, new SpectaclesContainer.SpectaclesGeometry()); + CurrentGeometryPerMaterial.uuid = uid_per_material; + CurrentGeometryPerMaterial.type = "Geometry"; + CurrentGeometryPerMaterial.data = new SpectaclesContainer.SpectaclesGeometryData(); + CurrentGeometryPerMaterial.data.faces = new List(); + CurrentGeometryPerMaterial.data.vertices = new List(); + CurrentGeometryPerMaterial.data.normals = new List(); + CurrentGeometryPerMaterial.data.uvs = new List(); + CurrentGeometryPerMaterial.data.visible = true; + CurrentGeometryPerMaterial.data.castShadow = true; + CurrentGeometryPerMaterial.data.receiveShadow = false; + CurrentGeometryPerMaterial.data.doubleSided = true; + CurrentGeometryPerMaterial.data.scale = 1.0; + } + + if (!_vertices.ContainsKey(uidMaterial)) + { + _vertices.Add(uidMaterial, new VertexLookupInt()); + } + } + + public SpectaclesExportContext(Document document, string filename) + { + _doc = document; + _currentDoc = document; + _filename = filename; + } + + public bool Start() + { + _materials = new Dictionary(); + _geometries = new Dictionary(); + _objects = new Dictionary(); + + _viewsAndLayersDict = new Dictionary(); + layerList = new List(); + + _transformationStack.Push(Transform.Identity); + + _container = new SpectaclesContainer(); + + _container.metadata = new SpectaclesContainer.Metadata(); + _container.metadata.type = "Object"; + _container.metadata.version = 4.3; + _container.metadata.generator = "Spectacles.RevitExporter Revit Spectacles exporter"; + _container.geometries = new List(); + + _container.obj = new SpectaclesContainer.SpectaclesObject(); + _container.obj.uuid = _currentDoc.ActiveView.UniqueId; + _container.obj.name = "BIM " + _currentDoc.Title; + _container.obj.type = "Scene"; + + // Scale entire BIM from millimetres to metres. + + _container.obj.matrix = new double[] { + _scale_bim, 0, 0, 0, + 0, _scale_bim, 0, 0, + 0, 0, _scale_bim, 0, + 0, 0, 0, _scale_bim }; + + return true; + } + + public void Finish() + { + // Finish populating scene + + _container.materials = _materials.Values.ToList(); + + _container.geometries = _geometries.Values.ToList(); + + _container.obj.children = _objects.Values.ToList(); + + if (ExportOptions.includeViews && Command.cameraNames.Count>0) + { + //create an empty string to append the list of views + string viewList = Command.cameraNames[0] + "," + Command.cameraPositions[0] + "," + Command.cameraTargets[0]; + for (int i = 1; i < Command.cameraPositions.Count; i++) + { + viewList += "," + Command.cameraNames[i] + "," + Command.cameraPositions[i] + "," + Command.cameraTargets[i]; + } + _viewsAndLayersDict.Add("views", viewList); + } + + _container.obj.userData = _viewsAndLayersDict; + + // Serialise scene + + //using( FileStream stream + // = File.OpenWrite( filename ) ) + //{ + // DataContractJsonSerializer serialiser + // = new DataContractJsonSerializer( + // typeof( SpectaclesContainer ) ); + // serialiser.WriteObject( stream, _container ); + //} + + JsonSerializerSettings settings + = new JsonSerializerSettings(); + + settings.NullValueHandling + = NullValueHandling.Ignore; + + Formatting formatting = Formatting.None; + + myjs = JsonConvert.SerializeObject( + _container, formatting, settings); + + + File.WriteAllText(_filename, myjs); + +#if USE_DYNAMIC_JSON + // This saves the whole hassle of explicitly + // defining a whole hierarchy of C# classes + // to serialise to JSON - do it all on the + // fly instead. + + // https://github.com/Spectacles/GHSpectacles/blob/master/GHSpectacles/GHSpectacles/Spectacles_geometry.cs + + dynamic jason = new ExpandoObject(); + + //populate object properties + + jason.geometry = new ExpandoObject(); + jason.groups = new object[0]; + jason.material = matName; + jason.position = new object[3]; + jason.position[0] = 0; jason.position[1] = 0; jason.position[2] = 0; + jason.rotation = new object[3]; + jason.rotation[0] = 0; jason.rotation[1] = 0; jason.rotation[2] = 0; + jason.quaternion = new object[4]; + jason.quaternion[0] = 0; jason.quaternion[1] = 0; jason.quaternion[2] = 0; jason.quaternion[3] = 0; + jason.scale = new object[3]; + jason.scale[0] = 1; jason.scale[1] = 1; jason.scale[2] = 1; + jason.visible = true; + jason.castShadow = true; + jason.receiveShadow = false; + jason.doubleSided = true; + + + //populate geometry object + jason.geometry.metadata = new ExpandoObject(); + jason.geometry.metadata.version = 3.1; + jason.geometry.metadata.generatedBy = "Spectacles.RevitExporter Revit Spectacles exporter"; + jason.geometry.metadata.vertices = mesh.Vertices.Count; + jason.geometry.metadata.faces = mesh.Faces.Count; + jason.geometry.metadata.normals = 0; + jason.geometry.metadata.colors = 0; + jason.geometry.metadata.uvs = 0; + jason.geometry.metadata.materials = 0; + jason.geometry.metadata.morphTargets = 0; + jason.geometry.metadata.bones = 0; + + jason.geometry.scale = 1.000; + jason.geometry.materials = new object[0]; + jason.geometry.vertices = new object[mesh.Vertices.Count * 3]; + jason.geometry.morphTargets = new object[0]; + jason.geometry.normals = new object[0]; + jason.geometry.colors = new object[0]; + jason.geometry.uvs = new object[0]; + jason.geometry.faces = new object[mesh.Faces.Count * 3]; + jason.geometry.bones = new object[0]; + jason.geometry.skinIndices = new object[0]; + jason.geometry.skinWeights = new object[0]; + jason.geometry.animation = new ExpandoObject(); + + //populate vertices + int counter = 0; + int i = 0; + foreach( var v in mesh.Vertices ) + { + jason.geometry.vertices[counter++] = mesh.Vertices[i].X; + jason.geometry.vertices[counter++] = mesh.Vertices[i].Y; + jason.geometry.vertices[counter++] = mesh.Vertices[i].Z; + i++; + } + + //populate faces + counter = 0; + i = 0; + foreach( var f in mesh.Faces ) + { + jason.geometry.faces[counter++] = mesh.Faces[i].A; + jason.geometry.faces[counter++] = mesh.Faces[i].B; + jason.geometry.faces[counter++] = mesh.Faces[i].C; + i++; + } + + return JsonConvert.SerializeObject( jason ); +#endif // USE_DYNAMIC_JSON + } + + public void OnPolymesh(PolymeshTopology polymesh) + { + //Debug.WriteLine( string.Format( + // " OnPolymesh: {0} points, {1} facets, {2} normals {3}", + // polymesh.NumberOfPoints, + // polymesh.NumberOfFacets, + // polymesh.NumberOfNormals, + // polymesh.DistributionOfNormals ) ); + + IList pts = polymesh.GetPoints(); + + Transform t = CurrentTransform; + + pts = pts.Select(p => t.OfPoint(p)).ToList(); + + int v1, v2, v3; + + foreach (PolymeshFacet facet + in polymesh.GetFacets()) + { + //Debug.WriteLine( string.Format( + // " {0}: {1} {2} {3}", i++, + // facet.V1, facet.V2, facet.V3 ) ); + + v1 = CurrentVerticesPerMaterial.AddVertex(new PointInt( + pts[facet.V1], _switch_coordinates)); + + v2 = CurrentVerticesPerMaterial.AddVertex(new PointInt( + pts[facet.V2], _switch_coordinates)); + + v3 = CurrentVerticesPerMaterial.AddVertex(new PointInt( + pts[facet.V3], _switch_coordinates)); + + CurrentGeometryPerMaterial.data.faces.Add(0); + CurrentGeometryPerMaterial.data.faces.Add(v1); + CurrentGeometryPerMaterial.data.faces.Add(v2); + CurrentGeometryPerMaterial.data.faces.Add(v3); + } + } + + public void OnMaterial(MaterialNode node) + { + //Debug.WriteLine( " --> On Material: " + // + node.MaterialId + ": " + node.NodeName ); + + // OnMaterial method can be invoked for every + // single out-coming mesh even when the material + // has not actually changed. Thus it is usually + // beneficial to store the current material and + // only get its attributes when the material + // actually changes. + + ElementId id = node.MaterialId; + + if (ElementId.InvalidElementId != id) + { + Element m = _currentDoc.GetElement(node.MaterialId); + SetCurrentMaterial(m.UniqueId); + } + else + { + //string uid = Guid.NewGuid().ToString(); + + // Generate a GUID based on colour, + // transparency, etc. to avoid duplicating + // non-element material definitions. + + int iColor = Util.ColorToInt(node.Color); + + string uid = string.Format("MaterialNode_{0}_{1}", + iColor, Util.RealString(node.Transparency * 100)); + + if (!_materials.ContainsKey(uid)) + { + SpectaclesContainer.SpectaclesMaterial m + = new SpectaclesContainer.SpectaclesMaterial(); + + m.uuid = uid; + m.type = "MeshLambertMaterial"; + m.color = iColor; + m.ambient = m.color; + m.emissive = 0; + m.shading = 1; + m.opacity = 1; // 128 - material.Transparency; + m.opacity = 1.0 - node.Transparency; // Revit MaterialNode has double Transparency in ?range?, three.js expects opacity in [0.0,1.0] + m.transparent = 0.0 < node.Transparency; + m.wireframe = false; + + _materials.Add(uid, m); + } + SetCurrentMaterial(uid); + } + } + + public bool IsCanceled() + { + // This method is invoked many + // times during the export process. + + return false; + } + + public void OnDaylightPortal(DaylightPortalNode node) + { + Debug.WriteLine("OnDaylightPortal: " + node.NodeName); + Asset asset = node.GetAsset(); + Debug.WriteLine("OnDaylightPortal: Asset:" + + ((asset != null) ? asset.Name : "Null")); + } + + public void OnRPC(RPCNode node) + { + Debug.WriteLine("OnRPC: " + node.NodeName); + Asset asset = node.GetAsset(); + Debug.WriteLine("OnRPC: Asset:" + + ((asset != null) ? asset.Name : "Null")); + } + + public RenderNodeAction OnViewBegin(ViewNode node) + { + Debug.WriteLine("OnViewBegin: " + + node.NodeName + "(" + node.ViewId.IntegerValue + + "): LOD: " + node.LevelOfDetail); + + return RenderNodeAction.Proceed; + } + + public void OnViewEnd(ElementId elementId) + { + Debug.WriteLine("OnViewEnd: Id: " + elementId.IntegerValue); + // Note: This method is invoked even for a view that was skipped. + } + + public RenderNodeAction OnElementBegin( + ElementId elementId) + { + Element e = _currentDoc.GetElement(elementId); + + // note: because of links and that the linked models might have had the same template, we need to + // make this further unique... + string uid = e.UniqueId + "_" + _currentDoc.Title; + + Debug.WriteLine(string.Format( + "OnElementBegin: id {0} category {1} name {2}", + elementId.IntegerValue, e.Category.Name, e.Name)); + + + + if (_objects.ContainsKey(uid)) + { + Debug.WriteLine("\r\n*** Duplicate element!\r\n"); + return RenderNodeAction.Skip; + } + + if (null == e.Category) + { + Debug.WriteLine("\r\n*** Non-category element!\r\n"); + return RenderNodeAction.Skip; + } + + _elementStack.Push(elementId); + + ICollection idsMaterialGeometry = e.GetMaterialIds(false); + ICollection idsMaterialPaint = e.GetMaterialIds(true); + + int n = idsMaterialGeometry.Count; + + if (1 < n) + { + Debug.Print("{0} has {1} materials: {2}", + Util.ElementDescription(e), n, + string.Join(", ", idsMaterialGeometry.Select( + id => _currentDoc.GetElement(id).Name))); + } + + // We handle a current element, which may either + // be identical to the current object and have + // one single current geometry or have + // multiple current child objects each with a + // separate current geometry. + + _currentElement = new SpectaclesContainer.SpectaclesObject(); + + _currentElement.name = Util.ElementDescription(e); + _currentElement.material = _currentMaterialUid; + _currentElement.matrix = new double[] { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 }; + _currentElement.type = "RevitElement"; + _currentElement.uuid = uid; + + _currentObject = new Dictionary(); + _currentGeometry = new Dictionary(); + _vertices = new Dictionary(); + + if (null != e.Category + && null != e.Category.Material) + { + SetCurrentMaterial(e.Category.Material.UniqueId); + } + + return RenderNodeAction.Proceed; + } + + public void OnElementEnd( + ElementId id) + { + // Note: this method is invoked even for + // elements that were skipped. + + Element e = _currentDoc.GetElement(id); + string uid = e.UniqueId; + + Debug.WriteLine(string.Format( + "OnElementEnd: id {0} category {1} name {2}", + id.IntegerValue, e.Category.Name, e.Name)); + + if (_elementStack.Contains(id) == false) return; // it was skipped? + + if (_objects.ContainsKey(uid)) + { + Debug.WriteLine("\r\n*** Duplicate element!\r\n"); + return; + } + + if (null == e.Category) + { + Debug.WriteLine("\r\n*** Non-category element!\r\n"); + return; + } + + List materials = _vertices.Keys.ToList(); + + int n = materials.Count; + + _currentElement.children = new List(n); + + foreach (string material in materials) + { + SpectaclesContainer.SpectaclesObject obj = _currentObject[material]; + SpectaclesContainer.SpectaclesGeometry geo = _currentGeometry[material]; + + foreach (KeyValuePair p in _vertices[material]) + { + geo.data.vertices.Add(_scale_vertex * p.Key.X); + geo.data.vertices.Add(_scale_vertex * p.Key.Y); + geo.data.vertices.Add(_scale_vertex * p.Key.Z); + } + obj.geometry = geo.uuid; + + //QUESTION: Should we attempt to further ensure uniqueness? or should we just update the geometry that is there? + //old: _geometries.Add(geo.uuid, geo); + _geometries[geo.uuid] = geo; + _currentElement.children.Add(obj); + } + + //Dictionary d = Util.GetElementProperties(e, true); + Dictionary d = new Dictionary(); + if (Command._filterParameters) + { + d = Util.GetElementFilteredProperties(e, true); + } + else + { + d = Util.GetElementProperties(e, true); + } + + string layerName = e.Category.Name; + + //add a property for layer + d.Add("layer", layerName); + + + + if (!_viewsAndLayersDict.ContainsKey("layers")) _viewsAndLayersDict.Add("layers", layerName); + else + { + if (!layerList.Contains(layerName)) + { + _viewsAndLayersDict["layers"] += "," + layerName; + } + } + + if (!layerList.Contains(layerName)) layerList.Add(layerName); + + _currentElement.userData = d; + + //also add guid to user data dict + _currentElement.userData.Add("revit_id", uid); + + _objects[_currentElement.uuid] = _currentElement; + + _elementStack.Pop(); + } + + public RenderNodeAction OnFaceBegin(FaceNode node) + { + // This method is invoked only if the + // custom exporter was set to include faces. + + Debug.Assert(false, "we set exporter.IncludeFaces false"); + Debug.WriteLine(" OnFaceBegin: " + node.NodeName); + return RenderNodeAction.Proceed; + } + + public void OnFaceEnd(FaceNode node) + { + // This method is invoked only if the + // custom exporter was set to include faces. + + Debug.Assert(false, "we set exporter.IncludeFaces false"); + Debug.WriteLine(" OnFaceEnd: " + node.NodeName); + // Note: This method is invoked even for faces that were skipped. + } + + public RenderNodeAction OnInstanceBegin(InstanceNode node) + { + Debug.WriteLine(" OnInstanceBegin: " + node.NodeName + " symbol: " + node.GetSymbolId().IntegerValue); + // This method marks the start of processing a family instance + _transformationStack.Push(CurrentTransform.Multiply(node.GetTransform())); + + // We can either skip this instance or proceed with rendering it. + return RenderNodeAction.Proceed; + } + + public void OnInstanceEnd(InstanceNode node) + { + Debug.WriteLine(" OnInstanceEnd: " + node.NodeName); + // Note: This method is invoked even for instances that were skipped. + _transformationStack.Pop(); + } + + public RenderNodeAction OnLinkBegin(LinkNode node) + { + Debug.WriteLine(" OnLinkBegin: " + node.NodeName + " Document: " + node.GetDocument().Title + ": Id: " + node.GetSymbolId().IntegerValue); + _currentDoc = node.GetDocument(); + _transformationStack.Push(CurrentTransform.Multiply(node.GetTransform())); + return RenderNodeAction.Proceed; + } + + public void OnLinkEnd(LinkNode node) + { + Debug.WriteLine(" OnLinkEnd: " + node.NodeName); + // reset for the original document + _currentDoc = _doc; + + // Note: This method is invoked even for instances that were skipped. + _transformationStack.Pop(); + } + + public void OnLight(LightNode node) + { + Debug.WriteLine("OnLight: " + node.NodeName); + Asset asset = node.GetAsset(); + Debug.WriteLine("OnLight: Asset:" + ((asset != null) ? asset.Name : "Null")); + } + } +} diff --git a/src/Spectacles.RevitExporter/packages.config b/src/Spectacles.RevitExporter/packages.config index 945d431..55658b8 100644 --- a/src/Spectacles.RevitExporter/packages.config +++ b/src/Spectacles.RevitExporter/packages.config @@ -1,4 +1,4 @@ - - - + + + \ No newline at end of file