//================================================================
// EQUINOX-3D(TM) plugin
//
// - Edit selected Node, Object, PolyGroup etc
//
// AUTHOR:  Gabor Nagy
// DATE:    2026-Jul-29 17:32:51
//
// Copyright (C) 1996-2026 By Gabor Nagy. All rights reserved.
//================================================================
#include <stdio.h>

#include <EMalloc.h>
#include <EPlugins.h>

#ifndef _E3D_E3D_h
#include <E3D/E3D.h>
#endif

#include <E3D/Scene.h>
#include <E3D/Editing.h>
#include <E3D/Deleting.h>
#include <E3D/SplineOps.h>

#include <E3D/UI/Panel.h>
#include <E3D/UI/Panels.h>
#include <E3D/UI/StatusPanel.h>  // For E3dp_PrintMessage

#include <Xe/TextField.h>

#include <Xe/Shell.h>
#include <Xe/Dialog.h>
#include <Xe/ToggleButton.h>

#include "Edit.h"

#include "../EQXExt.h"


EPlugin*             _EditInfoPlugin = NULL;
static EPluginLock*  _NodeDialogLock = NULL;


static XeDWidget          _MenuButtons[16];static int  _MenuButtons_Count = 0;
static EScriptFunction*  _ScriptFunctions[32];static int  _ScriptFunctions_Count = 0;


// Node dialog box
//
static XeWidget       _NodeDialogW = NULL;
static E3dNodePanel*  _NodePanel = NULL;


static void     _CB_Scene(ECoreObject* PScn, EPointer PClientData, ECallbackStruct* PCallData);

static EBool    _NodeSelectionCallbackAdded = FALSE;


enum
{
	CR_EDIT_SELECTION = XeCR_CUSTOM0
};


//========================================
// Close a Node dialog box
//========================================
static void _NodeDialog_Close(E3dNodePanel* PPanel)
{
	E3dScene*  LScene = E3d_Scene;

//printf("%s() %s:%d  %sGREEN%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
	E3dNode*  LNode = PPanel->Node;if(LNode)
	{
		{
			EIndex  LI = 0;for(;LI < LNode->Components_Count;LI++)
			{
				E3dNodeComponent*  LInfo = LNode->Components[LI];

//printf("%s() %s:%d  [%s] {%s}\n", __FUNCTION__, __FILE__, __LINE__, LNode->Name, LInfo->Class->Name);fflush(stdout);
				XeComponentClassDrawer*  LDrawer = LI < PPanel->ComponentClassFolders.Count - 1  ? PPanel->ComponentClassFolders.Items[LI] : NULL;
				if(LInfo->Class && LInfo->Class->GUI) LInfo->Class->GUI(LNode, LInfo, (XeInfoPanel*)PPanel, LDrawer, E_CLOSE);
			}
		}

		XeWidget_Unmap(_NodeDialogW);

		EChangeFlags  LChanged = E3dNode_LockDel(LNode, &E3dNode_EditLock);

		E_Delete(PPanel->Node);LNode = NULL;

		E3dWindows_RedrawByChangeFlags(LScene, LChanged);
	}

	E3dScene_CallbackRemove(LScene,  _CB_Scene, NULL);
	_NodeSelectionCallbackAdded = FALSE;
}


//========================================
// Node dialog callback
//========================================
static void _DCB_NodeClose(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	XeCallbackStruct*  LCBS = (XeCallbackStruct*)PCallData;
	if(LCBS->Reason == XeCR_PROTOCOLS)
	{
		E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
		_NodeDialog_Close(LPanel);
		EPluginLock_Del(_EditInfoPlugin, &_NodeDialogLock, FALSE);
	}
}


//========================================
// Delete-lock callback
//========================================
static int _NodeDialog_LockDel(EPlugin* PPlugin, EPluginLock* PLock)
{
	_NodeDialog_Close(_NodePanel);

	return(E_SUCCESS);
}


//========================================
// Edit Node attributes
//========================================
static void _NodePopupDialog(E3dNode* PNode, E3dNodeComponentClass* POpenFolderForThisComponentClass)
{
	if(_NodeDialogLock == NULL) _NodeDialogLock = EPluginLock_Add(_EditInfoPlugin, "EditNodeInfo", _NodeDialog_LockDel);

	E3dNodePanel*  LPanel;
	E3dScene*      LScene = E3d_Scene;

	if(_NodeDialogW == NULL)
	{
		EArg  Args[256];int  ArgCnt;

		EXeStart;
		EXeSetArg(XeNAllowShellResize, TRUE);
		EXeSetArg(XeNCreateButtons, FALSE);
		EXeSetArg(XeNWorkAreaType, XeMATRIX);
		EXeSetArg(XeNMinSizeX, 240);
		EXeSetArg(XeNCreateButtons, FALSE);
		EXeSetArg(XeNWMDecorations, XeWMDecorCLOSE|XeWMDecorBORDER|XeWMDecorRESIZEH|XeWMDecorTITLE);
		EXeSetArg(XeNTitle, "Edit Node attributes");EXeSetArg(XeNIconName, "Node");
		XeWidget  LDialogW = XeDialog_New(XeDIALOG_TEMPLATE, "Node", E3dp_TopShellW,  Args, ArgCnt);if(LDialogW == NULL) return;

		_NodeDialogW = LDialogW;

		XeWidget  LTopMatrixW = XeDialog_GetChild(LDialogW, XeDIALOG_WORK_AREA);
		EXeStart;
		EXeSetArg(XeNOrientation, XeVERTICAL);
		EXeSetArg(XeNSpacingY, 2);
		Xe_SetValues(LTopMatrixW,  Args, ArgCnt);

		_NodePanel = LPanel = E3dNodePanel_New(LTopMatrixW, PNode, -1, POpenFolderForThisComponentClass);
//		E3dNodePanel_Update(_NodePanel, PNode, -1,  NULL, NULL);

		EList_PointerAddA_s(E3d_NodePanels, 1, _NodePanel);

		XeAddCallback(LDialogW, XeNWMCallback, _DCB_NodeClose, (EPointer)_NodePanel);

		XeShell_Raise(_NodeDialogW);
		XeShell_CenterToWidget(_NodeDialogW, E3dp_TopShellW);

		if(E3dNode_LockAdd(PNode, &E3dNode_EditLock))  E3dWindows_RedrawByChangeFlags(LScene, E3dChgLOCK_UNLOCK);
	}
	else
	{
		EBool  LRedraw = FALSE;

		LPanel = _NodePanel;

// Is the dialog-box already up?
//
		if(LPanel->Node == PNode)
		{
			E3dNodePanel_Update(LPanel, PNode, -1,  NULL, POpenFolderForThisComponentClass);

// Prevent the dialog box stealing keyboard focus, by only raising it, if it's not visible
//
			if(XeShell_DisplayStatus(_NodeDialogW) != XeDISPLAYED) XeShell_Raise(_NodeDialogW);

			return;
		}
		else if(LPanel->Node)
		{
			E3dNode_LockDel(LPanel->Node, &E3dNode_EditLock);
			LRedraw = TRUE;
		}

		E3dNodePanel_Update(_NodePanel, PNode, -1,  NULL, POpenFolderForThisComponentClass);
		if(E3dNode_LockAdd(LPanel->Node, &E3dNode_EditLock)) LRedraw = TRUE;

		if(LRedraw) E3dWindows_RedrawByChangeFlags(LScene, E3dChgLOCK_UNLOCK);
	}

	if(Eqx_Prefs.SelectAndFocusOnName)
	{
		XeShell_Raise(_NodeDialogW);
		XeDisplay*  LDisplay = Xe_GetDisplay(LPanel->NameW);
		if(!E_IsKeyDown(LDisplay, 0))
		{
			Xe_SetFocus(LPanel->NameW, TRUE, TRUE);
			XeTextField_SelectAll(LPanel->NameW);
		}
	}

	if(!_NodeSelectionCallbackAdded)
	{
		E3dScene_CallbackAdd(E3d_Scene, E3dChgSELECTION, _CB_Scene, (EPointer)0);
		_NodeSelectionCallbackAdded = TRUE;
	}
}


//========================================
// Edit Node attributes
//========================================
static void _Node_Edit(E3dNode* PNode, E3dNodeComponentClass* POpenFolderForThisComponentClass)
{
	EBool  LDoEdit = TRUE;

	if(PNode->Components_Count > 0)
	{
		E3dNodeComponentClass*  LClass = PNode->Components[0]->Class;if(LClass)
		{
			if(LClass->Edit)
			{
				const int  LResult = LClass->Edit((E3dNode*)PNode, PNode->Components[0]);switch(LResult)
				{
					case E3dEDITP_NOTHING:
					case E3dEDITP_INFO_ADDED:
					break;

					default:  LDoEdit = FALSE;break;
				}
			}
		}
	}

	if(LDoEdit) _NodePopupDialog(PNode, POpenFolderForThisComponentClass);
}


//========================================================================================
// Return TRUE, if PNode's attributes are being edited (the dialog box is displayed)
//========================================================================================
static EBool _Node_IsEdited(E3dNode* PNode)
{
	if(_NodePanel && _NodePanel->Node == PNode) return(TRUE);
	return(FALSE);
}


//========================================
// Menu callback
//========================================
static void _MCB_Edit(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dItem  LSelectedItem;

	switch((EPointerInt)PClientData)
	{
		case CR_EDIT_SELECTION:
			switch(E3dScene_GetSingleSelection(E3d_Scene, &LSelectedItem, E3dItemALL, 0))
			{
				case E3dItemMODEL:
					_Node_Edit(LSelectedItem.ObjItem.Node, NULL);
				break;

				case E3dItemOBJECT:
					{
						E3dObjItem*               LObjItem = &LSelectedItem.ObjItem;
						E3dObject*                LObject = LObjItem->Object;
						E3dObjectComponentClass*  LOpenFolderForThisComponentClass = NULL;
						if(LObject->Components_Count == 1) LOpenFolderForThisComponentClass = LObject->Components[0]->Class;

						_Object_Edit((E3dModel*)LObjItem->Node, LObject, 0, NULL, -1,  LOpenFolderForThisComponentClass);
					}
				break;

				case E3dItemFACE_CONTOUR:
					{
						E3dSplineItem*  LSplineItem = &LSelectedItem.SplineItem;

						_Object_Edit((E3dModel*)(LSplineItem->Node), LSplineItem->Object, 0, (EObject*)(LSplineItem->Spline), 0,  NULL);
					}
				break;

				case E3dItemSPLINE_SEGMENT:
					{
						E3dSplineItem*  LSplineItem = &LSelectedItem.SplineItem;
						E3dObject*      LObject = LSplineItem->Object;

						if(EObject_IsSubClass(LObject, E3dSpline)) _Object_Edit((E3dModel*)(LSplineItem->Node), LObject, 0, NULL, LSplineItem->PointIndex,  NULL);
						else if(EObject_IsSubClass(LObject, E3dFace)) _Object_Edit((E3dModel*)(LSplineItem->Node), LObject, 0, (EObject*)(LSplineItem->Spline), LSplineItem->PointIndex,  NULL);
					}
				break;

				case E3dItemPOLYGROUP:
					{
						E3dPolyItem*  LPolyItem = &LSelectedItem.PolyItem;

printf("%s() %s:%d  %sGREEN%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);

						_PolyGroupUI_PopupDialog(LPolyItem->PolyGroup, NULL, TRUE);
					}
				break;
			}
		break;
	}
}


//========================================
// Key callback	
//========================================
static void _KCB_Edit(const EKeyCode PKey, const unsigned int PModifiers, const int PKeyEventType, EPointer PClientData)
{
	_MCB_Edit(NULL, PClientData, NULL);
}



//========================================
// Scene changed callback
//========================================
static void _CB_Scene(ECoreObject* PScn, EPointer PClientData, ECallbackStruct* PCallData)
{
	switch(PCallData->Reason)
	{
		case E3dCR_SELECTION_CHANGED:
			{
				E3dNodePanel*  LPanel = _NodePanel;if(LPanel)
				{
					if(LPanel->FollowSelection)
					{
						if(LPanel->Node)
						{
							E3dItem  LSelectedItem;

							switch(E3dScene_GetSingleSelection(E3d_Scene, &LSelectedItem, E3dItemALL, 0))
							{
								case E3dItemMODEL:
									_Node_Edit(LSelectedItem.ObjItem.Node, NULL);
								break;
							}
						}
					}
				}
			}
		break;
	}
}









//-------------------------------------------------------------------------------------------------------
// Deleting
//-------------------------------------------------------------------------------------------------------

static EOperatorClass*  _OperatorDelete_class = NULL;
static EOperatorClass*  _OperatorDeleteAnimations_class = NULL;

// E3dFINISHME this avoids calling E3dMesh_PolyGroupsEdgesFromMeshEdges() after Mesh Edges were added / removed, so it may be faster at the cost of using more memory
// to store the removed Mesh Edge indices for UNDO.
// Implementation of this is not quite finished yet. It only remaps Mesh Edge indices in PolyGroups with added/removed Edges and/or Polygons...
// We need to do this in all PolyGroups of an affected Mesh!
// Test and see if the speed-up is worth finishing the implementation.
//
//#define E3dRESTORE_POLYGROUP_EDGES  1

//#define E3dDebugEDGES              1
//#define E3dDebugDELETE_VERTICES    1


//========================================
// Free an array of ObjectAndIndex
//========================================
static void _ObjectAndIndexListFree(EObjectAndIndex*** PListP, EIndex* PCountP)
{
	if(PListP)
	{
		EObjectAndIndex**  LList = *PListP;

		EIndex  LI = 0;const EIndex  LN = *PCountP;for(;LI < LN;LI++)
		{
			EObjectAndIndex*  LEntry = LList[LI];
			E_Delete(LEntry->Object);
			EFree(LEntry);
		}
		EFree(LList);*PListP = NULL;*PCountP = 0;
	}
}


static void _OpObjectFreeContents(E3dOpObject* POpObject)
{
	E_DeleteChk(POpObject->Object);

	_ObjectAndIndexListFree(&POpObject->Models, &POpObject->Models_Count);

	if(POpObject->SubObjects)
	{
		EIndex  LSOI = 0;const EIndex  LSON = POpObject->SubObjects_Count;E3dOpSubObject*  LOpSubObject = POpObject->SubObjects;for(;LSOI < LSON;LSOI++, LOpSubObject++)
		{
			E_Delete(LOpSubObject->SubObject);
		}
		EFree(POpObject->SubObjects);
	}
}


#define _PolyGroupPrintEdges(mPolyGroup) do\
{\
	const EIndex  LPGEN = mPolyGroup->Edges_Count;\
printf("%s() %s:%d  PolyGroup Edges: %3d:\n", __FUNCTION__, __FILE__, __LINE__, (int)LPGEN);fflush(stdout);\
	EIndex*  LPolyGroupEdgeIndices = mPolyGroup->Edges;\
\
	EIndex  LPGEI = 0;for(;LPGEI < LPGEN;LPGEI++)\
	{\
printf(" %s%d %d%s|", ECodeNormal, (int)LPGEI, (int)LPolyGroupEdgeIndices[LPGEI], ECodeNormal);fflush(stdout);\
	}\
printf("\n");fflush(stdout);\
} while(0)


//========================================
// Initialize ObjectsEdit Operator
//========================================
static void _EditObjects_Init(EOperator* POperator)
{
	E3dOperatorEditObjects*  LOperator = (E3dOperatorEditObjects*)POperator;

	EList_Init(LOperator->Objects);
}


//========================================
// Free ObjectsEdit Operator
//========================================
static int _EditObjects_Free(EOperator* POperator)
{
	E3dOperatorEditObjects*  LOperator = (E3dOperatorEditObjects*)POperator;
	const EIndex             LObjects_Count = LOperator->Objects_Count;if(LObjects_Count)
	{
		EIndex  LI = 0;for(;LI < LObjects_Count;LI++)
		{
			E3dOpObject*  LOpObject = LOperator->Objects[LI];_OpObjectFreeContents(LOpObject);EFree(LOpObject);
		}
		EList_Free(LOperator->Objects);
	}

	return(E_SUCCESS);
}


//================================================
// EditObjects Operator proc
//================================================
static EChangeFlags _EditObjects_Func(EOps* POps, EOperator* POperator, const int POperation)
{
	E3dScene*                LScene = E3d_Scene;
	E3dOperatorEditObjects*  LOperator = (E3dOperatorEditObjects*)POperator;
	const EIndex             LObjects_Count = LOperator->Objects_Count;

	EListAI_s(, EOpNode, LOpNodes);
	EChangeFlags  LChanged = 0;

	switch(POperation)
	{
		case EOpDO:
			{
				if(LObjects_Count)
				{
					EIndex  LI = 0;for(;LI < LObjects_Count;LI++)
					{
						E3dOpObject*  LOpObject = LOperator->Objects[LI];
						E3dObject*    LObject = LOpObject->Object;

						if(LOpObject->SubObjects)
						{
							if(EObject_IsSubClass(LObject, E3dFace))
							{
								E3dFace*  LFace = (E3dFace*)LObject;
								EIndex    LFaceHolesRemoved = 0;

								EIndex  LSOI = 0;const EIndex  LSON = LOpObject->SubObjects_Count;E3dOpSubObject*  LOpSubObject = LOpObject->SubObjects;for(;LSOI < LSON;LSOI++, LOpSubObject++)
								{
									E3dSpline*  LHole = E_DynamicCast(LOpSubObject->SubObject, E3dSpline);if(LHole)
									{
										E3dFace_RemoveHole(LFace, LHole);

										LFaceHolesRemoved++;
									}
								}

								if(LFaceHolesRemoved)
								{
// Free tesselation and E3dFace_GLDraw() or E3dObject_GetRenderShapes() etc. will call E3dFace_Tesselate() when necessary
// This will speed things up when there are no Windows with Solid display modes
//
//printf("%s() %s:%d <%s>\n", __FUNCTION__, __FILE__, __LINE__, LFace->Name);fflush(stdout);
									E3dFace_FreeTesselation(LFace);
									LChanged |= E3dChgFACE;

									EOpNode*  LOpNode = EList_StructAddAClr_s(LOpNodes, 4);if(LOpNode)
									{
										LOpNode->Object  = (ECoreObject*)LObject;
										LOpNode->Changed = E3dChgFACE;
									}
								}
							}
						}
						else
						{
							LChanged |= E3dObject_GetChangeFlagsByClass(LObject) | E3dChgHIERARCHY;

							EIndex  LMI = 0;const EIndex  LMN = LOpObject->Models_Count;for(;LMI < LMN;LMI++)
							{
								E3dModel_ObjectRemove((E3dModel*)(LOpObject->Models[LMI]->Object), LObject);
							}

							if(LObject->Models.Count == 0) E3dObject_FreeRenderData(LObject);
						}
					}
				}
			}
		break;

		case EOpUNDO:
			{
				if(LObjects_Count)
				{
					EIndex  LI = 0;for(;LI < LObjects_Count;LI++)
					{
						E3dOpObject*  LOpObject = LOperator->Objects[LI];
						E3dObject*    LObject = LOpObject->Object;

						if(LOpObject->SubObjects)
						{
							if(EObject_IsSubClass(LObject, E3dFace))
							{
								E3dFace*  LFace = (E3dFace*)LObject;
								EIndex    LFaceHolesAdded = 0;

								EIndex  LSOI = 0;const EIndex  LSON = LOpObject->SubObjects_Count;E3dOpSubObject*  LOpSubObject = LOpObject->SubObjects;for(;LSOI < LSON;LSOI++, LOpSubObject++)
								{
									E3dSpline*  LHole = E_DynamicCast(LOpSubObject->SubObject, E3dSpline);if(LHole)
									{
										E3dFace_InsertHole(LFace, LHole, LOpSubObject->OriginalIndex);
										LFaceHolesAdded++;

/*
										if(EList_ObjectInsert(LFace->Holes, LOpSubObject->OriginalIndex, LHole))
										{
											LFaceHolesAdded++;
										}
*/
									}
								}

								if(LFaceHolesAdded)
								{
// Free tesselation and E3dFace_GLDraw() or E3dObject_GetRenderShapes() etc. will call E3dFace_Tesselate() when necessary
// This will speed things up when there are no Windows with Solid display modes
//
									E3dFace_FreeTesselation(LFace);
									LChanged |= E3dChgFACE;

									EOpNode*  LOpNode = EList_StructAddAClr_s(LOpNodes, 4);if(LOpNode)
									{
										LOpNode->Object  = (ECoreObject*)LObject;
										LOpNode->Changed = E3dChgFACE;
									}
								}
							}
						}
						else
						{
							EIndex  LMI = 0;const EIndex  LMN = LOpObject->Models_Count;for(;LMI < LMN;LMI++)
							{
								EObjectAndIndex*  LEntry = LOpObject->Models[LMI];
								E3dModel*         LModel = (E3dModel*)(LEntry->Object);

								E3dModel_ObjectInsert(LModel, LObject, LEntry->Index);

								{
									E3dNode*  LRootNode = (E3dNode*)LModel;

									while(LRootNode->Parent) LRootNode = LRootNode->Parent;

									E3dNodeHrc_UpdateMaterialInheritance(LRootNode);
								}

								E3dScene_MaterialsAddFromObject(LScene, LObject);
							}

							E3dObject_UpdateForDisplay(LObject,  E3dUpdateALL & ~(E3dUpdateEDGES|E3dUpdateREMOVE_STRIPS|E3dUpdateTRIANGULATE), E3dCallALL & ~E3dCallCHANGEPROCS,  LScene);
							LChanged |= E3dObject_GetChangeFlagsByClass(LObject) | E3dChgHIERARCHY;
						}
					}
				}
			}
		break;
	}

	if(LOpNodes.Items)
	{
		LChanged |= E3dObjects_CallOutputs(ECbINTERNAL|ECbUSER,  &LOpNodes,  NULL);
		EList_FreeA_s(LOpNodes);
	}

	return(LChanged);
}


//========================================
// Free E3dOpPolyGroup
//========================================
static void _OpPolyGroupFreeContents(E3dOpPolyGroup* POpPolyGroup)
{
	if(POpPolyGroup->PolygonsToModify)
	{
		EIndex  LPI = 0;const EIndex  LPN = POpPolyGroup->PolygonsToModify_Count;E3dOpPolygon*  LDVPolygon = POpPolyGroup->PolygonsToModify;for(;LPI < LPN;LPI++, LDVPolygon++)
		{
			EFree(LDVPolygon->VertexNodesToModify_Indices);
			EFree(LDVPolygon->VertexNodesToModify);
		}
		EFree(POpPolyGroup->PolygonsToModify);
	}

	if(POpPolyGroup->PolygonsToDelete)
	{
		EIndex  LPI = 0;const EIndex  LPN = POpPolyGroup->PolygonsToDelete_Count;E3dPolygon*  LPolygon = POpPolyGroup->PolygonsToDelete;for(;LPI < LPN;LPI++, LPolygon++)
		{
			E3dPolygon_FreeContents(LPolygon);
		}
		EFree(POpPolyGroup->PolygonsToDelete);
	}
	EFreeChk(POpPolyGroup->PolygonsToDelete_Indices);

	if(POpPolyGroup->PolygonsToAdd)
	{
		EIndex  LPI = 0;const EIndex  LPN = POpPolyGroup->PolygonsToAdd_Count;E3dPolygon*  LPolygon = POpPolyGroup->PolygonsToAdd;for(;LPI < LPN;LPI++, LPolygon++)
		{
			E3dPolygon_FreeContents(LPolygon);
		}
		EFree(POpPolyGroup->PolygonsToAdd);
	}
	EFreeChk(POpPolyGroup->PolygonsToAdd_Indices);


	EList_FreeChk(POpPolyGroup->EdgeIndicesToDelete);
	EFreeChk(POpPolyGroup->EdgeIndicesToDelete_Indices);

	E_Delete(POpPolyGroup->PolyGroup);
}


static void _AddDeleteVertices_Init(EOperator* POperator)
{
	E3dOperatorAddDeleteVertices*  LOperator = (E3dOperatorAddDeleteVertices*)POperator;

	LOperator->MeshNewT = NULL;

	LOperator->Vertices_Indices = NULL;
	EList_Init(LOperator->Vertices);
	E3dVertexAttribute_ListInit(&LOperator->VertexAttributes);
	LOperator->VertexSize = 0;

	EList_Init(LOperator->OpPolyGroups);

	LOperator->EdgesToDelete_Indices = NULL;EList_Init(LOperator->EdgesToDelete);
	LOperator->EdgesToAdd_Indices    = NULL;EList_Init(LOperator->EdgesToAdd);

	LOperator->DeleteMeshVertices = TRUE;
}


static int _AddDeleteVertices_Free(EOperator* POperator)
{
	E3dOperatorAddDeleteVertices*  LOperator = (E3dOperatorAddDeleteVertices*)POperator;

//printf("%s() %s:%d  %p:%s\n", __FUNCTION__, __FILE__, __LINE__, LOperator, LOperator->Name);fflush(stdout);
	EFreeChkClr(LOperator->Vertices_Indices);
	E3dVertices_Free((E3dVertex*)LOperator->Vertices, LOperator->Vertices_Count, LOperator->VertexSize,  &LOperator->VertexAttributes);LOperator->Vertices = NULL;

	E3dVertexAttribute_ListFree(&LOperator->VertexAttributes);
	LOperator->VertexSize = 0;

	if(LOperator->OpPolyGroups)
	{
		EIndex  LSOI = 0;const EIndex  LSON = LOperator->OpPolyGroups_Count;for(;LSOI < LSON;LSOI++)
		{
			_OpPolyGroupFreeContents(LOperator->OpPolyGroups + LSOI);
		}
		EFree(LOperator->OpPolyGroups);
	}

	EFreeChk(LOperator->EdgesToDelete_Indices);EList_FreeChk(LOperator->EdgesToDelete);
	EFreeChk(LOperator->EdgesToAdd_Indices);   EList_FreeChk(LOperator->EdgesToAdd);

	E_DeleteChk(LOperator->MeshNewT);

	return(E_SUCCESS);
}


//================================================
// DeleteVertices Operator proc
//================================================
static EChangeFlags _DeleteVertices_Func(EOps* POps, EOperator* POperator, const int POperation)
{
	E3dOperatorAddDeleteVertices*  LOperator = (E3dOperatorAddDeleteVertices*)POperator;
	EChangeFlags                   LChanged = 0;
	E3dMesh*                       LMesh = LOperator->MeshNewT;
	E3dScene*                      LScene = E3d_Scene;

	switch(POperation)
	{
		case EOpDO:
			if(LOperator->Vertices_Count || LOperator->EdgesToDelete_Count || LOperator->EdgesToAdd_Count)
			{
				E3dMesh_FreeCaches(LMesh);

				if(LOperator->OpPolyGroups)
				{
					EIndex  LPGI = 0;const EIndex  LPGN = LOperator->OpPolyGroups_Count;E3dOpPolyGroup*  LOpPolyGroup = LOperator->OpPolyGroups;for(;LPGI < LPGN;LPGI++, LOpPolyGroup++)
					{
						E3dPolyGroup*            LPolyGroup = LOpPolyGroup->PolyGroup;
						const E3dVertexNodeSize  LVertexNodeSize = LPolyGroup->VertexNodeSize;

						EIndex  LPI = 0;const EIndex  LPN = LOpPolyGroup->PolygonsToModify_Count;E3dOpPolygon*  LDVPolygon = LOpPolyGroup->PolygonsToModify;for(;LPI < LPN;LPI++, LDVPolygon++)
						{
							E3dPolygon*           LPolygon = LPolyGroup->Polygons.Items + LDVPolygon->PolygonIndex;
							const E3dVertexNode*  LVertexNodes = LPolygon->VertexNodes;
							const EIndex*         LVertexNodesToDelete_Indices = LDVPolygon->VertexNodesToModify_Indices;

#ifdef E3dDebugDELETE_VERTICES
printf("\n%s() %s:%d  %sDO%s   Poly %lld  ", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal,  (long long)(LPolygon - LPolyGroup->Polygons.Items));fflush(stdout);
E3dPolygon_Print(LPolygon, LVertexNodeSize);printf("\n");fflush(stdout);
#endif
							const EIndex  LOrigNumOfExteriorVertices = LPolygon->NumOfExteriorVertices;
							EIndex  LI = 0;const EIndex  LN = LDVPolygon->VertexNodesToModify_Count;for(;LI < LN;LI++)
							{
								const EIndex  LVertexNodeIndex = LVertexNodesToDelete_Indices[LI];
								if(LVertexNodeIndex < LOrigNumOfExteriorVertices) LPolygon->NumOfExteriorVertices -= 1;

								const E3dVertexNode*  LVertexNode = EStruct_ByIndex(LVertexNodes, LVertexNodeSize, LVertexNodeIndex);
#ifdef E3dDebugDELETE_VERTICES
printf("  VertexNode %d (%d)", (int)LVertexNodeIndex, (int)LVertexNode->VertexID);fflush(stdout);
#endif
								if(LVertexNode->VertexID >= 0) LPolygon->NumOfVertices -= 1;
							}
#ifdef E3dDebugDELETE_VERTICES
printf("\n");fflush(stdout);
#endif

							ELst_StructsBackupAndRemove(&LPolygon->VertexNodes, &LPolygon->NumOfVertexNodes,  LDVPolygon->VertexNodesToModify_Indices, LDVPolygon->VertexNodesToModify_Count,
							 &LDVPolygon->VertexNodesToModify,  LVertexNodeSize);

							E3d_PolygonCorrectHoleVertexCounts(LPolygon, LVertexNodeSize);

#ifdef E3dDebugDELETE_VERTICES
E3dPolygon_Print(LPolygon, LVertexNodeSize);printf("\n");fflush(stdout);
printf("%s() %s:%d    LPolygon %p  VertexNodes %d  Vertices %d  ExteriorVertices %d\n", __FUNCTION__, __FILE__, __LINE__, LPolygon, (int)LPolygon->NumOfVertexNodes,
 (int)LPolygon->NumOfVertices, (int)LPolygon->NumOfExteriorVertices);fflush(stdout);
#endif
							E3dPolygon_UpdateNormal(LPolygon, LVertexNodeSize,  LMesh->Vertices.Items, LMesh->Vertices.Count, LMesh->VertexSize);
							E3dPolygon_Triangulate(LPolygon, LVertexNodeSize,  LMesh->Vertices.Items, LMesh->Vertices.Count, LMesh->VertexSize,  TRUE);
						}

						if(LOpPolyGroup->PolygonsToDelete_Indices)
						{
							EList_StructsBackupAndRemoveA_s(LPolyGroup->Polygons, LOpPolyGroup->PolygonsToDelete,  sizeof(E3dPolygon));

//							LRemovedPolysInMesh += LOpPolyGroup->PolygonsToDelete_Count;
						}

					}
				}

//printf("%s() %s:%d DO0\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);E3dMesh_EdgesPrint(LMesh);

				EList_StructsBackupAndRemoveA_s(LMesh->Edges, LOperator->EdgesToDelete,  sizeof(E3dEdge));
				EList_StructsInsertFromBackupA_s(LMesh->Edges, LOperator->EdgesToAdd,  sizeof(E3dEdge));

/*
{
printf("%s() %s:%d  [%s] V %lld, deleting %lld\n", __FUNCTION__, __FILE__, __LINE__, LMesh->Name, (long long)LMesh->Vertices.Count, (long long)LOperator->Vertices_Count);
fflush(stdout);
	const EIndex  LValuesPerLine = 10;
	EIndex  LVI = 0;for(;LVI < LOperator->Vertices_Count;LVI++)
	{
		const EIndex  LIndex = LOperator->Vertices_Indices[LVI];
		const EIndex  LMod = LVI % LValuesPerLine;
		const EBool   LLineStart = LMod == 0;
		const EBool   LLineEnd   = LMod == LValuesPerLine - 1;

		if(LLineStart) printf("%s%6lld%s: ", ECodeCyan, (long long)LVI, ECodeNormal);

		if(LIndex < 0 || LIndex >= LMesh->Vertices.Count) printf(" %s%6lld%s", ECodeRed, (long long)LIndex, ECodeNormal);
		else printf(" %6lld", (long long)LIndex);

		if(LLineEnd) printf("\n");

		fflush(stdout);
	}
printf("\n");fflush(stdout);
}
*/

				if(LOperator->DeleteMeshVertices)
				{
					EIndex*  LOldToNewVertexTable = ELst_DelCreateRemappingTable(LMesh->Vertices.Count, LOperator->Vertices_Indices, LOperator->Vertices_Count);if(LOldToNewVertexTable)
					{
						E3dMesh_EdgesAndPolyGroupsRemapVertexIndices(LMesh, LOldToNewVertexTable);

						ELst_StructsBackupAndRemove(&LMesh->Vertices, &LMesh->Vertices.Count,  LOperator->Vertices_Indices, LOperator->Vertices_Count, &LOperator->Vertices,
						 LOperator->VertexSize);

						EFree(LOldToNewVertexTable);
//printf("%s() %s:%d\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);if(E3dMesh_Check(LMesh, 0) != E_SUCCESS) assert(0);
					}
				}

				E3dMesh_PolyGroupsEdgesFromMeshEdges(LMesh, E3dp_Prefs.OptimizeFor == EOptimizeSPEED, FALSE);
				E3dObject_UpdateForDisplay((E3dObject*)LMesh,  E3dUpdateALL & ~(E3dUpdateTRIANGULATE|E3dUpdateEDGES), 0,  LScene);

				LChanged |= E3dChgMeshSHAPE;

				if(LChanged)
				{
					if(POps)
					{
						EOpNode*  LOpNode = EList_StructAddAClr_s(POps->OpNodes, 4);if(LOpNode)
						{
							LOpNode->Object = (ECoreObject*)LMesh;
							LOpNode->Changed = E3dChgMeshSHAPE;
						}
					}
				}
			}
		break;

		case EOpUNDO:
			if(LOperator->Vertices_Count || LOperator->EdgesToDelete_Count || LOperator->EdgesToAdd_Count)
			{
				E3dMesh_FreeCaches(LMesh);

				if(LOperator->DeleteMeshVertices)
				{
					EIndex*  LOldToNewVertexTable = ELst_AddCreateRemappingTable(LMesh->Vertices.Count, LOperator->Vertices_Indices, LOperator->Vertices_Count);if(LOldToNewVertexTable)
					{
						EList_StructsInsertFromBackup_s(LMesh->Vertices, LOperator->Vertices,  LOperator->VertexSize);
						E3dVertices_Free((E3dVertex*)LOperator->Vertices, LOperator->Vertices_Count, LOperator->VertexSize,  &LOperator->VertexAttributes);LOperator->Vertices = NULL;

						E3dMesh_EdgesAndPolyGroupsRemapVertexIndices(LMesh, LOldToNewVertexTable);
						EFree(LOldToNewVertexTable);
					}
				}

				if(LOperator->OpPolyGroups)
				{
					EIndex  LPGI = 0;const EIndex  LPGN = LOperator->OpPolyGroups_Count;E3dOpPolyGroup*  LOpPolyGroup = LOperator->OpPolyGroups;for(;LPGI < LPGN;LPGI++, LOpPolyGroup++)
					{
						E3dPolyGroup*            LPolyGroup = LOpPolyGroup->PolyGroup;
						const E3dVertexNodeSize  LVertexNodeSize = LPolyGroup->VertexNodeSize;

						if(LOpPolyGroup->PolygonsToDelete_Indices)
						{
							EList_StructsInsertFromBackupA_s(LPolyGroup->Polygons, LOpPolyGroup->PolygonsToDelete,  sizeof(E3dPolygon));

// Free struct backup copies to make sure that _EditObjectsFree won't free Polygons' VertexNodes (we only need PolygonsToDelete_Indices to perform the DO action again)
							EFreeClr(LOpPolyGroup->PolygonsToDelete);

//							LAddedPolysInMesh += LOpPolyGroup->PolygonsToDelete_Count;
						}

						const E3dVertex*  LMeshVertices = LMesh->Vertices.Items;const EIndex  LMeshNVertices = LMesh->Vertices.Count;const E3dVertexSize  LMeshVertexSize = LMesh->VertexSize;
						EIndex  LPI = 0;const EIndex  LPN = LOpPolyGroup->PolygonsToModify_Count;E3dOpPolygon*  LDVPolygon = LOpPolyGroup->PolygonsToModify;for(;LPI < LPN;LPI++, LDVPolygon++)
						{
							E3dPolygon*  LPolygon = LPolyGroup->Polygons.Items + LDVPolygon->PolygonIndex;

//printf("%s() %s:%d  %sUNDO%s Poly %lld", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, (long long)(LPolygon - LPolyGroup->Polygons));fflush(stdout);

							ELst_StructsInsertFromBackup(&LPolygon->VertexNodes, &LPolygon->NumOfVertexNodes, LDVPolygon->VertexNodesToModify_Indices, LDVPolygon->VertexNodesToModify_Count,
							 &LDVPolygon->VertexNodesToModify,  LVertexNodeSize);

							EFreeClr(LDVPolygon->VertexNodesToModify);  // Free struct backup copies to make sure that _DeleteVerticesFree won't free Polygons' VertexNodes

							const E3dVertexNode*  LVertexNodes = LPolygon->VertexNodes;

							const EIndex  LOrigNumOfExteriorVertices = LDVPolygon->OrigNumOfExteriorVertices;
							EIndex  LI = 0;const EIndex  LN = LDVPolygon->VertexNodesToModify_Count;for(;LI < LN;LI++)
							{
								const EIndex  LVertexNodeIndex = LDVPolygon->VertexNodesToModify_Indices[LI];
//printf("  VertexNode %d", (int)LVertexNodeIndex);fflush(stdout);
								if(LVertexNodeIndex < LOrigNumOfExteriorVertices) LPolygon->NumOfExteriorVertices += 1;

								const E3dVertexNode*  LVertexNode = EStruct_ByIndex(LVertexNodes, LVertexNodeSize, LVertexNodeIndex);
								if(LVertexNode->VertexID >= 0) LPolygon->NumOfVertices += 1;
							}
//printf("  VN %d  Vtx %d  ExtV %d\n", (int)LPolygon->NumOfVertexNodes, (int)LPolygon->NumOfVertices, (int)LPolygon->NumOfExteriorVertices);fflush(stdout);

							E3d_PolygonCorrectHoleVertexCounts(LPolygon, LVertexNodeSize);

//E3dPolygon_Print(LPolygon, LVertexNodeSize);printf("\n");fflush(stdout);

							E3dPolygon_UpdateNormal(LPolygon, LVertexNodeSize,  LMeshVertices, LMeshNVertices, LMeshVertexSize);
							E3dPolygon_Triangulate(LPolygon, LVertexNodeSize,  LMeshVertices, LMeshNVertices, LMeshVertexSize,  TRUE);

//E3dPolygon_Print(LPolygon, LVertexNodeSize);printf("\n");fflush(stdout);
						}
					}
				}

				EList_StructsRemoveA_s(LMesh->Edges, LOperator->EdgesToAdd);
				EList_StructsInsertFromBackupA_s(LMesh->Edges, LOperator->EdgesToDelete,  sizeof(E3dEdge));

				E3dMesh_PolyGroupsEdgesFromMeshEdges(LMesh, E3dp_Prefs.OptimizeFor == EOptimizeSPEED, FALSE);
				E3dObject_UpdateForDisplay((E3dObject*)LMesh,  E3dUpdateALL & ~(E3dUpdateTRIANGULATE|E3dUpdateEDGES), 0, LScene);

				LChanged |= E3dChgMeshSHAPE;

				if(LChanged)
				{
					if(POps)
					{
						EOpNode*  LOpNode = EList_StructAddAClr_s(POps->OpNodes, 4);if(LOpNode)
						{
							LOpNode->Object = (ECoreObject*)LMesh;
							LOpNode->Changed = E3dChgMeshSHAPE;
						}
					}
				}
			}
		break;
	}

	return(LChanged);
}


//========================================
// Initialize EditMesh Operator
//========================================
static void _EditMesh_Init(EOperator* POperator)
{
	E3dOperatorEditMesh*  LOperator = (E3dOperatorEditMesh*)POperator;

	LOperator->MeshNewT = NULL;

	EList_Init(LOperator->OpPolyGroups);

	EList_Init(LOperator->EdgesToDelete);   LOperator->EdgesToDelete_Indices = NULL;
	EList_Init(LOperator->EdgesToAdd);      LOperator->EdgesToAdd_Indices = NULL;

	E3dVertexAttribute_ListInit(&LOperator->VertexAttributes);
	LOperator->VertexSize = 0;

	EList_Init(LOperator->VerticesToDelete);LOperator->VerticesToDelete_Indices = NULL;
	EList_Init(LOperator->VerticesToAdd);   LOperator->VerticesToAdd_Indices = NULL;
}


//========================================
// Free ObjectsEdit Operator
//========================================
static int _EditMesh_Free(EOperator* POperator)
{
	E3dOperatorEditMesh*  LOperator = (E3dOperatorEditMesh*)POperator;

//printf("%s() %s:%d  %p:%s\n", __FUNCTION__, __FILE__, __LINE__, LOperator, LOperator->Name);fflush(stdout);
	E_DeleteChk(LOperator->MeshNewT);

	if(LOperator->OpPolyGroups)
	{
		EIndex  LSOI = 0;const EIndex  LSON = LOperator->OpPolyGroups_Count;for(;LSOI < LSON;LSOI++)
		{
			_OpPolyGroupFreeContents(LOperator->OpPolyGroups + LSOI);
		}
		EFreeClr(LOperator->OpPolyGroups);
	}

	EFreeChk(LOperator->EdgesToDelete_Indices);EList_FreeChk(LOperator->EdgesToDelete);
	EFreeChk(LOperator->EdgesToAdd_Indices);   EList_FreeChk(LOperator->EdgesToAdd);

	EFreeChk(LOperator->VerticesToDelete_Indices);

	E3dVertices_Free((E3dVertex*)LOperator->VerticesToDelete, LOperator->VerticesToDelete_Count, LOperator->VertexSize,  &LOperator->VertexAttributes);
	LOperator->VerticesToDelete = NULL;

	EFreeChk(LOperator->VerticesToAdd_Indices);
	E3dVertices_Free((E3dVertex*)LOperator->VerticesToAdd, LOperator->VerticesToAdd_Count, LOperator->VertexSize,  &LOperator->VertexAttributes);LOperator->VerticesToAdd = NULL;

	E3dVertexAttribute_ListFree(&LOperator->VertexAttributes);
	LOperator->VertexSize = 0;

	return(E_SUCCESS);
}


//================================================
// EditObjects Operator proc
//================================================
static EChangeFlags _EditMesh_Func(EOps* POps, EOperator* POperator, const int POperation)
{
	E3dScene*             LScene = E3d_Scene;
	E3dOperatorEditMesh*  LOperator = (E3dOperatorEditMesh*)POperator;

	switch(POperation)
	{
		case EOpDO:
			{
				EChangeFlags  LChanged = 0;
				E3dMesh*      LMesh = LOperator->MeshNewT;

//ETime  LPrev_ms = E_GetMilliseconds();

//printf("%s() %s:%d <%s>\n", __FUNCTION__, __FILE__, __LINE__, LMesh->Name);fflush(stdout);

#ifdef E3dDebugEDGES
printf("%s() %s:%d  <%s>  %sDO%s  LOperator %p LOperator->EdgesToDelete_Indices %p\n", __FUNCTION__, __FILE__, __LINE__,  LMesh->Name,  ECodeGreen, ECodeNormal,
 LOperator, LOperator->EdgesToDelete_Indices);fflush(stdout);
#endif

				E3dMesh_FreeCaches(LMesh);

				EIndex*  LOldToNewVertexTable = ELst_AddCreateRemappingTable(LMesh->Vertices.Count, LOperator->VerticesToAdd_Indices, LOperator->VerticesToAdd_Count);
				if(LOldToNewVertexTable)
				{
					EList_StructsInsertFromBackup_s(LMesh->Vertices, LOperator->VerticesToAdd,  LOperator->VertexSize);

					E3dVertices_Free((E3dVertex*)LOperator->VerticesToAdd, LOperator->VerticesToAdd_Count, LOperator->VertexSize,  &LOperator->VertexAttributes);
					LOperator->VerticesToAdd = NULL;

					E3dMesh_EdgesAndPolyGroupsRemapVertexIndices(LMesh, LOldToNewVertexTable);
					EFree(LOldToNewVertexTable);
				}

				EList_StructsBackupAndRemoveA_s(LMesh->Edges, LOperator->EdgesToDelete,  sizeof(E3dEdge));
				EList_StructsInsertFromBackupA_s(LMesh->Edges, LOperator->EdgesToAdd,  sizeof(E3dEdge));

				{
					EIndex  LPolyGroupsRemoved = 0, LPolyGroupsAdded = 0, LRemovedPolysInMesh = 0, LAddedPolysInMesh = 0;

					EIndex  LSOI = 0;const EIndex  LSON = LOperator->OpPolyGroups_Count;E3dOpPolyGroup*  LOpPolyGroup = LOperator->OpPolyGroups;for(;LSOI < LSON;LSOI++, LOpPolyGroup++)
					{
						E3dPolyGroup*  LPolyGroup = LOpPolyGroup->PolyGroup;if(LPolyGroup)
						{
							switch(LOpPolyGroup->Operation)
							{
								case EEditMODIFY:
									{
										const E3dVertexNodeSize  LVertexNodeSize = LPolyGroup->VertexNodeSize;

// Polygons to modify (add/delete VertexNodes etc.)
//
										{
											const E3dVertex*  LMeshVertices = LMesh->Vertices.Items;const EIndex  LMeshNVertices = LMesh->Vertices.Count;
											const E3dVertexSize  LMeshVertexSize = LMesh->VertexSize;
											EIndex  LPI = 0;const EIndex  LPN = LOpPolyGroup->PolygonsToModify_Count;E3dOpPolygon*  LDVPolygon = LOpPolyGroup->PolygonsToModify;
											for(;LPI < LPN;LPI++, LDVPolygon++)
											{
												E3dPolygon*  LPolygon = LPolyGroup->Polygons.Items + LDVPolygon->PolygonIndex;

if(0)
{
	printf("%s() %s:%d  %sDO%s  %sPoly %3lld%s ", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, ECodeGreen, (long long)(LPolygon - LPolyGroup->Polygons.Items),
	 ECodeNormal);fflush(stdout);E3dPolygon_Print(LPolygon, LVertexNodeSize);printf("\n");
	fflush(stdout);
}
												ELst_StructsInsertFromBackup(&LPolygon->VertexNodes, &LPolygon->NumOfVertexNodes, LDVPolygon->VertexNodesToModify_Indices,
												 LDVPolygon->VertexNodesToModify_Count, &LDVPolygon->VertexNodesToModify,  LVertexNodeSize);

//printf("%s() %s:%d  %sFREEING %p%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, LDVPolygon->VertexNodesToModify, ECodeNormal);fflush(stdout);
												EFreeClr(LDVPolygon->VertexNodesToModify);  // Free struct backup copies to make sure that _DeleteVerticesFree won't free Polygons' VertexNodes

												const E3dVertexNode*  LVertexNodes = LPolygon->VertexNodes;

												const EIndex  LOrigNumOfExteriorVertices = LDVPolygon->OrigNumOfExteriorVertices;
												EIndex  LI = 0;const EIndex  LN = LDVPolygon->VertexNodesToModify_Count;for(;LI < LN;LI++)
												{
													const EIndex  LVertexNodeIndex = LDVPolygon->VertexNodesToModify_Indices[LI];
//printf("  Adding VertexNode %d", (int)LVertexNodeIndex);fflush(stdout);

													const E3dVertexNode*  LVertexNode = EStruct_ByIndex(LVertexNodes, LVertexNodeSize, LVertexNodeIndex);const EIndex  LVertexID = LVertexNode->VertexID;
													if(LVertexNodeIndex <= LOrigNumOfExteriorVertices && LVertexID >= 0) LPolygon->NumOfExteriorVertices += 1;
													if(LVertexID >= 0) LPolygon->NumOfVertices += 1;
												}
//printf("\n");fflush(stdout);

												E3d_PolygonCorrectHoleVertexCounts(LPolygon, LVertexNodeSize);

//printf("%s() %s:%d               ", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);E3dPolygon_Print(LPolygon, LVertexNodeSize);printf("\n");fflush(stdout);

												E3dPolygon_UpdateNormal(LPolygon, LVertexNodeSize,  LMeshVertices, LMeshNVertices, LMeshVertexSize);
												E3dPolygon_Triangulate(LPolygon, LVertexNodeSize,  LMeshVertices, LMeshNVertices, LMeshVertexSize,  TRUE);

//printf("%s() %s:%d", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);E3dPolygon_Print(LPolygon, LVertexNodeSize);printf("\n");fflush(stdout);
											}
										}

// Polygons to delete
//
										if(LOpPolyGroup->PolygonsToDelete_Indices)
										{
											EList_StructsBackupAndRemoveA_s(LPolyGroup->Polygons, LOpPolyGroup->PolygonsToDelete,  sizeof(E3dPolygon));

											LRemovedPolysInMesh += LOpPolyGroup->PolygonsToDelete_Count;
										}

// Polygons to add
//
										if(LOpPolyGroup->PolygonsToAdd)
										{
											const E3dPolygon*  LSrcPolygon = LOpPolyGroup->PolygonsToAdd;
											const EIndex       LPolygonsToAdd_Count = LOpPolyGroup->PolygonsToAdd_Count;

											LAddedPolysInMesh += LPolygonsToAdd_Count;

											if(LOpPolyGroup->PolygonsToAdd_Indices)
											{
												const EIndex*  LPolygonsToAdd_Indices = LOpPolyGroup->PolygonsToAdd_Indices;

// Free struct backup copies to make sure that _EditObjectsFree won't free Polygons' VertexNodes
												EList_StructsInsertFromBackupA_s(LPolyGroup->Polygons, LOpPolyGroup->PolygonsToAdd,  sizeof(E3dPolygon));EFreeClr(LOpPolyGroup->PolygonsToAdd);

// We must duplicate the Polygon's VertexNodes and ITriangles because other tools may reallocate them, which would leave dangling pointers in LOpPolyGroup->PolygonsToAdd
//
												{
													EIndex  LPI = 0;for(;LPI < LPolygonsToAdd_Count;LPI++, LSrcPolygon++)
													{
														E3dPolygon*  LDstPolygon = LPolyGroup->Polygons.Items + LPolygonsToAdd_Indices[LPI];

														{
															const EIndex  LSize = LSrcPolygon->NumOfVertexNodes * LVertexNodeSize;
															EMallocT(E3dVertexNode*, LSize, LDstPolygon->VertexNodes);memcpy(LDstPolygon->VertexNodes, LSrcPolygon->VertexNodes, LSize);
														}

														if(LSrcPolygon->ITriangles_Count)
														{
															const EIndex  LSize = sizeof(E3dITriangle) * LSrcPolygon->ITriangles_Count;
															EMallocT(E3dITriangle*, LSize, LDstPolygon->ITriangles);memcpy(LDstPolygon->ITriangles, LSrcPolygon->ITriangles, LSize);
														}
													}
												}
											}
											else
											{
// If PolygonsToAddIndices = NULL, add Polygons to the end
//
												E3dPolygon*  LTmp;EResizeNN(E3dPolygon, LPolyGroup->Polygons.Count + LPolygonsToAdd_Count, LPolyGroup->Polygons.Items, LTmp);if(LTmp)
												{
													memcpy(LPolyGroup->Polygons.Items + LPolyGroup->Polygons.Count, LOpPolyGroup->PolygonsToAdd, sizeof(E3dPolygon) * LPolygonsToAdd_Count);

// We must duplicate the Polygon's VertexNodes and ITriangles because other tools may reallocate them, which would leave dangling pointers in LOpPolyGroup->PolygonsToAdd
//
													{
														EIndex  LPI = 0;E3dPolygon*  LDstPolygon = LPolyGroup->Polygons.Items + LPolyGroup->Polygons.Count;
														for(;LPI < LPolygonsToAdd_Count;LPI++, LSrcPolygon++, LDstPolygon++)
														{
															{
																const EIndex  LSize = LSrcPolygon->NumOfVertexNodes * LVertexNodeSize;
																EMallocT(E3dVertexNode*, LSize, LDstPolygon->VertexNodes);memcpy(LDstPolygon->VertexNodes, LSrcPolygon->VertexNodes, LSize);
															}

															if(LSrcPolygon->ITriangles_Count)
															{
																const EIndex  LSize = (EIndex)sizeof(E3dITriangle) * LSrcPolygon->ITriangles_Count;
																EMallocT(E3dITriangle*, LSize, LDstPolygon->ITriangles);memcpy(LDstPolygon->ITriangles, LSrcPolygon->ITriangles, LSize);
															}
														}
													}

													LPolyGroup->Polygons.Count += LPolygonsToAdd_Count;
													LPolyGroup->Polygons.CountAllocated = LPolyGroup->Polygons.Count;
												}
											}
										}


#ifdef E3dRESTORE_POLYGROUP_EDGES

// If we're deleting Mesh Edges, back up and remove the corresponding PolyGroup Mesh Edge indices
//
										if(LOperator->EdgesToDelete && LMesh->PolyGroups.Count > 1)
										{
											const EIndex  LEDN = LOperator->EdgesToDelete_Count;const EIndex*  LED = LOperator->EdgesToDelete_Indices;

// Update PolyGroup Edge indices
//
											if(LOpPolyGroup->EdgeIndicesToDelete_Indices == NULL)
											{
#ifdef E3dDebugEDGES
printf("%s() %s:%d  LSOI %2d  LOpPolyGroup->EdgeIndicesToDelete_Indices empty\n", __FUNCTION__, __FILE__, __LINE__, (int)LSOI);fflush(stdout);
#endif
// Build list of indices in LPolyGroup->Edges to the indices of Mesh Edges to be deleted
//
												EIndex*  LEdgeIndicesToDelete;ENewN(LEDN, LEdgeIndicesToDelete);
												const EIndex*  LPolyGroupEdges = LPolyGroup->Edges;const EIndex  LPGEN = LPolyGroup->Edges_Count;
												EIndex  LEdgeIndicesToDelete_Count = 0;
												EIndex  LEDI = 0;for(;LEDI < LEDN;LEDI++)
												{
													const EIndex  LMeshEdgeIndex = LED[LEDI];
													EIndex  LPGEI = 0;for(;LPGEI < LPGEN;LPGEI++)
													{
														if(LMeshEdgeIndex == LPolyGroupEdges[LPGEI])
														{
															LEdgeIndicesToDelete[LEdgeIndicesToDelete_Count++] = LPGEI;
														}
													}
												}
												if(LEdgeIndicesToDelete_Count < LEDN) EResizeNT(EIndex, LEdgeIndicesToDelete_Count, LEdgeIndicesToDelete);

												LOpPolyGroup->EdgeIndicesToDelete_Indices = LEdgeIndicesToDelete;
												LOpPolyGroup->EdgeIndicesToDelete_Count   = LEdgeIndicesToDelete_Count;
											}

printf("%s() %s:%d  %sGREEN%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
#ifdef E3dDebugEDGES
{
	_PolyGroupPrintEdges(LPolyGroup);

	const EIndex  LTN = LOpPolyGroup->EdgeIndicesToDelete_Count;
	printf("%s() %s:%d   LEDN %d  LEdgeIndicesToDelete_Count %d\n", __FUNCTION__, __FILE__, __LINE__, (int)LEDN, (int)LTN);fflush(stdout);
	{
		EIndex  LTI = 0;for(;LTI < LTN;LTI++)
		{
			printf(" %d", (int)LOpPolyGroup->EdgeIndicesToDelete_Indices[LTI]);fflush(stdout);
		}
	}
printf("\n");fflush(stdout);
}
#endif

											EList_IndicesBackupAndRemove(LPolyGroup->Edges, LOpPolyGroup->EdgeIndicesToDelete,  TRUE);

// Remap (compact) LPolyGroup->Edges index list after the given Edge indices were removed
//
											{
												const EIndex  LPGEN = LPolyGroup->Edges_Count;

#ifdef E3dDebugEDGES
printf("%s() %s:%d  PolyGroup Edges: %3d:\n", __FUNCTION__, __FILE__, __LINE__, (int)LPGEN);fflush(stdout);
#endif
												EIndex*  LPolyGroupEdgeIndices = LPolyGroup->Edges;

// LOperator->EdgesToDelete_Indices is in ascending order, but we need to do this in descending order...
//
												EIndex  LEDI = LEDN - 1;for(;LEDI >= 0;LEDI--)
												{
													const EIndex  LEI = LED[LEDI];
//printf("%s() %s:%d  PolyGroup removing Mesh Edge %3d:\n", __FUNCTION__, __FILE__, __LINE__, (int)LEI);fflush(stdout);
													EIndex  LPGEI = 0;for(;LPGEI < LPGEN;LPGEI++)
													{
														const EIndex  LPI = LPolyGroupEdgeIndices[LPGEI];if(LPI > LEI)
														{
															LPolyGroupEdgeIndices[LPGEI] -= 1;
//printf(" %d %s%d%s|", (int)LPGEI, ECodeYellow, (int)LPolyGroupEdgeIndices[LPGEI], ECodeNormal);fflush(stdout);
														}
														else if(LPI == LEI)
														{
//printf(" %s%d %d%s|", ECodeRed, (int)LPGEI, (int)LPolyGroupEdgeIndices[LPGEI], ECodeNormal);fflush(stdout);
														}
														else
														{
//printf(" %d %d|", (int)LPGEI, (int)LPolyGroupEdgeIndices[LPGEI]);fflush(stdout);
														}
													}
//printf("\n");fflush(stdout);
												}
											}
										}
#endif  // E3dRESTORE_POLYGROUP_EDGES
									}
								break;

								case EEditADD:
									{
										if(E3dMesh_AppendPolyGroup(LMesh, LPolyGroup) == E_SUCCESS)
										{
//printf("%s() %s:%d  <%s%s%s> %lld \n", __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, LPolyGroup->Name, ECodeNormal,  (long long)LPolyGroup->Polygons.Count);fflush(stdout);
											LPolyGroupsAdded++;
										}
									}
								break;

								case EEditDEL:
									if(E3dMesh_PolyGroupDel(LMesh, LPolyGroup) == E_SUCCESS) LPolyGroupsRemoved++;
								break;
							}
						}
					}

					if(LPolyGroupsRemoved || LPolyGroupsAdded || LAddedPolysInMesh || LRemovedPolysInMesh || LOperator->EdgesToAdd || LOperator->EdgesToDelete_Indices)
					{
if(0)
{
	printf("%s() %s:%d  LPolyGroupsRemoved %lld  LAddedPolysInMesh %lld  LRemovedPolysInMesh %lld  EdgesToAdd %p EdgesToDelete_Indices %p\n",  __FUNCTION__, __FILE__, __LINE__,
	 (long long)LPolyGroupsRemoved, (long long)LAddedPolysInMesh, (long long)LRemovedPolysInMesh, LOperator->EdgesToAdd, LOperator->EdgesToDelete_Indices);
	fflush(stdout);
}
						EUpdateFlags  LUpdateFlags = 0;
						if(LPolyGroupsRemoved || LPolyGroupsAdded || LAddedPolysInMesh || LRemovedPolysInMesh) LUpdateFlags = E3dUpdateALL & ~E3dUpdateTRIANGULATE;
						else LUpdateFlags = E3dUpdateALL & ~(E3dUpdateREMOVE_STRIPS|E3dUpdateVERTEX_NORMAL);

// If the caller specified Edges to add and/or delete, it means it took care of tracking Edges, so we don't need to build them from the Polygons
// (Check EdgesTo*_Indices (vs. LOperator->Edges*) because LOperator->EdgesTo* may get freed).
//
//					if(LOperator->EdgesToAdd || LOperator->EdgesToDelete_Indices)
						{
							LUpdateFlags &= ~E3dUpdateEDGES;
#ifndef E3dRESTORE_POLYGROUP_EDGES
							E3dMesh_PolyGroupsEdgesFromMeshEdges(LMesh, E3dp_Prefs.OptimizeFor == EOptimizeSPEED, FALSE);
#endif
						}

						E3dObject_UpdateForDisplay((E3dObject*)LMesh,  LUpdateFlags, 0,  LScene);
						LChanged |= E3dChgMeshSHAPE;
					}
				}

{
//	const ETime  LNow_ms = E_GetMilliseconds();char  LTStr[256];EStr_MillisecondsToString(LNow_ms - LPrev_ms, LTStr, FALSE);LPrev_ms = LNow_ms;printf("%s() %s:%d  %s\n",
//	 __FUNCTION__, __FILE__, __LINE__, LTStr);fflush(stdout);
}

				if(LChanged && POps)
				{
					EOpNode*  LOpNode = EList_StructAddAClr_s(POps->OpNodes, 4);if(LOpNode)
					{
						LOpNode->Object = (ECoreObject*)LMesh;
						LOpNode->Changed = E3dChgMeshSHAPE;
					}
				}

				return(LChanged);
			}
		break;

		case EOpUNDO:
			{
				EChangeFlags  LChanged = 0;

				E3dMesh*  LMesh = LOperator->MeshNewT;

				E3dMesh_FreeCaches(LMesh);

				EList_StructsRemoveA_s(LMesh->Edges, LOperator->EdgesToAdd);
				EList_StructsInsertFromBackupA_s(LMesh->Edges, LOperator->EdgesToDelete,  sizeof(E3dEdge));

				EIndex  LPolyGroupsAdded = 0, LPolyGroupsRemoved = 0, LAddedPolysInMesh = 0, LRemovedPolysInMesh = 0;

				EIndex  LSOI = 0;const EIndex  LSON = LOperator->OpPolyGroups_Count;E3dOpPolyGroup*  LOpPolyGroup = LOperator->OpPolyGroups;for(;LSOI < LSON;LSOI++, LOpPolyGroup++)
				{
					E3dPolyGroup*  LPolyGroup = LOpPolyGroup->PolyGroup;if(LPolyGroup)
					{
						switch(LOpPolyGroup->Operation)
						{
							case EEditMODIFY:
								{
									const E3dVertexNodeSize  LVertexNodeSize = LPolyGroup->VertexNodeSize;

// Polygons to add
//
									if(LOpPolyGroup->PolygonsToAdd)
									{
										const EIndex  LPolygonsToAdd_Count = LOpPolyGroup->PolygonsToAdd_Count;

										if(LPolyGroup->Polygons.Count > LPolygonsToAdd_Count)  // Sanity check
										{
											LRemovedPolysInMesh += LPolygonsToAdd_Count;

											if(LOpPolyGroup->PolygonsToAdd_Indices)
											{
												const EIndex*  LPolygonsToAdd_Indices = LOpPolyGroup->PolygonsToAdd_Indices;

// Free Polygons (we have a backup in the Operator)
//
												{
													EIndex  LPI = 0;for(;LPI < LPolygonsToAdd_Count;LPI++)
													{
														E3dPolygon*  LPolygon = LPolyGroup->Polygons.Items + LPolygonsToAdd_Indices[LPI];
														E3dPolygon_FreeContents(LPolygon);
													}
												}

// Just remove, no backup (we have a backup in the Operator)
//
												EList_StructsRemoveA_s(LPolyGroup->Polygons, LPolygonsToAdd);
											}
											else
											{
// If PolygonsToAddIndices = NULL, Polygons were added to the end
//
												const EIndex  LNewCount = LPolyGroup->Polygons.Count - LPolygonsToAdd_Count;

// Free Polygons (we have a backup in the Operator)
//
												{
													EIndex  LPI = 0;E3dPolygon*  LPolygon = LPolyGroup->Polygons.Items + LNewCount;for(;LPI < LPolygonsToAdd_Count;LPI++, LPolygon++)
													{
														E3dPolygon_FreeContents(LPolygon);
													}
												}

												E3dPolygon*  LTmp;EResizeNN(E3dPolygon, LNewCount, LPolyGroup->Polygons.Items, LTmp);if(LTmp)
												{
													LPolyGroup->Polygons.Count = LPolyGroup->Polygons.CountAllocated = LNewCount;
												}
											}
										}
									}


// Polygons to delete
//
									if(LOpPolyGroup->PolygonsToDelete_Indices)
									{
										EList_StructsInsertFromBackupA_s(LPolyGroup->Polygons, LOpPolyGroup->PolygonsToDelete,  sizeof(E3dPolygon));

// Free struct backup copies to make sure that _EditObjectsFree won't free Polygons' VertexNodes (we only need PolygonsToDelete_Indices to perform the DO action again)
										EFreeClr(LOpPolyGroup->PolygonsToDelete);

										LAddedPolysInMesh += LOpPolyGroup->PolygonsToDelete_Count;
									}


// Polygons to modify (add/delete VertexNodes etc.)
//
									{
										const E3dVertex*  LMeshVertices = LMesh->Vertices.Items;const EIndex  LMeshNVertices = LMesh->Vertices.Count;
										const E3dVertexSize  LMeshVertexSize = LMesh->VertexSize;
										EIndex  LPI = 0;const EIndex  LPN = LOpPolyGroup->PolygonsToModify_Count;E3dOpPolygon*  LDVPolygon = LOpPolyGroup->PolygonsToModify;
										for(;LPI < LPN;LPI++, LDVPolygon++)
										{
											E3dPolygon*           LPolygon = LPolyGroup->Polygons.Items + LDVPolygon->PolygonIndex;
											const E3dVertexNode*  LVertexNodes = LPolygon->VertexNodes;
											const EIndex*         LVertexNodesToDelete_Indices = LDVPolygon->VertexNodesToModify_Indices;

//printf("%s() %s:%d  %sUNDO%s Poly %lld  ", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, (long long)(LPolygon - LPolyGroup->Polygons.Items));fflush(stdout);

//E3dPolygon_Print(LPolygon, LVertexNodeSize);printf("\n");fflush(stdout);

											const EIndex  LOrigNumOfExteriorVertices = LPolygon->NumOfExteriorVertices;
											EIndex  LI = 0;const EIndex  LN = LDVPolygon->VertexNodesToModify_Count;for(;LI < LN;LI++)
											{
												const EIndex  LVertexNodeIndex = LVertexNodesToDelete_Indices[LI];
//printf("  VertexNode %d", (int)LVertexNodeIndex);fflush(stdout);

												const E3dVertexNode*  LVertexNode = EStruct_ByIndex(LVertexNodes, LVertexNodeSize, LVertexNodeIndex);const EIndex  LVertexID = LVertexNode->VertexID;
												if(LVertexNodeIndex <= LOrigNumOfExteriorVertices && LVertexID >= 0) LPolygon->NumOfExteriorVertices -= 1;
												if(LVertexID >= 0) LPolygon->NumOfVertices -= 1;
											}
//printf("\n");fflush(stdout);


											ELst_StructsBackupAndRemove(&LPolygon->VertexNodes, &LPolygon->NumOfVertexNodes,  LDVPolygon->VertexNodesToModify_Indices,
											 LDVPolygon->VertexNodesToModify_Count, &LDVPolygon->VertexNodesToModify,  LVertexNodeSize);

											E3d_PolygonCorrectHoleVertexCounts(LPolygon, LVertexNodeSize);

//E3dPolygon_Print(LPolygon, LVertexNodeSize);printf("\n");fflush(stdout);


if(0)
{
	printf("   VertexNodes %d  Vertices %d  ExteriorVertices %d\n", (int)LPolygon->NumOfVertexNodes, (int)LPolygon->NumOfVertices, (int)LPolygon->NumOfExteriorVertices);
	fflush(stdout);
}
											E3dPolygon_UpdateNormal(LPolygon, LVertexNodeSize,  LMeshVertices, LMeshNVertices, LMeshVertexSize);
											E3dPolygon_Triangulate(LPolygon, LVertexNodeSize,  LMeshVertices, LMeshNVertices, LMeshVertexSize,  TRUE);
										}
									}

#ifdef E3dDebugEDGES
			printf("%s() %s:%d  %sUNDO%s  PolyGroup Edges: %3d:\n", __FUNCTION__, __FILE__, __LINE__, ECodeCyan, ECodeNormal, (int)LPolyGroup->Edges_Count);fflush(stdout);
#endif

// If we're restoring Mesh Edges, restore the corresponding PolyGroup Mesh Edge indices
//
#ifdef E3dRESTORE_POLYGROUP_EDGES
									if(LOperator->EdgesToDelete_Indices && LMesh->PolyGroups.Count > 1)
									{
										const EIndex  LEDN = LOperator->EdgesToDelete_Count;const EIndex*  LED = LOperator->EdgesToDelete_Indices;

// Update PolyGroup Edge indices
//
										if(LOpPolyGroup->EdgeIndicesToDelete_Indices)
										{
// Remap (expand) LPolyGroup->Edges index list after the given Edge indices were restored
//
											{
												const EIndex  LPGEN = LPolyGroup->Edges_Count;
												EIndex*  LPolyGroupEdgeIndices = LPolyGroup->Edges;

// LOperator->EdgesToDelete_Indices is in ascending order, which is what we need here
//
												EIndex  LEDI = 0;for(;LEDI < LEDN;LEDI++)
												{
													const EIndex  LEI = LED[LEDI];
//printf("%s() %s:%d  PolyGroup re-adding Mesh Edge %3d:\n", __FUNCTION__, __FILE__, __LINE__, (int)LEI);fflush(stdout);
													EIndex  LPGEI = 0;for(;LPGEI < LPGEN;LPGEI++)
													{
														const EIndex  LPI = LPolyGroupEdgeIndices[LPGEI];if(LPI >= LEI)
														{
															LPolyGroupEdgeIndices[LPGEI] += 1;
//printf(" %d %s%d%s|", (int)LPGEI, ECodeYellow, (int)LPolyGroupEdgeIndices[LPGEI], ECodeNormal);fflush(stdout);
														}
														else if(LPI == LEI)
														{
//printf(" %s%d %d%s|", ECodeRed, (int)LPGEI, (int)LPolyGroupEdgeIndices[LPGEI], ECodeNormal);fflush(stdout);
														}
														else
														{
//printf(" %d %d|", (int)LPGEI, (int)LPolyGroupEdgeIndices[LPGEI]);fflush(stdout);
														}
													}
//printf("\n");fflush(stdout);
												}
											}

											EList_IndicesInsertFromBackup(LPolyGroup->Edges, LOpPolyGroup->EdgeIndicesToDelete);
										}
									}
#endif  // E3dRESTORE_POLYGROUP_EDGES
								}
							break;

							case EEditADD:  // Undo adding this PolyGroup
								if(LOpPolyGroup->OriginalIndex >= 0 && LOpPolyGroup->OriginalIndex < LMesh->PolyGroups.Count)
								{
									E3dPolyGroup*  LNewPolyGroup = LMesh->PolyGroups.Items[LOpPolyGroup->OriginalIndex];
									if(E3dMesh_PolyGroupDel(LMesh, LNewPolyGroup) == E_SUCCESS) LPolyGroupsRemoved++;
								}
							break;

							case EEditDEL:  // Undo deleting this PolyGroup
								{
									if(EList_ObjectInsert_s(LMesh->PolyGroups, LOpPolyGroup->OriginalIndex, LPolyGroup))
									{
										E3dScene_MaterialAdd(LScene, LPolyGroup->Material);
										LPolyGroupsAdded++;
									}
								}
							break;
						}
					}
				}



				{
					EIndex*  LOldToNewVertexTable = ELst_DelCreateRemappingTable(LMesh->Vertices.Count, LOperator->VerticesToAdd_Indices, LOperator->VerticesToAdd_Count);
					if(LOldToNewVertexTable)
					{
						ELst_StructsBackupAndRemove(&LMesh->Vertices, &LMesh->Vertices.Count,  LOperator->VerticesToAdd_Indices, LOperator->VerticesToAdd_Count, &LOperator->VerticesToAdd,
						 LOperator->VertexSize);

//printf("%s() %s:%d %sUNDO1%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeMagenta, ECodeNormal);fflush(stdout);E3dMesh_EdgesPrint(LMesh);
						E3dMesh_EdgesAndPolyGroupsRemapVertexIndices(LMesh, LOldToNewVertexTable);
//printf("%s() %s:%d %sUNDO2%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeMagenta, ECodeNormal);fflush(stdout);E3dMesh_EdgesPrint(LMesh);
						EFree(LOldToNewVertexTable);
					}
				}



				if(LPolyGroupsAdded || LPolyGroupsRemoved || LAddedPolysInMesh || LRemovedPolysInMesh || LOperator->EdgesToAdd || LOperator->EdgesToDelete_Indices)
				{
if(0)
{
	printf("%s() %s:%d  LPolyGroupsAdded %lld  LAddedPolysInMesh %lld  LRemovedPolysInMesh %lld  EdgesToAdd %p EdgesToDelete_Indices %p\n", __FUNCTION__, __FILE__, __LINE__,
	 (long long)LPolyGroupsAdded, (long long)LAddedPolysInMesh, (long long)LRemovedPolysInMesh, LOperator->EdgesToAdd, LOperator->EdgesToDelete_Indices);
	fflush(stdout);
}	
				EUpdateFlags  LUpdateFlags = 0;
					if(LPolyGroupsAdded || LPolyGroupsRemoved || LAddedPolysInMesh || LRemovedPolysInMesh) LUpdateFlags = E3dUpdateALL & ~E3dUpdateTRIANGULATE;
					else LUpdateFlags = E3dUpdateALL & ~(E3dUpdateREMOVE_STRIPS|E3dUpdateVERTEX_NORMAL);

if(0)
{
	printf("%s() %s:%d  <%s>  LOperator %p LOperator->EdgesToDelete_Indices %p\n", __FUNCTION__, __FILE__, __LINE__, LMesh->Name, LOperator, LOperator->EdgesToDelete_Indices);
	fflush(stdout);
}

// If the caller specified Edges to add and/or delete, it means it took care of tracking Edges, so we don't need to build them from the Polygons
// (Check EdgesTo*_Indices (vs. LOperator->Edges*) because LOperator->EdgesTo* may get freed).
//
//					if(LOperator->EdgesToAdd || LOperator->EdgesToDelete_Indices)
					{
						LUpdateFlags &= ~E3dUpdateEDGES;
#ifndef E3dRESTORE_POLYGROUP_EDGES
//_PolyGroupPrintEdges(LPolyGroup);
						E3dMesh_PolyGroupsEdgesFromMeshEdges(LMesh, E3dp_Prefs.OptimizeFor == EOptimizeSPEED, FALSE);
//_PolyGroupPrintEdges(LPolyGroup);
#endif
					}

					E3dObject_UpdateForDisplay((E3dObject*)LMesh,  LUpdateFlags, 0,  LScene);
					LChanged |= E3dChgMeshSHAPE;
				}

				if(LChanged && POps)
				{
					EOpNode*  LOpNode = EList_StructAddAClr_s(POps->OpNodes, 4);if(LOpNode)
					{
						LOpNode->Object = (ECoreObject*)LMesh;
						LOpNode->Changed = E3dChgMeshSHAPE;
					}
				}

				return(LChanged);
			}
		break;
	}
	return(0);
}


//================================================================================================
// Correct selections:
// Make sure that Models' Selection is E3dNodeSelOBJECT if any of their Objects are selected
//================================================================================================
static void _SceneUpdateSelections(E3dScene* PScene)
{
	EIndex  LRI = 0;const EIndex  LRN = PScene->RootNodes.Count;for(;LRI < LRN;LRI++)
	{
		E3dNode*  LNode = PScene->RootNodes.Items[LRI];for(;LNode;LNode = LNode->Next)
		{
			E3dModel*  LModel = E_DynamicCast(LNode, E3dModel);if(LModel && LNode->Selection == 0)
			{
				EIndex  LObjI = 0;const EIndex  LON = LModel->Objects.Count;for(;LObjI < LON;LObjI++)
				{
					E3dObject*  LObject = LModel->Objects.Items[LObjI];if(LObject->Selection)
					{
						LNode->Selection = E3dNodeSelOBJECT;
						break;
					}
				}
			}
		}
	}
}


//========================================================================================
// Check if a Node has selected Geometries, PolyGroups, Polygons, Edges or Vertices
//========================================================================================
static EBool _NodeCheckForSelectedComponents(const E3dNode* PNode,  const uint8_t PDeleteComponentsFlags, E3dObjectPtrListA* PObjectsWithSelectedComponents)
{
	EIndex  LHasObjectsWithSelectedComponent = 0;

	E3dModel*  LModel = E_DynamicCast(PNode, E3dModel);if(LModel)
	{
		const EBool        LNodeSelected = E3dNode_IsSelected(PNode);

		if(LNodeSelected || PNode->Selection == E3dNodeSelOBJECT)
		{
			EIndex  LObjI = 0;const EIndex  LObjN = LModel->Objects.Count;E3dObject* const*  LObjects = LModel->Objects.Items;for(;LObjI < LObjN;LObjI++)
			{
				E3dObject*  LObject = LObjects[LObjI];

				if(LNodeSelected || LObject->Selection)
				{

//printf("%s() %s:%d <%s>\n", __FUNCTION__, __FILE__, __LINE__, LObject->Name);fflush(stdout);


// Check if LObject is already in PObjectsWithSelectedComponentsP.
// If so, don't bother checking its Face contours, Polygons etc.
//
					if(EList_PointerGetIndex_p(PObjectsWithSelectedComponents, LObject) < 0)
					{
						if(EObject_IsSubClass(LObject, E3dSpline))
						{
							const E3dSpline*  LSpline = (const E3dSpline*)LObject;

							if(PDeleteComponentsFlags & E3dDeleteSELECTED_POINTS)
							{
								const EIndex  LSplineSelectedCVs_Count = E3dSpline_NumOfSelectedCVs(LSpline);if(LSplineSelectedCVs_Count > 0)
								{
printf("%s() %s:%d <%s> SelectedCVs_Count %lld\n", __FUNCTION__, __FILE__, __LINE__, LSpline->Name, (long long)LSplineSelectedCVs_Count);fflush(stdout);
									EIndex  LIdx;(void)LIdx;
									EList_ObjectAddAChk_p(PObjectsWithSelectedComponents,  8,  LObject,  LIdx);
									LHasObjectsWithSelectedComponent++;
								}
							}
						}
						else if(EObject_IsSubClass(LObject, E3dFace))
						{
							const E3dFace*  LFace = (const E3dFace*)LObject;

							if(LObject->Selection == E3dObjectSelFACE_CONTOUR)
							{
								if(LFace->Exterior->Selection == E3dObjectSelOBJECT)
								{
									if(PDeleteComponentsFlags & E3dDeleteSELECTED_POINTS && E3dSpline_NumOfSelectedCVs(LFace->Exterior) > 0)
									{
										EIndex  LIdx;(void)LIdx;EList_ObjectAddAChk_p(PObjectsWithSelectedComponents,  8,  LObject,  LIdx);
										LHasObjectsWithSelectedComponent++;
									}
//								else E3dp_PrintMessage(ELogWARNING, 5000, Lng_CanNotDeleteFaceExterior);
								}

								EIndex  LHI = 0;for(;LHI < LFace->Holes_Count;LHI++)
								{
									const E3dSpline*  LHole = LFace->Holes[LHI];

									if(LHole->Selection == E3dObjectSelOBJECT)
									{
										EIndex  LIdx;(void)LIdx;EList_ObjectAddAChk_p(PObjectsWithSelectedComponents,  8,  LObject,  LIdx);

										LHasObjectsWithSelectedComponent++;
									}
								}
							}
							else if(LObject->Selection && PDeleteComponentsFlags & E3dDeleteSELECTED_POINTS)
							{
								if(LFace->Exterior && E3dSpline_NumOfSelectedCVs(LFace->Exterior) > 0)
								{
									EIndex  LIdx;(void)LIdx;EList_ObjectAddAChk_p(PObjectsWithSelectedComponents,  8,  LObject,  LIdx);
								}

								EIndex  LHI = 0;for(;LHI < LFace->Holes_Count;LHI++)
								{
									const E3dSpline*  LHole = LFace->Holes[LHI];

									if(E3dSpline_NumOfSelectedCVs(LHole) > 0)
									{
										EIndex  LIdx;(void)LIdx;EList_ObjectAddAChk_p(PObjectsWithSelectedComponents, 4,  LObject,  LIdx);
										LHasObjectsWithSelectedComponent++;
									}
								}
							}
						}
						else if(EObject_IsSubClass(LObject, E3dMesh))
						{
							EBool  LMeshAdded = FALSE;

//printf("%s() %s:%d  <%s%s%s>\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, LObject->Name, ECodeNormal);fflush(stdout);
							const E3dMesh*  LMesh = (E3dMesh*)LObject;if(LMesh->PolyGroups.Count > 0)
							{
								if(PDeleteComponentsFlags & (E3dDeleteSELECTED_POLYGONS|E3dDeleteSELECTED_POLYGROUPS))
								{
//printf("%s() %s:%d  <%s%s%s>\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, LObject->Name, ECodeNormal);fflush(stdout);
									EIndex  LPGI = 0;const EIndex  LPGN = LMesh->PolyGroups.Count;E3dPolyGroup* const*  LPolyGroups = LMesh->PolyGroups.Items;for(;LPGI < LPGN;LPGI++)
									{
										const E3dPolyGroup*  LPolyGroup = LPolyGroups[LPGI];

#define E3dDEBUG  0

										const EIndex  LNumOfSelectedPolygons = PDeleteComponentsFlags & E3dDeleteSELECTED_POLYGONS ? E3dPolyGroup_NumOfSelectedPolygons(LPolyGroup) : 0;

if(E3dDEBUG)
{
	printf("%s() %s:%d  %s/%s PolyGroup %lld  LPolyGroup->Selected %d  SelectedPolys %lld  LPolyGroup->Flags %d\n", __FUNCTION__, __FILE__, __LINE__,
	 LMesh->Name, LPolyGroup->Name, (long long)LPGI, LPolyGroup->Selected, (long long)E3dPolyGroup_NumOfSelectedPolygons(LPolyGroup), LPolyGroup->Flags);
	fflush(stdout);
}

										if(LPolyGroup->Selected || LNumOfSelectedPolygons > 0)
										{
											EIndex  LIdx;(void)LIdx;EList_ObjectAddAChk_p(PObjectsWithSelectedComponents,  8,  LObject,  LIdx);
											LMeshAdded = TRUE;

if(E3dDEBUG)
{
	printf("%s() %s:%d  %s/%s  selected Polys %d\n", __FUNCTION__, __FILE__, __LINE__, LMesh->Name, LPolyGroup->Name, (int)LNumOfSelectedPolygons);fflush(stdout);
}

//printf("%s() %s:%d Polygons\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);

											break;
										}
									}
								}

// No selected Polygons, check for selected Edges
//
								if(PDeleteComponentsFlags & E3dDeleteSELECTED_EDGES)
								{
//printf("%s() %s:%d EDGES\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
									if(!LMeshAdded)
									{
										const EIndex  LNumOfSelectedEdges = E3dMesh_NumOfSelectedEdges(LMesh);if(LNumOfSelectedEdges > 0)
										{
//printf("%s() %s:%d Edges\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
											EIndex  LIdx;(void)LIdx;EList_ObjectAddAChk_p(PObjectsWithSelectedComponents,  8,  LObject,  LIdx);

											LMeshAdded = TRUE;
										}
									}
								}

// No selected Edges, check for selected Vertices
//
								if(!LMeshAdded && PDeleteComponentsFlags & (E3dDeleteSELECTED_POINTS|E3dDisconnectSELECTED_POINTS))
								{
printf("%s() %s:%d VERTS\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
									if(!LMeshAdded)
									{
										EIndex  LNSoftSelectedVertices;const EIndex  LNSelectedVertices = E3dMesh_NumOfSelectedVertices(LMesh, 0.0, NULL);if(LNSelectedVertices > 0)
										{
printf("%s() %s:%d Vertices\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
											EIndex  LIdx;(void)LIdx;EList_ObjectAddAChk_p(PObjectsWithSelectedComponents,  8,  LObject,  LIdx);

											LMeshAdded = TRUE;
										}
									}
								}
							}

							if(LMeshAdded) LHasObjectsWithSelectedComponent++;
						}

// Object is selected, but its components are not, return TRUE, but don't add it to PObjectsWithSelectedComponentsP,
// because the Object will be deleted and removed from its Models
//
//					if(LObject->Selection == E3dObjectSelOBJECT) LHasObjectsWithSelectedComponent++;
					}
				}
			}
		}
	}

	return(LHasObjectsWithSelectedComponent);
}


//========================================================================================
// Add selected Objects (Geometries, Lights etc.) from a Model to a Delete operator if
// they're not in the list PObjectsWithSelectedComponentsP.
//========================================================================================
static EChangeFlags _NodeDeleteSelectedObjects(E3dOperatorEditObjects** POperatorP, E3dNode* PNode,  E3dObjectPtrListA* PObjectsWithSelectedComponents)
{
	EChangeFlags  LChanged = 0;

printf("%s() %s:%d  [%s]\n", __FUNCTION__, __FILE__, __LINE__, PNode->Name);fflush(stdout);
	E3dModel*     LModel = E_DynamicCast(PNode, E3dModel);if(LModel)
	{
		EBool        LModelSelected;

		switch(LModel->Selection)
		{
			case E3dNodeSelNODE:
			case E3dNodeSelBRANCH:
			case E3dNodeSelBRANCH_ROOT:
				LModelSelected = TRUE;
			break;

			default:
				LModelSelected = FALSE;
			break;
		}

(void)LModelSelected;

		EIndex  LObjI = 0;const EIndex  LObjN = LModel->Objects.Count;E3dObject**  LObjects = LModel->Objects.Items;for(;LObjI < LObjN;LObjI++)
		{
			E3dObject*  LObject = LObjects[LObjI];
			EBool       LObjSelected = FALSE;

			switch(LObject->Selection)
			{
				case E3dObjectSelOBJECT:  LObjSelected = TRUE;break;
			}

			if(EObject_IsSubClass(LObject, E3dCamera))
			{
				E3dCamera*  LCamera = (E3dCamera*)LObject;
				EIndex  LPI = 0;for(;LPI < E3dp_Panels.Count;LPI++)
				{
					E3dPanel*  L3DPanel = E3dp_Panels.Items[LPI];
					EIndex  LWI = 0;const EIndex  LWN = L3DPanel->Windows.Count;for(;LWI < LWN;LWI++)
					{
						E3dpWindow*  L3DWindow = E_DynamicCast(L3DPanel->Windows.Items[LWI], E3dpWindow);if(L3DWindow)
						{
							EIndex  LVI = 0;const EIndex  LVN = L3DWindow->Views.Count;for(;LVI < LVN;LVI++)
							{
								E3dView*  LView = L3DWindow->Views.Items[LVI];if(LCamera == LView->VCamera)
								{
									LObjSelected = FALSE;
									E3dp_PrintMessage(ELogWARNING, 5000, E3dLng_Can_not_delete_active_Camera);
								}
							}
						}
					}
				}
			}

			if(LObjSelected)
			{
				if(EList_PointerGetIndex_p(PObjectsWithSelectedComponents, LObject) < 0)
				{
					if(*POperatorP == NULL) *POperatorP = (E3dOperatorEditObjects*)EOperator_New(E3dOperatorEditObjects_class);

					E3dOpObject*      LOpObject = E3dEditObjects_AddOpObject(*POperatorP, LObject);
					EObjectAndIndex*  LModelRec;ENew(LModelRec);

					EList_PointerAdd(LOpObject->Models, LModelRec);

					E_AssignNoChkNoFree((EObject*)LModel, LModelRec->Object);
					LModelRec->Index = LObjI;

					LChanged |= E3dObject_GetChangeFlagsByClass(LObject) | E3dChgHIERARCHY;
				}
			}
		}
	}

	return(LChanged);
}


static EIndex _Spline_GetSelectedCVIndices(const E3dSpline* PSpline, EIndex** PRet)
{
	EIndex*       LIndices = NULL;
	const EIndex  LCVN = PSpline->CVs.Count;
	EIndex        LN = 0;

	{
		EIndex  LCVC = 0;const E3dSplineCV*  LCV = PSpline->CVs.Items;for(;LCVC < LCVN;LCVC++, LCV++)
		{
			if(LCV->CVFlags & E3dCV_SELECTED) LN++;
		}
	}

	if(PRet)
	{
		if(LN)
		{
			ENewN(LN, LIndices);

			EIndex  LCVC = 0, LN1 = 0;const E3dSplineCV*  LCV = PSpline->CVs.Items;for(;LCVC < LCVN;LCVC++, LCV++)
			{
				if(LCV->CVFlags & E3dCV_SELECTED)
				{
					LIndices[LN1++] = LCVC;
				}
			}
		}

		*PRet = LIndices;
	}

	return(LN);
}


//================================================================================================
// Collect indices of selected Edges in a Mesh
//
// Argument
//  PMesh      Pointer to the Mesh
//  PIndicesP  Pointer to store the resulting array
//
// Description
//  This function returns the number of selected Polygons in the given PolyGroup.
//
// Return value
//  The number of selected Edges.
//================================================================================================
static EIndex _MeshGetSelectedEdgeIndices(E3dMesh* PMesh, EIndex** PIndicesP, const EBool PKeepWingedEdgesAndVertexLinks)
{
	EListAI(, EIndex, LIndices);

// Count selected Polygons
//
	const EIndex  LEN = PMesh->Edges.Count;
	EIndex  LSelectedEdges_Count = 0;const E3dEdge*  LSEdge = NULL;

	{
		EIndex  LEC = 0;const E3dEdge*  LEdge = PMesh->Edges.Items;for(;LEC < LEN;LEC++, LEdge++)
		{
			if(LEdge->Flags & E3dEdgeSELECTED)
			{
				LSelectedEdges_Count++;
				LSEdge = LEdge;
			}
		}
	}

	EBool  LWeCreatedWingedEdges = FALSE, LWeCreatedVertexLinks = FALSE;

	EIndex  LBoundaryEdgesC = 0, LPolyGroupSeparatorEdgesC = 0;

	if(LSelectedEdges_Count > 1)
	{
// If there's more than one selected Edge, build WingedEdges
//
		if(PMesh->WingedEdges == NULL)
		{
			const EBool  LHadVertexLinks = PMesh->VertexLinksNew != NULL;
			E3dMesh_CreateWingedEdgesFromEdges(PMesh, TRUE, FALSE);LWeCreatedWingedEdges = TRUE;
			if(!LHadVertexLinks) LWeCreatedVertexLinks = TRUE;
		}

		EIndex  LEC = 0;const E3dEdge*  LEdge = PMesh->Edges.Items;const E3dWingedEdge*  LWingedEdge = PMesh->WingedEdges;for(;LEC < LEN;LEC++, LEdge++, LWingedEdge++)
		{
			if(LEdge->Flags & E3dEdgeSELECTED)
			{
				if(LWingedEdge->Polygons_Count == 2)
				{
					const E3dEdgePolygonInfo*  LEdgeDescriptors = LWingedEdge->Polygons;

					if(LEdgeDescriptors[0].PolyGroupIndex == LEdgeDescriptors[1].PolyGroupIndex)
					{
						EList_IndexAddA(LIndices, 64, LEC);
					}
					else LPolyGroupSeparatorEdgesC++;  // PolyGroup separating Edge
				}
				else LBoundaryEdgesC++;  // Boundary Edge
			}
		}

		if(LBoundaryEdgesC) E3dp_PrintMessage(ELogWARNING, 5000, E3dLng_Can_not_delete_boundary_Edge_Try_deleting_Vertices);
		else if(LPolyGroupSeparatorEdgesC) E3dp_PrintMessage(ELogWARNING, 5000, E3dLng_Can_not_delete_Edge_that_separates_2_PolyGroups);
	}
	else if(LSelectedEdges_Count == 1)
	{
// If there's only one selected Edge, get info about it via E3dMesh_EdgeGetPolygons().
//
		unsigned int  LDescription;E3dEdgePolygonInfo*  LEdgeDescriptors;const EIndex  LNumOfPolygonsOnEdge = E3dMesh_EdgeGetPolygons(PMesh, LSEdge, &LEdgeDescriptors, &LDescription);

		if(LNumOfPolygonsOnEdge != 2)
		{
			EFreeChk(LEdgeDescriptors);
			E3dp_PrintMessage(ELogWARNING, 5000, E3dLng_Can_not_delete_boundary_Edge_Try_deleting_Vertices);
			return(E3dEdgeNOT_SHARED_BY_2_POLYS);
		}

		if(LEdgeDescriptors[0].PolyGroupIndex != LEdgeDescriptors[1].PolyGroupIndex)
		{
			EFreeChk(LEdgeDescriptors);
			E3dp_PrintMessage(ELogWARNING, 5000, E3dLng_Can_not_delete_Edge_that_separates_2_PolyGroups);
			return(E3dEdgePOLYS_IN_DIFFERENT_POLYGROUPS);
		}

		EList_IndexAdd(LIndices, LSEdge - PMesh->Edges.Items);
	}


// We must free WingedEdges and VertexLinks of PSrcMesh to avoid data corruption with stale data
//
	if(!PKeepWingedEdgesAndVertexLinks)
	{
		if(LWeCreatedWingedEdges) E3dMesh_WingedEdgesFree(PMesh);
		if(LWeCreatedVertexLinks) E3dMesh_VertexLinksNewFree(PMesh);
	}

	*PIndicesP = LIndices;

	if(LBoundaryEdgesC) return(E3dEdgeNOT_SHARED_BY_2_POLYS);
	else if(LPolyGroupSeparatorEdgesC) return(E3dEdgePOLYS_IN_DIFFERENT_POLYGROUPS);
	else return(LIndices_Count);
}


//========================================================================================
// Add Objects and their selected components to a Delete operator.
//========================================================================================
static EChangeFlags _ObjectsDeleteSelectedComponents(EOps* POps, E3dOperatorEditObjects** POperatorEditObjectsP,
 const uint8_t PDeleteComponentsFlags,  E3dObjectPtrListA* PObjects)
{
	EChangeFlags  LChanged = 0;

	EIndex  LObjI = 0;const EIndex  LObjN = PObjects->Count;for(;LObjI < LObjN;LObjI++)
	{
		E3dObject*  LObject = PObjects->Items[LObjI];

		if(EObject_IsSubClass(LObject, E3dSpline))
		{
			const E3dSpline*  LSpline = (const E3dSpline*)LObject;

			if(PDeleteComponentsFlags & E3dDeleteSELECTED_POINTS)
			{
				EIndex*  LSelectedCVIndices = NULL;

				const EIndex  LSelectedCVIndices_Count = _Spline_GetSelectedCVIndices(LSpline, &LSelectedCVIndices);if(LSelectedCVIndices_Count > 0)
				{
					E3dOperatorAddDelSplineCVs*  LOperator = (E3dOperatorAddDelSplineCVs*)EOperator_New(E3dOperator_DeleteSplineCVs_class);

					E_AssignToMember(LSpline, LOperator,Object);
					E_AssignToMember(LSpline, LOperator,Spline);
					LOperator->CVs_Indices = LSelectedCVIndices;LOperator->CVs = NULL;LOperator->CVs_Count = LSelectedCVIndices_Count;

					EList_ObjectAddA_s(POps->Operators, 1, LOperator);
				}
			}
		}
		else if(EObject_IsSubClass(LObject, E3dFace))
		{
			E3dFace*  LFace = (E3dFace*)LObject;

			if(LObject->Selection == E3dObjectSelFACE_CONTOUR)
			{
				if(LFace->Exterior->Selection == E3dObjectSelOBJECT)
				{
					E3dp_PrintMessage(ELogWARNING, 5000, E3dLng_Can_not_delete_Face_exterior);
				}

				EIndex  LHI = 0;for(;LHI < LFace->Holes_Count;LHI++)
				{
					E3dSpline*  LHole = LFace->Holes[LHI];

					if(LHole->Selection == E3dObjectSelOBJECT)
					{
						if(*POperatorEditObjectsP == NULL) *POperatorEditObjectsP = (E3dOperatorEditObjects*)EOperator_New(E3dOperatorEditObjects_class);
						E3dOpObject*     LOpObject    = E3dEditObjects_AddOpObject(*POperatorEditObjectsP, LObject);
						E3dOpSubObject*  LOpSubObject = EList_StructAdd(LOpObject->SubObjects);if(LOpSubObject)
						{
printf("%s() %s:%d <%s> %d\n", __FUNCTION__, __FILE__, __LINE__, LHole->Name, LHole->RefCnt);fflush(stdout);
							E3dEditObjects_OpSubObjectInit(LOpSubObject, LHI, (EObject*)LHole);
printf("%s() %s:%d <%s> %d\n", __FUNCTION__, __FILE__, __LINE__, LHole->Name, LHole->RefCnt);fflush(stdout);
						}
					}
				}
			}
		}
		else if(EObject_IsSubClass(LObject, E3dMesh))
		{
			E3dMesh*  LMesh = (E3dMesh*)LObject;
			EBool     LHasSelectedPolygons = FALSE, LHasSelectedPolyGroups = FALSE;

			if(PDeleteComponentsFlags & (E3dDeleteSELECTED_POLYGROUPS|E3dDeleteSELECTED_POLYGONS))
			{
				EIndex  LPGI = 0;const EIndex  LPGN = LMesh->PolyGroups.Count;E3dPolyGroup* const*  LPolyGroups = LMesh->PolyGroups.Items;for(;LPGI < LPGN;LPGI++)
				{
					const E3dPolyGroup*  LPolyGroup = LPolyGroups[LPGI];
					if(PDeleteComponentsFlags & E3dDeleteSELECTED_POLYGROUPS && LPolyGroup->Selected) LHasSelectedPolyGroups = TRUE;

					if(PDeleteComponentsFlags & E3dDeleteSELECTED_POLYGONS)
					{
						const EIndex  LNumOfSelectedPolygons = E3dPolyGroup_NumOfSelectedPolygons(LPolyGroup);if(LNumOfSelectedPolygons > 0)
						{
							LHasSelectedPolygons = TRUE;
							break;
						}
					}
				}
			}

			E3dOperatorAddDeleteVertices*  LOperatorDeleteVertices = NULL;

			EIndex*  LSelectedEdgeIndices = NULL;EIndex  LSelectedEdgeIndices_Count;
			if(LHasSelectedPolygons)
			{
				LSelectedEdgeIndices_Count = 0;
			}
			else
			{
// No selected Polygons, check for selected PolyGroups
//
				if(LHasSelectedPolyGroups) LSelectedEdgeIndices_Count = 0;
				else
				{
					if(PDeleteComponentsFlags & E3dDeleteSELECTED_EDGES) LSelectedEdgeIndices_Count = _MeshGetSelectedEdgeIndices(LMesh, &LSelectedEdgeIndices, TRUE);
					else LSelectedEdgeIndices_Count = 0;

					if(LSelectedEdgeIndices_Count < 1)
					{
// No Edges to delete, check for Vertices
//
						EBool   LWeCreatedVertexLinks = FALSE;
						EIndex  LVerticesToDelete_Count = 0;

						if(PDeleteComponentsFlags & (E3dDeleteSELECTED_POINTS|E3dDisconnectSELECTED_POINTS))
						{
							const EIndex  LNSelectedVertices = E3dMesh_NumOfSelectedVertices(LMesh, 0.0, NULL);if(LNSelectedVertices)
							{
								if(LMesh->VertexLinksNew == NULL) { E3dMesh_GetVertexLinksNew(LMesh, E3dVertexLinksEDGES);LWeCreatedVertexLinks = TRUE; }

								if(LMesh->VertexLinksNew)
								{
									const EIndex  LVN = LMesh->Vertices.Count;const E3dVertexSize  LMeshVertexSize = LMesh->VertexSize;

									const EIndex  LVertexLinksStride = LMesh->VertexLinksStride;

									{
										EIndex  LVI = 0;const E3dVertex*  LVertex = LMesh->Vertices.Items;const E3dVertexLinkNew*  LVertexLinksRec = LMesh->VertexLinksNew;
										for(;LVI < LVN;LVI++, EStruct_Inc(LVertex, LMeshVertexSize), EStruct_Inc(LVertexLinksRec, LVertexLinksStride))
										{
											if(LVertex->Flags & E3dVtxSELECTED)
											{
												if(LVertexLinksRec->EdgeEnds.Count == 0 || LVertexLinksRec->EdgeEnds.Count == 2) LVerticesToDelete_Count++;
											}
										}
									}

									EIndex*  LVerticesToDelete_Indices;ENewN(LVerticesToDelete_Count, LVerticesToDelete_Indices);
									EIndex   LVCC = 0;

									{
										EIndex  LVI = 0;const E3dVertex*  LVertex = LMesh->Vertices.Items;const E3dVertexLinkNew*  LVertexLinksRec = LMesh->VertexLinksNew;
										for(;LVI < LVN;LVI++, EStruct_Inc(LVertex, LMeshVertexSize), EStruct_Inc(LVertexLinksRec, LVertexLinksStride))
										{
											if(LVertex->Flags & E3dVtxSELECTED)
											{
												if(LVertexLinksRec->EdgeEnds.Count == 0 || LVertexLinksRec->EdgeEnds.Count == 2) LVerticesToDelete_Indices[LVCC++] = LVI;
											}
										}
									}

									if(LWeCreatedVertexLinks) E3dMesh_VertexLinksNewFree(LMesh);

//printf("%s() %s:%d  LVerticesToDelete_Count %d\n", __FUNCTION__, __FILE__, __LINE__, (int)LVerticesToDelete_Count);fflush(stdout);

									if(LVerticesToDelete_Count)
									{
										E3dOperatorAddDeleteVertices*  LOperator = (E3dOperatorAddDeleteVertices*)EOperator_New(E3dOperatorDeleteVertices_class);
										LOperatorDeleteVertices = LOperator;

										LOperator->Vertices_Indices = LVerticesToDelete_Indices;
										LOperator->Vertices_Count = LVerticesToDelete_Count;

										E_AssignToMember(LMesh, LOperator,MeshNewT);
										EResourceList_Copy(&LMesh->VertexAttributes, &LOperator->VertexAttributes,  EResSEMANTIC_STATIC | EResUI_STATIC, EResARGS);
										LOperator->VertexSize = LMesh->VertexSize;

										E3d_DeleteVerticesAddAffectedPolygons(LOperator);
										E3d_DeleteVerticesAddAffectedEdges(LOperator);

										if((PDeleteComponentsFlags & E3dDeleteSELECTED_POINTS) == 0) LOperator->DeleteMeshVertices = FALSE;
										EList_ObjectAddA_s(POps->Operators, 1, LOperator);
									}
								}
								else
								{
printf("%s() %s:%d <%s> %sNO VertexLinks%s\n", __FUNCTION__, __FILE__, __LINE__, LMesh->Name, ECodeRed, ECodeNormal);fflush(stdout);
								}
							}
						}

					}
				}
			}

			if(LSelectedEdgeIndices_Count < 0)
			{
printf("%s() %s:%d  Deleting Edges %sIMPLEMENT ME%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, ECodeNormal);fflush(stdout);
			}
			else
			{
//printf("%s() %s:%d  %d\n", __FUNCTION__, __FILE__, __LINE__, (int)LSelectedEdgeIndices_Count);fflush(stdout);

				E3dOperatorEditMesh*  LOperatorEditMesh = (E3dOperatorEditMesh*)EOperator_New(E3dOperatorEditMesh_class);

				EBool  LDeletingPolygons = FALSE;
				const int  LResult = E3dMesh_DeleteComponents(LMesh,  LSelectedEdgeIndices, LSelectedEdgeIndices_Count,  TRUE,  LOperatorEditMesh,  NULL,  &LDeletingPolygons);switch(LResult)
				{
					case E_SUCCESS:
						EList_ObjectAddA_s(POps->Operators, 1, LOperatorEditMesh);
						if(LDeletingPolygons)
						{
							if(E3dp_Prefs.DeleteSelection_DeleteUnusedVertices)
							{
								const EBool  LHadVertexUsageList = LMesh->VertexLinksNew != NULL ? TRUE : FALSE;

								const E3dVertexLinkPolys*  LVertexLinks = (E3dVertexLinkPolys*)E3dMesh_GetVertexLinksNew(LMesh, E3dVertexLinksEDGES|E3dVertexLinksPOLYGONS);if(LVertexLinks)
								{
									const EIndex  LVertexLinksStride = LMesh->VertexLinksStride;
									const EIndex  LVN = LMesh->Vertices.Count;
									EIndex*  LVertexRefCounts;ENewN(LVN, LVertexRefCounts);
									{
										EIndex  LVI = 0;const E3dVertexLinkPolys*  LVertexLinksRec = LVertexLinks;for(;LVI < LVN;LVI++, EStruct_Inc(LVertexLinksRec, LVertexLinksStride))
										{
											EIndex  LVertexRefCount = LVertexLinksRec->VertexNodes_Count;
											EIndex  LVVC = 0;const EIndex  LVVN = LVertexRefCount;for(;LVVC < LVVN;LVVC++)
											{
												const E3dPolygon*  LPolygon = LVertexLinksRec->Polygons[LVVC];
												if(LPolygon->Flags & E3dPolygonSELECTED) LVertexRefCount--;
											}
											if(LVertexRefCount < 0) LVertexRefCount = 0;
											LVertexRefCounts[LVI] = LVertexRefCount;
										}
									}

									if(!LHadVertexUsageList) E3dMesh_VertexLinksNewFree(LMesh);

									EListI(, EIndex, LVerticesToDeleteIndices);

									{
										EIndex  LVI = 0;for(;LVI < LVN;LVI++)
										{
											if(LVertexRefCounts[LVI] == 0) LVerticesToDeleteIndices_Count++;
										}
									}

//									E3dMes_hRemoveUnusedVertices(POps, LMesh);

									if(LVerticesToDeleteIndices_Count)
									{
										ENewN(LVerticesToDeleteIndices_Count, LVerticesToDeleteIndices);
										{
											EIndex  LVI = 0, LVCT = 0;for(;LVI < LVN;LVI++)
											{
												if(LVertexRefCounts[LVI] == 0) LVerticesToDeleteIndices[LVCT++] = LVI;
											}
										}

										E3dOperatorAddDeleteVertices*  LOperatorDeleteUnusedVertices = (E3dOperatorAddDeleteVertices*)EOperator_New(E3dOperatorDeleteVertices_class);
										if(LOperatorDeleteUnusedVertices)
										{
											E_AssignToMember(LMesh, LOperatorDeleteUnusedVertices,MeshNewT);
											EResourceList_Copy(&LMesh->VertexAttributes, &LOperatorDeleteUnusedVertices->VertexAttributes,  EResSEMANTIC_STATIC | EResUI_STATIC, EResARGS);
											LOperatorDeleteUnusedVertices->VertexSize = LMesh->VertexSize;
											LOperatorDeleteUnusedVertices->Vertices_Indices = LVerticesToDeleteIndices;
											LOperatorDeleteUnusedVertices->Vertices_Count = LVerticesToDeleteIndices_Count;

											EList_ObjectAddA_s(POps->Operators, 1, LOperatorDeleteUnusedVertices);
										}
									}

									EFree(LVertexRefCounts);
								}
							}
						}
					break;

					case E_NOTHING_TO_DO:
						E_Delete(LOperatorEditMesh);
//						if(LOperatorDeleteVertices->Vertices_Count == 0)
						if(LOperatorDeleteVertices == NULL)
						{
							E3dp_PrintMessage(ELogWARNING, 5000, "Internal error : Mesh %s marked for component deletion, but no selected components found", LMesh->Name);
printf("%s() %s:%d  %sMesh %s marked for component deletion, but no selected components found.%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeRed, LMesh->Name, ECodeNormal);fflush(stdout);
						}
					break;
				}
			}

			if(E3dp_Prefs.OptimizeFor == EOptimizeMEMORY)
			{
				E3dMesh_WingedEdgesFree(LMesh);
				E3dMesh_VertexLinksNewFree(LMesh);
			}
		}
	}

	return(LChanged);
}


//========================================
// Initialize DeleteSelection Operator
//========================================
static void _DeleteSelection_Init(EOperator* POperator)
{
	E3dOperatorDelete*  LOperator = (E3dOperatorDelete*)POperator;

	EList_Init(LOperator->Hierarchies);
	EList_Init(LOperator->Branches);
}


//========================================
// Free DeleteSelection Operator
//========================================
static int _DeleteSelection_Free(EOperator* POperator)
{
	E3dOperatorDelete*  LOperator = (E3dOperatorDelete*)POperator;

// Free Hierarchies
//
	_ObjectAndIndexListFree(&LOperator->Hierarchies, &LOperator->Hierarchies_Count);


// Free Branches
//
	if(LOperator->Branches)
	{
		EIndex  LI = 0;const EIndex  LN = LOperator->Branches_Count;for(LI = 0;LI < LN;LI++)
		{
			E3dBranch*  LEntry = LOperator->Branches[LI];
			E_DeleteChk(LEntry->Node);
			E_DeleteChk(LEntry->Parent);
			EFree(LEntry);
		}
		EList_Free(LOperator->Branches);
	}

	return(E_SUCCESS);
}


//================================================
// DeleteSelection Operator proc
//================================================
static EChangeFlags _DeleteSelection_Func(EOps* POps, EOperator* POperator, const int POperation)
{
	E3dScene*           LScene = E3d_Scene;
	E3dOperatorDelete*  LOperator = (E3dOperatorDelete*)POperator;

	switch(POperation)
	{
		case EOpDO:
			{
				EChangeFlags  LChanged = 0;

				if(LOperator->Branches_Count)
				{
					E3dNode**  LRootNodes = NULL;EIndex  LRootNodes_Count = 0;

					{
						EIndex  LI = 0;const EIndex  LN = LOperator->Branches_Count;for(;LI < LN;LI++)
						{
							E3dBranch*  LEntry = LOperator->Branches[LI];
							E3dNode*    LNode = (E3dNode*)(LEntry->Node);

							E3dNode*  LRootNode = LNode;while(LRootNode->Parent) LRootNode = LRootNode->Parent;
							EList_PointerAddChk(LRootNodes, LRootNode);

							E3dNode_DisconnectFromParent(LNode, FALSE);
							E3dNodeHrc_CallRemoveProcs(LNode);
							E3dNode_FreeRenderData(LNode);
							LChanged |= E3dNodeHrc_GetTransformChangeFlagsByObjects(LNode) | E3dChgHIERARCHY;
						}
					}

					if(LRootNodes)
					{
						EIndex  LI = 0;for(;LI < LRootNodes_Count;LI++)
						{
							E3dNode*  LRootNode = LRootNodes[LI];

// Although NodeHrcBranchDisconnectFromParent updates link members (Prev, Next etc.),
// we still need to call NodeHrcUpdateConnections to update PopN and PushN
//
							E3dNodeHrc_UpdateConnections(LRootNode);
						}
						EFree(LRootNodes);
					}
				}


				if(LOperator->Hierarchies_Count)
				{
					{
						EIndex  LI = 0;const EIndex  LN = LOperator->Hierarchies_Count;for(;LI < LN;LI++)
						{
							E3dNode*  LRootNode = (E3dNode*)(LOperator->Hierarchies[LI]->Object);

							if(E3dScene_NodeHrcRemove(LScene, LRootNode, &LChanged, FALSE))
							{
								LChanged |= E3dChgHIERARCHY;
								LChanged |= E3dNodeHrc_GetTransformChangeFlagsByObjects(LRootNode);
							}
						}
					}

// Call Scene callbacks
//
					{
						EIndex     LN = LOperator->Hierarchies_Count;
						E3dNode**  LList;ENewN(LN, LList);

						EIndex  LI = 0;for(;LI < LN;LI++)
						{
							E_AssignNoChkNoFree((E3dNode*)(LOperator->Hierarchies[LI]->Object), LList[LI]);
						}

						E3dSceneNodesCallbackStruct  LCBS;ECallbackStruct_Init((ECallbackStruct*)&LCBS, E3dSceneNodesCallbackStruct_class,  E3dCR_HIERARCHY_REMOVED);
						LCBS.Nodes = LList;LCBS.NumOfNodes = LN;
						E3d_CallCallbacks((ECoreObject*)LScene,  LScene->Callbacks, LScene->Callbacks_Count,  (ECallbackStruct*)&LCBS);

						ELst_ObjectsFree(&LList, &LN);
					}
				}

				_SceneUpdateSelections(LScene);
				return(LChanged);
			}
		break;

		case EOpUNDO:
			{
				EChangeFlags  LChanged = 0;

				if(LOperator->Branches_Count)
				{
					E3dNode**  LRootNodes = NULL;EIndex  LRootNodes_Count = 0;

					{
						EIndex  LI = 0;const EIndex  LN = LOperator->Branches_Count;for(;LI < LN;LI++)
						{
							E3dBranch*  LEntry = LOperator->Branches[LI];
							E3dNode*    LNode = LEntry->Node;
							E3dNode*    LParent = LEntry->Parent;


//printf("%s() %s:%d Parent [%s]  Restoring Branch root [%s] at index %d\n", __FUNCTION__, __FILE__, __LINE__, LParent->Name, LNode->Name, LEntry->Index);fflush(stdout);
							E3dNode_ConnectChild(LParent, LNode, LEntry->Index, FALSE, FALSE);

							while(LParent->Parent) LParent = LParent->Parent;
							EList_PointerAddChk(LRootNodes, LParent);
						}
					}

					if(LRootNodes)
					{
						EIndex  LI = 0;for(LI = 0;LI < LRootNodes_Count;LI++)
						{
							E3dNode*  LRootNode = LRootNodes[LI];

printf("%s() %s:%d [%s]\n", __FUNCTION__, __FILE__, __LINE__, LRootNode->Name);fflush(stdout);
							E3dNodeHrc_UpdateConnections(LRootNode);
							E3dNodeHrc_UpdateMatrices(LRootNode, TRUE);
							E3dNodeHrc_UpdateMaterialInheritance(LRootNode);
							E3dNodeHrc_CallNodeInfoUpdateProcs(LRootNode, E3dChgHIERARCHY, FALSE);
						}
						EFree(LRootNodes);
					}

// Add back Materials to the Scene
//
					{
						EIndex  LI = 0;const EIndex  LN = LOperator->Branches_Count;for(;LI < LN;LI++)
						{
							E3dBranch*  LEntry = LOperator->Branches[LI];
							E3dNode*    LNode = LEntry->Node;

							E3dScene_MaterialsAddFromNodeHrc(LScene, LNode);
						}
					}



// Now that the hierarchies are up-to-date, we can call E3dNodeHrc_GetTransformChangeFlagsByObjects
//
					{
						EIndex  LI = 0;const EIndex  LN = LOperator->Branches_Count;for(;LI < LN;LI++)
						{
							E3dBranch*  LEntry = LOperator->Branches[LI];
							E3dNode*    LNode = LEntry->Node;

							LChanged |= E3dNodeHrc_GetTransformChangeFlagsByObjects(LNode) | E3dChgHIERARCHY;
						}
					}
				}

				if(LOperator->Hierarchies_Count)
				{
					{
						LChanged |= E3dChgHIERARCHY;

						EIndex  LI = 0;const EIndex  LN = LOperator->Hierarchies_Count;for(;LI < LN;LI++)
						{
							EObjectAndIndex*  LEntry = LOperator->Hierarchies[LI];
							E3dNode*          LRootNode = (E3dNode*)LEntry->Object;
							E3dNode*          LNode;

							E3dScene_NodeHrcInsert(LScene, LRootNode, LEntry->Index, FALSE);
							for(LNode = LRootNode;LNode;LNode = LNode->Next) E3dNode_UpdateForDisplay(LNode, E3duf_FULLGEO);

							E3dScene_MaterialsAddFromNodeHrc(LScene, LRootNode);

							LChanged |= E3dNodeHrc_GetTransformChangeFlagsByObjects(LRootNode);
						}
					}

// Call Scene callbacks
//
					{
						EIndex     LN = LOperator->Hierarchies_Count;
						E3dNode**  LList;ENewN(LN, LList);

						EIndex  LI = 0;for(;LI < LN;LI++)
						{
							E_AssignNoChkNoFree((E3dNode*)(LOperator->Hierarchies[LI]->Object), LList[LI]);
						}

						E3dSceneNodesCallbackStruct  LCBS;ECallbackStruct_Init((ECallbackStruct*)&LCBS, E3dSceneNodesCallbackStruct_class,  E3dCR_HIERARCHY_ADDED);
						LCBS.Nodes = LList;LCBS.NumOfNodes = LN;
						E3d_CallCallbacks((ECoreObject*)LScene,  LScene->Callbacks, LScene->Callbacks_Count,  (ECallbackStruct*)&LCBS);

						ELst_ObjectsFree(&LList, &LN);
					}
				}

				_SceneUpdateSelections(LScene);

				return(LChanged);
			}
		break;
	}
	return(0);
}


typedef struct
{
	E3dAnimation*         Animation;
	E3dAnimationChannel*  Channel;
	EIndex                ChannelIndex;
} E3dChannelRec;


typedef struct
{
	EOperatorCore;

	E3dScene*      Scene;
	E3dAnimStack*  AnimStack;

	EListA(, EObjectAndIndex*, Animations);
	EListA(, E3dChannelRec*, Channels);
} E3dOperatorDeleteAnimations;


//========================================
// Initialize DeleteAnimations Operator
//========================================
static void _DeleteAnimations_Init(EOperator* POperator)
{
	E3dOperatorDeleteAnimations*  LOperator = (E3dOperatorDeleteAnimations*)POperator;

	LOperator->AnimStack = NULL;

	EList_InitA(LOperator->Animations);
	EList_InitA(LOperator->Channels);
}


//========================================
// Free DeleteAnimations Operator
//========================================
static int _DeleteAnimations_Free(EOperator* POperator)
{
	E3dOperatorDeleteAnimations*  LOperator = (E3dOperatorDeleteAnimations*)POperator;

	if(LOperator->Animations)
	{
		EIndex  LI = 0;const EIndex  LN = LOperator->Animations_Count;for(;LI < LN;LI++)
		{
			E_Delete(LOperator->Animations[LI]->Object);
			EFree(LOperator->Animations[LI]);
		}

		EList_FreeA(LOperator->Animations);
	}

	if(LOperator->Channels)
	{
		EIndex  LI = 0;const EIndex  LN = LOperator->Channels_Count;for(;LI < LN;LI++)
		{
			E3dChannelRec*  LEntry = LOperator->Channels[LI];

			E_Delete(LEntry->Animation);
			E_Delete(LEntry->Channel);
			EFree(LEntry);
		}

		EList_FreeA(LOperator->Channels);
	}

	E_DeleteChk(LOperator->AnimStack);

	return(E_SUCCESS);
}


//================================================
// DeleteAnimations Operator proc
//================================================
static EChangeFlags _DeleteAnimations_Func(EOps* POps, EOperator* POperator, const int POperation)
{
	E3dOperatorDeleteAnimations*  LOperator = (E3dOperatorDeleteAnimations*)POperator;
	E3dAnimStack*                 LAnimStack = LOperator->AnimStack;
	const EIndex                  LNumOfAnimations = LOperator->Animations_Count;
	const EIndex                  LNumOfChannels = LOperator->Channels_Count;


	switch(POperation)
	{
		case EOpDO:
			{
				EChangeFlags  LChanged = 0;

				if(LNumOfAnimations)
				{
					EIndex  LI = 0;for(;LI < LNumOfAnimations;LI++)
					{
						EObjectAndIndex*  LEntry = LOperator->Animations[LI];
						E3dAnimation*     LAnimation = (E3dAnimation*)(LEntry->Object);

						E3dAnimStack_AnimationRemove(LAnimStack, LAnimation, FALSE);

						LChanged |= E3dChgANIMATION;
					}
				}


				if(LNumOfChannels)
				{
					EIndex  LI = 0;for(;LI < LNumOfChannels;LI++)
					{
						E3dChannelRec*  LEntry = LOperator->Channels[LI];

						E3dAnimation_RemoveChannel(LEntry->Animation, LEntry->Channel);

						LChanged |= E3dChgANIMATION;
					}
				}
				return(LChanged);
			}
		break;

		case EOpUNDO:
			{
				EChangeFlags  LChanged = 0;

				if(LNumOfAnimations)
				{
					EIndex  LI = 0;for(;LI < LNumOfAnimations;LI++)
					{
						EObjectAndIndex*  LEntry = LOperator->Animations[LI];
						E3dAnimation*     LAnimation = (E3dAnimation*)(LEntry->Object);

						EList_ObjectInsertA(LAnimStack->Animations, 8, LEntry->Index, LAnimation);

						LChanged |= E3dChgANIMATION;
					}
				}


				if(LNumOfChannels)
				{
					EIndex  LI = 0;for(;LI < LNumOfChannels;LI++)
					{
						E3dChannelRec*        LEntry = LOperator->Channels[LI];
						E3dAnimation*         LAnimation = LEntry->Animation;
						E3dAnimationChannel*  LChannel = LEntry->Channel;

						EList_ObjectInsertA(LAnimation->Channels, LEntry->ChannelIndex, 4, LChannel);

						LChanged |= E3dChgANIMATION;
					}
				}

				return(LChanged);
			}
		break;
	}

	return(0);
}


//================================================================================================
// If the target Object of an animation is deleted, that animation must also be deleted
//================================================================================================
static EBool _AnimationIndirectlySelectedForDeletion(E3dAnimation* PAnimation)
{
	if(EObject_IsSubClass(PAnimation, E3dKeyFrameAnimation))
	{
		E3dKeyFrameAnimation*  LKeyFrameAnimation = (E3dKeyFrameAnimation*)PAnimation;
		EIndex                 LCI = 0;const EIndex  LCN = LKeyFrameAnimation->Channels_Count;for(;LCI < LCN;LCI++)
		{
			if(E3dAnimationChannel_TargetSelected(LKeyFrameAnimation->Channels[LCI])) return(TRUE);
		}
	}

	return(FALSE);
}


//========================================
// Delete selection
//========================================
static EChangeFlags _DeleteSelection(E3dScene* PScene, const uint16_t PDeleteComponentsFlags)
{
	EChangeFlags  LChanged = 0;
	EOps          LOps;EOps_Init(&LOps);

// Nodes, Objects etc.
//
	{
		E3dOperatorDelete*       LOperatorNodes = (E3dOperatorDelete*)EOperator_New(_OperatorDelete_class);
		E3dOperatorEditObjects*  LOperatorEditObjects = NULL;

		EListAI_s(, E3dObjectPtr, LObjectsWithSelectedComponents);  // Objects with selected components (Meshes with selected PolyGroups/Polygons etc.)

		{
			EIndex  LRI = 0;const EIndex  LRN = PScene->RootNodes.Count;E3dNode**  LRootNodes = PScene->RootNodes.Items;for(;LRI < LRN;LRI++)
			{
				E3dNode*  LRootNode = LRootNodes[LRI];
				EBool     LHierarchyDeletable = TRUE;

				{
					E3dNode*  LNode = LRootNode;for(;LNode;LNode = LNode->Next)
					{
						if(_NodeCheckForSelectedComponents(LNode,  PDeleteComponentsFlags,  &LObjectsWithSelectedComponents) > 0)
						{
							LHierarchyDeletable = FALSE;
						}
					}
				}

				if(LHierarchyDeletable)
				{
					switch(LRootNode->Selection)
					{
						case E3dNodeSelBRANCH_ROOT:  break;
						case E3dNodeSelNODE:         if(LRootNode->Child) LHierarchyDeletable = FALSE;break;
						default:                     LHierarchyDeletable = FALSE;break;
					}

					if(LHierarchyDeletable)
					{
						if(E3d_IsActiveCameraNode(LRootNode)) { LHierarchyDeletable = FALSE;E3dp_PrintMessage(ELogWARNING, 5000, E3dLng_Can_not_delete_active_Camera); }
						else if(E3d_IsActiveCameraInterestNode(LRootNode)) { LHierarchyDeletable = FALSE;E3dp_PrintMessage(ELogWARNING, 5000, E3dLng_Cant_delete_interest_point_of_Camera); }
					}
				}

				if(LHierarchyDeletable)
				{
					EObjectAndIndex*  LEntry;ENew(LEntry);
					EList_PointerAdd(LOperatorNodes->Hierarchies, LEntry);

// Store this Hierarchy in the Delete Operator
//
					E_AssignNoChkNoFree((EObject*)LRootNode, LEntry->Object);
					LEntry->Index = LRI;
					LChanged |= E3dNodeHrc_GetTransformChangeFlagsByObjects(LRootNode) | E3dChgHIERARCHY;
					E3dNodeHrc_CallRemoveProcs(LRootNode);
				}
			}
		}


// Now check the remaining hierarchies for Node, Branch or Object / Component to delete
//
		{
			EIndex  LRI = 0;const EIndex  LRN = PScene->RootNodes.Count;E3dNode**  LRootNodes = PScene->RootNodes.Items;for(;LRI < LRN;LRI++)
			{
				E3dNode*  LRootNode = LRootNodes[LRI];
				EBool     LBranchDeleted = FALSE;

				E3dNode*  LNode = LRootNode;for(;LNode;)
				{
					if(LNode->Locks_Count == 0)
					{
						switch(LNode->Selection)
						{
							case E3dNodeSelBRANCH_ROOT:
								if(LNode->Parent)
								{
// Store this Branch in the Delete Operator
//
									E3dBranch*  LEntry;ENew(LEntry);
									EList_PointerAdd(LOperatorNodes->Branches, LEntry);

									E_AssignNoChkNoFree(LNode, LEntry->Node);
									E_AssignNoChkNoFree(LNode->Parent, LEntry->Parent);
									LEntry->Index = E3dNode_GetChildIndex(LNode);
								}
								LNode = LNode->Next;
							break;

							case E3dNodeSelNODE:
								if(LNode->Parent)
								{
									EBool  LDelThis = TRUE;

									if(E3d_IsActiveCameraNode(LNode)) { LDelThis = FALSE;E3dp_PrintMessage(ELogWARNING, 5000, E3dLng_Can_not_delete_active_Camera); }
									else if(E3d_IsActiveCameraInterestNode(LNode)) { LDelThis = FALSE;E3dp_PrintMessage(ELogWARNING, 5000, E3dLng_Cant_delete_interest_point_of_Camera); }

									if(LDelThis)
									{
printf("%s() %s:%d  [%s] %04x\n", __FUNCTION__, __FILE__, __LINE__, LNode->Name, PDeleteComponentsFlags);fflush(stdout);
										const EChangeFlags  LChangedT = PDeleteComponentsFlags & E3dDeleteSELECTED_OBJECTS  ?
										 _NodeDeleteSelectedObjects(&LOperatorEditObjects,  LNode,  &LObjectsWithSelectedComponents) : 0;

//printf("%s() %s:%d SEL_NODE\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
										LChanged |= LChangedT;
										if(LChangedT) LNode = LNode->Next;
										else if(PDeleteComponentsFlags & E3dDeleteSELECTED_NODES)
										{
											if(LNode->Child == NULL)
											{
// Store this Branch in the Delete Operator
//
												E3dBranch*	LEntry;ENew(LEntry);
												EList_PointerAdd(LOperatorNodes->Branches, LEntry);

												E_AssignNoChkNoFree(LNode, LEntry->Node);
												E_AssignNoChkNoFree(LNode->Parent, LEntry->Parent);
												LEntry->Index = E3dNode_GetChildIndex(LNode);

												LNode = LNode->Next;
											}
											else
											{
												E3dp_PrintMessage(ELogWARNING, 10000, "Node: '%s' has children!", LNode->Name);
												LNode = LNode->Next;
											}
										}
									}
									else LNode = LNode->Next;
								}
								else LNode = LNode->Next;
							break;

							case E3dNodeSelOBJECT:
printf("%s() %s:%d  [%s] %04x\n", __FUNCTION__, __FILE__, __LINE__, LNode->Name, PDeleteComponentsFlags);fflush(stdout);
								if(PDeleteComponentsFlags & E3dDeleteSELECTED_OBJECTS)
								{
									LChanged |= _NodeDeleteSelectedObjects(&LOperatorEditObjects, LNode,  &LObjectsWithSelectedComponents);
								}
								LNode = LNode->Next;
							break;

							default:
								LNode = LNode->Next;
							break;
						}
					}
					else
					{
						E3dp_PrintMessage(ELogWARNING, 10000, "Node '%s' is locked (it's probably being edited), thus can not be deleted.", LNode->Name);
						LNode = LNode->Next;
					}
				}

				if(LBranchDeleted)
				{
//printf("%s() root [%s]\n", __FUNCTION__, LRootNode->Name);fflush(stdout);
					E3dNodeHrc_UpdateConnections(LRootNode);
					E3dNodeHrc_UpdateMatrices(LRootNode, TRUE);
					E3dNodeHrc_CallNodeInfoUpdateProcs(LRootNode, E3dChgHIERARCHY, FALSE);
				}
			}
		}


		_ObjectsDeleteSelectedComponents(&LOps, &LOperatorEditObjects,  PDeleteComponentsFlags,  &LObjectsWithSelectedComponents);


		if(LOperatorNodes->Hierarchies_Count || LOperatorNodes->Branches_Count)
		{
			EList_ObjectAddA_s(LOps.Operators, 1, LOperatorNodes);
		}
		else E_Delete(LOperatorNodes);    // Free Operator struct (it's empty)



		if(LOperatorEditObjects && LOperatorEditObjects->Objects_Count)
		{
			EList_ObjectAddA_s(LOps.Operators, 1, LOperatorEditObjects);
		}
		else E_DeleteChk(LOperatorEditObjects);    // Free Operator struct (it's empty)


		EList_ObjectsFreeA_s(LObjectsWithSelectedComponents);
	}



// Animations
//
	if(PScene->CurrentAnimStack)
	{
		E3dAnimStack*  LAnimStack = PScene->CurrentAnimStack;

		E3dOperatorDeleteAnimations*  LOperatorAnimationsAndChannels = NULL;
		EIndex  LI = 0;const EIndex  LN = LAnimStack->Animations_Count;for(;LI < LN;LI++)
		{
			E3dAnimation*  LAnimation = LAnimStack->Animations[LI];

			if(LAnimation->Selection == E3dNodeSelNODE || _AnimationIndirectlySelectedForDeletion(LAnimation))
			{
				if(LOperatorAnimationsAndChannels == NULL)
				{
					LOperatorAnimationsAndChannels = (E3dOperatorDeleteAnimations*)EOperator_New(_OperatorDeleteAnimations_class);
					E_AssignToMember(LAnimStack, LOperatorAnimationsAndChannels,AnimStack);
				}

				EObjectAndIndex*  LEntry;ENew(LEntry);
				EList_PointerAddA(LOperatorAnimationsAndChannels->Animations, 8, LEntry);

				E_AssignNoChkNoFree((EObject*)LAnimation, LEntry->Object);
				LEntry->Index = LI;
			}
			else
			{
				if(EObject_IsSubClass(LAnimation, E3dKeyFrameAnimation))
				{
					E3dKeyFrameAnimation*  LKeyFrameAnimation = (E3dKeyFrameAnimation*)LAnimation;
					EIndex                 LCI = 0;const EIndex  LCN = LKeyFrameAnimation->Channels_Count;for(;LCI < LCN;LCI++)
					{
						E3dKeyFrameAnimationChannel*  LChannel = (E3dKeyFrameAnimationChannel*)LKeyFrameAnimation->Channels[LCI];
						E3dSpline*                    LSpline = LChannel->Curve;if(LSpline)
						{
							switch(LSpline->Selection)
							{
								case 0:
								break;

								default:
									{
										if(LOperatorAnimationsAndChannels == NULL)
										{
											LOperatorAnimationsAndChannels = (E3dOperatorDeleteAnimations*)EOperator_New(_OperatorDeleteAnimations_class);
											E_AssignToMember(LAnimStack, LOperatorAnimationsAndChannels,AnimStack);
										}

										E3dChannelRec*  LEntry;ENewClr(LEntry);
										EList_PointerAddA(LOperatorAnimationsAndChannels->Channels, 8, LEntry);

										E_Assign(LAnimation, LEntry->Animation);
										E_Assign((E3dAnimationChannel*)LChannel, LEntry->Channel);
										LEntry->ChannelIndex = LCI;
									}
								break;
							}
						}
					}
				}
			}
		}

		if(LOperatorAnimationsAndChannels) EList_ObjectAddA_s(LOps.Operators, 1, LOperatorAnimationsAndChannels);
	}

	if(LOps.Operators.Count)
	{
		E3dp_PickStop();

// Run Operators
//
		LChanged |= EOps_Run(&LOps, EOpDO);
		if(LOps.OpNodes.Items) E3dObjects_CallOutputs(ECbINTERNAL|ECbUSER,  &LOps.OpNodes,  NULL);

// Add OperatorList to undo stack
//
		E3dpUndoStack_AddOps(&LOps, E3dLng_Delete_selection);
		EOps_FreeContents(&LOps);

		E3dp_PickResume();
	}

	return(LChanged);
}



static XeWidget  _DeleteSelectionDialogW = NULL;
static XeWidget  _DeleteSelection_DeleteSelectedPointsW = NULL, _DeleteSelection_DeleteSelectedEdgesW = NULL, _DeleteSelection_DeleteUnusedVerticesW = NULL;


//========================================
// Settings dialog callback
//========================================
static void _DCB_DeleteSelectionClose(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	XeCallbackStruct*  LCBS = (XeCallbackStruct*)PCallData;
	if(LCBS->Reason == XeCR_PROTOCOLS) XeWidget_Unmap(_DeleteSelectionDialogW);
}


//========================================
//========================================
static void _DCB_DeleteSelection_DeleteSelectedPoints(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dp_Prefs.DeleteSelection_DeleteSelectedPoints = ((XeToggleButtonCallbackStruct*)PCallData)->Set;
}

static void _DCB_DeleteSelectionDeleteSelectedEdges(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dp_Prefs.DeleteSelection_DeleteSelectedEdges = ((XeToggleButtonCallbackStruct*)PCallData)->Set;
}

static void _DCB_DeleteSelection_DeleteUnusedVertices(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dp_Prefs.DeleteSelection_DeleteUnusedVertices = ((XeToggleButtonCallbackStruct*)PCallData)->Set;
}


//========================================
// Settings for Delete selection
//========================================
static void _MCB_Delete_Selection(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	if(PCallData && ((XePushButtonCallbackStruct*)PCallData)->Reason == XeCR_ACTIVATE_OPTIONS)
	{
		if(_DeleteSelectionDialogW == NULL)
		{
			XeWidget  LW;
			EArg      Args[256];int  ArgCnt;

			EXeStart;
			EXeSetArg(XeNAllowShellResize, TRUE);
			EXeSetArg(XeNButtonsStretch, FALSE);
			EXeSetArg(XeNWorkAreaType, XeMATRIX);
			EXeSetArg(XeNWMDecorations, XeWMDecorCLOSE|XeWMDecorBORDER|XeWMDecorTITLE);
			EXeSetArg(XeNTitle, "Delete selection options");EXeSetArg(XeNIconName, "Delete selection");
			XeWidget  LDialogW = Xe_TopDialogNew(Xe_App, XeDIALOG_TEMPLATE, "Delete selection",  Args, ArgCnt);if(LDialogW == NULL) return;
			XeAddCallback(LDialogW, XeNWMCallback, _DCB_DeleteSelectionClose, (EPointer)0);

			_DeleteSelectionDialogW = LDialogW;

			XeWidget  LTopMatrixW = XeDialog_GetChild(LDialogW, XeDIALOG_WORK_AREA);

			EXeStart;
			EXeSetArg(XeNOrientation, XeVERTICAL);
			EXeSetArg(XeNMarginSizeX, 4);
			Xe_SetValues(LTopMatrixW, Args, ArgCnt);

			EXeStart;
			EXeSetArg(XeNShadowThickness, 0);
			EXeSetArg(XeNHighlightThickness, 0);
			EXeSetArg(XeNResizableY, FALSE);
			EXeSetArg(XeNSet, E3dp_Prefs.DeleteSelection_DeleteSelectedPoints);
			XeToolTip8("Delete selected Mesh Vertices or Spline CVs (in addition to selected Polygons) when the 'Del' key is pressed.\nCtrl + Del will also do this.");
			_DeleteSelection_DeleteSelectedPointsW = LW = XeWidget_ManagedNew("Delete selected Points", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
			XeAddCallback(LW, XeNValueChangedCallback, _DCB_DeleteSelection_DeleteSelectedPoints, (EPointer)NULL);

			EXeStart;
			EXeSetArg(XeNShadowThickness, 0);
			EXeSetArg(XeNHighlightThickness, 0);
			EXeSetArg(XeNResizableY, FALSE);
			EXeSetArg(XeNSet, E3dp_Prefs.DeleteSelection_DeleteSelectedEdges);
			XeToolTip8("Delete selected Mesh Edges (in addition to selected Polygons) when the 'Del' key is pressed.\nShift + Del will also do this.");
			_DeleteSelection_DeleteSelectedEdgesW = LW = XeWidget_ManagedNew("Delete selected Edges", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
			XeAddCallback(LW, XeNValueChangedCallback, _DCB_DeleteSelectionDeleteSelectedEdges, (EPointer)NULL);

			EXeStart;
			EXeSetArg(XeNShadowThickness, 0);
			EXeSetArg(XeNHighlightThickness, 0);
			EXeSetArg(XeNResizableY, FALSE);
			EXeSetArg(XeNSet, E3dp_Prefs.DeleteSelection_DeleteUnusedVertices);
			XeToolTip8("When deleting selected Polygons in a Mesh, also delete the unused Vertices.");
			_DeleteSelection_DeleteUnusedVerticesW = LW = XeWidget_ManagedNew("Delete unused Vertices", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
			XeAddCallback(LW, XeNValueChangedCallback, _DCB_DeleteSelection_DeleteUnusedVertices, (EPointer)NULL);

			XeShell_Raise(LDialogW);
			XeShell_CenterToWidget(LDialogW, Eqx_TopShellW);
		}
		else
		{
			XeToggleButton_SetState(_DeleteSelection_DeleteSelectedPointsW, E3dp_Prefs.DeleteSelection_DeleteSelectedPoints, FALSE);
			XeToggleButton_SetState(_DeleteSelection_DeleteSelectedEdgesW,  E3dp_Prefs.DeleteSelection_DeleteSelectedEdges,  FALSE);
			XeToggleButton_SetState(_DeleteSelection_DeleteUnusedVerticesW, E3dp_Prefs.DeleteSelection_DeleteUnusedVertices, FALSE);
			XeShell_Raise(_DeleteSelectionDialogW);
		}
	}
	else
	{
		E3dScene*     LScene = E3d_Scene;
		EChangeFlags  LSceneChangeFlags = 0;

		const uint16_t  LInDeleteComponentsFlags = (EPointerInt)PClientData;
		uint16_t        LDeleteComponentsFlags = 0;

//printf("%s() %s:%d  %p  %d %d\n", __FUNCTION__, __FILE__, __LINE__, PW, E3dp_Prefs.DeleteSelection_DeleteSelectedPoints, E3dp_Prefs.DeleteSelection_DeleteSelectedEdges);fflush(stdout);
		if(PW)
		{
// Called from menu
//
			switch(LInDeleteComponentsFlags)
			{
				case E3dDisconnectSELECTED_POINTS:
					LDeleteComponentsFlags = E3dDisconnectSELECTED_POINTS;
				break;

				default:
					if(E3dp_Prefs.DeleteSelection_DeleteSelectedPoints)  LDeleteComponentsFlags |= E3dDeleteSELECTED_POINTS;
					if(E3dp_Prefs.DeleteSelection_DeleteSelectedEdges)   LDeleteComponentsFlags |= E3dDeleteSELECTED_EDGES;
					if(E3dp_Prefs.DeleteSelection_DeleteUnusedVertices)  LDeleteComponentsFlags |= E3dDeleteUNUSED_POINTS;
				break;
			}
		}
		else
		{
// Called from hot-key
//
			LDeleteComponentsFlags = (uint32_t)LInDeleteComponentsFlags & (E3dDeleteSELECTED_POINTS | E3dDeleteSELECTED_EDGES);
		}

		LSceneChangeFlags |= _DeleteSelection(LScene, LDeleteComponentsFlags);

		if(LSceneChangeFlags & E3dChgHIERARCHY)
		{
			E3dScene_ObjectsUpdateModelList(LScene);
			E3dScene_LayoutOnSchematics(E3d_Scene, TRUE);
		}

		E3dWindows_RedrawByChangeFlags(LScene, LSceneChangeFlags);

		if(LSceneChangeFlags & E3dChgHIERARCHY) E3dTransformPanel_Update(NULL, E3dDoALL, E3dDoALL, E3dDoALL,  E3dpDO_NOT_CHANGE,  E3dTrPanelALL);
	}
}


static void _KCB_Delete_Selection(const EKeyCode PKey, const unsigned int PModifiers, const int PKeyEventType, EPointer PClientData)
{
	E3dScene*  LScene = E3d_Scene;

	const uint16_t  LDeleteComponentsFlags = (EPointerInt)PClientData;

	const EChangeFlags  LSceneChangeFlags = _DeleteSelection(LScene, LDeleteComponentsFlags);

	if(LSceneChangeFlags & E3dChgHIERARCHY)
	{
		E3dScene_ObjectsUpdateModelList(LScene);
		E3dScene_LayoutOnSchematics(LScene, TRUE);
	}

	E3dWindows_RedrawByChangeFlags(LScene, LSceneChangeFlags);

	if(LSceneChangeFlags & E3dChgHIERARCHY) E3dTransformPanel_Update(NULL, E3dDoALL, E3dDoALL, E3dDoALL,  E3dpDO_NOT_CHANGE,  E3dTrPanelALL);
}


static int _Sc_DisconnectSelectedPoints(EPointer PParamValues)
{
	E3dScene*  LScene = E3d_Scene;

	const EChangeFlags  LSceneChangeFlags = _DeleteSelection(LScene, E3dDisconnectSELECTED_POINTS);

	if(LSceneChangeFlags) return(E_SUCCESS);
	else return(E_NOTHING_TO_DO);
}


//================================================
// Delete Animations from the selected objects
//================================================
static void _MCB_Delete_Animations_from_selection(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*      LScene = E3d_Scene;
	EChangeFlags   LChanged = 0;
	EIndex         LDeleted = 0;
	EOps           LOps;EOps_Init(&LOps);

	EIndex  LASI = 0;const EIndex  LASN = LScene->AnimStacks.Count;for(;LASI < LASN;LASI++)
	{
		const E3dAnimStack*  LAnimStack = LScene->AnimStacks.Items[LASI];const EIndex  LAN = LAnimStack->Animations_Count;

		EBool  LStackHasAnimToDelete = FALSE;

		{
			EIndex  LAI = 0;for(;LAI < LAN;LAI++)
			{
				E3dAnimation*  LAnimation = LAnimStack->Animations[LAI];

				if(LAnimation->Removable && LAnimation->Class == E3dKeyFrameAnimation_class)
				{
					EIndex  LCI = 0;const EIndex  LCN = LAnimation->Channels_Count;for(;LCI < LCN;LCI++)
					{
						E3dKeyFrameAnimationChannel*  LChannel = (E3dKeyFrameAnimationChannel*)(LAnimation->Channels[LCI]);

						if(LChannel->Curve && LChannel->TargetObject)
						{
							if(E3dAnimationChannel_TargetSelected((E3dAnimationChannel*)LChannel)) { LStackHasAnimToDelete = TRUE;goto Found; }
						}
					}
				}
			}
		}

Found:
		if(LStackHasAnimToDelete)
		{
			E3dOperatorDeleteAnimations*  LOperatorAnimationsAndChannels = (E3dOperatorDeleteAnimations*)EOperator_New(_OperatorDeleteAnimations_class);
			E_AssignToMember(LAnimStack, LOperatorAnimationsAndChannels,AnimStack);

			EIndex  LAI = 0;for(;LAI < LAN;LAI++)
			{
				E3dAnimation*  LAnimation = LAnimStack->Animations[LAI];
				EBool          LDelete = FALSE;

				if(LAnimation->Removable && LAnimation->Class == E3dKeyFrameAnimation_class)
				{
					EIndex  LCI = 0;const EIndex  LCN = LAnimation->Channels_Count;for(;LCI < LCN;LCI++)
					{
						E3dKeyFrameAnimationChannel*  LChannel = (E3dKeyFrameAnimationChannel*)(LAnimation->Channels[LCI]);

						if(LChannel->Curve && LChannel->TargetObject)
						{
							if(E3dAnimationChannel_TargetSelected((E3dAnimationChannel*)LChannel))
							{
								EObjectAndIndex*  LEntry;ENew(LEntry);
								EList_PointerAddA(LOperatorAnimationsAndChannels->Animations, 8, LEntry);

								E_AssignNoChkNoFree((EObject*)LAnimation, LEntry->Object);
								LEntry->Index = LAI;LDelete = TRUE;
							}
						}
					}
					if(LDelete) LDeleted++;
				}
			}


			EList_ObjectAddA_s(LOps.Operators, 1, LOperatorAnimationsAndChannels);
		}
	}

	if(LOps.Operators.Count)
	{
// Run Operators
//
		LChanged |= EOps_Run(&LOps, EOpDO);

// Add OperatorList to undo stack
//
		E3dpUndoStack_AddOps(&LOps, E3dLng_Delete_selection);
		EOps_FreeContents(&LOps);
	}

	E3dWindows_RedrawByChangeFlags(LScene, LChanged);

	if(LDeleted) E3dp_PrintMessage(ELogMESSAGE, 10000, "Animation(s) deleted: %d", LDeleted);
	else E3dp_PrintMessage(ELogMESSAGE, 10000, "No animations deleted");
}


//========================================
// Delete all Animations
//========================================
static void _MCB_Delete_All_animations(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*     LScene = E3d_Scene;
	EChangeFlags  LChanged = 0;
	EIndex        LDeleted = 0;
	EOps          LOps;EOps_Init(&LOps);

	EIndex  LASI = 0;const EIndex  LASN = LScene->AnimStacks.Count;for(;LASI < LASN;LASI++)
	{
		const E3dAnimStack*  LAnimStack = LScene->AnimStacks.Items[LASI];const EIndex  LAN = LAnimStack->Animations_Count;

		EBool  LStackHasAnimToDelete = FALSE;

		{
			EIndex  LAI = 0;for(;LAI < LAN;LAI++)
			{
				E3dAnimation*  LAnimation = LAnimStack->Animations[LAI];

				if(LAnimation->Removable && LAnimation->Class == E3dKeyFrameAnimation_class) { LStackHasAnimToDelete = TRUE;goto Found; }
			}
		}

Found:
		if(LStackHasAnimToDelete)
		{
			E3dOperatorDeleteAnimations*  LOperatorAnimationsAndChannels = (E3dOperatorDeleteAnimations*)EOperator_New(_OperatorDeleteAnimations_class);
			E_AssignToMember(LAnimStack, LOperatorAnimationsAndChannels,AnimStack);

			EIndex  LAI = 0;for(;LAI < LAN;LAI++)
			{
				E3dAnimation*  LAnimation = LAnimStack->Animations[LAI];

				if(LAnimation->Removable && LAnimation->Class == E3dKeyFrameAnimation_class)
				{
					EObjectAndIndex*  LEntry;ENew(LEntry);
					EList_PointerAddA(LOperatorAnimationsAndChannels->Animations, 8, LEntry);

					E_AssignNoChkNoFree((EObject*)LAnimation, LEntry->Object);
					LEntry->Index = LAI;

					LDeleted++;
				}
			}

			EList_ObjectAddA_s(LOps.Operators, 1, LOperatorAnimationsAndChannels);
		}
	}

	if(LOps.Operators.Count)
	{
// Run Operators
//
		LChanged |= EOps_Run(&LOps, EOpDO);

// Add OperatorList to undo stack
//
		E3dpUndoStack_AddOps(&LOps, E3dLng_Delete_selection);
		EOps_FreeContents(&LOps);
	}

	E3dWindows_RedrawByChangeFlags(LScene, LChanged);

	if(LDeleted) E3dp_PrintMessage(ELogMESSAGE, 10000, "Animation(s) deleted: %d", LDeleted);
	else E3dp_PrintMessage(ELogMESSAGE, 10000, "No animations deleted");
}


//========================================
// Delete scene or selection
//========================================
static void _MCB_Delete_All(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*  LScene = E3d_Scene;
	E3dScene_Delete_All(LScene);
}


//========================================
// Delete scene or selection
//========================================
static void _MCB_Delete_All_hierarchies(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*     LScene = E3d_Scene;
	EChangeFlags  LChanged = 0;

	if(LScene->RootNodes.Count > 0)
	{
		const EIndex  LRootNodes_Count = LScene->RootNodes.Count;
		E3dNode**     LRootNodes;ENewN(LRootNodes_Count, LRootNodes);

		memcpy(LRootNodes, LScene->RootNodes.Items, sizeof(E3dNode*) * LRootNodes_Count);

		EIndex  LI = 0;for(;LI < LRootNodes_Count;LI++)
		{
			E3dNode*  LRootNode = LRootNodes[LI];

			if(!E3d_IsActiveCameraNode(LRootNode))
			{
				if(LRootNode->Locks_Count == 0)
				{
					LChanged |= E3dNodeHrc_GetTransformChangeFlagsByObjects(LRootNode) | E3dChgHIERARCHY;
					E3dScene_NodeHrcRemove(LScene, LRootNode, &LChanged, TRUE);
				}
				else
				{
					E3dp_PrintMessage(ELogWARNING, 10000, "Node '%s' is locked (it's probably being edited), thus can not be deleted", LRootNode->Name);
				}
			}
		}

		EFree(LRootNodes);
		E3dScene_ObjectsUpdateModelList(LScene);
		E3dScene_LayoutOnSchematics(LScene, TRUE);
		E3dTransformPanel_Update(NULL, E3dDoALL, E3dDoALL, E3dDoALL,  E3dpDO_NOT_CHANGE,  E3dTrPanelALL);
		E3dWindows_RedrawByChangeFlags(LScene, LChanged);
	}
}


//========================================
// Menu callback
//========================================
static void _MCB_Delete_Empty_animations(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*      LScene = E3d_Scene;
	E3dAnimStack*  LAnimStack = LScene->CurrentAnimStack;if(LAnimStack == NULL) return;

	EIndex	LAI = 0;for(;LAI < LAnimStack->Animations_Count;)
	{
		E3dAnimation*  LAnim = LAnimStack->Animations[LAI];
		EBool          LAnimRemoved = FALSE;

		if(LAnim->Removable && LAnim->Class == E3dKeyFrameAnimation_class)
		{
			E3dKeyFrameAnimation*	LAnimation = (E3dKeyFrameAnimation*)LAnim;

// Delete empty Channels
//
			if(LAnimation->Channels_Count)
			{
				EIndex  LChC = 0;for(;LChC < LAnimation->Channels_Count;)
				{
					E3dKeyFrameAnimationChannel*  LChannel = (E3dKeyFrameAnimationChannel*)(LAnimation->Channels[LChC]);
					EBool                         LChannelRemoved = FALSE;


// Remove Channel if it has no Curve / KeyFrames
//
					if(LChannel->Curve)
					{
printf("%s() %s:%d  CVs_Count %lld\n", __FUNCTION__, __FILE__, __LINE__, (long long)LChannel->Curve->CVs.Count);fflush(stdout);
						if(LChannel->Curve->CVs.Count == 0)
						{
							E3dAnimation_RemoveChannel(LAnim, (E3dAnimationChannel*)LChannel);LChannelRemoved = TRUE;
						}
					}
					else
					{
						E3dAnimation_RemoveChannel(LAnim, (E3dAnimationChannel*)LChannel);LChannelRemoved = TRUE;
					}
					printf("%s() LChannelRemoved %d\n", __FUNCTION__, LChannelRemoved);fflush(stdout);
					if(!LChannelRemoved) LChC++;
				}
			}

// Now, delete LAnim, if it has no (remaining) Channels
//
			if(LAnimation->Channels_Count == 0)
			{
				E3dScene_AnimationRemove(LScene, LAnim, FALSE);LAnimRemoved = TRUE;
			}

		}

		if(!LAnimRemoved) LAI++;
	}
}


//========================================
// Entry point of the plugin
//========================================
int E_PluginInit(EPlugin* PPlugin)
{
	_EditInfoPlugin = PPlugin;

	E3dNode_EditFunc = _Node_Edit;
	E3dNode_IsEditedFunc = _Node_IsEdited;

	E3dPointSet_class->GUI = _PointSet_GUI;
	E3dMesh_class->GUI = _Mesh_GUI;

	E3dPolyGroup_IsEditedFunc = _PolyGroup_IsEdited;
	E3dPolyGroup_UpdateDialogFunc = _PolyGroupPanel_Update;

	E3dObjectEdit_Init();

	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Info", "Edit selection", '\0', "~Shift ~Ctrl <Key>E", "E", FALSE, NULL, _MCB_Edit, (EPointer)CR_EDIT_SELECTION);
	E_KeySetCallback("Edit selection", 'e', 0, EKeyPRESS, _KCB_Edit, (EPointer)CR_EDIT_SELECTION);

	{
		static EOperatorClass  LClass[] =
		{
			{
				.Name=        "Delete selection",
				.StructSize=  sizeof(E3dOperatorDelete),
				.Init=        _DeleteSelection_Init,
				.Free=        _DeleteSelection_Free,
				.Func=        _DeleteSelection_Func
			}
		};
		_OperatorDelete_class = EOperator_ClassRegister(LClass);
	}

	{
		static EOperatorClass  LClass[] =
		{
			{
				.Name=        "Delete animations and channels",
				.StructSize=  sizeof(E3dOperatorDeleteAnimations),
				.Init=        _DeleteAnimations_Init,
				.Free=        _DeleteAnimations_Free,
				.Func=        _DeleteAnimations_Func
			}
		};
		_OperatorDeleteAnimations_class = EOperator_ClassRegister(LClass);
	}

// Operator for deleting Objects, or modifying a Face:
// Face
// - Delete hole
//
	{
		static EOperatorClass  LClass[] =
		{
			{
				.Name=        "Edit Objects",
				.StructSize=  sizeof(E3dOperatorEditObjects),
				.Init=        _EditObjects_Init,
				.Free=        _EditObjects_Free,
				.Func=        _EditObjects_Func
			}
		};
		E3dOperatorEditObjects_class = EOperator_ClassRegister(LClass);
	}


// Operator for modifying a Mesh
// -Add/delete Edges
// -Add/delete Polygons
//
	{
		EOperatorClass  LClass[] =
		{
			{
				.Name=        "Edit Mesh",
				.StructSize=  sizeof(E3dOperatorEditMesh),
				.Init=        _EditMesh_Init,
				.Free=        _EditMesh_Free,
				.Func=        _EditMesh_Func
			}
		};
		E3dOperatorEditMesh_class = EOperator_ClassRegister(LClass);
	}

	{
		EOperatorClass  LClass[] =
		{
			{
				.Name=        "Delete Vertices",
				.StructSize=  sizeof(E3dOperatorAddDeleteVertices),
				.Init=        _AddDeleteVertices_Init,
				.Free=        _AddDeleteVertices_Free,
				.Func=        _DeleteVertices_Func
			}
		};
		E3dOperatorDeleteVertices_class = EOperator_ClassRegister(LClass);
	}

	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Delete", "Delete selection", '\0', "~Shift ~Ctrl <Key>Delete", "Del", TRUE, NULL, _MCB_Delete_Selection, (EPointer)0);
	const EKeyModifiers  LCtrlMask = Xe_CommandKeyForCtrl ? E_CommandModifierMask : EControlMask;

	E_KeySetCallback("Delete selection", EKeyDELETE,    0,                    EKeyPRESS, _KCB_Delete_Selection,
	 (EPointer)(E3dDeleteSELECTED_NODES|E3dDeleteSELECTED_OBJECTS|E3dDeleteSELECTED_POLYGROUPS|E3dDeleteSELECTED_POLYGONS|E3dDeleteSELECTED_EDGES|E3dDeleteSELECTED_POINTS));

	E_KeySetCallback("Delete selection", EKeyDELETE,    LCtrlMask,            EKeyPRESS, _KCB_Delete_Selection, (EPointer)E3dDeleteSELECTED_POINTS);
	E_KeySetCallback("Delete selection", EKeyDELETE,    EShiftMask,           EKeyPRESS, _KCB_Delete_Selection, (EPointer)E3dDeleteSELECTED_EDGES);
	E_KeySetCallback("Delete selection", EKeyDELETE,    LCtrlMask|EShiftMask, EKeyPRESS, _KCB_Delete_Selection, (EPointer)(E3dDeleteSELECTED_POINTS|E3dDeleteSELECTED_EDGES));

	E_KeySetCallback("Delete selection", EKeyBACKSPACE, 0,                    EKeyPRESS, _KCB_Delete_Selection,
	 (EPointer)(E3dDeleteSELECTED_NODES|E3dDeleteSELECTED_OBJECTS|E3dDeleteSELECTED_POLYGROUPS|E3dDeleteSELECTED_POLYGONS|E3dDeleteSELECTED_EDGES|E3dDeleteSELECTED_POINTS));

	E_KeySetCallback("Delete selection", EKeyBACKSPACE, LCtrlMask,            EKeyPRESS, _KCB_Delete_Selection, (EPointer)E3dDeleteSELECTED_POINTS);
	E_KeySetCallback("Delete selection", EKeyBACKSPACE, EShiftMask,           EKeyPRESS, _KCB_Delete_Selection, (EPointer)E3dDeleteSELECTED_EDGES);
	E_KeySetCallback("Delete selection", EKeyBACKSPACE, LCtrlMask|EShiftMask, EKeyPRESS, _KCB_Delete_Selection, (EPointer)(E3dDeleteSELECTED_POINTS|E3dDeleteSELECTED_EDGES));

	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Delete", "All hierarchies",               '\0', NULL, NULL, FALSE, NULL, _MCB_Delete_All_hierarchies,           (EPointer)0);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Delete", "All",                           '\0', NULL, NULL, FALSE, NULL, _MCB_Delete_All,                       (EPointer)0);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Delete", "Animations from the selection", '\0', NULL, NULL, FALSE, NULL, _MCB_Delete_Animations_from_selection, (EPointer)0);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Delete", "Empty animations",              '\0', NULL, NULL, FALSE, NULL, _MCB_Delete_Empty_animations,          (EPointer)0);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Delete", "All animations",                '\0', NULL, NULL, FALSE, NULL, _MCB_Delete_All_animations,            (EPointer)0);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Delete", "Disconnect Vertices",           '\0', NULL, NULL, FALSE, NULL, _MCB_Delete_Selection, 
	 (EPointer)E3dDisconnectSELECTED_POINTS);

	E3dScriptFunction_Register("Disconnect_SelectedPoints", "Delete selected Edges", "Delete selected Edges on selected Meshes.",  _Sc_DisconnectSelectedPoints, NULL,
	 _ScriptFunctions + _ScriptFunctions_Count++);

	return(E_SUCCESS);
}


//========================================
// Exit method of the plugin
//========================================
int E_PluginExit(const EBool PExiting)
{
	if(!PExiting || Xe_GUIFreeOnExit)
	{
		{ int  LI = 0;for(;LI < _MenuButtons_Count;LI++) { XeItemFree(_MenuButtons[LI]); } _MenuButtons_Count = 0; }

// XeFIXME
//		XeWidget_Free(_NodeDialogW);  // Don't free top-level shells
	}

	{ int  LI = 0;for(;LI < _ScriptFunctions_Count;LI++) { E3dScriptFunction_Remove(_ScriptFunctions[LI]); } _ScriptFunctions_Count = 0; }

	if(_NodePanel)
	{
		EList_PointerRemoveA_s(E3d_NodePanels, _NodePanel);
		E3dNodePanel_Free(_NodePanel);_NodePanel = NULL;
	}

	E3dNode_EditFunc = NULL;
	E3dNode_IsEditedFunc = NULL;

	E3dPointSet_class->GUI = NULL;
	E3dMesh_class->GUI = NULL;

	E3dPolyGroup_IsEditedFunc = NULL;
	E3dPolyGroup_UpdateDialogFunc = NULL;

	E3dObjectEdit_Cleanup();

	E_KeyRemoveCallback('e', 0, EKeyPRESS, _KCB_Edit, (EPointer)CR_EDIT_SELECTION);

	const EKeyModifiers  LCtrlMask = Xe_CommandKeyForCtrl ? E_CommandModifierMask : EControlMask;
	E_KeyRemoveCallback(EKeyDELETE,    0,                    EKeyPRESS, _KCB_Delete_Selection,
	 (EPointer)(E3dDeleteSELECTED_NODES|E3dDeleteSELECTED_OBJECTS|E3dDeleteSELECTED_POLYGROUPS|E3dDeleteSELECTED_POLYGONS|E3dDeleteSELECTED_EDGES|E3dDeleteSELECTED_POINTS));

	E_KeyRemoveCallback(EKeyDELETE,    LCtrlMask,            EKeyPRESS, _KCB_Delete_Selection, (EPointer)E3dDeleteSELECTED_POINTS);
	E_KeyRemoveCallback(EKeyDELETE,    EShiftMask,           EKeyPRESS, _KCB_Delete_Selection, (EPointer)E3dDeleteSELECTED_EDGES);
	E_KeyRemoveCallback(EKeyDELETE,    LCtrlMask|EShiftMask, EKeyPRESS, _KCB_Delete_Selection, (EPointer)(E3dDeleteSELECTED_POINTS|E3dDeleteSELECTED_EDGES));

	E_KeyRemoveCallback(EKeyBACKSPACE, 0,                    EKeyPRESS, _KCB_Delete_Selection,
	 (EPointer)(E3dDeleteSELECTED_NODES|E3dDeleteSELECTED_OBJECTS|E3dDeleteSELECTED_POLYGROUPS|E3dDeleteSELECTED_POLYGONS|E3dDeleteSELECTED_EDGES|E3dDeleteSELECTED_POINTS));

	E_KeyRemoveCallback(EKeyBACKSPACE, LCtrlMask,            EKeyPRESS, _KCB_Delete_Selection, (EPointer)E3dDeleteSELECTED_POINTS);
	E_KeyRemoveCallback(EKeyBACKSPACE, EShiftMask,           EKeyPRESS, _KCB_Delete_Selection, (EPointer)E3dDeleteSELECTED_EDGES);
	E_KeyRemoveCallback(EKeyBACKSPACE, LCtrlMask|EShiftMask, EKeyPRESS, _KCB_Delete_Selection, (EPointer)(E3dDeleteSELECTED_POINTS|E3dDeleteSELECTED_EDGES));

	if(_OperatorDelete_class) { EOperator_ClassDeactivate(_OperatorDelete_class);_OperatorDelete_class = NULL; }
	if(_OperatorDeleteAnimations_class) { EOperator_ClassDeactivate(_OperatorDeleteAnimations_class);_OperatorDeleteAnimations_class = NULL; }

	if(E3dOperatorEditObjects_class) { EOperator_ClassDeactivate(E3dOperatorEditObjects_class);E3dOperatorEditObjects_class = NULL; }
	if(E3dOperatorEditMesh_class) { EOperator_ClassDeactivate(E3dOperatorEditMesh_class);E3dOperatorEditMesh_class = NULL; }
	if(E3dOperatorDeleteVertices_class) { EOperator_ClassDeactivate(E3dOperatorDeleteVertices_class);E3dOperatorDeleteVertices_class = NULL; }
	return(E_SUCCESS);
}
