/// <summary>
        /// Upload the selected rooms and the furniture
        /// they contain to the cloud database.
        /// </summary>
        public static void UploadRoom(
            Document doc,
            Room room)
        {
            BoundingBoxXYZ bb = room.get_BoundingBox(null);

            if (null == bb)
            {
                Util.ErrorMsg(string.Format("Skipping room {0} "
                                            + "because it has no bounding box.",
                                            Util.ElementDescription(room)));

                return;
            }

            JtLoops roomLoops = GetRoomLoops(room);

            ListLoops(room, roomLoops);

            List <Element> furniture
                = GetFurniture(room);

            // Map symbol UniqueId to symbol loop

            Dictionary <string, JtLoop> furnitureLoops
                = new Dictionary <string, JtLoop>();

            // List of instances referring to symbols

            List <JtPlacement2dInt> furnitureInstances
                = new List <JtPlacement2dInt>(
                      furniture.Count);

            int nFailures;

            foreach (FamilyInstance f in furniture)
            {
                FamilySymbol s = f.Symbol;

                string uid = s.UniqueId;

                if (!furnitureLoops.ContainsKey(uid))
                {
                    nFailures = 0;

                    JtLoops loops = GetPlanViewBoundaryLoops(
                        f, ref nFailures);

                    if (0 < nFailures)
                    {
                        Debug.Print("{0}: {1}",
                                    Util.ElementDescription(f),
                                    Util.PluralString(nFailures,
                                                      "extrusion analyser failure"));
                    }
                    ListLoops(f, loops);

                    if (0 < loops.Count)
                    {
                        // Assume first loop is outer one

                        furnitureLoops.Add(uid, loops[0]);
                    }
                }
                furnitureInstances.Add(
                    new JtPlacement2dInt(f));
            }
            IWin32Window revit_window
                = new JtWindowHandle(
                      ComponentManager.ApplicationWindow);

            string caption = doc.Title
                             + " : " + doc.GetElement(room.LevelId).Name
                             + " : " + room.Name;

            Bitmap bmp = GeoSnoop.DisplayRoom(roomLoops,
                                              furnitureLoops, furnitureInstances);

            GeoSnoop.DisplayImageInForm(revit_window,
                                        caption, false, bmp);

            DbUpload.DbUploadRoom(room, furniture,
                                  roomLoops, furnitureLoops);
        }
        public Result Execute(
            ExternalCommandData commandData,
            ref string message,
            ElementSet elements)
        {
            UIApplication uiapp = commandData.Application;
            UIDocument    uidoc = uiapp.ActiveUIDocument;
            Application   app   = uiapp.Application;
            Document      doc   = uidoc.Document;

            //IWin32Window revit_window
            //  = new JtWindowHandle(
            //    ComponentManager.ApplicationWindow ); // pre-2020

            IWin32Window revit_window
                = new JtWindowHandle(uiapp.MainWindowHandle); // 2020

            if (null == doc)
            {
                Util.ErrorMsg("Please run this command in a valid"
                              + " Revit project document.");
                return(Result.Failed);
            }

            // Interactive sheet selection.

            FrmSelectSheets form = new FrmSelectSheets(doc);

            if (DialogResult.OK == form.ShowDialog(
                    revit_window))
            {
                List <ViewSheet> sheets
                    = form.GetSelectedSheets();

                int n = sheets.Count;

                string caption = Util.PluralString(
                    n, "Sheet") + " Selected";

                string msg = string.Join(", ",
                                         sheets.Select <Element, string>(
                                             e => Util.SheetDescription(e))) + ".";

                // Determine all floor plan views displayed
                // in the selected sheets.

                Dictionary <View, int> views
                    = new Dictionary <View, int>(
                          new ElementEqualityComparer());

                int nFloorPlans = 0;

                foreach (ViewSheet sheet in sheets)
                {
                    foreach (View v in sheet.GetAllPlacedViews()
                             .Select <ElementId, View>(id =>
                                                       doc.GetElement(id) as View))
                    {
                        if (!views.ContainsKey(v))
                        {
                            if (IsFloorPlan(v))
                            {
                                ++nFloorPlans;
                            }
                            views.Add(v, 0);
                        }
                        ++views[v];
                    }
                }

                msg += (1 == n)
          ? "\nIt contains"
          : "\nThey contain";

                n = views.Count;

                msg += string.Format(
                    " {0} including {1}: ",
                    Util.PluralString(n, "view"),
                    Util.PluralString(nFloorPlans,
                                      "floor plan"));

                msg += string.Join(", ",
                                   views.Keys.Select <Element, string>(
                                       e => e.Name)) + ".";

                Util.InfoMsg2(caption, msg, false);

                // Determine all categories occurring
                // in the views displayed by the sheets.

                List <Category> categories
                    = new List <Category>(
                          new CategoryCollector(views.Keys).Keys);

                // Sort categories alphabetically by name
                // to display them in selection form.

                categories.Sort(
                    delegate(Category c1, Category c2)
                {
                    return(string.Compare(c1.Name, c2.Name));
                });

                // Interactive category selection.

                FrmSelectCategories form2
                    = new FrmSelectCategories(categories);

                if (DialogResult.OK == form2.ShowDialog(
                        revit_window))
                {
                    categories = form2.GetSelectedCategories();

                    n = categories.Count;

                    caption = Util.PluralString(n, "Category")
                              + " Selected";

                    msg = string.Join(", ",
                                      categories.Select <Category, string>(
                                          e => e.Name)) + ".";

                    Util.InfoMsg2(caption, msg, false);

                    // Convert category list to a dictionary for
                    // more effective repeated lookup.
                    //
                    //Dictionary<ElementId, Category> catLookup =
                    //  categories.ToDictionary<Category, ElementId>(
                    //    c => c.Id );
                    //
                    // No, much better: set up a reusable element
                    // filter for the categories of interest:

                    ElementFilter categoryFilter
                        = new LogicalOrFilter(categories
                                              .Select <Category, ElementCategoryFilter>(
                                                  c => new ElementCategoryFilter(c.Id))
                                              .ToList <ElementFilter>());

                    // Instantiate a container for all
                    // cloud data repository content.

                    SheetModelCollections modelCollections
                        = new SheetModelCollections(
                              DbUpload.GetProjectInfo(doc).Id);

                    foreach (ViewSheet sheet in sheets)
                    {
                        // Define preview form caption.

                        caption = "Sheet and Viewport Loops - "
                                  + Util.SheetDescription(sheet);

                        // This is currently not used for anything.

                        ListSheetAndViewTransforms(sheet);

                        // Determine the polygon loops representing
                        // the size and location of given sheet and
                        // the viewports it contains.

                        JtLoops sheetViewportLoops
                            = GetSheetViewportLoops(
                                  modelCollections, sheet);

                        // Determine graphics for family instances,
                        // their symbols and other BIM parts.

                        GetBimGraphics(modelCollections,
                                       sheet, categoryFilter);

                        // Display sheet and viewports with the
                        // geometry retrieved in a temporary GeoSnoop
                        // form generated on the fly for debugging
                        // purposes.

                        Bitmap bmp = GeoSnoop.DisplaySheet(
                            sheet.Id, sheetViewportLoops,
                            modelCollections);

                        GeoSnoop.DisplayImageInForm(
                            revit_window, caption, false, bmp);

                        // Upload data to the cloud database.

                        DbUpload.DbUploadSheet(sheet,
                                               sheetViewportLoops, modelCollections);
                    }
                    DbUpdater.SetLastSequence();
                }
            }
            return(Result.Succeeded);
        }