swap family type instance and specify instance parameters

swap family type instance and specify instance parameters

baleti3266
Advocate Advocate
188 Views
1 Reply
Message 1 of 2

swap family type instance and specify instance parameters

baleti3266
Advocate
Advocate

I'm trying to write a command that replaces family types but keeps instance parameter values the same - it manages to swap the family in place but instance parameters get reset to default values - does anyone know a way to fix it?

 

#region Namespaces
using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.Attributes;
#endregion

namespace MyRevitCommands
{
    [Transaction(TransactionMode.Manual)]
    public class SwapFamilyTypeOfSelectedElements : IExternalCommand
    {
        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
        {
            // Get the current document and UIDocument.
            UIDocument uidoc = commandData.Application.ActiveUIDocument;
            Document doc = uidoc.Document;

            // Retrieve the currently selected elements.
            ICollection<ElementId> selIds = uidoc.Selection.GetElementIds();
            if (selIds == null || selIds.Count == 0)
            {
                TaskDialog.Show("Swap Family Type", "Please select one or more family instances.");
                return Result.Failed;
            }

            // Filter to FamilyInstances.
            List<FamilyInstance> famInstances = selIds
                .Select(id => doc.GetElement(id))
                .OfType<FamilyInstance>()
                .ToList();

            if (famInstances.Count == 0)
            {
                TaskDialog.Show("Swap Family Type", "No family instances were selected.");
                return Result.Failed;
            }

            // For simplicity, assume all selected family instances are of the same category.
            Category targetCategory = famInstances.First().Symbol.Category;
            if (famInstances.Any(fi => fi.Symbol.Category.Id != targetCategory.Id))
            {
                TaskDialog.Show("Swap Family Type", "Selected elements do not share the same category.");
                return Result.Failed;
            }

            // Build a list of available FamilySymbols for the target category.
            List<FamilySymbol> validSymbols = new FilteredElementCollector(doc)
                .OfClass(typeof(FamilySymbol))
                .Cast<FamilySymbol>()
                .Where(fs => fs.Category != null && fs.Category.Id.Equals(targetCategory.Id))
                .ToList();

            if (validSymbols.Count == 0)
            {
                TaskDialog.Show("Swap Family Type", "No valid family types found for the selected category.");
                return Result.Failed;
            }

            // Create a mapping from a unique key to the FamilySymbol Id.
            // The key is a composite of "Family|Type|Category".
            Dictionary<string, ElementId> symbolMapping = new Dictionary<string, ElementId>();
            List<Dictionary<string, object>> gridEntries = new List<Dictionary<string, object>>();
            foreach (FamilySymbol fs in validSymbols)
            {
                string familyName = fs.Family.Name;
                string typeName = fs.Name;
                string categoryName = fs.Category.Name;
                string key = $"{familyName}|{typeName}|{categoryName}";

                // To avoid duplicates.
                if (!symbolMapping.ContainsKey(key))
                {
                    symbolMapping.Add(key, fs.Id);

                    // Prepare grid entry.
                    var entry = new Dictionary<string, object>
                    {
                        { "Family", familyName },
                        { "Type", typeName },
                        { "Category", categoryName }
                    };
                    gridEntries.Add(entry);
                }
            }

            // Use the provided CustomGUIs.DataGrid to prompt the user to select a new family type.
            // (The DataGrid shows only the "Family", "Type" and "Category" columns.)
            List<string> columns = new List<string> { "Family", "Type", "Category" };
            List<Dictionary<string, object>> selectedRows = CustomGUIs.DataGrid(gridEntries, columns, spanAllScreens: false);

            // If the user cancelled or did not select anything.
            if (selectedRows == null || selectedRows.Count == 0)
                return Result.Cancelled;

            // We take the first selection.
            Dictionary<string, object> selectedEntry = selectedRows.First();
            string selFamily = selectedEntry["Family"].ToString();
            string selType = selectedEntry["Type"].ToString();
            string selCategory = selectedEntry["Category"].ToString();
            string selKey = $"{selFamily}|{selType}|{selCategory}";

            if (!symbolMapping.ContainsKey(selKey))
            {
                TaskDialog.Show("Swap Family Type", "Selected family type could not be found.");
                return Result.Failed;
            }

            ElementId newSymbolId = symbolMapping[selKey];
            FamilySymbol newSymbol = doc.GetElement(newSymbolId) as FamilySymbol;
            if (newSymbol == null)
            {
                TaskDialog.Show("Swap Family Type", "The selected family type is invalid.");
                return Result.Failed;
            }

            // Activate the new symbol if necessary.
            if (!newSymbol.IsActive)
            {
                using (Transaction t = new Transaction(doc, "Activate Family Symbol"))
                {
                    t.Start();
                    newSymbol.Activate();
                    doc.Regenerate();
                    t.Commit();
                }
            }

            // Begin transaction to swap family types and copy parameter values.
            using (Transaction t = new Transaction(doc, "Swap Family Type"))
            {
                t.Start();
                foreach (FamilyInstance fi in famInstances)
                {
                    // Get the old family type.
                    FamilySymbol oldSymbol = fi.Symbol;

                    // Collect parameter values from the old type.
                    // We only consider parameters that are not read-only.
                    Dictionary<string, object> paramValues = new Dictionary<string, object>();
                    foreach (Parameter oldParam in oldSymbol.Parameters)
                    {
                        if (!oldParam.IsReadOnly)
                        {
                            string paramName = oldParam.Definition.Name;
                            // Store the value based on storage type.
                            switch (oldParam.StorageType)
                            {
                                case StorageType.Double:
                                    paramValues[paramName] = oldParam.AsDouble();
                                    break;
                                case StorageType.Integer:
                                    paramValues[paramName] = oldParam.AsInteger();
                                    break;
                                case StorageType.String:
                                    paramValues[paramName] = oldParam.AsString();
                                    break;
                                case StorageType.ElementId:
                                    paramValues[paramName] = oldParam.AsElementId();
                                    break;
                            }
                        }
                    }

                    // Swap the family type.
                    // Note: Changing the type replaces the instance’s type with the new one.
                    fi.ChangeTypeId(newSymbol.Id);

                    // Now copy matching parameter values into the new family type.
                    // Since type parameters belong to the FamilySymbol, setting them here
                    // affects all instances of this type.
                    foreach (var kvp in paramValues)
                    {
                        Parameter newParam = newSymbol.LookupParameter(kvp.Key);
                        if (newParam != null && !newParam.IsReadOnly)
                        {
                            try
                            {
                                switch (newParam.StorageType)
                                {
                                    case StorageType.Double:
                                        if (kvp.Value is double dVal)
                                            newParam.Set(dVal);
                                        break;
                                    case StorageType.Integer:
                                        if (kvp.Value is int iVal)
                                            newParam.Set(iVal);
                                        break;
                                    case StorageType.String:
                                        newParam.Set(kvp.Value?.ToString());
                                        break;
                                    case StorageType.ElementId:
                                        if (kvp.Value is ElementId idVal)
                                            newParam.Set(idVal);
                                        break;
                                }
                            }
                            catch (Exception ex)
                            {
                                // Optionally log or handle exceptions when setting parameter values.
                                TaskDialog.Show("Parameter Copy Warning",
                                    $"Could not copy parameter '{kvp.Key}': {ex.Message}");
                            }
                        }
                    }
                }
                t.Commit();
            }
            return Result.Succeeded;
        }
    }
}

 

0 Likes
189 Views
1 Reply
Reply (1)
Message 2 of 2

jeremy_tammik
Alumni
Alumni

I see that you programmatically collect and cache the existing symbol parameter values and restore them after the swap. Why don't you simply do exactly the same with the instance parameters as well?

   

By the way, I see that you paramValues dictionary uses the parameter definition name as key. That is risky, because the same key may be repeated multiple times. You could use the Guid for shared parameters instead, and the built-in parameter id for built-in ones.

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open