The end result I'm after is to propagate attached schema from Family (added to OwnerFamily at family editor stage) to the FamilyInstance when it first gets added to project. And I'm struggling to make it less trigger happy and more selective.
It's an adaptive family so I'm also trying to propagate it to ReferencePoints as well.
-using GetChangeTypeAny allows me to propagate to both;
-using GetChangeTypeGeometry allows me to propagate to FamilyInstance only;
-using GetChangeTypeElementAddition does nothing, it does get triggered when Family gets loaded into the project.
For filters I've tried:
LogicalOrFilter logicFilter = new LogicalOrFilter(
new ElementClassFilter(typeof(FamilyInstance)),
new ElementClassFilter(typeof(ReferencePoint))
);
LogicalOrFilter logicFilter = new LogicalOrFilter(
new ElementCategoryFilter(BuiltInCategory.OST_GenericModel),
new ElementCategoryFilter(BuiltInCategory.OST_AdaptivePoints)
);
Because of the remark on AddTrigger - "This method only works with CategoryFilter and ParameterFilter" which made no difference in this context.
I really want to avoid unnecessarily firing the IUpdater when I go to modify the FamilyInstance or its Adaptive Placement Points, and using GetChangeTypeElementAddition() is a logical choice, propagate the schema once and done, rather than have it get called upon any slight interaction or modification, but the way FamilyInstances get added to the project is bizarre to me, you'll only capture their ID's with GetModifiedElementIds() not GetAddedElementIds() same with ReferencePoints connected to the FamilyInstance.
Can the GetChangePriority be the culprit? I don't have a good grasp on it's purpose. But it didn't interfere when used with GetChangeTypeAny
public ChangePriority GetChangePriority() => ChangePriority.FreeStandingComponents;
Here's my Execute Implementation for the IUpdater:
public class MyElementAdditionUpdater : IUpdater
{
private readonly UpdaterId _updaterId;
public MyElementAdditionUpdater(AddInId addInId, Guid updaterGuid)
{
_updaterId = new UpdaterId(addInId, updaterGuid);
}
public void Execute(UpdaterData data)
{
Document doc = data.GetDocument();
Schema schema = Schema.Lookup(schemaGuid);
if (schema == null)
{
Debug.Print("Schema not found, exiting.");
return; // Exit if schema doesn't exist
}
ElementClassFilter familyInstanceFilter = new ElementClassFilter(typeof(FamilyInstance));
// Iterate over modified elements that are either FamilyInstance or ReferencePoint
foreach (ElementId modifiedElementId in data.GetModifiedElementIds())
{
Element modifiedElement = doc.GetElement(modifiedElementId);
if (!(modifiedElement is FamilyInstance || modifiedElement is ReferencePoint))
{
continue; // Skip if the element is neither FamilyInstance nor ReferencePoint
}
//GetDependentElements return FamilyInstance, from which Family schema could be copied
var dependentElementIds = modifiedElement.GetDependentElements(familyInstanceFilter);
foreach (ElementId dependentElementId in dependentElementIds)
{
FamilyInstance dependentFamilyInstance = doc.GetElement(dependentElementId) as FamilyInstance;
if (dependentFamilyInstance == null)
{
continue; // Skip if the dependent element is not a FamilyInstance
}
Family family = dependentFamilyInstance.Symbol.Family;
Entity familyEntity = family.GetEntity(schema);
if (familyEntity == null)
{
Debug.Print($"Schema not found in family: {family.Name}, skipping.");
continue; // Skip if the family does not have the schema
}
// Apply schema from family to both FamilyInstance and ReferencePoint
Entity instanceEntity = new Entity(schema);
bool schemaApplied = false;
foreach (Field field in schema.ListFields())
{
if (familyEntity.Schema == null)
{
continue; // Skip if the schema field does not exist in the family entity
}
string value = familyEntity.Get<string>(field); // Assuming field values are string for simplicity
instanceEntity.Set(field, value);
schemaApplied = true;
}
if (schemaApplied)
{
Debug.Print($"Preparing to apply schema to element ID: {modifiedElementId}, Type: {modifiedElement.GetType().Name}.");
try
{
modifiedElement.SetEntity(instanceEntity);
Debug.Print($"Schema applied from Family {family.Name} to element ID: {modifiedElementId}.");
}
catch (Exception ex)
{
Debug.Print($"Exception applying schema: {ex.Message}");
}
}
}
}
}
public string GetAdditionalInformation() => "Handles addition of new elements with a specific schema.";
public ChangePriority GetChangePriority() => ChangePriority.FreeStandingComponents;
public UpdaterId GetUpdaterId() => _updaterId;
public string GetUpdaterName() => "Custom Element Addition Updater";
}
The purpose of this attempt at propagation is so that I can have another IUpdater that will only get triggered in response to elements with schema that are getting manipulated by the user and to that end ExtensibleStorageFilter should do the job. But surely there's a better strategy to propagate Family Schema to it's FamilyInstance and other relevant objects.
Appreciate any help!
Your question sounds rather complex and fine-tuned to me. Does the schema have to be applied immediately when the elements are added? Could you defuse the situation tremendously by implementing some other kind of check? Maybe you could scan the BIM and add the schema to elements lacking it on a regular basis when the model is closed, or every couple of minutes in a timer-triggered external event? Then it would not be quite as performance critical as it seems to be now, would it?
People have complained in the past about DMUs using specific change types not working as expected, and being forced to use ChangeTypeAny to avoid those problems.
The change priority defines the priority and thus the sort order of the modification:
Hi @jeremy_tammik, an honor!
Thank you for clarifying that the change priority isn’t impacting the targeting/filtering aspects of my implementation. It's somewhat comforting to know that the unpredictability with specific ChangeTypes and DMUs isn't just on my end, though it does add a layer of complexity to navigating around it.
As of right now my implementation is targeting single family element, and sure there's few workaround to be explored, but this code is being written with intend to expand to other families of the model in the future. The strategy was that if I could employs a non-interactable marker or proxy — that is essentially invisible to the end-user but detectable by the system then I could employ second IUpdater to automate some of the family modifications while keeping it light and efficient.
Right after a family instance is first placed, it's supposed to be automatically updated by my code, afterwards I foresee minor interaction with said FamilyInstance consisting of just slight adjustments compared to edits made to the rest of the project. That's why it's integral to make second IUpdater highly selective of when it fires off making use of ExtensibleStorageFilter highly desirable.
I had some thoughts about inverting the process of filtering, instead of looking at FamilyInstances and working my way to Family to check if it contains schema as I'm doing now, I could get single Family with said schema, and look at it's GetDependentElements. But that's likely irrelevant thought. As I would still need some efficient trigger for when FamilyInstance comes into being, which begs a question...
Why addition of FamilyInstance or it's ReferencePoints isn't being captured by GetAddedElementIds() at least from RevitLookup Event monitor? If there's some trick to getting GetAddedElementIds() to work on FamilyInstances as per the post here, it might be acceptable workaround to help exit updater if nothing of interest it getting added to the model.
Is there maybe some other way of selectively monitoring for when FamilyInstance get's added to the project rather than modified? Or even a totally different strategy to consider?
Main considerations:
Any further advice or ideas would be greatly appreciated. I'm willing to go deeper into rabbit hole of Revit API before I opt for an easy way out.
Thanks again!
As I'm drilling down further into my code, I've reverted back to simple trigger.
UpdaterRegistry.AddTrigger(updater.GetUpdaterId(), logicFilter, Element.GetChangeTypeElementAddition());
Added few debug lines in the Execute()
public void Execute(UpdaterData data)
{
Document doc = data.GetDocument();
Schema schema = Schema.Lookup(schemaGuid);
// Log all modified element IDs
foreach (ElementId modifiedElementId in data.GetModifiedElementIds())
{
Element modifiedElement = doc.GetElement(modifiedElementId);
Debug.Print($"Modified Element ID: {modifiedElementId}, Type: {modifiedElement.GetType().Name}");
}
// Log all added element IDs
foreach (ElementId addedElementId in data.GetAddedElementIds())
{
Element addedElement = doc.GetElement(addedElementId);
Debug.Print($"Added Element ID: {addedElementId}, Type: {addedElement.GetType().Name}");
}
And surprisingly I'm getting the following in my Debug output
Updater has been registered.
Added Element ID: 694197, Type: FamilySymbol
.................
Added Element ID: 694750, Type: ReferencePoint
Added Element ID: 694834, Type: ReferencePoint
Updater gets registered when on DocumentOpened or FamilyLoadedIntoDocument we check for presence of schema and it exists.
The family in question is Generic Model Adaptive, with three Placement Points, but only 2 get recorded, so I tested the approach with simple Generic Model instead...
Added Element ID: 695845, Type: FamilySymbol
............
Added Element ID: 695861, Type: FamilyInstance
Going back to more encompassing GetChangeType
UpdaterRegistry.AddTrigger(updater.GetUpdaterId(), logicFilter, ChangeType.ConcatenateChangeTypes(Element.GetChangeTypeGeometry(), Element.GetChangeTypeElementAddition()));
And my Debug Outputs:
Updater has been registered.
.............
Added Element ID: 694647, Type: ReferencePoint
Added Element ID: 694674, Type: ReferencePoint
Modified Element ID: 694221, Type: FamilyInstance
Preparing to apply schema to element ID: 694221, Type: FamilyInstance.
Schema applied from Family Stockpile Radial Adj to element ID: 694221.
But where's the 3rd point?
Switching to GetChangeTypeAny() and my debug now looks like this
Modified Element ID: 694213, Type: FamilyInstance
Modified Element ID: 694214, Type: ReferencePoint
Modified Element ID: 694215, Type: ReferencePoint
Modified Element ID: 694216, Type: ReferencePoint
Modified Element ID: 694217, Type: ReferencePoint
Preparing to apply schema to element ID: 694213, Type: FamilyInstance.
Schema applied from Family Stockpile Radial Adj to element ID: 694213.
Preparing to apply schema to element ID: 694214, Type: ReferencePoint.
Schema applied from Family Stockpile Radial Adj to element ID: 694214.
Preparing to apply schema to element ID: 694215, Type: ReferencePoint.
Schema applied from Family Stockpile Radial Adj to element ID: 694215.
Preparing to apply schema to element ID: 694216, Type: ReferencePoint.
Schema applied from Family Stockpile Radial Adj to element ID: 694216.
Preparing to apply schema to element ID: 694217, Type: ReferencePoint.
Schema applied from Family Stockpile Radial Adj to element ID: 694217.
Just how do I tackle Generic Family Adaptive 😫?
Can't find what you're looking for? Ask the community or share your knowledge.