Blog

BIM model: vise 3D model af rum i browser

Dato:

En af mine kunder er en arkitekt tegnestue, der laver rigtige flotte modeller af bygninger og hele beboelsesområder via Autodesk Revit.

Autodesk Forge gør det muligt at vise enhver model i browser ved at konvertere den til et JSON objekt og åbne med Autodesk 3D viewer Javascript plugin.

I denne blogpost vil jeg gerne demonstrere hvordan samme resultat kan opnås uden at benytte Forge. Jeg har tidligere skrevet et par Revit plugins for at trække forskellige oplysninger fra modellen og vise dem på et website. På samme måde kan der trækkes bygningsoverflader ud og sættes sammen i et JSON objekt, som kan efterfølgende vises med en WebGLRender komponent, f.eks. THREE.js

Lad os gå i gang med at oprette et simpelt projekt i Visual Studio. Tilføj en reference til RevitAPI.dll og en ny Main.cs class-fil:

[Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
[Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
public class Main : IExternalApplication
{

    public Result OnStartup(UIControlledApplication application)
    {
        application.ControlledApplication.DocumentOpened += ControlledApplication_DocumentOpened;
        return Result.Succeeded;
    }

    private void ControlledApplication_DocumentOpened(object sender, Autodesk.Revit.DB.Events.DocumentOpenedEventArgs e)
    {
        if (e.Status == Autodesk.Revit.DB.Events.RevitAPIEventStatus.Succeeded)
        {
            // Her kan vi trække bygningsdata ud
        }
    }

}

Der skal kun vises rumene i 3D viweren, resten skal vi se bort fra. Så vi trækker objekterne ud med denne query:

var apartments = from r in new FilteredElementCollector(e.Document).OfCategory(BuiltInCategory.OST_Rooms).ToElements()
            let housingNumber = r.LookupParameterAsString(LookupField.HousingNumber)
            where housingNumber != null
            orderby housingNumber
            group r by housingNumber into g
            select new { HousingNumber = g.Key, Rooms = g.Cast().ToList() };

I hver lejlighed er der flere rum, lad os løbe dem igennem og beregne deres overflader

var calculator = new SpatialElementGeometryCalculator(e.Document);
foreach (var apartment in apartments)
{
    foreach (var room in apartment.Rooms)
    {
        var faces = GetRoomFaces(room, calculator, ref boundingBox);
        foreach (var face in faces)
        {
            if (face == null || face.Visibility == Autodesk.Revit.DB.Visibility.Invisible)
            {
                continue;
            }
            rectangularFaces.Add(new KeyValuePair<Face, string>(face, apartment.HousingNumber));
        }
    }
}

Lad os kigge nærmere på GetRoomFaces metode:

private static List GetRoomFaces(Room room, SpatialElementGeometryCalculator calculator, ref BoundingBoxXYZ boundingBox)
{
    var result = new List();
    SpatialElementGeometryResults results;
    try
    {
        results = calculator.CalculateSpatialElementGeometry(room);
    }
    catch (Autodesk.Revit.Exceptions.ArgumentException)
    {
        return result;
    }
    var roomSolid = results.GetGeometry();
    ReferenceIntersector wallIntersector = null;
    ReferenceIntersector floorIntersector = null;

    var openDoc = room.Document;
    View3D view3D;
    var collector = new FilteredElementCollector(openDoc);
    try
    {
        view3D = collector.OfClass(typeof(View3D)).Cast().First(v3 => !(v3.IsTemplate));
    }
    catch
    {
        throw new Exception("Document must contain a 3D view.");
    }
    wallIntersector = new ReferenceIntersector(new ElementClassFilter(typeof(Wall)), FindReferenceTarget.Face, view3D);
    var horizontalObjectFilters = new List();
    horizontalObjectFilters.Add(new ElementClassFilter(typeof(Floor)));
    horizontalObjectFilters.Add(new ElementClassFilter(typeof(RoofType)));
    horizontalObjectFilters.Add(new ElementClassFilter(typeof(Ceiling)));
    floorIntersector = new ReferenceIntersector(new LogicalOrFilter(horizontalObjectFilters), FindReferenceTarget.Face, view3D);
            
    foreach (Face face in roomSolid.Faces)
    {
        if (IsFaceVisible(face, wallIntersector, floorIntersector, ref boundingBox))
        {
            result.Add(face);
        }
    }
    return result;
}

undefined

Revit GetGeometry() metode henter geometri fra hvert rum: vægge, lofte, gulve. Hvert geomtri objekt består af overflader (faces). Vi vælger kun de udvendige overflader af en lejlighed. Derfor skal der sendes en stråle fra hver en overflade, hvis den rammer et andet objekt, tilhørende lejligheden, er det ikke en udvendig overflade. I illustrationen til venstre er det kun de udvendige vægge, markerede med sort, vil blive trukket ud.

Hver en overflade bliver kontrolleret for at den afviger fra basis Z stråle (0,0,1) eller (0,0,-1) mere end 5 grader. Hvis den gør, er den placeret vandret (loft, gulv eller tag), ellers er det en væg.

private static bool IsFaceVisible(Face face, bool removeInternalWalls, ReferenceIntersector verticalIntersector, ReferenceIntersector horizontalIntersector, ref BoundingBoxXYZ boundingBox)
{
    var vertices = face.GetEdgesAsCurveLoops().SelectMany(e => e).Select(c => c.GetEndPoint(0)).ToList();
    TriangulatedFace.UpdateBoundingBox(vertices, ref boundingBox);
    if (!removeInternalWalls)
    {
        return true;
    }
    var faceNormal = face.ComputeNormal(new UV(0, 0));
    var intersector = (Math.Abs(faceNormal.AngleTo(XYZ.BasisZ)) < 5 * Math.PI / 180) || (Math.Abs(faceNormal.AngleTo(XYZ.BasisZ.Negate())) < 5 * Math.PI / 180) ? horizontalIntersector : verticalIntersector;
    var visible = false;
    foreach (var v in vertices)
    {
        var intersections = intersector.Find(v, faceNormal).ToList();
        if (!intersections.Any(p => p.Proximity > 1.5))
        {
            visible = true;
            break;
        }
    }
    return visible;
}

Bemærk, at hvis et andet objekt ligger mindre end 1,5 tommer fra væggen, antager vi, det er samme væg. Da startpunktet kan ligger inde i væggen. Efterfølgende firkantede overflader skal konverteres til trekantede, som vil kunne blive vist med 3D viweren. Strukturen ser sådan ud:

{
indices: [],   // int[]
vertices: []   // XYZ[]
}

Man kan med fordel bruge face.Triangulate() metode fra Revit API. Den returnerer Mesh typen. Resultatet ser sådan ud i browservinduet.

undefined

Semyon

Ejer / .NET udvikler