By Adam Nagy
In preparation for our DevCamp's we looked into what sort of demo applications we could prepare that would show examples of how to take advantage of Cloud and Mobile technologies in context of Autodesk products.
My colleague, Philippe, already created an Inventor file viewer for Android, so I had a look at that thinking about writing a similar solution for Revit with a viewer on iOS. His solution includes an Inventor AddIn that can upload Inventor files to a custom web service, which on request from a mobile client can generate the geometrical information from an Inventor file using Inventor Apprentice, and then pass it back to the mobile device that renders the geometry using OpenGL ES.
There will be some differences in our case though:
- There is no publicly available technology like Inventor Apprentice for Revit, so we'll have to retrieve the geometry from the model inside the Revit Add-In
- The geometry data we get back from Revit is organized differently from Inventor
- iOS 5 introduced GLKit, so I was interested in using that instead of solely relying on OpenGL function calls
My solution includes the following:
- A Revit AddIn that gets all the geometrical information (facets) from the model and uploads that to the Cloud - in this case to my Amazon Simple Storage Service (S3) account. Just to keep things simple the format of the data will be proprietary - it will simply contain a list of the coordinates of all the facet triangles. I chose Amazon S3 because this seemed to have really good toolkits for both iOS and .NET, the two platforms I will be using.
- An iOS application that accesses the data from the cloud storage and displays it using OpenGL ES
The Revit AddIn part
For easy access to the Amazon service I installed the AWS .NET SDK: http://aws.amazon.com/sdkfornet/
Inside my project I just needed to add a reference to C:\Program Files\AWS SDK for .NET\bin\AWSSDK.dll and then I could start accessing the cloud storage. Here is the function that uploads the data that we gather from the Revit model to the storage service - you just need to replace kAccessKey and kSecretAccessKey with your own keys:
Private Const kAccessKey As String = ""
Private Const kSecretAccessKey As String = ""
Private Const kBucketName As String = "RevitModelViewer"
Private Function UploadToAmazon(
ByVal memoryStream As MemoryStream) As Boolean
Using client As AmazonS3 =
Amazon.AWSClientFactory.CreateAmazonS3Client(
kAccessKey, kSecretAccessKey)
Try
Dim bucketRequest As New PutBucketRequest
bucketRequest.WithBucketName(kBucketName)
Using response As S3Response = client.PutBucket(bucketRequest)
End Using
Dim objectRequest As New PutObjectRequest
objectRequest.WithBucketName(kBucketName)
objectRequest.WithInputStream(memoryStream)
objectRequest.WithKey(mDoc.Title)
Using response As S3Response = client.PutObject(objectRequest)
End Using
Catch ex As AmazonS3Exception
MessageBox.Show(ex.Message, "Model Uploader")
Return False
End Try
End Using
Return True
End Function
Revit SDK already contains a sample that gathers geometrical information from the model and displays it in a custom dialog: <Revit SDK>\Samples\Viewers\ElementViewer. I modified it to only collect solid geometry and then instead of showing it in our dialog it uploads the data to the cloud storage.
In this case we do not even need the transformation functions since we can simply use the model space coordinates.
The geometrical information will be stored on a per face basis as the color can change at that level. Inside each Face section we will list all facets of the face with their coordinates - 3 coordinates and the corresponding normal vector which will be needed by OpenGL. We will calculate the normal vector from the coordinates of the 3 vertices.
>>>>>
Face;<red>, <green>, <blue>
<x1>, <y1>, <z1>;<x2>, <y2>, <z2>;<x3>, <y3>, <z3>;<xn>, <yn>, <zn>
etc.
<<<<<
Private mOptions As Options
Private mApplication _
As Autodesk.Revit.ApplicationServices.Application
Private mSolidCounter As Long
Private mFaceCounter As Long
Private mDoc As Document
Private mFile As TextWriter
Public Function Execute( _
ByVal commandData As ExternalCommandData, ByRef message As String, _
ByVal elements As ElementSet) _
As Result Implements IExternalCommand.Execute
' Initialize global variables
mApplication = commandData.Application.Application
mDoc = commandData.Application.ActiveUIDocument.Document
mOptions = _
commandData.Application.Application.Create.NewGeometryOptions
mOptions.DetailLevel = ViewDetailLevel.Fine
mSolidCounter = 0
mFaceCounter = 0
' Collect all the solids in the model
Using ms As New MemoryStream
Using textFile As TextWriter = New StreamWriter(ms)
mFile = textFile
For Each elem As Element In GetAllModelElements(mDoc)
DrawElement(elem)
Next
textFile.Flush()
If Not UploadToAmazon(ms) Then mSolidCounter = -1
End Using
End Using
mApplication = Nothing
' Show result
If mSolidCounter >= 0 Then
MessageBox.Show(
"File """ + mDoc.Title + """ with " +
mSolidCounter.ToString() + " solids having " +
mFaceCounter.ToString() + " faces has been uploaded " +
"to the Cloud",
"Model Uploader")
End If
Return Result.Succeeded
End Function
Private Function GetAllModelElements(ByVal doc As Document) _
As IList(Of Element)
Dim elements As New List(Of Element)()
Dim collector As FilteredElementCollector = _
New FilteredElementCollector(doc).WhereElementIsNotElementType()
For Each e As Element In collector
If e.Category IsNot Nothing Then
elements.Add(e)
End If
Next
Return elements
End Function
Private Sub DrawElement(ByVal elem As Element)
If TypeOf elem Is Group Then
Dim group As Group = elem
Dim memberIds As IList(Of ElementId) = group.GetMemberIds()
For Each id As ElementId In memberIds
Dim elm As Element = mDoc.GetElement(id)
DrawElement(elem)
Next
Else
Dim geom As GeometryElement = elem.Geometry(mOptions)
If Not (geom Is Nothing) Then
' If a family instance then we need the version
' that is transformed into the model space
If TypeOf elem Is FamilyInstance Then geom = _
geom.GetTransformed(Transform.Identity)
DrawElement(geom)
End If
End If
End Sub
Private Sub DrawElement(ByVal elementGeom As GeometryElement)
If elementGeom Is Nothing Then
Exit Sub
End If
Dim geomObject As GeometryObject
For Each geomObject In elementGeom
If (TypeOf geomObject Is GeometryInstance) Then
DrawInstance(geomObject)
ElseIf (TypeOf geomObject Is Solid) Then
DrawSolid(geomObject)
End If
Next
End Sub
Private Sub DrawInstance(ByVal geomInstance As GeometryInstance)
Dim geomSymbol As GeometryElement
geomSymbol = geomInstance.SymbolGeometry
If Not (geomSymbol Is Nothing) Then
DrawElement(geomSymbol)
End If
End Sub
Private Function XyzToString(ByVal pt As XYZ) As String
Return (
pt.X.ToString(CultureInfo.InvariantCulture) + ", " +
pt.Y.ToString(CultureInfo.InvariantCulture) + ", " +
pt.Z.ToString(CultureInfo.InvariantCulture))
End Function
Private Sub DrawMesh(ByVal geomMesh As Mesh)
Dim i As Integer
For i = 0 To geomMesh.NumTriangles - 1
Dim triangle As MeshTriangle
triangle = geomMesh.Triangle(i)
' Calculate normal
Dim u As XYZ = triangle.Vertex(1).Subtract(triangle.Vertex(0))
Dim v As XYZ = triangle.Vertex(2).Subtract(triangle.Vertex(0))
Dim n As XYZ = u.CrossProduct(v).Normalize()
mFile.WriteLine("{0};{1};{2};{3}",
XyzToString(triangle.Vertex(0)),
XyzToString(triangle.Vertex(1)),
XyzToString(triangle.Vertex(2)),
XyzToString(n))
Next
End Sub
Private Sub DrawSolid(ByVal geomSolid As Solid)
If geomSolid.Faces.Size > 0 Then
Dim face As Face
For Each face In geomSolid.Faces
DrawFace(face)
Next
mSolidCounter += 1
End If
End Sub
Private Sub DrawFace(ByVal geomFace As Face)
Dim mat As Material = mDoc.GetElement(geomFace.MaterialElementId)
Dim col As Color = New Color(128, 128, 128)
If mat IsNot Nothing Then col = mat.Color
mFile.WriteLine( _
"Face;{0}, {1}, {2}", col.Red, col.Green, col.Blue)
DrawMesh(geomFace.Triangulate)
mFaceCounter += 1
End Sub
The Revit AddIn part is done - you can download it from here: Download RevitModelViewer - Revit AddIn - 2012-06-19
Once you compiled the project and set it up according to the readme.txt file so that it gets automatically loaded into Revit, then you can just open up any project and run the command from the Ribbon, Add-Ins tab >> External Tools >> Model Uploader. If the upload succeeds then you'll see a message like this:
The AWS .NET SDK also includes a Visual Studio Add-In that makes it quite easy to check the uploaded content:
In the next post we will look at implementing the iOS model viewer application.