Message 1 of 2
swap family type instance and specify instance parameters
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report
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;
}
}
}