/// <summary>
        /// Draws a point around the center of an analysis surface.  Useful for sorting/grouping surfaces upstream of a SetSurfaceParameters node.
        /// </summary>
        /// <param name="SurfaceId">The ElementId of the surface to create a point from.  Get this from the AnalysisZones > CreateFrom* > SurfaceIds output list</param>
        /// <returns></returns>
        public static Autodesk.DesignScript.Geometry.Point AnalysisSurfacePoint(ElementId SurfaceId)
        {
            //local varaibles
            Document RvtDoc = DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument.Document;
            MassSurfaceData surf = null;
            Autodesk.Revit.DB.ElementId myEnergyModelId = null;

            //try to get the MassSurfaceData object from the document
            try
            {
                surf = (MassSurfaceData)RvtDoc.GetElement(new Autodesk.Revit.DB.ElementId(SurfaceId.InternalId));
                if (surf == null) throw new Exception();
            }
            catch (Exception)
            {
                throw new Exception("Couldn't find a MassSurfaceData object with Id #: " + SurfaceId.ToString());
            }

            //try to get the element id of the MassEnergyAnalyticalModel - we need this to pull faces from
            try
            {
                myEnergyModelId = surf.ReferenceElementId;
                if (myEnergyModelId == null) throw new Exception();
            }
            catch (Exception)
            {
                throw new Exception("Couldn't find a MassEnergyAnalyticalModel object belonging to the Mass instance with Id #: " + surf.ReferenceElementId.ToString());
            }

            //try to get the MassSurfaceData object from the document
            try
            {
                surf = (MassSurfaceData)RvtDoc.GetElement(new Autodesk.Revit.DB.ElementId(SurfaceId.InternalId));
                if (surf == null) throw new Exception();
            }
            catch (Exception)
            {
                throw new Exception("Couldn't find a MassSurfaceData object with Id #: " + SurfaceId.ToString());
            }

            //get the smallest face
            Autodesk.Revit.DB.Face smallFace = GetSmallestFace(RvtDoc, surf, myEnergyModelId);

            //get the average point of all points on the face
            Autodesk.DesignScript.Geometry.Point outPoint = getAveragePointFromFace(smallFace);
            return outPoint;
        }
        /// <summary>
        /// Sets an analysis zone's energy parameters
        /// </summary>
        /// <param name="ZoneId">The ElementId of the zone to modify.  Get this from the AnalysisZones > CreateFrom* > ZoneIds output list</param>
        /// <param name="SpaceType">Sets the zone's space type.  Use the Space Types Dropdown node from our EnergySetting tab to specify a value.</param>
        /// <param name="ConditionType">Sets the zone's condition type.  Use the Condition Types Dropdown node from our EnergySetting tab to specify a value.</param>
        /// <returns></returns>
        public static ElementId SetZoneParameters(ElementId ZoneId, string SpaceType = "", string ConditionType = "")
        {
            //local varaibles
            Document RvtDoc = DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument.Document;
            MassZone zone = null;

            //try to get MassZone using the ID
            try
            {
                zone = (MassZone)RvtDoc.GetElement(new Autodesk.Revit.DB.ElementId(ZoneId.InternalId));

                if (zone == null) throw new Exception();
            }
            catch (Exception)
            {
                throw new Exception("Couldn't find a zone object with Id #: " + ZoneId.ToString());
            }

            //defense
            if (!string.IsNullOrEmpty(ConditionType) && !(gbXMLConditionType.IsDefined(typeof(gbXMLConditionType), ConditionType)))
            {
                throw new Exception(ConditionType.ToString() + " is not a valid condition type. Use conditionTypes dropdown to input a valid condition type.");
            }

            if (!string.IsNullOrEmpty(SpaceType) && !(gbXMLSpaceType.IsDefined(typeof(gbXMLSpaceType), SpaceType)))
            {
                throw new Exception(SpaceType.ToString() + " is not a valid space type. Use spaceTypes dropdown to input a valid space type.");
            }

            try
            {
                //start a transaction task
                TransactionManager.Instance.EnsureInTransaction(RvtDoc);

                //set condiotn type
                if (!string.IsNullOrEmpty(ConditionType))
                {
                    zone.ConditionType = (gbXMLConditionType)Enum.Parse(typeof(gbXMLConditionType), ConditionType);
                }

                //set space type
                if (!string.IsNullOrEmpty(SpaceType))
                {
                    zone.SpaceType = (gbXMLSpaceType)Enum.Parse(typeof(gbXMLSpaceType), SpaceType);
                }

                //done with transaction task
                TransactionManager.Instance.TransactionTaskDone();

            }
            catch (Exception)
            {
                throw new Exception("Something went wrong when trying to set the parameters on zone # " + ZoneId.ToString());
            }

            //return the zone ID so the zone can be used downstream
            return ZoneId;
        }
        /// <summary>
        /// Sets an exterior wall surface's energy parameters
        /// </summary>
        /// <param name="SurfaceId">The ElementId of the surface to modify.  Get this from the AnalysisZones > CreateFrom* > SurfaceIds output list</param>
        /// <param name="glazingPercent">Percentage of glazed area.  Should be a double between 0.0 - 1.0</param>
        /// <param name="shadingDepth">Shading Depth, specified as a double.  We assume the double value represents a length using Dynamo's current length unit.</param>
        /// <param name="sillHeight">Target sill height, specified as a double.  We assume the double value represents a length using Dynamo's current length unit.</param>
        /// <param name="ConstType">Conceptual Construction Type.  Use the Conceptual Construction Types Dropdown node from our EnergySettings tab to specify a value.</param>
        /// <returns></returns>
        public static ElementId SetWallSurfaceParameters(ElementId SurfaceId, double glazingPercent = 0.4, double shadingDepth = 0.0, double sillHeight = 0.0, string ConstType = "default")
        {
            //local varaibles
            Document RvtDoc = DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument.Document;
            MassSurfaceData surf = null;

            //try to get the MassSurfaceData object from the document
            try
            {
                surf = (MassSurfaceData)RvtDoc.GetElement(new Autodesk.Revit.DB.ElementId(SurfaceId.InternalId));
                if (surf == null) throw new Exception();
            }
            catch (Exception)
            {
                throw new Exception("Couldn't find a MassSurfaceData object with Id #: " + SurfaceId.ToString());
            }

            //defense
            if (!(glazingPercent >= 0.0 && glazingPercent <= 1.0))
            {
                throw new Exception("Glazing percentage must be between 0.0 and 1.0");
            }
            if (shadingDepth < 0.0)
            {
                throw new Exception("Shading Depth must be positive");
            }
            if (sillHeight < 0.0)
            {
                throw new Exception("Sill Height must be positive");
            }

            try
            {
                //start a transaction task
                TransactionManager.Instance.EnsureInTransaction(RvtDoc);

                //change the 'Values' param to 1 - by surface
                var val = surf.get_Parameter("Values");
                if (val != null)
                {
                    val.Set(1);
                }

                //set target sill height
                //surf.SillHeight = sillHeight * UnitConverter.DynamoToHostFactor;
                surf.SillHeight = sillHeight;

                //set glazing percentage
                surf.PercentageGlazing = glazingPercent;

                //set shading if positive
                if (shadingDepth > 0)
                {
                    surf.IsGlazingShaded = true;
                    //surf.ShadeDepth = shadingDepth * UnitConverter.DynamoToHostFactor;
                    surf.ShadeDepth = shadingDepth;
                }
                else
                {
                    surf.IsGlazingShaded = false;
                    surf.ShadeDepth = 0;

                }

                //set conceptual construction if not empty
                if (!string.IsNullOrEmpty(ConstType) && ConstType != "default")
                {
                    // check if construction is a valid Revit construction for walls
                    if (Enum.IsDefined(typeof(ConceptualConstructionWallType), ConstType))
                    {
                        Autodesk.Revit.DB.ElementId myTypeId = getConceptualConstructionIdFromName(RvtDoc, ConstType, surf.Category.Name);

                        if (myTypeId != null)
                        {
                            surf.IsConceptualConstructionByEnergyData = false;
                            surf.ConceptualConstructionId = myTypeId;
                        }
                    }
                    else
                    {
                        throw new Exception(ConstType + " is not a valid construction type for walls.");
                    }
                }

                //done with transaction task
                TransactionManager.Instance.TransactionTaskDone();

            }
            catch (Exception ex)
            {
                throw new Exception("Failed to set the parameters on surface # " + SurfaceId.ToString() + ":\n" + ex.Message);
            }

            //return the surface ID so the surface can be used downstream
            return SurfaceId;
        }
        /// <summary>
        /// Sets surface's construction type. This node works for all the surface types.
        /// </summary>
        /// <param name="SurfaceId">The ElementId of the surface to modify.  Get this from the AnalysisZones > CreateFrom* > SurfaceIds output list</param>
        /// <param name="ConstType">Conceptual Construction Type. Use the Conceptual Construction Types Dropdown node from our EnergySettings tab to specify a value.</param>
        /// <returns></returns>
        public static ElementId SetSurfaceConceptualConstruction(ElementId SurfaceId, string ConstType)
        {
            //local varaibles
            Document RvtDoc = DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument.Document;
            MassSurfaceData surf = null;

            //try to get the MassSurfaceData object from the document
            try
            {
                surf = (MassSurfaceData)RvtDoc.GetElement(new Autodesk.Revit.DB.ElementId(SurfaceId.InternalId));
                if (surf == null) throw new Exception();
            }
            catch (Exception)
            {
                throw new Exception("Couldn't find a MassSurfaceData object with Id #: " + SurfaceId.ToString());
            }

            try
            {
                //start a transaction task
                TransactionManager.Instance.EnsureInTransaction(RvtDoc);

                //change the 'Values' param to 1 - by surface
                var val = surf.get_Parameter("Values");
                if (val != null)
                {
                    val.Set(1);
                }

                //set conceptual construction if not empty
                if (!string.IsNullOrEmpty(ConstType))
                {
                    // check if the construction matches the surface type
                    switch (surf.Category.Name)
                    {
                        case "Mass Exterior Wall":
                            if (Enum.IsDefined(typeof(ConceptualConstructionWallType), ConstType)!=true)
                            {
                                throw new Exception(ConstType + " is not a valid construction type for walls.");
                            }
                            break;

                        case "Mass Interior Wall":
                            if (Enum.IsDefined(typeof(ConceptualConstructionWallType), ConstType) != true)
                            {
                                throw new Exception(ConstType + " is not a valid construction type for walls.");
                            }
                            break;

                        case "Mass Glazing":
                            if (Enum.IsDefined(typeof(ConceptualConstructionWindowSkylightType), ConstType) != true)
                            {
                                throw new Exception(ConstType + " is not a valid construction type for glazing.");
                            }
                            break;

                        case "Mass Floor":
                            if (Enum.IsDefined(typeof(ConceptualConstructionFloorSlabType), ConstType) != true)
                            {
                                throw new Exception(ConstType + " is not a valid construction type for floors.");
                            }
                            break;

                        case "Mass Roof":
                            if (Enum.IsDefined(typeof(ConceptualConstructionRoofType), ConstType) != true)
                            {
                                throw new Exception(ConstType + " is not a valid construction type for roofs.");
                            }
                            break;
                    }

                    // it is all fine so let's change the construction type
                    Autodesk.Revit.DB.ElementId myTypeId = getConceptualConstructionIdFromName(RvtDoc, ConstType, surf.Category.Name);

                    if (myTypeId != null)
                    {
                        surf.IsConceptualConstructionByEnergyData = false;
                        surf.ConceptualConstructionId = myTypeId;
                    }

                }

                //done with transaction task
                TransactionManager.Instance.TransactionTaskDone();

            }
            catch (Exception ex)
            {
                throw new Exception("# " + SurfaceId.ToString() + ": " + ex.Message);
            }

            //return the surface ID so the surface can be used downstream
            return SurfaceId;
        }
        /// <summary>
        /// Draws an analysis zone in Dynamo.  Use this to identify which zone is which in the CreateFromMass/CreateFromMassAndLevels 'ZoneIds' output list.
        /// </summary>
        /// <param name="ZoneId">The ElementId of the zone to draw.  Get this from the AnalysisZones > CreateFrom* > ZoneIds output list</param>
        /// <returns>A list of Dynamo meshes for each zone.</returns>
        public static List<Autodesk.DesignScript.Geometry.Mesh> DrawAnalysisZone(ElementId ZoneId)
        {
            //local varaibles
            Document RvtDoc = DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument.Document;
            MassZone zone = null;
            Autodesk.Revit.DB.ElementId myEnergyModelId = null;

            // get zone data from the document using the id
            try
            {
                zone = (MassZone)RvtDoc.GetElement(new Autodesk.Revit.DB.ElementId(ZoneId.InternalId));

                if (zone == null) throw new Exception();
            }
            catch (Exception)
            {
                throw new Exception("Couldn't find a zone object with Id #: " + ZoneId.ToString());
            }

            //try to get the element id of the MassEnergyAnalyticalModel - we need this to pull faces from
            try
            {
                myEnergyModelId = zone.MassEnergyAnalyticalModelId;
                // myEnergyModelId = MassEnergyAnalyticalModel.GetMassEnergyAnalyticalModelIdForMassInstance(RvtDoc, MassFamilyInstance.InternalElement.Id);
                if (myEnergyModelId == null) throw new Exception();
            }
            catch (Exception)
            {
                //throw new Exception("Couldn't find a MassEnergyAnalyticalModel object belonging to the Mass instance with Id #: " + MassFamilyInstance.InternalElement.Id.ToString());
                throw new Exception("Couldn't find a MassEnergyAnalyticalModel object belonging to the Mass instance with Id #: " + zone.MassEnergyAnalyticalModelId.ToString());
            }

            //return a list of all fo the mesh faces for each zone
            List<Autodesk.DesignScript.Geometry.Mesh> outMeshes = new List<Autodesk.DesignScript.Geometry.Mesh>();
            //get references to all of the faces
            IList<Reference> faceRefs = zone.GetReferencesToEnergyAnalysisFaces();
            foreach (var faceRef in faceRefs)
            {
                //get the actual face and add the converted version to our list
                Autodesk.Revit.DB.Face face = (Autodesk.Revit.DB.Face)zone.GetGeometryObjectFromReference(faceRef);
                outMeshes.Add(Revit.GeometryConversion.RevitToProtoMesh.ToProtoType(face.Triangulate()));
            }
            return outMeshes;
        }
        /// <summary>
        /// Draws a mesh in Dynamo representing an analysis surface.  Useful when trying to identify a surface to modify.
        /// </summary>
        /// <param name="SurfaceId">The ElementId of the surface to draw.  Get this from AnalysisZones > CreateFrom* > SurfaceIds output list</param>
        /// <returns></returns>
        public static Autodesk.DesignScript.Geometry.Mesh DrawAnalysisSurface(ElementId SurfaceId)
        {
            //local varaibles
            Document RvtDoc = DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument.Document;
            MassSurfaceData surf = null;
            Autodesk.Revit.DB.ElementId myEnergyModelId = null;

            //try to get the MassSurfaceData object from the document
            try
            {
                surf = (MassSurfaceData)RvtDoc.GetElement(new Autodesk.Revit.DB.ElementId(SurfaceId.InternalId));
                if (surf == null) throw new Exception();
            }
            catch (Exception)
            {
                throw new Exception("Couldn't find a MassSurfaceData object with Id #: " + SurfaceId.ToString());
            }

            //try to get the element id of the MassEnergyAnalyticalModel - we need this to pull faces from
            try
            {
                myEnergyModelId = surf.ReferenceElementId;
                if (myEnergyModelId == null) throw new Exception();
            }
            catch (Exception)
            {
                throw new Exception("Couldn't find a MassEnergyAnalyticalModel object belonging to the Mass instance with Id #: " + surf.ReferenceElementId.ToString());
            }

            //get the smallest face
            Autodesk.Revit.DB.Face smallFace = GetSmallestFace(RvtDoc, surf, myEnergyModelId);

            Autodesk.Revit.DB.Mesh prettyMesh = smallFace.Triangulate();
            return Revit.GeometryConversion.RevitToProtoMesh.ToProtoType(prettyMesh);
        }
        public static Dictionary<string, object> DecomposeZone(ElementId ZoneId)
        {
            // local variables
            Document RvtDoc = DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument.Document;
            MassZone zone = null;
            gbXMLConditionType conditionType = gbXMLConditionType.NoConditionType;
            gbXMLSpaceType spaceType = gbXMLSpaceType.NoSpaceType;

            // get zone data from the document using the id
            try
            {
                zone = (MassZone)RvtDoc.GetElement(new Autodesk.Revit.DB.ElementId(ZoneId.InternalId));

                if (zone == null) throw new Exception();
            }
            catch (Exception)
            {
                throw new Exception("Couldn't find a zone object with Id #: " + ZoneId.ToString());
            }

            //get ids based on type. Maybe we should write a new function that loops once and returns them all at once
            List<EnergyAnalysisForDynamo.ElementId> outWallSurfaceIds = Helper.GetSurfaceIdsFromZoneBasedOnType(zone, "Mass Exterior Wall");
            List<EnergyAnalysisForDynamo.ElementId> outIntWallSurfaceIds = Helper.GetSurfaceIdsFromZoneBasedOnType(zone, "Mass Interior Wall");
            List<EnergyAnalysisForDynamo.ElementId> outGlazingSurfaceIds = Helper.GetSurfaceIdsFromZoneBasedOnType(zone, "Mass Glazing");
            List<EnergyAnalysisForDynamo.ElementId> outFloorSurfaceIds = Helper.GetSurfaceIdsFromZoneBasedOnType(zone, "Mass Floor");
            List<EnergyAnalysisForDynamo.ElementId> outRoofSurfaceIds = Helper.GetSurfaceIdsFromZoneBasedOnType(zone, "Mass Roof");
            List<EnergyAnalysisForDynamo.ElementId> outSkylightSurfaceIds = Helper.GetSurfaceIdsFromZoneBasedOnType(zone, "Mass Skylight");
            // Revit consider both floor and ceiling and floor so this one is out!
            // List<EnergyAnalysisForDynamo.ElementId> outCeilingSurfaceIds = Helper.GetSurfaceIdsFromZoneBasedOnType(zone, "Mass Interior Ceiling");

            // assign condition type
            conditionType = zone.ConditionType;

            // assign space type
            spaceType = zone.SpaceType;

            // return outputs
            return new Dictionary<string, object>
            {
                {"WallSurfaceIds", outWallSurfaceIds},
                {"IntWallSurfaceIds", outIntWallSurfaceIds},
                {"GlazingSurfaceIds", outGlazingSurfaceIds},
                {"FloorSurfaceIds", outFloorSurfaceIds},
                {"RoofSurfaceIds", outRoofSurfaceIds},
                {"SkylightSurfaceIds", outSkylightSurfaceIds},
                {"SpaceType", spaceType},
                {"conditionType", conditionType}
            };
        }
        /// <summary>
        /// Returns a vector represnting the normal of an analysis surface.  Useful for sorting/grouping surfaces upstream of a SetSurfaceParameters node.
        /// </summary>
        /// <param name="SurfaceId">The ElementId of the surface to create a vector from.  Get this from AnalysisZones > CreateFrom* > SurfaceIds output list</param>
        /// <returns></returns>
        public static Autodesk.DesignScript.Geometry.Vector AnalysisSurfaceVector(ElementId SurfaceId)
        {
            //local varaibles
            Document RvtDoc = DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument.Document;
            MassSurfaceData surf = null;
            Autodesk.Revit.DB.ElementId myEnergyModelId = null;

            //try to get the MassSurfaceData object from the document
            try
            {
                surf = (MassSurfaceData)RvtDoc.GetElement(new Autodesk.Revit.DB.ElementId(SurfaceId.InternalId));
                if (surf == null) throw new Exception();
            }
            catch (Exception)
            {
                throw new Exception("Couldn't find a MassSurfaceData object with Id #: " + SurfaceId.ToString());
            }

            //try to get the element id of the MassEnergyAnalyticalModel - we need this to pull faces from
            try
            {
                myEnergyModelId = surf.ReferenceElementId;
                if (myEnergyModelId == null) throw new Exception();
            }
            catch (Exception)
            {
                throw new Exception("Couldn't find a MassEnergyAnalyticalModel object belonging to the Mass instance with Id #: " + surf.ReferenceElementId.ToString());
            }

            //try to get the MassSurfaceData object from the document
            try
            {
                surf = (MassSurfaceData)RvtDoc.GetElement(new Autodesk.Revit.DB.ElementId(SurfaceId.InternalId));
                if (surf == null) throw new Exception();
            }
            catch (Exception)
            {
                throw new Exception("Couldn't find a MassSurfaceData object with Id #: " + SurfaceId.ToString());
            }

            //get the smallest face
            Autodesk.Revit.DB.Face bigFace = GetLargestFace(RvtDoc, surf, myEnergyModelId);

            // Find the face normal at the center of the face
            BoundingBoxUV bbox = bigFace.GetBoundingBox();
            // center of the face in the UV of the face
            Autodesk.Revit.DB.UV center = new Autodesk.Revit.DB.UV((bbox.Max.U - bbox.Min.U) / 2 + bbox.Min.U, (bbox.Max.V - bbox.Min.V) / 2 + bbox.Min.V);
            XYZ faceNormal = bigFace.ComputeNormal(center);
            XYZ normal = faceNormal.Normalize();
            return Autodesk.DesignScript.Geometry.Vector.ByCoordinates(normal.X, normal.Y, normal.Z, true);
        }
        /// <summary>
        /// Sets an analysis zone's energy parameters
        /// </summary>
        /// <param name="ZoneId">The ElementId of the zone to modify.  Get this from the AnalysisZones > CreateFrom* > ZoneIds output list</param>
        /// <param name="SpaceType">Sets the zone's space type.  Use the Space Types Dropdown node from our EnergySetting tab to specify a value.</param>
        /// <param name="ConditionType">Sets the zone's condition type.  Use the Condition Types Dropdown node from our EnergySetting tab to specify a value.</param>
        /// <returns></returns>
        public static ElementId SetZoneParameters(ElementId ZoneId, string SpaceType = "", string ConditionType = "")
        {
            //local varaibles
            Document RvtDoc = DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument.Document;
            MassZone zone   = null;


            //try to get MassZone using the ID
            try
            {
                zone = (MassZone)RvtDoc.GetElement(new Autodesk.Revit.DB.ElementId(ZoneId.InternalId));

                if (zone == null)
                {
                    throw new Exception();
                }
            }
            catch (Exception)
            {
                throw new Exception("Couldn't find a zone object with Id #: " + ZoneId.ToString());
            }


            //defense
            if (!string.IsNullOrEmpty(ConditionType) && !(gbXMLConditionType.IsDefined(typeof(gbXMLConditionType), ConditionType)))
            {
                throw new Exception(ConditionType.ToString() + " is not a valid condition type. Use conditionTypes dropdown to input a valid condition type.");
            }

            if (!string.IsNullOrEmpty(SpaceType) && !(gbXMLSpaceType.IsDefined(typeof(gbXMLSpaceType), SpaceType)))
            {
                throw new Exception(SpaceType.ToString() + " is not a valid space type. Use spaceTypes dropdown to input a valid space type.");
            }

            try
            {
                //start a transaction task
                TransactionManager.Instance.EnsureInTransaction(RvtDoc);

                //set condiotn type
                if (!string.IsNullOrEmpty(ConditionType))
                {
                    zone.ConditionType = (gbXMLConditionType)Enum.Parse(typeof(gbXMLConditionType), ConditionType);
                }

                //set space type
                if (!string.IsNullOrEmpty(SpaceType))
                {
                    zone.SpaceType = (gbXMLSpaceType)Enum.Parse(typeof(gbXMLSpaceType), SpaceType);
                }

                //done with transaction task
                TransactionManager.Instance.TransactionTaskDone();
            }
            catch (Exception)
            {
                throw new Exception("Something went wrong when trying to set the parameters on zone # " + ZoneId.ToString());
            }

            //return the zone ID so the zone can be used downstream
            return(ZoneId);
        }
        /// <summary>
        /// Sets an exterior wall surface's energy parameters
        /// </summary>
        /// <param name="SurfaceId">The ElementId of the surface to modify.  Get this from the AnalysisZones > CreateFrom* > SurfaceIds output list</param>
        /// <param name="glazingPercent">Percentage of glazed area.  Should be a double between 0.0 - 1.0</param>
        /// <param name="shadingDepth">Shading Depth, specified as a double.  We assume the double value represents a length using Dynamo's current length unit.</param>
        /// <param name="sillHeight">Target sill height, specified as a double.  We assume the double value represents a length using Dynamo's current length unit.</param>
        /// <param name="ConstType">Conceptual Construction Type.  Use the Conceptual Construction Types Dropdown node from our EnergySettings tab to specify a value.</param>
        /// <returns></returns>
        public static ElementId SetWallSurfaceParameters(ElementId SurfaceId, double glazingPercent = 0.4, double shadingDepth = 0.0, double sillHeight = 0.0, string ConstType = "default")
        {
            //local varaibles
            Document        RvtDoc = DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument.Document;
            MassSurfaceData surf   = null;

            //try to get the MassSurfaceData object from the document
            try
            {
                surf = (MassSurfaceData)RvtDoc.GetElement(new Autodesk.Revit.DB.ElementId(SurfaceId.InternalId));
                if (surf == null)
                {
                    throw new Exception();
                }
            }
            catch (Exception)
            {
                throw new Exception("Couldn't find a MassSurfaceData object with Id #: " + SurfaceId.ToString());
            }

            //defense
            if (!(glazingPercent >= 0.0 && glazingPercent <= 1.0))
            {
                throw new Exception("Glazing percentage must be between 0.0 and 1.0");
            }
            if (shadingDepth < 0.0)
            {
                throw new Exception("Shading Depth must be positive");
            }
            if (sillHeight < 0.0)
            {
                throw new Exception("Sill Height must be positive");
            }

            try
            {
                //start a transaction task
                TransactionManager.Instance.EnsureInTransaction(RvtDoc);

                //change the 'Values' param to 1 - by surface
                var val = surf.get_Parameter("Values");
                if (val != null)
                {
                    val.Set(1);
                }

                //set target sill height
                surf.SillHeight = sillHeight * UnitConverter.DynamoToHostFactor;

                //set glazing percentage
                surf.PercentageGlazing = glazingPercent;

                //set shading if positive
                if (shadingDepth > 0)
                {
                    surf.IsGlazingShaded = true;
                    surf.ShadeDepth      = shadingDepth * UnitConverter.DynamoToHostFactor;
                }
                else
                {
                    surf.IsGlazingShaded = false;
                    surf.ShadeDepth      = 0;
                }

                //set conceptual construction if not empty
                if (!string.IsNullOrEmpty(ConstType) && ConstType != "default")
                {
                    Autodesk.Revit.DB.ElementId myTypeId = getConceptualConstructionIdFromName(RvtDoc, ConstType);
                    if (myTypeId != null)
                    {
                        surf.IsConceptualConstructionByEnergyData = false;
                        surf.ConceptualConstructionId             = myTypeId;
                    }
                }

                //done with transaction task
                TransactionManager.Instance.TransactionTaskDone();
            }
            catch (Exception)
            {
                throw new Exception("Something went wrong when trying to set the parameters on surface # " + SurfaceId.ToString());
            }

            //return the surface ID so the surface can be used downstream
            return(SurfaceId);
        }
        /// <summary>
        /// Draws a mesh in Dynamo representing an analysis surface.  Useful when trying to identify a surface to modify.
        /// </summary>
        /// <param name="SurfaceId">The ElementId of the surface to draw.  Get this from AnalysisZones > CreateFrom* > SurfaceIds output list</param>
        /// <returns></returns>
        public static Autodesk.DesignScript.Geometry.Mesh DrawAnalysisSurface(ElementId SurfaceId)
        {
            //local varaibles
            Document        RvtDoc = DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument.Document;
            MassSurfaceData surf   = null;

            Autodesk.Revit.DB.ElementId myEnergyModelId = null;

            //try to get the MassSurfaceData object from the document
            try
            {
                surf = (MassSurfaceData)RvtDoc.GetElement(new Autodesk.Revit.DB.ElementId(SurfaceId.InternalId));
                if (surf == null)
                {
                    throw new Exception();
                }
            }
            catch (Exception)
            {
                throw new Exception("Couldn't find a MassSurfaceData object with Id #: " + SurfaceId.ToString());
            }

            //try to get the element id of the MassEnergyAnalyticalModel - we need this to pull faces from
            try
            {
                myEnergyModelId = surf.ReferenceElementId;
                if (myEnergyModelId == null)
                {
                    throw new Exception();
                }
            }
            catch (Exception)
            {
                throw new Exception("Couldn't find a MassEnergyAnalyticalModel object belonging to the Mass instance with Id #: " + surf.ReferenceElementId.ToString());
            }


            //get the smallest face
            Autodesk.Revit.DB.Face smallFace = GetSmallestFace(RvtDoc, surf, myEnergyModelId);

            Autodesk.Revit.DB.Mesh prettyMesh = smallFace.Triangulate();
            return(Revit.GeometryConversion.RevitToProtoMesh.ToProtoType(prettyMesh));
        }
        /// <summary>
        /// Draws a point around the center of an analysis surface.  Useful for sorting/grouping surfaces upstream of a SetSurfaceParameters node.
        /// </summary>
        /// <param name="SurfaceId">The ElementId of the surface to create a point from.  Get this from the AnalysisZones > CreateFrom* > SurfaceIds output list</param>
        /// <returns></returns>
        public static Autodesk.DesignScript.Geometry.Point AnalysisSurfacePoint(ElementId SurfaceId)
        {
            //local varaibles
            Document        RvtDoc = DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument.Document;
            MassSurfaceData surf   = null;

            Autodesk.Revit.DB.ElementId myEnergyModelId = null;

            //try to get the MassSurfaceData object from the document
            try
            {
                surf = (MassSurfaceData)RvtDoc.GetElement(new Autodesk.Revit.DB.ElementId(SurfaceId.InternalId));
                if (surf == null)
                {
                    throw new Exception();
                }
            }
            catch (Exception)
            {
                throw new Exception("Couldn't find a MassSurfaceData object with Id #: " + SurfaceId.ToString());
            }

            //try to get the element id of the MassEnergyAnalyticalModel - we need this to pull faces from
            try
            {
                myEnergyModelId = surf.ReferenceElementId;
                if (myEnergyModelId == null)
                {
                    throw new Exception();
                }
            }
            catch (Exception)
            {
                throw new Exception("Couldn't find a MassEnergyAnalyticalModel object belonging to the Mass instance with Id #: " + surf.ReferenceElementId.ToString());
            }

            //try to get the MassSurfaceData object from the document
            try
            {
                surf = (MassSurfaceData)RvtDoc.GetElement(new Autodesk.Revit.DB.ElementId(SurfaceId.InternalId));
                if (surf == null)
                {
                    throw new Exception();
                }
            }
            catch (Exception)
            {
                throw new Exception("Couldn't find a MassSurfaceData object with Id #: " + SurfaceId.ToString());
            }

            //get the smallest face
            Autodesk.Revit.DB.Face smallFace = GetSmallestFace(RvtDoc, surf, myEnergyModelId);

            //get the average point of all points on the face
            Autodesk.DesignScript.Geometry.Point outPoint = getAveragePointFromFace(smallFace);
            return(outPoint);
        }
        /// <summary>
        /// Sets a roof surface's energy parameters
        /// </summary>
        /// <param name="SurfaceId">The ElementId of the surface to modify.  Get this from the AnalysisZones > CreateFrom* > SurfaceIds output list</param>
        /// <param name="SkylightPer"></param>
        /// <param name="SkylightWidth"></param>
        /// <param name="ConstType">Conceptual Construction Type. Use the Conceptual Construction Types Dropdown node from our EnergySettings tab to specify a value.</param>
        /// <returns></returns>
        public static ElementId SetRoofSurfaceParameters(ElementId SurfaceId, double SkylightPer = 0.0, string ConstType = "default")
        {
            //local varaibles
            Document RvtDoc = DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument.Document;
            MassSurfaceData surf = null;

            //try to get the MassSurfaceData object from the document
            try
            {
                surf = (MassSurfaceData)RvtDoc.GetElement(new Autodesk.Revit.DB.ElementId(SurfaceId.InternalId));
                if (surf == null) throw new Exception();
            }
            catch (Exception)
            {
                throw new Exception("Couldn't find a MassSurfaceData object with Id #: " + SurfaceId.ToString());
            }

            //defense
            if (!(SkylightPer >= 0.0 && SkylightPer <= 1.0))
            {
                throw new Exception("Skylight percentage must be between 0.0 and 1.0");
            }


            //if (SkylightWidth < 0.0)
            //{
            //    throw new Exception("Skylight width must be positive");
            //}


            try
            {
                //start a transaction task
                TransactionManager.Instance.EnsureInTransaction(RvtDoc);

                //change the 'Values' param to 1 - by surface
                var val = surf.get_Parameter("Values");
                if (val != null)
                {
                    val.Set(1);
                }

                
                //set skylight percentage
                surf.PercentageSkylights = SkylightPer;

                //set skylight width
                //surf.SkylightWidth = SkylightWidth * UnitConverter.DynamoToHostFactor;


                //set conceptual construction if not empty
                if (!string.IsNullOrEmpty(ConstType) && ConstType != "default")
                {
                    Autodesk.Revit.DB.ElementId myTypeId = getConceptualConstructionIdFromName(RvtDoc, ConstType);
                    if (myTypeId != null)
                    {
                        surf.IsConceptualConstructionByEnergyData = false;
                        surf.ConceptualConstructionId = myTypeId;
                    }
                }

                //done with transaction task
                TransactionManager.Instance.TransactionTaskDone();

            }
            catch (Exception)
            {
                throw new Exception("Something went wrong when trying to set the parameters on surface # " + SurfaceId.ToString());
            }

            //return the surface ID so the surface can be used downstream            
            return SurfaceId;
        }
        public static Dictionary<string, object> DecomposeZone(ElementId ZoneId)
        {
            // local variables
            Document RvtDoc = DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument.Document;
            MassZone zone = null;
            Autodesk.Revit.DB.ElementId myEnergyModelId = null;
            gbXMLConditionType conditionType = gbXMLConditionType.NoConditionType;
            gbXMLSpaceType spaceType = gbXMLSpaceType.NoSpaceType;

            // get zone data from the document using the id
            try
            {
                zone = (MassZone)RvtDoc.GetElement(new Autodesk.Revit.DB.ElementId(ZoneId.InternalId));

                if (zone == null) throw new Exception();
            }
            catch (Exception)
            {
                throw new Exception("Couldn't find a zone object with Id #: " + ZoneId.ToString());
            }

            //get external faces belonging to this zone
            List<EnergyAnalysisForDynamo.ElementId> outWallSurfaceIds = Helper.GetSurfaceIdsFromZoneBasedOnType(zone, "Mass Exterior Wall");
            List<EnergyAnalysisForDynamo.ElementId> outRoofSurfaceIds = Helper.GetSurfaceIdsFromZoneBasedOnType(zone, "Mass Roof");
 
            // assign condition type
            conditionType = zone.ConditionType;

            // assign space type
            spaceType = zone.SpaceType;

            // return outputs
            return new Dictionary<string, object>
            {
                {"WallSurfaceIds", outWallSurfaceIds},
                {"RoofSurfaceIds", outRoofSurfaceIds},
                {"SpaceType", spaceType},
                {"conditionType", conditionType}
            };

        }