cancel
Showing results for 
Search instead for 
Did you mean: 
EN
cancel
Showing results for 
Search instead for 
Did you mean: 
Emkave
Enthusiast

Monitor undos

Hello guys. Have somebody implemented a trigger system where a certain method/function will be executed if "Undo" command was used either from Archicad ui or by api? If so - how did you implement that, else what can you recommend? 

 

Context: I am trying to code a mechanism, when user has an element with IFC properties, then the user deletes that object and ifc data is deleted with it as well, and if user decides to undo the deletion, the IFC data should be returned as well.  However, I am storing IFC properties in a std::unordered_map and I am unsure how can I make it behave certain way depending on undoing or redoing. 

 

Thank you in advance! 

1 Solution

Accepted Solutions

As long as the undo stack remains valid, an add‑on can safely track the elements it creates by installing element observers on them. If the user deletes one of these elements and later performs Undo, Archicad restores the element with the same GUID, and the observer notifies the add‑on of the element's "re‑creation" through the registered callback (as described in my previous answer). Archicad does not "recreate" the element in a new sense; instead, it restores the project database to the exact previous state, including the element with its original GUID. Naturally, a deletion that was undone can also be redone.

 

However, I think, it is important to know that certain operations in Archicad automatically clear the undo stack, for example:

  • deleting stories;
  • deleting layers or layer combinations;
  • modifying certain project attributes, etc.

When the undo stack is cleared, previously deleted elements cannot be restored by Undo, and therefore the add‑on cannot rely on Undo to recover its own data or at least to verify its data integrity.

 

To handle this scenario, the add‑on should not rely solely on Undo/Redo. Instead, it should maintain its own persistent record of the elements it manages. A descriptor-based integrity-checking strategy may work well here.

 

When the add‑on creates its initial elements, it should also create a descriptor object that stores the GUIDs (and other properties) of all elements under its control. This descriptor should be saved in the module data of the project file.

 

On subsequent startups (or when the add‑on is reloaded), the add‑on should:

  • load the stored module data;
  • check whether each stored GUID still corresponds to an existing element in the project;
  • detect missing or modified elements and react accordingly (e.g., recreate them, warn the user, or rebuild internal state).

This ensures that even if:

  • the undo stack was cleared,

  • the project was modified outside the add‑on's control,

  • or the project was saved/closed/reopened (in both teamwork and solo mode),

the add‑on can still validate the integrity of its managed elements and recover gracefully when needed.

After each integrity check, the module data should be updated accordingly.

 

Short answer: you can reliably track element identity as long as Archicad's undo stack is valid. For long‑term consistency, store the GUIDs of your managed elements in module data and perform integrity checks on startup (during the add-on's initialization). This combination covers both normal undo/redo operations and cases where the undo stack is cleared or the project is modified without the add‑on's knowledge.

Go to post

5 Replies 5
Tamas Polyak
Graphisoft
Graphisoft

Hi,

I'll look into some scenarios.

 

Regards,

Tamás

I am not an expert on this topic, but I would proceed as follows.

 

To track changes in specific elements, you must first attach an observer to each element using ACAPI_Element_AttachObserver. This marks the element as "observable." Next, you install a global element observer using ACAPI_Element_InstallElementObserver, which registers your APIElementEventHandlerProc callback. Whenever an observed element is modified, deleted, or restored, Archicad calls your handler and sends notifications grouped between APINotifyElement_BeginEvents and APINotifyElement_EndEvents. Inside this handler, you can collect GUIDs or react to the changes.

 

The source code below provides an abstract example that may be used as a starting point.

 

// ApiGuidSet is defined as a hash set of API_Guid values
using ApiGuidSet = GS::HashSet<API_Guid>;

// This handler demonstrates how GUIDs of modified or deleted elements
// are collected between APINotifyElement_BeginEvents and APINotifyElement_EndEvents.
// Its declaration matches the expected signature of APIElementEventHandlerProc.

GSErrCode ElementEventHandler (const API_NotifyElementType* notifyElementType)
{
    GSErrCode err = GS::NoError;

    // Tracks what kind of operation is currently being processed
    enum class OperationInProgress {
        ChangeOrModify,
        Delete,
        UndoDeleted,
        Undefined
    };

    // Stores GUIDs of elements that are collected during the current event batch
    static ApiGuidSet guidsOfApiElements;

    // Tracks the type of operation happening between BeginEvents and EndEvents
    static OperationInProgress operationInProgress = OperationInProgress::Undefined;

    // The GUID set and the operation in progress are declared static because element notifications
    // arrive in batches between APINotifyElement_BeginEvents and APINotifyElement_EndEvents.
    // The observer callback is invoked multiple times during a single batch, and static storage allows
    // us to accumulate all affected element GUIDs across these calls. When the final
    // APINotifyElement_EndEvents notification arrives, the complete set of collected GUIDs
    // can be processed as one coherent operation before the set is cleared for the next batch.

    switch (notifyElementType->notifID) {

        case API_ElementDBEventID::APINotifyElement_BeginEvents:
            // Marks the start of a new event batch.
            // GUID collection begins only after this point.
            operationInProgress = OperationInProgress::Undefined;
            guidsOfApiElements.Clear ();   // ensure clean start
            break;

        case API_ElementDBEventID::APINotifyElement_Delete:
        case API_ElementDBEventID::APINotifyElement_Redo_Deleted:
        {
            // A delete operation is happening in this batch.
            operationInProgress = OperationInProgress::Delete;

            // Collect GUID of deleted element
            guidsOfApiElements.Add (notifyElementType->elemHead.guid);
            break;
        }

        case API_ElementDBEventID::APINotifyElement_Undo_Deleted:
        {
            // An undo-delete operation is happening in this batch.
            operationInProgress = OperationInProgress::UndoDeleted;

            // Collect GUID of restored element
            guidsOfApiElements.Add (notifyElementType->elemHead.guid);
            break;
        }

        case API_ElementDBEventID::APINotifyElement_Change:
        case API_ElementDBEventID::APINotifyElement_Edit:
        case API_ElementDBEventID::APINotifyElement_Redo_Modified:
        case API_ElementDBEventID::APINotifyElement_Undo_Modified:
        {
            // A modification is happening in this batch.
            operationInProgress = OperationInProgress::ChangeOrModify;

            // Collect the GUID of the modified element.
            const API_Elem_Head& headerOfChangedApiElement = notifyElementType->elemHead;
            guidsOfApiElements.Add (headerOfChangedApiElement.guid);

            break;
        }

        case API_ElementDBEventID::APINotifyElement_EndEvents:
            // Marks the end of the event batch.
            // At this point, all collected GUIDs are processed depending on the operation type.

            // The GUIDs collected by the observer should be intersected with the GUID set
            // of elements that your add‑on created and attached observers to, ensuring that
            // only relevant elements are processed. This filtering should be performed inside
            // the corresponding processing functions (ProcessModifiedGuids,
            // ProcessDeletedGuids, ProcessUndoDeletedGuids), so that each function handles
            // only the subset of GUIDs that belong to elements managed by your add‑on.

            switch (operationInProgress) {

                case OperationInProgress::ChangeOrModify:
                {
                    // All GUIDs collected during this batch represent modified elements.
                    ProcessModifiedGuids (guidsOfApiElements);
                    break;
                }

                case OperationInProgress::Delete:
                {
                    // GUIDs represent deleted elements.
                    ProcessDeletedGuids (guidsOfApiElements);
                    break;
                }

                case OperationInProgress::UndoDeleted:
                {
                    // GUIDs represent restored elements.
                    ProcessUndoDeletedGuids (guidsOfApiElements);
                    break;
                }

                default:
                    break;
            }

            // Reset for the next batch.
            operationInProgress = OperationInProgress::Undefined;
            guidsOfApiElements.Clear ();
            break;
    }

    return err;
}

 

Emkave
Enthusiast

So I guess there are no functions to monitor undos and redos directly. However, what if I create element A, set observer to it, delete the element, and then Undo the deletion? Will the API consider this to be like "object creation" or? Will it have the same guid or different? Will it also affect Redo operations? 

As long as the undo stack remains valid, an add‑on can safely track the elements it creates by installing element observers on them. If the user deletes one of these elements and later performs Undo, Archicad restores the element with the same GUID, and the observer notifies the add‑on of the element's "re‑creation" through the registered callback (as described in my previous answer). Archicad does not "recreate" the element in a new sense; instead, it restores the project database to the exact previous state, including the element with its original GUID. Naturally, a deletion that was undone can also be redone.

 

However, I think, it is important to know that certain operations in Archicad automatically clear the undo stack, for example:

  • deleting stories;
  • deleting layers or layer combinations;
  • modifying certain project attributes, etc.

When the undo stack is cleared, previously deleted elements cannot be restored by Undo, and therefore the add‑on cannot rely on Undo to recover its own data or at least to verify its data integrity.

 

To handle this scenario, the add‑on should not rely solely on Undo/Redo. Instead, it should maintain its own persistent record of the elements it manages. A descriptor-based integrity-checking strategy may work well here.

 

When the add‑on creates its initial elements, it should also create a descriptor object that stores the GUIDs (and other properties) of all elements under its control. This descriptor should be saved in the module data of the project file.

 

On subsequent startups (or when the add‑on is reloaded), the add‑on should:

  • load the stored module data;
  • check whether each stored GUID still corresponds to an existing element in the project;
  • detect missing or modified elements and react accordingly (e.g., recreate them, warn the user, or rebuild internal state).

This ensures that even if:

  • the undo stack was cleared,

  • the project was modified outside the add‑on's control,

  • or the project was saved/closed/reopened (in both teamwork and solo mode),

the add‑on can still validate the integrity of its managed elements and recover gracefully when needed.

After each integrity check, the module data should be updated accordingly.

 

Short answer: you can reliably track element identity as long as Archicad's undo stack is valid. For long‑term consistency, store the GUIDs of your managed elements in module data and perform integrity checks on startup (during the add-on's initialization). This combination covers both normal undo/redo operations and cases where the undo stack is cleared or the project is modified without the add‑on's knowledge.

Emkave
Enthusiast

Also is it possible somehow to distinguish what exactly was undoed? 

For example, if I have an element A with certain parameters set, then I move it somewhere, then UNDO, the API does not distingush what exactly was undoed. It just returns APINotifyElement_Undo_Modified, just like it would return if I had an element A set with parameter B, then I update A with parameter C and then undo it. 

Didn't find the answer?

Check other topics in this Forum

Back to Forum

Read the latest accepted solutions!

Accepted Solutions

Start a new conversation!