public static void OnHierarchyModified()
        {
            if (External == null)
            {
                return;
            }

            HierarchyItem.CurrentLoopCount = (HierarchyItem.CurrentLoopCount + 1);

            UpdateRegistration();

            var startTime = EditorApplication.timeSinceStartup;

            if (OperationTransformChanged.Count > 0)
            {
                foreach (var item in OperationTransformChanged)
                {
                    if (item)
                    {
                        CheckOperationHierarchy(item);
                    }
                }
                OperationTransformChanged.Clear();
            }


            if (BrushTransformChanged.Count > 0)
            {
                foreach (var item in BrushTransformChanged)
                {
                    if (!item)
                    {
                        continue;
                    }

                    CheckBrushHierarchy(item);
                    ValidateBrush(item);                     // to detect material changes when moving between models
                }
                BrushTransformChanged.Clear();
            }
            hierarchyValidateTime = EditorApplication.timeSinceStartup - startTime;


            // remove all nodes that have been scheduled for removal
            if (RemovedBrushes.Count > 0)
            {
                External.DestroyNodes(RemovedBrushes.ToArray()); RemovedBrushes.Clear();
            }
            if (RemovedOperations.Count > 0)
            {
                External.DestroyNodes(RemovedOperations.ToArray()); RemovedOperations.Clear();
            }
            if (RemovedModels.Count > 0)
            {
                External.DestroyNodes(RemovedModels.ToArray()); RemovedModels.Clear();
            }


            for (var i = Brushes.Count - 1; i >= 0; i--)
            {
                var item = Brushes[i];
                if (item && item.brushNodeID != CSGNode.InvalidNodeID)
                {
                    continue;
                }

                UnregisterBrush(item);
            }

            for (var i = Operations.Count - 1; i >= 0; i--)
            {
                var item = Operations[i];
                if (!item || item.operationNodeID == CSGNode.InvalidNodeID)
                {
                    UnregisterOperation(item);
                    continue;
                }

                if (!item.ParentData.Transform)
                {
                    item.ParentData.Init(item, item.operationNodeID);
                }
            }

            for (var i = Models.Length - 1; i >= 0; i--)
            {
                var item = Models[i];
                if (!item || item.modelNodeID == CSGNode.InvalidNodeID)
                {
                    UnregisterModel(item);
                    continue;
                }

                if (!item.parentData.Transform)
                {
                    item.parentData.Init(item, item.modelNodeID);
                }
            }

            startTime = EditorApplication.timeSinceStartup;
            for (var i = Operations.Count - 1; i >= 0; i--)
            {
                var item = Operations[i];
                if (!item || item.operationNodeID == CSGNode.InvalidNodeID)
                {
                    continue;
                }

                var parentData = item.ChildData.OwnerParentData;
                if (parentData == null)
                {
                    continue;
                }

                ParentNodeDataExtensions.UpdateNodePosition(item.ParentData, parentData);
            }

            for (var i = Brushes.Count - 1; i >= 0; i--)
            {
                var item = Brushes[i];
                if (!item || item.brushNodeID == CSGNode.InvalidNodeID)
                {
                    continue;
                }

                var parentData = item.ChildData.OwnerParentData;
                if (parentData == null)
                {
                    continue;
                }

                ParentNodeDataExtensions.UpdateNodePosition(item.hierarchyItem, parentData);
            }

            if (External.SetChildNodes != null)
            {
                for (var i = 0; i < Operations.Count; i++)
                {
                    var item = Operations[i];
                    if (!item || item.operationNodeID == CSGNode.InvalidNodeID)
                    {
                        continue;
                    }

                    if (!item.ParentData.ChildrenModified)
                    {
                        continue;
                    }

                    var childList = UpdateChildList(item.ParentData);

                    External.SetChildNodes(item.operationNodeID, childList.Length, childList);
                    item.ParentData.ChildrenModified = false;
                }

                for (var i = 0; i < Models.Length; i++)
                {
                    var item = Models[i];
                    if (!item || item.modelNodeID == CSGNode.InvalidNodeID)
                    {
                        continue;
                    }

                    if (!item.parentData.ChildrenModified)
                    {
                        continue;
                    }

                    var childList = UpdateChildList(item.parentData);

                    External.SetChildNodes(item.modelNodeID, childList.Length, childList);
                    item.parentData.ChildrenModified = false;
                }
            }

            updateHierarchyTime = EditorApplication.timeSinceStartup - startTime;
        }
 public static void OnOperationTransformChanged(CSGOperation op)
 {
     // unfortunately this event is sent before it's destroyed, so we need to defer it.
     OperationTransformChanged.Add(op);
 }