/// <summary>
        /// Exports spatial elements, including rooms, areas and spaces. 1st level space boundaries.
        /// </summary>
        /// <param name="exporterIFC">
        /// The ExporterIFC object.
        /// </param>
        /// <param name="spatialElement">
        /// The spatial element.
        /// </param>
        /// <param name="productWrapper">
        /// The ProductWrapper.
        /// </param>
        public static void ExportSpatialElement(ExporterIFC exporterIFC, SpatialElement spatialElement, ProductWrapper productWrapper)
        {
            //quick reject
            bool isArea = spatialElement is Area;
            if (isArea)
            {
                if (!IsAreaGrossInterior(exporterIFC, spatialElement))
                    return;
            }

            IFCFile file = exporterIFC.GetFile();
            using (IFCTransaction transaction = new IFCTransaction(file))
            {
                ElementId levelId = spatialElement.Level != null ? spatialElement.Level.Id : ElementId.InvalidElementId;
                using (IFCPlacementSetter setter = IFCPlacementSetter.Create(exporterIFC, spatialElement, null, null, levelId))
                {
                    if (!CreateIFCSpace(exporterIFC, spatialElement, productWrapper, setter))
                        return;

                    // Do not create boundary information, or extra property sets.
                    if (spatialElement is Area)
                    {
                        transaction.Commit();
                        return;
                    }

                    if (ExporterCacheManager.ExportOptionsCache.SpaceBoundaryLevel == 1)
                    {
                        Document document = spatialElement.Document;
                        IFCLevelInfo levelInfo = exporterIFC.GetLevelInfo(levelId);
                        double baseHeightNonScaled = levelInfo.Elevation;

                        try
                        {
                            // This can throw an exception.  If it does, continue to export element without boundary information.
                            // TODO: warn user.
                            SpatialElementGeometryResults spatialElemGeomResult = s_SpatialElementGeometryCalculator.CalculateSpatialElementGeometry(spatialElement);

                            Solid spatialElemGeomSolid = spatialElemGeomResult.GetGeometry();
                            FaceArray faces = spatialElemGeomSolid.Faces;
                            foreach (Face face in faces)
                            {
                                IList<SpatialElementBoundarySubface> spatialElemBoundarySubfaces = spatialElemGeomResult.GetBoundaryFaceInfo(face);
                                foreach (SpatialElementBoundarySubface spatialElemBSubface in spatialElemBoundarySubfaces)
                                {
                                    if (spatialElemBSubface.SubfaceType == SubfaceType.Side)
                                        continue;

                                    if (spatialElemBSubface.GetSubface() == null)
                                        continue;

                                    ElementId elemId = spatialElemBSubface.SpatialBoundaryElement.LinkInstanceId;
                                    if (elemId == ElementId.InvalidElementId)
                                    {
                                        elemId = spatialElemBSubface.SpatialBoundaryElement.HostElementId;
                                    }

                                    Element boundingElement = document.GetElement(elemId);
                                    if (boundingElement == null)
                                        continue;

                                    bool isObjectExt = CategoryUtil.IsElementExternal(boundingElement, true);

                                    IFCGeometryInfo info = IFCGeometryInfo.CreateSurfaceGeometryInfo(spatialElement.Document.Application.VertexTolerance);

                                    Face subFace = spatialElemBSubface.GetSubface();
                                    ExporterIFCUtils.CollectGeometryInfo(exporterIFC, info, subFace, XYZ.Zero, false);

                                    foreach (IFCAnyHandle surfaceHnd in info.GetSurfaces())
                                    {
                                        IFCAnyHandle connectionGeometry = IFCInstanceExporter.CreateConnectionSurfaceGeometry(file, surfaceHnd, null);

                                        SpaceBoundary spaceBoundary = new SpaceBoundary(spatialElement.Id, boundingElement.Id, setter.LevelId, connectionGeometry, IFCPhysicalOrVirtual.Physical,
                                            isObjectExt ? IFCInternalOrExternal.External : IFCInternalOrExternal.Internal);

                                        if (!ProcessIFCSpaceBoundary(exporterIFC, spaceBoundary, file))
                                            ExporterCacheManager.SpaceBoundaryCache.Add(spaceBoundary);
                                    }
                                }
                            }
                        }
                        catch
                        {
                        }

                        IList<IList<BoundarySegment>> roomBoundaries = spatialElement.GetBoundarySegments(ExporterIFCUtils.GetSpatialElementBoundaryOptions(exporterIFC, spatialElement));
                        double roomHeight = GetHeight(spatialElement, exporterIFC.LinearScale, levelId, levelInfo);
                        XYZ zDir = new XYZ(0, 0, 1);

                        foreach (IList<BoundarySegment> roomBoundaryList in roomBoundaries)
                        {
                            foreach (BoundarySegment roomBoundary in roomBoundaryList)
                            {
                                Element boundingElement = roomBoundary.Element;

                                if (boundingElement == null)
                                    continue;

                                ElementId buildingElemId = boundingElement.Id;
                                Curve trimmedCurve = roomBoundary.Curve;

                                if (trimmedCurve == null)
                                    continue;

                                //trimmedCurve.Visibility = Visibility.Visible; readonly
                                IFCAnyHandle connectionGeometry = ExtrusionExporter.CreateExtrudedSurfaceFromCurve(
                                   exporterIFC, trimmedCurve, zDir, roomHeight, baseHeightNonScaled);

                                IFCPhysicalOrVirtual physOrVirt = IFCPhysicalOrVirtual.Physical;
                                if (boundingElement is CurveElement)
                                    physOrVirt = IFCPhysicalOrVirtual.Virtual;
                                else if (boundingElement is Autodesk.Revit.DB.Architecture.Room)
                                    physOrVirt = IFCPhysicalOrVirtual.NotDefined;

                                bool isObjectExt = CategoryUtil.IsElementExternal(boundingElement, true);
                                bool isObjectPhys = (physOrVirt == IFCPhysicalOrVirtual.Physical);

                                ElementId actualBuildingElemId = isObjectPhys ? buildingElemId : ElementId.InvalidElementId;

                                SpaceBoundary spaceBoundary = new SpaceBoundary(spatialElement.Id, actualBuildingElemId, setter.LevelId, !IFCAnyHandleUtil.IsNullOrHasNoValue(connectionGeometry) ? connectionGeometry : null,
                                    physOrVirt, isObjectExt ? IFCInternalOrExternal.External : IFCInternalOrExternal.Internal);

                                if (!ProcessIFCSpaceBoundary(exporterIFC, spaceBoundary, file))
                                    ExporterCacheManager.SpaceBoundaryCache.Add(spaceBoundary);

                                // try to add doors and windows for host objects if appropriate.
                                if (isObjectPhys && boundingElement is HostObject)
                                {
                                    HostObject hostObj = boundingElement as HostObject;
                                    HashSet<ElementId> elemIds = new HashSet<ElementId>();
                                    elemIds.UnionWith(hostObj.FindInserts(false, false, false, false));
                                    if (elemIds.Count == 0)
                                    {
                                        CurtainGridSet curtainGridSet = CurtainSystemExporter.GetCurtainGridSet(hostObj);
                                        if (curtainGridSet != null)
                                        {
                                            foreach (CurtainGrid curtainGrid in curtainGridSet)
                                                elemIds.UnionWith(curtainGrid.GetPanelIds());
                                        }
                                    }

                                    foreach (ElementId elemId in elemIds)
                                    {
                                        // we are going to do a simple bbox export, not complicated geometry.
                                        Element instElem = document.GetElement(elemId);
                                        if (instElem == null)
                                            continue;

                                        BoundingBoxXYZ instBBox = instElem.get_BoundingBox(null);
                                        if (instBBox == null)
                                            continue;

                                        // make copy of original trimmed curve.
                                        Curve instCurve = trimmedCurve.Clone();
                                        XYZ instOrig = instCurve.get_EndPoint(0);

                                        // make sure that the insert is on this level.
                                        if (instBBox.Max.Z < instOrig.Z)
                                            continue;
                                        if (instBBox.Min.Z > instOrig.Z + roomHeight)
                                            continue;

                                        double insHeight = Math.Min(instBBox.Max.Z, instOrig.Z + roomHeight) - Math.Max(instOrig.Z, instBBox.Min.Z);
                                        if (insHeight < (1.0 / (12.0 * 16.0)))
                                            continue;

                                        // move base curve to bottom of bbox.
                                        XYZ moveDir = new XYZ(0.0, 0.0, instBBox.Min.Z - instOrig.Z);
                                        Transform moveTrf = Transform.get_Translation(moveDir);
                                        instCurve = instCurve.get_Transformed(moveTrf);

                                        bool isHorizOrVert = false;
                                        if (instCurve is Line)
                                        {
                                            Line instLine = instCurve as Line;
                                            XYZ lineDir = instLine.Direction;
                                            if (MathUtil.IsAlmostEqual(Math.Abs(lineDir.X), 1.0) || (MathUtil.IsAlmostEqual(Math.Abs(lineDir.Y), 1.0)))
                                                isHorizOrVert = true;
                                        }

                                        double[] parameters = new double[2];
                                        double[] origEndParams = new double[2];
                                        bool paramsSet = false;

                                        if (!isHorizOrVert)
                                        {
                                            FamilyInstance famInst = instElem as FamilyInstance;
                                            if (famInst == null)
                                                continue;

                                            ElementType elementType = document.GetElement(famInst.GetTypeId()) as ElementType;
                                            if (elementType == null)
                                                continue;

                                            BoundingBoxXYZ symBBox = elementType.get_BoundingBox(null);
                                            if (symBBox != null)
                                            {
                                                Curve symCurve = trimmedCurve.Clone();
                                                Transform trf = famInst.GetTransform();
                                                Transform invTrf = trf.Inverse;
                                                Curve trfCurve = symCurve.get_Transformed(invTrf);
                                                parameters[0] = trfCurve.Project(symBBox.Min).Parameter;
                                                parameters[1] = trfCurve.Project(symBBox.Max).Parameter;
                                                paramsSet = true;
                                            }
                                        }

                                        if (!paramsSet)
                                        {
                                            parameters[0] = instCurve.Project(instBBox.Min).Parameter;
                                            parameters[1] = instCurve.Project(instBBox.Max).Parameter;
                                        }

                                        // ignore if less than 1/16".
                                        if (Math.Abs(parameters[1] - parameters[0]) < 1.0 / (12.0 * 16.0))
                                            continue;
                                        if (parameters[0] > parameters[1])
                                        {
                                            //swap
                                            double tempParam = parameters[0];
                                            parameters[0] = parameters[1];
                                            parameters[1] = tempParam;
                                        }

                                        origEndParams[0] = instCurve.get_EndParameter(0);
                                        origEndParams[1] = instCurve.get_EndParameter(1);

                                        if (origEndParams[0] > parameters[1] - (1.0 / (12.0 * 16.0)))
                                            continue;
                                        if (origEndParams[1] < parameters[0] + (1.0 / (12.0 * 16.0)))
                                            continue;

                                        if (parameters[0] > origEndParams[0])
                                            instCurve.set_EndParameter(0, parameters[0]);
                                        if (parameters[1] < origEndParams[1])
                                            instCurve.set_EndParameter(1, parameters[1]);

                                        double insHeightScaled = insHeight * exporterIFC.LinearScale;
                                        IFCAnyHandle insConnectionGeom = ExtrusionExporter.CreateExtrudedSurfaceFromCurve(exporterIFC, instCurve, zDir,
                                           insHeightScaled, baseHeightNonScaled);

                                        SpaceBoundary instBoundary = new SpaceBoundary(spatialElement.Id, elemId, setter.LevelId, !IFCAnyHandleUtil.IsNullOrHasNoValue(insConnectionGeom) ? insConnectionGeom : null, physOrVirt,
                                            isObjectExt ? IFCInternalOrExternal.External : IFCInternalOrExternal.Internal);
                                        if (!ProcessIFCSpaceBoundary(exporterIFC, instBoundary, file))
                                            ExporterCacheManager.SpaceBoundaryCache.Add(instBoundary);
                                    }
                                }
                            }
                        }
                    }
                    PropertyUtil.CreateInternalRevitPropertySets(exporterIFC, spatialElement, productWrapper);
                    CreateZoneInfos(exporterIFC, file, spatialElement, productWrapper);
                    CreateSpaceOccupantInfo(exporterIFC, file, spatialElement, productWrapper);
                }
                transaction.Commit();
            }
        }
        /// <summary>
        /// Creates space boundary.
        /// </summary>
        /// <param name="exporterIFC">
        /// The ExporterIFC object.
        /// </param>
        /// <param name="boundary">
        /// The space boundary object.
        /// </param>
        /// <param name="file">
        /// The IFC file.
        /// </param>
        /// <returns>
        /// True if processed successfully, false otherwise.
        /// </returns>
        public static bool ProcessIFCSpaceBoundary(ExporterIFC exporterIFC, SpaceBoundary boundary, IFCFile file)
        {
            string spaceBoundaryName = String.Empty;
            if (ExporterCacheManager.ExportOptionsCache.SpaceBoundaryLevel == 1)
                spaceBoundaryName = "1stLevel";
            else if (ExporterCacheManager.ExportOptionsCache.SpaceBoundaryLevel == 2)
                spaceBoundaryName = "2ndLevel";

            IFCAnyHandle spatialElemHnd = ExporterCacheManager.SpatialElementHandleCache.Find(boundary.SpatialElementId);
            if (IFCAnyHandleUtil.IsNullOrHasNoValue(spatialElemHnd))
                return false;

            IFCPhysicalOrVirtual boundaryType = boundary.SpaceBoundaryType;
            IFCAnyHandle buildingElemHnd = null;
            if (boundaryType == IFCPhysicalOrVirtual.Physical)
            {
                buildingElemHnd = exporterIFC.FindSpaceBoundingElementHandle(boundary.BuildingElementId, boundary.LevelId);
                if (IFCAnyHandleUtil.IsNullOrHasNoValue(buildingElemHnd))
                    return false;
            }

            IFCInstanceExporter.CreateRelSpaceBoundary(file, GUIDUtil.CreateGUID(), exporterIFC.GetOwnerHistoryHandle(), spaceBoundaryName, null,
               spatialElemHnd, buildingElemHnd, boundary.ConnectGeometryHandle, boundaryType, boundary.InternalOrExternal);

            return true;
        }
        /// <summary>
        /// Creates SpaceBoundary from a bounding element.
        /// </summary>
        /// <param name="file">
        /// The IFC file.
        /// </param>
        /// <param name="exporterIFC">
        /// The ExporterIFC object.
        /// </param>
        /// <param name="spatialElement">
        /// The spatial element.
        /// </param>
        /// <param name="boundingElement">
        /// The bounding element.
        /// </param>
        /// <param name="levelId">
        /// The level id.
        /// </param>
        /// <param name="connectionGeometry">
        /// The connection geometry handle.
        /// </param>
        static void CreateIFCSpaceBoundary(IFCFile file, ExporterIFC exporterIFC, SpatialElement spatialElement, Element boundingElement, ElementId levelId, IFCAnyHandle connectionGeometry)
        {
            IFCPhysicalOrVirtual physOrVirt = IFCPhysicalOrVirtual.Physical;
            if (boundingElement == null || boundingElement is CurveElement)
                physOrVirt = IFCPhysicalOrVirtual.Virtual;
            else if (boundingElement is Autodesk.Revit.DB.Architecture.Room)
                physOrVirt = IFCPhysicalOrVirtual.NotDefined;

            bool isObjectExt = CategoryUtil.IsElementExternal(boundingElement, true);

            SpaceBoundary spaceBoundary = new SpaceBoundary(spatialElement.Id, boundingElement != null ? boundingElement.Id : ElementId.InvalidElementId,
                levelId, connectionGeometry, physOrVirt, isObjectExt ? IFCInternalOrExternal.External : IFCInternalOrExternal.Internal);

            if (!ProcessIFCSpaceBoundary(exporterIFC, spaceBoundary, file))
                ExporterCacheManager.SpaceBoundaryCache.Add(spaceBoundary);
        }