寻求Revit转SVF/GLTF开源代码及Forge Viewer离线查看方案
Revit转SVF/GLTF用于Forge Viewer离线查看的开源方案
一、Revit转SVF的开源实现思路
SVF是Autodesk专属格式,没有完全开源的直接导出代码,但可以基于Revit API提取模型核心数据,参考SVF结构规范组织成兼容格式,以下是核心代码片段(C#,基于Revit API):
using Autodesk.Revit.DB; using System.Text.Json; using System.IO; public void ExportToSVFStructure(Document doc, string outputPath) { // 创建输出目录 Directory.CreateDirectory(outputPath); var manifestContent = new System.Text.StringBuilder("["); string geometryPath = Path.Combine(outputPath, "geometry.bin"); // 遍历可见元素 var collector = new FilteredElementCollector(doc) .WhereElementIsNotElementType() .WhereElementIsViewIndependent(); foreach (Element elem in collector) { var geoElem = elem.get_Geometry(new Options()); if (geoObj is Mesh mesh) { // 写入几何二进制数据 using (var writer = new BinaryWriter(File.Open(geometryPath, FileMode.Append))) { // 写入顶点数据 foreach (XYZ vertex in mesh.Vertices) { writer.Write((float)vertex.X); writer.Write((float)vertex.Y); writer.Write((float)vertex.Z); } // 写入三角索引 foreach (int idx in mesh.GetTriangles()) { writer.Write((ushort)idx); } } } // 写入元素元数据到清单 var metadata = new { id = elem.Id.IntegerValue, name = elem.Name, category = elem.Category?.Name }; manifestContent.Append(JsonSerializer.Serialize(metadata) + ","); } // 完善并保存清单文件 manifestContent.Remove(manifestContent.Length - 1, 1).Append("]"); File.WriteAllText(Path.Combine(outputPath, "manifest.json"), manifestContent.ToString()); }
注:上述代码仅实现基础数据提取,完整SVF格式需补充材质、纹理、层级关系等细节,可通过解析Forge导出的SVF文件参考完整结构规范。
二、Revit转GLTF并兼容Forge Viewer的无报错代码
如果选择GLTF,需确保导出文件符合glTF 2.0规范,且适配Forge Viewer的加载逻辑,以下是基于Revit API和glTF-SDK的完整实现:
using Autodesk.Revit.DB; using glTFLoader.Schema; using System.Numerics; using System.Text.Json; using System.IO; public void ExportToGLTF(Document doc, string outputPath) { var gltf = new Gltf { Accessors = new List<Accessor>(), Buffers = new List<Buffer>(), BufferViews = new List<BufferView>(), Meshes = new List<Mesh>(), Nodes = new List<Node>(), Scenes = new List<Scene> { new Scene { Nodes = new List<int> { 0 } } } }; using (var ms = new MemoryStream()) { var collector = new FilteredElementCollector(doc) .WhereElementIsNotElementType() .WhereElementIsViewIndependent(); int nodeIdx = 0; foreach (Element elem in collector) { var geoElem = elem.get_Geometry(new Options { DetailLevel = ViewDetailLevel.Fine }); Mesh mesh = null; foreach (var geoObj in geoElem) { if (geoObj is Solid solid) mesh = solid.Triangulate(); else if (geoObj is Mesh m) mesh = m; if (mesh != null) break; } if (mesh == null) continue; // 写入顶点数据 long vertexOffset = ms.Position; foreach (XYZ vertex in mesh.Vertices) { ms.Write(BitConverter.GetBytes((float)vertex.X)); ms.Write(BitConverter.GetBytes((float)vertex.Y)); ms.Write(BitConverter.GetBytes((float)vertex.Z)); } // 写入索引数据 long indexOffset = ms.Position; foreach (int idx in mesh.GetTriangles()) { ms.Write(BitConverter.GetBytes((ushort)idx)); } // 创建顶点Accessor gltf.Accessors.Add(new Accessor { BufferView = gltf.BufferViews.Count, ComponentType = Accessor.ComponentType.UNSIGNED_SHORT, Count = mesh.Vertices.Count, Type = Accessor.Type.VEC3, Min = new List<float> { mesh.GetBoundingBox().Min.X, mesh.GetBoundingBox().Min.Y, mesh.GetBoundingBox().Min.Z }, Max = new List<float> { mesh.GetBoundingBox().Max.X, mesh.GetBoundingBox().Max.Y, mesh.GetBoundingBox().Max.Z } }); // 创建索引Accessor gltf.Accessors.Add(new Accessor { BufferView = gltf.BufferViews.Count + 1, ComponentType = Accessor.ComponentType.UNSIGNED_SHORT, Count = mesh.NumTriangles * 3, Type = Accessor.Type.SCALAR }); // 创建顶点BufferView gltf.BufferViews.Add(new BufferView { Buffer = 0, ByteOffset = (int)vertexOffset, ByteLength = mesh.Vertices.Count * 12, Target = BufferView.Target.ARRAY_BUFFER }); // 创建索引BufferView gltf.BufferViews.Add(new BufferView { Buffer = 0, ByteOffset = (int)indexOffset, ByteLength = mesh.NumTriangles * 3 * 2, Target = BufferView.Target.ELEMENT_ARRAY_BUFFER }); // 创建Mesh和Node gltf.Meshes.Add(new Mesh { Primitives = new List<MeshPrimitive> { new MeshPrimitive { Attributes = new Dictionary<string, int> { { "POSITION", gltf.Accessors.Count - 2 } }, Indices = gltf.Accessors.Count - 1 } }, Name = elem.Name }); gltf.Nodes.Add(new Node { Mesh = gltf.Meshes.Count - 1, Name = elem.Name, Matrix = GetTransformMatrix(elem.GetTransform()) }); gltf.Scenes[0].Nodes.Add(nodeIdx); nodeIdx++; } // 写入主Buffer gltf.Buffers.Add(new Buffer { ByteLength = (int)ms.Length, Uri = $"data:application/octet-stream;base64,{Convert.ToBase64String(ms.ToArray())}" }); // 保存GLTF文件 File.WriteAllText(outputPath, JsonSerializer.Serialize(gltf, new JsonSerializerOptions { WriteIndented = true })); } } // 转换Revit Transform为glTF矩阵 private float[] GetTransformMatrix(Transform transform) { var matrix = Matrix4x4.CreateTranslation(new Vector3((float)transform.Origin.X, (float)transform.Origin.Y, (float)transform.Origin.Z)) * Matrix4x4.CreateFromQuaternion(transform.Basis.ToQuaternion()); return new float[] { matrix.M11, matrix.M12, matrix.M13, matrix.M14, matrix.M21, matrix.M22, matrix.M23, matrix.M24, matrix.M31, matrix.M32, matrix.M33, matrix.M34, matrix.M41, matrix.M42, matrix.M43, matrix.M44 }; }
加载注意事项:
- 先通过NuGet安装
glTF-SDK包确保格式合规- Forge Viewer加载时需指定单位匹配Revit:
viewer.loadModel('./your-model.gltf', { applyScaling: 'm' });
内容的提问来源于stack exchange,提问作者oğuzhan kömcü
相关产品推荐
相关产品推荐

