//================================================================
// EQUINOX-3D plugin
//
// Interactive Extrude tool
//
// AUTHOR:  Gabor Nagy
// DATE:    2026-Apr-16 02:14:07
//
// Copyright (C) 1996-2026 By Gabor Nagy. All rights reserved.
//================================================================
#include <assert.h>
#include <math.h>
#include <stdio.h>

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

#include <Xe/Label.h>
#include <Xe/Matrix.h>
#include <Xe/Scale.h>
#include <Xe/PushButton.h>
#include <Xe/Separator.h>
#include <Xe/ToggleButton.h>

#include <E3D/Math.h>
#include <E3D/Matrix.h>
#include <E3D/PolygonEditing.h>
#include <E3D/Select.h>

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


static EPlugin*	_Plugin = NULL;

char*  E_PluginInfo = "Extrude a Spline or Face along a straight line, or along a Spline.";

static E3dObjectComponentClass*  _ObjectComponent_class = NULL;

XeDWidget  _MenuButtons[2];int  _MenuButtons_Count = 0;
XeDWidget  _ToolBarButtons[2];int  _ToolBarButtons_Count = 0;


#include "ExtrudeSpline.h"

//----------------------------------------
// Extrude
//----------------------------------------
EpIconDef3I(static, Extrude);

static const char  _NewObjectAddingMode = E3dNEW_INSTANCE;
//static const char  _NewObjectAddingMode = E3dNEW_MODEL;

typedef struct
{
	E3dExtrudeSplineComponentCore;

// Parameters
//
	E3dCoordinate  Depth, Offset;
	E3dCoord2      Scale;     // Gradually scale size along extrusion
	E3dCoord3      Axis;
	short          ProfileFollowing;
	EBool          Profile3D, CenterAlongPath, ScaleBevels;
} E3dInfo;



#define ROffset(field)   (void*)(EPtrOffset(E3dInfo, field))
#define ROffsetI(field)  EPtrOffset(E3dInfo, field)
static EResource  _ExtrudeResources[]=
{
	{ "ProfileGeometries",            EResOBJECT|EResDYNAMIC,  ROffset(ProfileGeometries),    .Ext.CountOffset= ROffsetI(ProfileGeometries_Count) },

	{ "ProfileFollowing",             EResINT16,               ROffset(ProfileFollowing) },
	{ "Profile3D",                    EResBOOLEAN,             ROffset(Profile3D) },
	{ "PathSteps",                    EResINT32,               ROffset(PathSteps) },

	{ "AxX",                          E3dCOORDINATE_TYPE,      ROffset(Axis.X) },
	{ "AxY",                          E3dCOORDINATE_TYPE,      ROffset(Axis.Y) },
	{ "AxZ",                          E3dCOORDINATE_TYPE,      ROffset(Axis.Z) },
	{ "Depth",                        E3dCOORDINATE_TYPE,      ROffset(Depth) },
	{ "Offset",                       E3dCOORDINATE_TYPE,      ROffset(Offset) },
	{ "Twist",                        E3dCOORDINATE_TYPE,      ROffset(Twist) },
	{ "Twist bevels",                 EResBOOLEAN,             ROffset(TwistBevels) },
	{ "Scale.X",                      E3dCOORDINATE_TYPE,      ROffset(Scale.X) },
	{ "Scale.Y",                      E3dCOORDINATE_TYPE,      ROffset(Scale.Y) },
	{ "Scale bevels",                 EResBOOLEAN,             ROffset(ScaleBevels) },

	{ "StartBevel.Steps",             EResINT32,               ROffset(StartBevel.NumOfRoundingSteps) },
	{ "StartBevel.Radius",            E3dCOORDINATE_TYPE,      ROffset(StartBevel.Radius) },
	{ "StartBevel.Invert",            EResBOOLEAN,             ROffset(StartBevel.Invert) },
	{ "StartBevel.AddStartSmoother",  EResBOOLEAN,             ROffset(StartBevel.AddStartSmoother) },
	{ "StartBevel.AddEndSmoother",    EResBOOLEAN,             ROffset(StartBevel.AddEndSmoother) },

	{ "EndBevel.Steps",               EResINT32,               ROffset(EndBevel.NumOfRoundingSteps) },
	{ "EndBevel.Radius",              E3dCOORDINATE_TYPE,      ROffset(EndBevel.Radius) },
	{ "EndBevel.Invert",              EResBOOLEAN,             ROffset(EndBevel.Invert) },
	{ "EndBevel.AddStartSmoother",    EResBOOLEAN,             ROffset(EndBevel.AddStartSmoother) },
	{ "EndBevel.AddEndSmoother",      EResBOOLEAN,             ROffset(EndBevel.AddEndSmoother) },

	{ "SmoothingAngle",               EResFLOAT32,             ROffset(SmoothingAngle) },
	{ "Center_along_path",            EResBOOLEAN,             ROffset(CenterAlongPath) },
	{ "Triangles on sides",           EResBOOLEAN,             ROffset(TrianglesOnSides) },
	{ "Triangles on caps",            EResBOOLEAN,             ROffset(TrianglesOnCaps) },
	{ "Create_UVs",                   EResBOOLEAN,             ROffset(CreateUVs) },
	{ "Create_tangents",              EResBOOLEAN,             ROffset(CreateTangents) },
	{ "Cap_start",                    EResBOOLEAN,             ROffset(CloseStart) },
	{ "Cap_end",                      EResBOOLEAN,             ROffset(CloseEnd) },
	{ "Sides",                        EResBOOLEAN,             ROffset(CloseSides) },
	{ "Include bevels in length",     EResBOOLEAN,             ROffset(IncludeBevelsInLength) },
};
#undef ROffset
#undef ROffsetI


//----------------------------------------
// Extrude along Spline
//----------------------------------------


enum
{
	CR_SET_CLOSE_FRONT = XeCR_CUSTOM0,
	CR_SET_CLOSE_BACK,
	CR_TRIANGLES,
	CR_SET_DEPTH,
	CR_SET_PATH_STEPS,
	CR_CHANGE_SMOOTHING_ANGLE
};


static E3dCoordinate* _GetUs(const E3dExtrudeInfoCommon* PInfo, const E3dSpline* PPathSpline, const E3dCoordinate PPathLength, const int PPathSteps, EIndex* PNumOfUsPtr, E3dCoordinate* PTotalLengthRet)
{
	E3dCoordinate     LPathLength = PPathLength;
	int               LPathSteps  = PPathSteps;
	E3dCoordinate*    LUs;
	E3dCoordinate*    LEndBevelUs = NULL;
	const E3dSpline*  LStartBevelProfile = PInfo->StartBevel.Profile;
	const E3dSpline*  LEndBevelProfile = PInfo->EndBevel.Profile;
	E3dCoordinate     LTotalULength, LStartBevelProfileLength = 0.0, LEndBevelProfileLength = 0.0;
	EIndex            LNumOfUs = 0, LOpenPath = TRUE;

	if(PPathSpline && PPathSpline->SNumOfLinearSegments > 0)
	{
		if(PPathSpline->Closed)
		{
			LNumOfUs = PPathSpline->SNumOfLinearSegments + 1;  // E3dSpline_GetDrawingArcLengthParameters stores U for the end point as well
			LOpenPath = FALSE;
		}
		else
		{
			if(LStartBevelProfile && LStartBevelProfile->SNumOfLinearSegments) LNumOfUs += LStartBevelProfile->SNumOfLinearSegments;
			LNumOfUs += PPathSpline->SNumOfLinearSegments + 1;
			if(LEndBevelProfile && LEndBevelProfile->SNumOfLinearSegments) LNumOfUs += LEndBevelProfile->SNumOfLinearSegments;
			LPathSteps = PPathSpline->SNumOfLinearSegments;
		}
	}
	else
	{
		EIndex  LSideSteps = 0;

		if(LPathSteps) LSideSteps = LPathSteps + 1;
		else LSideSteps = 0;

		if(LStartBevelProfile && LStartBevelProfile->SNumOfLinearSegments) LNumOfUs += LStartBevelProfile->SNumOfLinearSegments;
		if(LSideSteps) LNumOfUs += LSideSteps;
		if(LEndBevelProfile && LEndBevelProfile->SNumOfLinearSegments) LNumOfUs += LEndBevelProfile->SNumOfLinearSegments;
	}

	ENewN(LNumOfUs, LUs);
	*PNumOfUsPtr = LNumOfUs;

// Use arc-length parameterization for the whole StartBevel-Sides-EndBevel curve, as if it was a single Spline
//
// First, arc-length parameterize the bevel Splines and get their
// tesseleted length (sum of the lengths of the linear segments)
//
	if(LStartBevelProfile && LStartBevelProfile->SNumOfLinearSegments && LOpenPath)
	{
		LStartBevelProfileLength = E3dSpline_GetDrawingArcLengthParameters(LStartBevelProfile, 0.0, FALSE,  LUs, NULL);
	}

	if(LEndBevelProfile && LEndBevelProfile->SNumOfLinearSegments && LOpenPath)
	{
		ENewN(LEndBevelProfile->SNumOfLinearSegments + 1, LEndBevelUs);E3dCoordinate*	LUPtr = LEndBevelUs;
		LEndBevelProfileLength = E3dSpline_GetDrawingArcLengthParameters(LEndBevelProfile, 0.0, TRUE,  LUPtr, NULL);
	}


// If a PPathSpline is given, do arc-length parameterization for U
//
	{
		E3dCoordinate*  LUPtr;

		if(LStartBevelProfile && LStartBevelProfile->SNumOfLinearSegments && LOpenPath) LUPtr = LUs + LStartBevelProfile->SNumOfLinearSegments;
		else LUPtr = LUs;

		if(PPathSpline)
		{
			LPathLength = E3dSpline_GetDrawingArcLengthParameters(PPathSpline, 0.0, FALSE,  LUPtr, NULL);
		}

		if(PPathSpline && LPathLength > 0.0)
		{
			E3dCoordinate  LUMult, LUOffset;
			const EIndex   LUN = PPathSpline->SNumOfLinearSegments + 1;


			LTotalULength = LStartBevelProfileLength + LPathLength + LEndBevelProfileLength;
			if(LTotalULength < 0.00001) LTotalULength = 0.00001;

			LUMult = LPathLength / LTotalULength;LUOffset = LStartBevelProfileLength / LTotalULength;
			EIndex  LUC = 0;for(;LUC < LUN;LUC++, LUPtr++)
			{
				*LUPtr = *LUPtr * LUMult + LUOffset;
			}
		}
		else
		{
			E3dCoordinate  LU, LUStep;
			const EIndex   LUN = LPathSteps + 1;

			LTotalULength = LStartBevelProfileLength + LPathLength + LEndBevelProfileLength;
			if(LTotalULength < 0.00001) LTotalULength = 0.00001;
			LUStep = LPathLength / LTotalULength / (E3dCoordinate)LPathSteps;

			EIndex  LUC = 0;for(LU = LStartBevelProfileLength / LTotalULength;LUC < LUN;LUC++, LU += LUStep, LUPtr++)
			{
				*LUPtr = LU;
			}
		}
	}


	{
		E3dCoordinate*  LUPtr = LUs;

		if(LStartBevelProfile && LStartBevelProfile->SNumOfLinearSegments && LOpenPath)
		{
		 E3dCoordinate  LUMult = LStartBevelProfileLength / LTotalULength;
		 EIndex  LUC = 0;const EIndex  LUN = LStartBevelProfile->SNumOfLinearSegments;for(;LUC < LUN;LUC++) { *LUPtr = *LUPtr * LUMult;LUPtr++; }
		}

		if(LEndBevelProfile && LEndBevelProfile->SNumOfLinearSegments && LOpenPath)
		{
			E3dCoordinate   LUMult = LEndBevelProfileLength / LTotalULength, LUOffset = (LStartBevelProfileLength + LPathLength) / LTotalULength;
			E3dCoordinate*  LSUPtr;
			EIndex          LUN = LEndBevelProfile->SNumOfLinearSegments + 1;

			LUPtr += LPathSteps;

			LSUPtr = LEndBevelUs + LEndBevelProfile->SNumOfLinearSegments;
			EIndex  LUC = 0;for(;LUC < LUN;LUC++, LSUPtr--, LUPtr++) *LUPtr = *LSUPtr * LUMult + LUOffset;
		}
	}

	EFreeChk(LEndBevelUs);

	if(PTotalLengthRet) *PTotalLengthRet = LTotalULength;

	return(LUs);
}


//========================================
// Set Vertex coordinates for Extrude
//========================================
static int _SetVertices(E3dGeometry* PProfileGeometry,  E3dVertex* PVertices, const E3dVertexSize PVertexSize,  E3dInfo* PInfo, const EIndex PNumOfProfilePoints)
{
	E3dSpline*  LProfileSpline = NULL;
	E3dFace*    LProfileFace = NULL;
	E3dMatrix   LMatrix;
	E3dCoord3*  LPoints;
	EBool       LUsingDefaultAxis;

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

	if(EObject_IsSubClass(PProfileGeometry, E3dSpline))
	{
		LProfileSpline = (E3dSpline*)PProfileGeometry;
	}
	else if(EObject_IsSubClass(PProfileGeometry, E3dFace))
	{
		LProfileFace = (E3dFace*)PProfileGeometry;
	}
	else return(0);


	E3dCoord3  LAxis = PInfo->Axis;
	if(LAxis.X == 0.0 && LAxis.Y == 0.0 && LAxis.Z == 0.0) LAxis.Y = 1.0;
	E3d_Normalize3_v(LAxis);


	ENewN(PNumOfProfilePoints, LPoints);

// If the Extrude is not along the Z axis, transform the profile coordinates accordingly
//
	if(LAxis.X == 0.0 && LAxis.Y == 0.0 && LAxis.Z == 1.0)
	{
		E3dCoord3*  LPoint = LPoints;

		if(LProfileSpline)
		{
			E3dCoordinate*  LLinearSegment = LProfileSpline->SLinearSegments;if(LLinearSegment)
			{
				const int  LDimensions = LProfileSpline->Dimensions;
				EIndex  LVI = 0;for(;LVI < PNumOfProfilePoints;LVI++, LLinearSegment += LDimensions, LPoint++)
				{
					LPoint->X = LLinearSegment[E3dX];
					LPoint->Y = LLinearSegment[E3dY];
					LPoint->Z = LLinearSegment[E3dZ];
				}
			}
			else if(LProfileSpline->CVs.Count > 0)
			{
				const E3dSplineCV*  LCV = LProfileSpline->CVs.Items;
				*LPoint = LCV->Position;
			}
		}
		else if(LProfileFace)
		{
			if(LProfileFace->Vertices.Count > 0)
			{
				EIndex  LVI = 0;const E3dVertex*  LProfileVertex = LProfileFace->Vertices.Items;const E3dVertexSize  LProfileVertexSize = LProfileFace->VertexSize;for(;LVI < PNumOfProfilePoints;LVI++, EStruct_Inc(LProfileVertex, LProfileVertexSize), LPoint++)
				{
					*LPoint = LProfileVertex->Position;
				}
			}
			else
			{
printf("%s() %s:%d  %sIMPLEMENT ME%s  LProfileFace has no Vertices!\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, ECodeNormal);fflush(stdout);
			}
		}

		LUsingDefaultAxis = TRUE;
	}
	else
	{
		E3dCoord3*  LPoint = LPoints;

// Rotate contour into the proper coordinate system for extrusion
//
		E3dMatrix_LoadIdentity(LMatrix);  // E3d_VectorsGetRotMatrix only fills out the top-left 3x3 elements
		const E3dCoord3  LZAxis = E3dCoord3_s(0.0, 0.0, 1.0);
		E3d_VectorsGetRotMatrix(&LAxis,  &LZAxis,  LMatrix);

		if(LProfileSpline)
		{
			const int  LDimensions = LProfileSpline->Dimensions;
			EIndex  LVCnt = 0;const E3dCoordinate*  LLinearSegment = LProfileSpline->SLinearSegments;for(;LVCnt < PNumOfProfilePoints;LVCnt++, LLinearSegment += LDimensions, LPoint++)
			{
				EMatrix_Transform3x4_sp(LMatrix,  LLinearSegment[E3dX], LLinearSegment[E3dY], LLinearSegment[E3dZ],  LPoint);
			}
		}
		else if(LProfileFace)
		{
			EIndex  LVCnt = 0;const E3dVertex*  LProfileVertex = LProfileFace->Vertices.Items;const E3dVertexSize  LProfileVertexSize = LProfileFace->VertexSize;for(;LVCnt < PNumOfProfilePoints;LVCnt++, EStruct_Inc(LProfileVertex, LProfileVertexSize), LPoint++)
			{
				EMatrix_Transform3x4_vp(LMatrix, LProfileVertex->Position,  LPoint);
			}
		}
		LUsingDefaultAxis = FALSE;
	}


	EIndex  LNumOfPathPoints = PInfo->PathSteps + 1;

	{
		E3dCoordinate  LT;
		EIndex         LUN = LNumOfPathPoints;
//	E3dCoordinate  LStartBevelWidth = _BevelWidth(&PInfo->StartBevel), LEndBevelWidth = _BevelWidth(&PInfo->EndBevel);



		if(PInfo->CenterAlongPath)
		{
			const E3dCoordinate  LTotalDepth = E3d_Abs(PInfo->Depth);
			LT = -LTotalDepth * 0.5;
		}
		else LT = PInfo->Offset;


		E3dCoordinate  LSideLength = E3d_Abs(PInfo->Depth);

		if(PInfo->StartBevel.Type)
		{
			if(PInfo->IncludeBevelsInLength)
			{
				LT += PInfo->StartBevel.Radius;
				LSideLength -= PInfo->StartBevel.Radius;
			}
		}


		E3dVertex*  LVertex = PVertices;

// Starting bevel
//
		if(PInfo->StartBevel.Type)
		{
			E3dBevel*      LBevel = &PInfo->StartBevel;
			E3dCoord3      LSplineNormal = E3dCoord3_s(0.0, 0.0, 1.0);
			E3dCoord3*     LPoint = LPoints;
			E3dCoordinate  LNormalDir;
			int            LDir;

			if(LBevel->Invert) { LNormalDir = 1.0;LDir = -1; }
			else { LNormalDir = 1.0;LDir = 1; }

			if(LProfileSpline)
			{
				E3dExtrude_Bevel(LPoint, PNumOfProfilePoints, LProfileSpline->Closed, LSplineNormal, LBevel,  LVertex, PVertexSize,  PNumOfProfilePoints, LT,  LNormalDir, LDir);

if(0)
{
	const int  LDimensions = LBevel->Profile->Dimensions;
	printf("%s() %s:%d  PNumOfProfilePoints %lld  %lld  Dim %d\n", __FUNCTION__, __FILE__, __LINE__, (long long)PNumOfProfilePoints, (long long)LBevel->Profile->SNumOfLinearSegments, LDimensions);fflush(stdout);
	EIndex  LI = 0;const EIndex  LN = LBevel->Profile->SNumOfLinearSegments;E3dCoordinate*  LBevelV =LBevel->Profile->SLinearSegments ;for(;LI < LN;LI++, LBevelV += LDimensions)
	{
		printf(" %f %f %f\n", XYZ_a(LBevelV));fflush(stdout);
	}
}
				LVertex = EStruct_ByIndex(LVertex, PVertexSize,  PNumOfProfilePoints * LBevel->Profile->SNumOfLinearSegments);
			}
			else if(LProfileFace)
			{
printf("%s() %s:%d  PNumOfProfilePoints %d  %d\n", __FUNCTION__, __FILE__, __LINE__, (int)PNumOfProfilePoints, (int)LBevel->Profile->SNumOfLinearSegments);fflush(stdout);
				{
					E3dSpline*    LProfileContour = LProfileFace->Exterior;
					const EIndex  LVN = LProfileContour->Closed ? LProfileContour->SNumOfLinearSegments : LProfileContour->SNumOfLinearSegments + 1;

//					const EIndex  LVN = LProfileFace->Exterior->SNumOfLinearSegments;

					E3dExtrude_Bevel(LPoint, LVN, TRUE, LSplineNormal, LBevel,  LVertex, PVertexSize,  PNumOfProfilePoints, LT,  LNormalDir, LDir);
					LPoint += LVN;LVertex = EStruct_ByIndex(LVertex, PVertexSize,  LVN);
				}

				EIndex  LHI = 0;const EIndex  LHN = LProfileFace->Holes_Count;for(;LHI < LHN;LHI++)
				{
					E3dSpline*    LProfileContour = LProfileFace->Holes[LHI];
					const EIndex  LVN = LProfileContour->Closed ? LProfileContour->SNumOfLinearSegments : LProfileContour->SNumOfLinearSegments + 1;

					E3dExtrude_Bevel(LPoint, LVN, TRUE, LSplineNormal, LBevel,  LVertex, PVertexSize,  PNumOfProfilePoints, LT,  LNormalDir, LDir);
					LPoint += LVN;LVertex = EStruct_ByIndex(LVertex, PVertexSize,  LVN);
				}
				LVertex = EStruct_ByIndex(PVertices, PVertexSize, LBevel->Profile->SNumOfLinearSegments * PNumOfProfilePoints);
			}

			LNumOfPathPoints += LBevel->Profile->SNumOfLinearSegments;
		}



// Sides
//
		if(PInfo->EndBevel.Type)
		{
			if(PInfo->IncludeBevelsInLength)
			{
				LSideLength -= PInfo->EndBevel.Radius;
			}
		}


		const E3dCoordinate  LTStep = 1.0 / (E3dCoordinate)(PInfo->PathSteps) * LSideLength;
		EIndex  LUCnt = 0;for(;LUCnt < LUN;LUCnt++)
		{
			E3dCoord3*  LPoint = LPoints;

			if(PInfo->Profile3D)
			{
				EIndex  LVCnt = 0;for(;LVCnt < PNumOfProfilePoints;LVCnt++, LPoint++, EStruct_Inc(LVertex, PVertexSize))
				{
					LVertex->Position.X = LPoint->X;LVertex->Position.Y = LPoint->Y;LVertex->Position.Z = LPoint->Z + LT;
				}
			}
			else
			{
				EIndex  LVCnt = 0;for(;LVCnt < PNumOfProfilePoints;LVCnt++, LPoint++, EStruct_Inc(LVertex, PVertexSize))
				{
					LVertex->Position.X = LPoint->X;LVertex->Position.Y = LPoint->Y;LVertex->Position.Z = LT;
				}
			}
			if(LUCnt < LUN - 1) LT += LTStep;
		}



// End bevel
//
		if(PInfo->EndBevel.Type)
		{
			E3dBevel*         LBevel = &PInfo->EndBevel;
			const E3dCoord3*  LPoint = LPoints;
			E3dCoord3         LSplineInvNormal = E3dCoord3_s(0.0, 0.0, -1.0);
			E3dCoordinate     LNormalDir;
			int               LDir;

			if(LBevel->Invert) { LNormalDir = -1.0;LDir = 1; }
			else { LNormalDir = -1.0;LDir = -1; }

			if(LProfileSpline)
			{
				E3dExtrude_Bevel(LPoints, PNumOfProfilePoints, LProfileSpline->Closed, LSplineInvNormal, LBevel,  LVertex, PVertexSize,  PNumOfProfilePoints, LT,  LNormalDir, LDir);LVertex = EStruct_ByIndex(LVertex, PVertexSize, PNumOfProfilePoints * LBevel->Profile->SNumOfLinearSegments);
			}
			else if(LProfileFace)
			{
				{
					E3dSpline*    LProfileContour = LProfileFace->Exterior;
					const EIndex  LVN = LProfileContour->Closed ? LProfileContour->SNumOfLinearSegments : LProfileContour->SNumOfLinearSegments + 1;
					E3dExtrude_Bevel(LPoint, LVN, TRUE, LSplineInvNormal, LBevel,  LVertex, PVertexSize,  PNumOfProfilePoints, LT,  LNormalDir, LDir);LPoint += LVN;LVertex = EStruct_ByIndex(LVertex, PVertexSize,  LVN);
				}

				EIndex  LHI = 0;const EIndex  LHN = LProfileFace->Holes_Count;for(;LHI < LHN;LHI++)
				{
					E3dSpline*    LProfileContour = LProfileFace->Holes[LHI];
					const EIndex  LVN = LProfileContour->Closed ? LProfileContour->SNumOfLinearSegments : LProfileContour->SNumOfLinearSegments + 1;

					E3dExtrude_Bevel(LPoint, LVN, TRUE, LSplineInvNormal, LBevel,  LVertex, PVertexSize,  PNumOfProfilePoints, LT,  LNormalDir, LDir);LPoint += LVN;LVertex = EStruct_ByIndex(LVertex, PVertexSize,  LVN);
				}
			}

			LNumOfPathPoints += LBevel->Profile->SNumOfLinearSegments;
		}
	}


	{
		const E3dCoordinate  LStartBevelRadius = PInfo->StartBevel.Type ? PInfo->StartBevel.Radius : 0.0;
		const E3dCoordinate  LEndBevelRadius   = PInfo->EndBevel.Type   ? PInfo->EndBevel.Radius : 0.0;
		const E3dCoordinate  LDepthAbs = E3d_Abs(PInfo->Depth);

		const EBool          LIncludeBevelsInLength = PInfo->IncludeBevelsInLength;
		const E3dCoordinate  LDepth = LIncludeBevelsInLength ? LDepthAbs : LDepthAbs + (LStartBevelRadius + LEndBevelRadius);
		E3dCoordinate        LZStart = PInfo->CenterAlongPath ? -LDepthAbs / 2.0 : PInfo->Offset;

		if(!LIncludeBevelsInLength) LZStart -= LStartBevelRadius;

		const EIndex  LStartBevelUPoints = PInfo->StartBevel.Type ? PInfo->StartBevel.Profile->SNumOfLinearSegments : 0;
		const EIndex  LEndBevelUPoints   = PInfo->EndBevel.Type   ? PInfo->EndBevel.Profile->SNumOfLinearSegments : 0;

		if(PInfo->Twist != 0.0)
		{
			const E3dCoordinate  LTwistAngle = PInfo->Twist;
			E3dCoordinate        LTDepth = LDepth, LTZStart = LZStart;
			EIndex               LTUPoints = LNumOfPathPoints;
			EIndex               LLastUPoints;

//			EIndex  LDebugUC = 0;

			E3dVertex*           LVertex = PVertices;

			if(!PInfo->TwistBevels)
			{
				LTDepth  -= LStartBevelRadius + LEndBevelRadius;
				LTZStart += LStartBevelRadius;

				const EIndex  LUStart = LStartBevelUPoints;
//				LDebugUC += LUStart;

				LTUPoints -= LUStart + LEndBevelUPoints;
				LVertex = EStruct_ByIndex(LVertex, PVertexSize, LUStart * PNumOfProfilePoints);
				LLastUPoints = LEndBevelUPoints;
			}
			else LLastUPoints = 0;


			{
				EIndex  LUC = 0;for(;LUC < LTUPoints;LUC++)
				{
					const E3dCoordinate  LT = (LVertex->Position.Z - LTZStart) / LTDepth;
					const E3dCoordinate  LAngle = LT * LTwistAngle;
					E3dMatrix_LoadIdentity(LMatrix);E3dMatrix_RotateDeg(LMatrix, 'z', LAngle);

					EIndex  LVI = 0;for(;LVI < PNumOfProfilePoints;LVI++, EStruct_Inc(LVertex, PVertexSize))
					{
						EMatrix_Transform3x3_v(LMatrix, LVertex->Position);
					}
				}
			}

// We need to apply the final twist to the end bevel (we just won't change it through the U steps)
//
			if(LLastUPoints)
			{
				EIndex  LUC = 0;for(;LUC < LLastUPoints;LUC++)
				{
					EIndex  LVI = 0;for(;LVI < PNumOfProfilePoints;LVI++, EStruct_Inc(LVertex, PVertexSize))
					{
						EMatrix_Transform3x3_v(LMatrix, LVertex->Position);
					}
				}
			}

//printf("%s() %s:%d  LTUPoints %d  LDebugUC %d  LNumOfPathPoints %d\n", __FUNCTION__, __FILE__, __LINE__, LTUPoints, LDebugUC, LNumOfPathPoints);fflush(stdout);
		}


		if(PInfo->Scale.X != 1.0 || PInfo->Scale.Y != 1.0)
		{
			const E3dCoord2  LScaleV = PInfo->Scale;
			E3dCoordinate    LTDepth = LDepth, LTZStart = LZStart;
			EIndex           LTUPoints = LNumOfPathPoints;
			EIndex           LLastUPoints;

			EIndex  LDebugUC = 0;

			E3dVertex*       LVertex = PVertices;
			if(!PInfo->ScaleBevels)
			{
				LTDepth  -= LStartBevelRadius + LEndBevelRadius;
				LTZStart += LStartBevelRadius;

				const EIndex  LUStart = LStartBevelUPoints;

				LDebugUC += LUStart;

				LTUPoints -= LUStart + LEndBevelUPoints;
				LVertex = EStruct_ByIndex(LVertex, PVertexSize, LUStart * PNumOfProfilePoints);
				LLastUPoints = LEndBevelUPoints;
			}
			else LLastUPoints = 0;
			

			E3dCoord2            LScale = E3dCoord2_s(1.0, 1.0);
			const E3dCoordinate  LTTX = 1.0 - LScaleV.X;
			const E3dCoordinate  LTTY = 1.0 - LScaleV.Y;

			{
				E3dCoordinate  LU = 0.0;const E3dCoordinate  LUStep = 1.0 / (E3dCoordinate)(LTUPoints - 1);
				EIndex  LUC = 0;for(;LUC < LTUPoints;LUC++, LDebugUC++, LU += LUStep)
				{
					if(PInfo->Profile3D)
					{
						LScale.X = LScaleV.X * LU + (1.0 - LU);
						LScale.Y = LScaleV.Y * LU + (1.0 - LU);
						EIndex  LVI = 0;for(;LVI < PNumOfProfilePoints;LVI++, EStruct_Inc(LVertex, PVertexSize))
						{
							LVertex->Position.X *= LScale.X;
							LVertex->Position.Y *= LScale.X;
							LVertex->Position.Z *= LScale.X;
						}
					}
					else
					{
						const E3dCoordinate  LT = (LVertex->Position.Z - LTZStart) / LTDepth;
						LScale.X = (1.0 - LT) * LTTX + LScaleV.X;
						LScale.Y = (1.0 - LT) * LTTY + LScaleV.Y;
						EIndex  LVI = 0;for(;LVI < PNumOfProfilePoints;LVI++, EStruct_Inc(LVertex, PVertexSize))
						{
							LVertex->Position.X *= LScale.X;
							LVertex->Position.Y *= LScale.Y;
						}
					}
				}
			}

// We need to apply the final scale to the end bevel (just don't change it through the U steps)
// We also need to scale down along Z.
//
			if(LLastUPoints)
			{
				const E3dCoordinate  LZBase = LVertex->Position.Z;

				EIndex  LUC = 0;for(;LUC < LLastUPoints;LUC++, LDebugUC++)
				{
					const E3dCoordinate  LZ = (LVertex->Position.Z - LZBase) * LScale.X + LZBase;

// Keep extrapolating LScale to have a smooth transition into the scaled down bevel.
//
					const E3dCoordinate  LT = (LZ - LTZStart) / LTDepth;
					LScale.X = (1.0 - LT) * LTTX + LScaleV.X;
					LScale.Y = (1.0 - LT) * LTTY + LScaleV.Y;

					EIndex  LVI = 0;for(;LVI < PNumOfProfilePoints;LVI++, EStruct_Inc(LVertex, PVertexSize))
					{
						LVertex->Position.X *= LScale.X;
						LVertex->Position.Y *= LScale.Y;
						LVertex->Position.Z = LZ;
					}
				}
			}

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

		}
	}


	const EIndex  LVN = LNumOfPathPoints * PNumOfProfilePoints;

// If Depth is negative, invert along Z
//
	if(PInfo->Depth < 0.0)
	{
		EIndex  LVI = 0;E3dVertex*  LVertex = PVertices;for(;LVI < LVN;LVI++, EStruct_Inc(LVertex, PVertexSize)) LVertex->Position.Z *= -1.0;
	}


// If the Extrude is not along the Z axis, transform the contour coordinates accordingly
//
	if(!LUsingDefaultAxis)
	{
// Rotate extruded vertices back to the proper coordinate system
//
		E3dMatrix_LoadIdentity(LMatrix);  // E3d_VectorsGetRotMatrix only fills out the top-left 3x3 elements
		const E3dCoord3  LZAxis = E3dCoord3_s(0.0, 0.0, 1.0);
		E3d_VectorsGetRotMatrix(&LZAxis,  &LAxis,  LMatrix);

		EIndex  LVI = 0;E3dVertex*  LVertex = PVertices;for(;LVI < LVN;LVI++, EStruct_Inc(LVertex, PVertexSize))
		{
			EMatrix_Transform3x4_v(LMatrix, LVertex->Position);
		}
	}

	EFree(LPoints);

	return(LNumOfPathPoints * PNumOfProfilePoints);
}


void E3dExtrude_CheckProfiles(E3dObject* PObject, E3dExtrudeSplineInfoCommon* PInfo)
{
//printf("%s() %s:%d  <%s>  ProfileGeometries %p  Profiles %p\n", __FUNCTION__, __FILE__, __LINE__, PObject->Name, PInfo->ProfileGeometries, PInfo->Profiles);fflush(stdout);
	if(PInfo->Profiles == NULL && PInfo->ProfileGeometries)
	{
// Update profile extra information
//
		PInfo->Profiles = E3dExtrude_ProfilesCreate(PInfo->ProfileGeometries, PInfo->ProfileGeometries_Count);

// Add output to the Profile Geometries
//
		EIndex  LI = 0;for(;LI < PInfo->ProfileGeometries_Count;LI++)
		{
			if(PInfo->ProfileGeometries[LI])
			{
			 EObject*  LProfileG = PInfo->ProfileGeometries[LI];
			 if(EObject_IsSubClass(LProfileG, E3dGeometry)) E3dBaseObject_AddOutput((E3dBaseObject*)LProfileG, &PInfo->InputInterface);
			}
		}
	}
}


void E3dExtrude_CheckBevelsAndProfiles(E3dObject* PObject, E3dExtrudeSplineInfoCommon* PInfo)
{
// Update Bevels
//
	{
		E3dBevel*  LBevel = &PInfo->StartBevel;

		if(LBevel->NumOfRoundingSteps) E3dBevel_CreateSegments(LBevel);
		else
		{
			if(LBevel->Profile) { E3dSpline_FreeRenderData(LBevel->Profile);E3dSpline_FreeCVs(LBevel->Profile); }
			LBevel->Type = 0;
		}
	}

	{
		E3dBevel*	LBevel = &PInfo->EndBevel;

		if(LBevel->NumOfRoundingSteps) E3dBevel_CreateSegments(LBevel);
		else
		{
			if(LBevel->Profile) { E3dSpline_FreeRenderData(LBevel->Profile);E3dSpline_FreeCVs(LBevel->Profile); }
			LBevel->Type = 0;
		}
	}

	E3dExtrude_CheckProfiles(PObject, PInfo);
}


//========================================================================================================
// Get 2D bbox of PProfile, to calculate the UV distribution between the caps and the sides
//========================================================================================================
E3dCoord2* E3d_UVLayout(E3dExtrudeSplineInfoCommon* PInfo, const E3dExtrudeSplineProfile* PProfile, E3dFace* PProfileFace, E3dSpline* PPathSpline, const E3dCoordinate PDepth,  E3dCoord2* PProfile2DBBoxMin, E3dCoord2* PProfile2DBBoxMax, E3dCoordinate** PUsP, EIndex* PNumOfUsP, const char PCloseEnds)
{
	E3dCoord2*     L2DVertices = NULL;
	E3dCoordinate  LCapULength = 0.0, LCapVLength = 0.0;

	PInfo->SidesBelowInUVSpace = TRUE;
	const EBool  LSidesBelowInUVSpace = PInfo->SidesBelowInUVSpace;

	if(PCloseEnds)
	{
		if(PProfileFace->Vertices.Items == NULL) E3dFace_Tesselate(PProfileFace);

		L2DVertices = E3dFace_Flatten(PProfileFace);if(L2DVertices)
		{
			const E3dCoord2*  L2DVertex = L2DVertices;

			PProfile2DBBoxMin->X = PProfile2DBBoxMax->X = L2DVertex->X;
			PProfile2DBBoxMin->Y = PProfile2DBBoxMax->Y = L2DVertex->Y;
			EIndex  LVI = 1;const EIndex  LVN = PProfileFace->Vertices.Count;for(;LVI < LVN;LVI++, L2DVertex++)
			{
				if(PProfile2DBBoxMin->X > L2DVertex->X) PProfile2DBBoxMin->X = L2DVertex->X;
				else if(PProfile2DBBoxMax->X < L2DVertex->X) PProfile2DBBoxMax->X = L2DVertex->X;
				if(PProfile2DBBoxMin->Y > L2DVertex->Y) PProfile2DBBoxMin->Y = L2DVertex->Y;
				else if(PProfile2DBBoxMax->Y < L2DVertex->Y) PProfile2DBBoxMax->Y = L2DVertex->Y;
			}
		}
		const E3dCoordinate  LCapXLength = PProfile2DBBoxMax->X - PProfile2DBBoxMin->X;
		const E3dCoordinate  LCapYLength = PProfile2DBBoxMax->Y - PProfile2DBBoxMin->Y;

		if(LSidesBelowInUVSpace)
		{
			if(LCapXLength < LCapYLength)  // Orient cap Polygons in UV space with optimal usage
			{
				LCapULength = LCapYLength;
				LCapVLength = LCapXLength;
				PInfo->CapUVOrientation = E3dXY_TO_VU;
			}
			else
			{
				LCapULength = LCapXLength;
				LCapVLength = LCapYLength;
				PInfo->CapUVOrientation = E3dXY_TO_UV;
			}
		}
		else
		{
			if(LCapXLength < LCapYLength)  // Orient cap Polygons in UV space with optimal usage
			{
				LCapULength = LCapXLength;
				LCapVLength = LCapYLength;
				PInfo->CapUVOrientation = E3dXY_TO_UV;
			}
			else
			{
				LCapULength = LCapYLength;
				LCapVLength = LCapXLength;
				PInfo->CapUVOrientation = E3dXY_TO_VU;
			}
		}
	}

	E3dCoordinate  LSidesTotalULength;E3dCoordinate*  LUs = _GetUs((E3dExtrudeInfoCommon*)PInfo,  PPathSpline, PDepth, PInfo->PathSteps,  PNumOfUsP, &LSidesTotalULength);
	E3dCoordinate  LSidesTotalVLength = 0.0;
	EBool          LHaveVisibleProfileSegment = FALSE;

	{
		E3dSpline*  LContour = PProfileFace->Exterior;
		LSidesTotalVLength += LContour->Length;

		EIndex  LI = 0;const EIndex  LN = LContour->CVs.Count;const E3dSplineCV*  LCV = LContour->CVs.Items;for(;LI < LN;LI++, LCV++)
		{
			if((LContour->Closed || LI < LN - 1) && (LCV->Segment.Flags & E3dSegmentVISIBLE)) { LHaveVisibleProfileSegment = TRUE;break; }
		}
	}

	EIndex	LHI = 0;const EIndex  LHN = PProfileFace->Holes_Count;for(;LHI < LHN;LHI++)
	{
		E3dSpline*  LContour = PProfileFace->Holes[LHI];
		LSidesTotalVLength = LContour->Length;

		EIndex  LI = 0;const EIndex  LN = LContour->CVs.Count;const E3dSplineCV*  LCV = LContour->CVs.Items;for(;LI < LN;LI++, LCV++)
		{
			if((LContour->Closed || LI < LN - 1) && (LCV->Segment.Flags & E3dSegmentVISIBLE)) { LHaveVisibleProfileSegment = TRUE;break; }
		}
	}

	const E3dCoordinate  LSidesSmallerExtent = LSidesTotalULength < LSidesTotalVLength ? LSidesTotalULength : LSidesTotalVLength;
	if(LSidesBelowInUVSpace)
	{
		if(LSidesTotalULength > LSidesTotalVLength) PInfo->SidesUVOrientation = E3dXY_TO_UV;
		else PInfo->SidesUVOrientation = E3dXY_TO_VU;
	}
	else
	{
		if(LSidesTotalULength < LSidesTotalVLength) PInfo->SidesUVOrientation = E3dXY_TO_UV;
		else PInfo->SidesUVOrientation = E3dXY_TO_VU;
	}

	EBool  LSides = LHaveVisibleProfileSegment;


	E3dCoordinate  LTotalULength = 0.0;

	if(LSides && !PInfo->SidesBelowInUVSpace) LTotalULength += LSidesTotalULength;
	if(PCloseEnds & ExtrCLOSE_START) LTotalULength += LCapULength;
	if(PCloseEnds & ExtrCLOSE_END)   LTotalULength += LCapULength;

	const E3dCoordinate  LUMargin  = E3dU_MARGIN  * LTotalULength;
	const E3dCoordinate  LUSpacing = E3dU_SPACING * LTotalULength;
	PInfo->UMargin  = LUMargin;
	PInfo->USpacing = LUSpacing;

	E3dCoordinate  LU = LUMargin;
	EBool          LAddUSpacing = FALSE, LAddVSpacing = FALSE;

	PInfo->NormStartCapUStart = LU;

	if(PCloseEnds & ExtrCLOSE_START)
	{
		PInfo->NormStartCapUStart = LU;LU += LCapULength;
		LAddUSpacing = TRUE;
	}

	if(LSides && !LSidesBelowInUVSpace)
	{
		if(LAddUSpacing) LU += LUSpacing;

		PInfo->NormSidesUStart = LU;
		LU += LSidesTotalULength;

		LAddUSpacing = TRUE;
	}

	if(PCloseEnds & ExtrCLOSE_END)
	{
		if(LAddUSpacing) LU += LUSpacing;
		PInfo->NormEndCapUStart = LU;
		LU += LCapULength;
	}

	LTotalULength = LU + LUMargin;

	PInfo->SidesULength = LSidesTotalULength;

// Normalize Us
//
	PInfo->NormCapULength      = LCapULength / LTotalULength;
	PInfo->NormStartCapUStart /= LTotalULength;
	PInfo->NormEndCapUStart   /= LTotalULength;

	const EIndex  LSidesGapCount = PProfileFace->Holes_Count;

	if(LSidesBelowInUVSpace)
	{
// The sides are below the caps in UV space
//

//printf("%s() %s:%d LSidesBelowInUVSpace TRUE\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
		E3dCoordinate  LTotalVLength = 0.0;

		if(PCloseEnds) LTotalVLength += LCapVLength;
		if(LSides) LTotalVLength += LSidesSmallerExtent;

		const E3dCoordinate  LVMargin  = E3dV_MARGIN  * LTotalVLength;
		const E3dCoordinate  LVSpacing = E3dV_SPACING * LTotalVLength;
		E3dCoordinate        LV = LVMargin;

		if(PCloseEnds)
		{
			PInfo->NormCapVStart = LV;LV += LCapVLength;
			LAddVSpacing = TRUE;
		}

		if(LSides)
		{
			if(LAddVSpacing) LV += LVSpacing;
			PInfo->NormSidesVStart = LV;LV += LSidesSmallerExtent;
		}

		LTotalVLength = LV + LVMargin;

		PInfo->NormCapVStart  /= LTotalVLength;
		PInfo->NormCapVLength  = LCapVLength / LTotalVLength;

		PInfo->NormSidesUStart   = E3dU_MARGIN;
		PInfo->NormSidesVStart  /= LTotalVLength;

		if(PInfo->SidesUVOrientation == E3dXY_TO_UV)
		{
//printf("%s() %s:%d UV\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
// U will be along the extrusion path, V will be along the profile contours

			PInfo->NormSidesULength  = 1.0 - (E3dU_MARGIN * 2.0);
			PInfo->NormSidesVLength = LSidesSmallerExtent / LTotalVLength - ((E3dCoordinate)LSidesGapCount * E3dV_SPACING);
		}
		else
		{
// U will be along the profile contours, V will be along the extrusion path
			PInfo->NormSidesULength = 1.0 - (E3dU_MARGIN * 2.0 + (E3dCoordinate)LSidesGapCount * E3dU_SPACING);
			PInfo->NormSidesVLength  = LSidesSmallerExtent / LTotalVLength;
//printf("%s() %s:%d VU  gaps %d  %f\n", __FUNCTION__, __FILE__, __LINE__, (int)LSidesGapCount, PInfo->NormSidesULength);fflush(stdout);

//PInfo->NormSidesULength   0.859375
		}
	}
	else
	{
// The sides are to the left and right of the caps in UV space.
//
//printf("%s() %s:%d LSidesBelowInUVSpace FALSE\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
		PInfo->NormCapVLength = LCapVLength / (E3dV_MARGIN * LCapVLength + LCapVLength + E3dV_MARGIN * LCapVLength);
		PInfo->NormCapVStart =  E3dV_MARGIN;

		PInfo->NormSidesUStart  /= LTotalULength;
		PInfo->NormSidesVStart   = E3dV_MARGIN;

		if(PInfo->SidesUVOrientation == E3dXY_TO_UV)
		{
// U will be along the extrusion path, V will be along the profile contours
//printf("%s() %s:%d  E3dXY_TO_UV\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
			PInfo->NormSidesULength  = LSidesTotalULength / LTotalULength;
			PInfo->NormSidesVLength  = 1.0 - (E3dV_MARGIN * 2.0 + (E3dCoordinate)LSidesGapCount * E3dV_SPACING);
		}
		else
		{
// U will be along the profile contours, V will be along the extrusion path
//printf("%s() %s:%d  E3dXY_TO_VU  PInfo->CloseEnd %d\n", __FUNCTION__, __FILE__, __LINE__, PInfo->CloseEnd);fflush(stdout);
			PInfo->NormSidesULength  = LSidesTotalULength / LTotalULength - ((E3dCoordinate)LSidesGapCount * E3dU_SPACING);
			PInfo->NormSidesVLength = 1.0 - (E3dV_MARGIN * 2.0);
		}
	}

	*PUsP = LUs;
	return(L2DVertices);
}


typedef struct
{
	XeWidget          CloseStartW, CloseSidesW, CloseEndW, TrianglesOnCapsW, TrianglesOnSidesW, CenteredW, IncludeBevelsInLengthW,
	                  Profile3DW,
	                  PathStepsW, DepthW, OffsetW, TwistW, ScaleXW, ScaleYW,
	                  SmoothingAngleW;

	E3dpAxisSelector  AxisSelector;

	E3dBevelPanel     StartBevelPanel, EndBevelPanel;
} E3dResourcesGUI;


//========================================
// AxisSelector callback
//========================================
static void _CB_AxisSelector(ECoreObject* PStruct, EPointer PClientData, ECallbackStruct* PCallData)
{
	E3dObjectPanel*    LPanel = (E3dObjectPanel*)PClientData;
	E3dObject*         LObject = LPanel->Object;
	E3dInfo*           LInfo = (E3dInfo*)E3dObjectComponent_ByClass(LObject, _ObjectComponent_class, FALSE);
	E3dpAxisSelector*  LAxisSelector = (E3dpAxisSelector*)PStruct;

	LInfo->Axis = LAxisSelector->Axis;

	_CustomObjectChanged(LObject, (E3dObjectComponent*)LInfo, LPanel);
}


static void _DCB_IncludeBevelsInLength(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dObjectPanel*  LPanel = (E3dObjectPanel*)PClientData;
	E3dObject*       LObject = LPanel->Object;
	E3dInfo*         LInfo = (E3dInfo*)E3dObjectComponent_ByClass(LObject, _ObjectComponent_class, FALSE);

	LInfo->IncludeBevelsInLength = ((XeToggleButtonCallbackStruct*)PCallData)->Set;

	_CustomObjectChanged(LObject, (E3dObjectComponent*)LInfo, LPanel);
}


static void _DCB(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dObjectPanel*  LPanel = (E3dObjectPanel*)PClientData;
	E3dObject*       LObject = LPanel->Object;
	E3dInfo*         LInfo = (E3dInfo*)E3dObjectComponent_ByClass(LObject, _ObjectComponent_class, FALSE);
	EChangeFlags     LChanged = E3dObject_GetChangeFlagsByClass(LObject);

	XeComponentClassDrawer*  LDrawer = XeInfoPanel_GetFolderByClass((XeInfoPanel*)LPanel, _ObjectComponent_class);if(LDrawer)
	{
		E3dResourcesGUI*  LGUI = (E3dResourcesGUI*)LDrawer->ResourcesGUI;

		if(PW == LGUI->CenteredW)
		{
			LInfo->CenterAlongPath = ((XeToggleButtonCallbackStruct*)PCallData)->Set;
			if(LInfo->CenterAlongPath) Xe_SetValuesVa(LGUI->OffsetW,  XeNSensitive, FALSE,  NULL);else Xe_SetValuesVa(LGUI->OffsetW,  XeNSensitive, TRUE,  NULL);
			LInfo->AttributeChangeCount++;E3dObject_CustomUpdate(LObject, LInfo,  E3dCallALL,  &LChanged);
		}
		else if(PW == LGUI->DepthW)
		{
			LInfo->Depth = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;
			LInfo->AttributeChangeCount++;E3dObject_CustomUpdate(LObject, LInfo,  E3dCallALL,  &LChanged);
		}
		else if(PW == LGUI->OffsetW)
		{
			LInfo->Offset = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;
			LInfo->AttributeChangeCount++;E3dObject_CustomUpdate(LObject, LInfo,  E3dCallALL,  &LChanged);
		}
		else if(PW == LGUI->TwistW)
		{
			LInfo->Twist = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;
			LInfo->AttributeChangeCount++;E3dObject_CustomUpdate(LObject, LInfo,  E3dCallALL,  &LChanged);
		}
		else if(PW == LGUI->ScaleXW)
		{
			LInfo->Scale.X = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;
			LInfo->AttributeChangeCount++;E3dObject_CustomUpdate(LObject, LInfo,  E3dCallALL,  &LChanged);
		}
		else if(PW == LGUI->ScaleYW)
		{
			LInfo->Scale.Y = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;
			LInfo->AttributeChangeCount++;
			E3dObject_CustomUpdate(LObject, LInfo,  E3dCallALL,  &LChanged);
		}
		else if(PW == LGUI->Profile3DW)
		{
			LInfo->Profile3D = ((XeToggleButtonCallbackStruct*)PCallData)->Set;
			LInfo->AttributeChangeCount++;E3dObject_CustomUpdate(LObject, LInfo,  E3dCallALL,  &LChanged);
		}
	}

	E3dScene*  LScene = E3d_Scene;
	E3dWindows_RedrawByChangeFlags(LScene, LChanged);
}


//================================================================
// Callback for Profile-Spline changes
// This function should Not call any 3DWindow redrawing
// functions, just return with the appropriate change-flags
// (e.g. E3dChgMeshSHAPE) if a redraw is needed!
//================================================================
static EChangeFlags _InputChanged(EInterface* PInterface, EInterfaceCallbackStruct* PCBS)
{
	E3dObject*    LObject  = (E3dObject*)PInterface->Object;
	E3dInfo*      LInfo    = (E3dInfo*)PInterface->ObjectComponent;
	EChangeFlags  LChanged = E3dObject_GetChangeFlagsByClass(LObject);

//{ char  s[256];printf("%s() %s:%d [%s]\n", __FUNCTION__, __FILE__, __LINE__, E3dSceneChangeFlags_ToString(LChanged, EfCOLOR,  s));fflush(stdout); }

	LInfo->InputChangeCount++;

// Only respond if the Object is instantiated (e.g. not in the undo stack after being deleted)
//
	if(LObject->Models.Count > 0)
	{
//{ char  s[256];printf("%s() %s:%d [%s]\n", __FUNCTION__, __FILE__, __LINE__, E3dSceneChangeFlags_ToString(LChanged, EfCOLOR,  s));fflush(stdout); }
// E3dFIXME - only update the Profile entry for the Profile Geometry that actually changed
//
		E3dExtrude_ProfilesUpdateFaces(LInfo->Profiles, LInfo->ProfileGeometries, LInfo->ProfileGeometries_Count);
		E3dObject_CustomUpdate(LObject, LInfo,  PCBS->CallOutputs ? E3dCallALL : E3dCallALL & ~E3dCallOUTPUTS,  &LChanged);
	}

	return(LChanged);
}


//================================================
// Constructor
//================================================
static int _Init(E3dObject* PObject, E3dObjectComponent* PInfo)
{
	E3dInfo*  LThis = (E3dInfo*)PInfo;

	EList_Init(LThis->ProfileGeometries);
	LThis->Profiles = NULL;

	LThis->ProfileFollowing = E3dSTRICT;

	LThis->Profile3D = FALSE;
	LThis->Depth  = 1.0;
	LThis->Offset = 0.0;
	LThis->Twist = 0.0;LThis->TwistBevels = FALSE;
	LThis->Scale = E3dCoord2_s(1.0, 1.0);LThis->ScaleBevels = FALSE;
	LThis->PathSteps = 1;
	LThis->Axis.X = 0.0;LThis->Axis.Y = 0.0;LThis->Axis.Z = 1.0;  // Extrude along the Z axis
	LThis->SmoothingAngle = 60.0;

	LThis->CenterAlongPath = TRUE;

	LThis->TrianglesOnCaps = FALSE;
	LThis->TrianglesOnSides = FALSE;
	LThis->CloseStart = TRUE;
	LThis->CloseEnd = TRUE;
	LThis->CloseSides = TRUE;
	LThis->CreateUVs = TRUE;
	LThis->CreateTangents = TRUE;

	LThis->IncludeBevelsInLength = TRUE;

	E3dBevel_Init(&LThis->StartBevel);
	E3dBevel_Init(&LThis->EndBevel);

// Initialize InputInterface
//
	{
		EInterface*  LInput = &LThis->InputInterface;EInterface_Init(LInput);
		LInput->InputChanged = _InputChanged;
		LInput->Object = (ECoreObject*)PObject;
		LInput->ObjectComponent = (ECoreObject*)PInfo;
	}

	return(E_SUCCESS);
}


//========================================
// Copy-constructor
//========================================
static void _Copy(const E3dObject* PSObject, const E3dObjectComponent* PSInfo,  E3dObject* PDObject, E3dObjectComponent* PDInfo)
{
	const E3dInfo*  LSInfo = (E3dInfo*)PSInfo;
	E3dInfo*        LDInfo = (E3dInfo*)PDInfo;

	_Init(PDObject, PDInfo);

#define _CopyField(f)  LDInfo->f = LSInfo->f
	_CopyField(ProfileFollowing);
	_CopyField(Profile3D);
	_CopyField(Depth);
	_CopyField(Offset);
	_CopyField(Twist);_CopyField(TwistBevels);
	_CopyField(Scale);_CopyField(ScaleBevels);
	_CopyField(PathSteps);
	_CopyField(Axis);
	_CopyField(SmoothingAngle);
	_CopyField(TrianglesOnCaps);
	_CopyField(TrianglesOnSides);
	_CopyField(CloseStart);
	_CopyField(CloseEnd);
	_CopyField(CloseSides);
	_CopyField(CreateUVs);
	_CopyField(CreateTangents);
	_CopyField(CenterAlongPath);
	_CopyField(IncludeBevelsInLength);

/*
	_CopyField(StartBevel.NumOfRoundingSteps);
	_CopyField(StartBevel.Radius);
	_CopyField(EndBevel.NumOfRoundingSteps);
	_CopyField(EndBevel.Radius);
*/
#undef _CopyField

	if(LSInfo->ProfileGeometries_Count)
	{
		ENewNClr(LSInfo->ProfileGeometries_Count, LDInfo->ProfileGeometries);
		EIndex  LI = 0;for(;LI < LSInfo->ProfileGeometries_Count;LI++)
		{
			EObject*  LProfileG = LSInfo->ProfileGeometries[LI];if(LProfileG)
			{
				E_AssignToMember(LProfileG, LDInfo,ProfileGeometries[LI]);
				if(EObject_IsSubClass(LProfileG, E3dGeometry)) E3dBaseObject_AddOutput((E3dBaseObject*)LProfileG, &LDInfo->InputInterface);
			}
		}

		LDInfo->Profiles = E3dExtrude_ProfilesCreate(LDInfo->ProfileGeometries, LSInfo->ProfileGeometries_Count);

		LDInfo->ProfileGeometries_Count = LSInfo->ProfileGeometries_Count;
	}

	{
		E3dBevel*  LBevel = &LDInfo->StartBevel;
		E3dBevel_Copy(&LSInfo->StartBevel, &LDInfo->StartBevel);
		if(LBevel->NumOfRoundingSteps) E3dBevel_CreateSegments(LBevel);else LBevel->Type = 0;
	}

	{
		E3dBevel*  LBevel = &LDInfo->EndBevel;
		E3dBevel_Copy(&LSInfo->EndBevel,   &LDInfo->EndBevel);
		if(LBevel->NumOfRoundingSteps) E3dBevel_CreateSegments(LBevel);else LBevel->Type = 0;
	}
}


//========================================================
// "Extrude" ObjectComponent class destructor
//========================================================
static int _Free(E3dObject* PObject, E3dObjectComponent* PInfo)
{
	E3dInfo*  LInfo = (E3dInfo*)PInfo;


//printf("%s() %s:%d  %p %p %d\n", __FUNCTION__, __FILE__, __LINE__, LInfo->Profiles, LInfo->ProfileGeometries, (int)(LInfo->ProfileGeometries_Count));fflush(stdout);
	E3dExtrude_ProfilesFree(PObject, (E3dExtrudeSplineInfoCommon*)PInfo);

	E3dBevel_FreeContents(&LInfo->StartBevel);
	E3dBevel_FreeContents(&LInfo->EndBevel);

	EInterface_Clear(&LInfo->InputInterface);

	return(E_SUCCESS);
}


static E3dInfo*         _TmpInfo = NULL;
static E3dObjectPanel*  _TmpPanel = NULL;

#include "ExtrudeCommon.h"

static void _RequestProfilePick(void)
{
	const E3dpPrefs*  LPrefs = &E3dp_Prefs;
	const uint32_t    LLabelRGBAp8Color = Xe_ForegroundRGBA8pColor;

	E3dp_PickRequest(
	 XeString_NewVa(TRUE,
	 XeFONT, XeFontDEFAULT,  XeCOLOR_RGBA, LLabelRGBAp8Color,  XeTEXT, "Extrude:    ",
	 E3dp_LMouseButtonS,     XeTEXT, ": pick Spline or Face as the new profile  ",
	 E3dp_RMouseButtonEscS,  XeTEXT, ": cancel",
	 0),
	 E3dItemSPLINE|E3dItemFACE|E3dItemMODEL,  _PickCB_Profile, NULL,  0, FALSE);
}


static void _DCB_ProfilePick(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dObjectPanel*  LPanel = (E3dObjectPanel*)PClientData;
	E3dObject*       LObject = LPanel->Object;
	E3dInfo*         LInfo = (E3dInfo*)E3dObjectComponent_ByClass(LObject, _ObjectComponent_class, FALSE);

	_TmpInfo = LInfo;
	_TmpPanel = LPanel;
	_RequestProfilePick();
}


static void _DCB_ProfileShow(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dObjectPanel*  LPanel = (E3dObjectPanel*)PClientData;
	E3dObject*       LObject = LPanel->Object;
	E3dInfo*         LInfo = (E3dInfo*)E3dObjectComponent_ByClass(LObject, _ObjectComponent_class, FALSE);
	EChangeFlags     LChanged = 0;

	EIndex  LI = 0, LFound = 0;const EIndex  LN = LInfo->ProfileGeometries_Count;for(;LI < LN;LI++)
	{
		E3dObject*  LProfileObject = (E3dObject*)LInfo->ProfileGeometries[LI];if(LProfileObject)
		{
			LFound++;
			if(E3dSel_ObjectSelect(E3d_Scene, LProfileObject, FALSE, FALSE)) LChanged |= E3dChgSELECTION;
		}
	}

	if(LFound)
	{
		E3dScene*  LScene = E3d_Scene;
		E3dWindows_RedrawByChangeFlags(LScene, LChanged);
		E3dTransformPanel_Update(NULL, E3dDoALL, E3dDoALL, E3dDoALL,  E3dpDO_NOT_CHANGE,  E3dTrPanelALL);
		E3dp_PrintMessage(ELogMESSAGE, 3000, "Extrude: selected profile Geometries: %d", LFound);
	}
	else E3dp_PrintMessage(ELogMESSAGE, 3000, "Extrude: no profile Geometries!");
}


//================================================================
// GUIProc for "Extrude"
//================================================================
void _GUI(E3dObject* PObject, E3dObjectComponent* PInfo, void* PPanel, void* PDrawer, const int PAction)
{
	E3dObjectPanel*          LPanel = (E3dObjectPanel*)PPanel;
	XeComponentClassDrawer*  LDrawer = (XeComponentClassDrawer*)PDrawer;
	E3dResourcesGUI*         LGUI;
	E3dInfo*                 LInfo = (E3dInfo*)PInfo;

	switch(PAction)
	{
		case E_UPDATE:
			if(LDrawer->ResourcesGUI)
			{
				LGUI = (E3dResourcesGUI*)(LDrawer->ResourcesGUI);

				E3dBevelPanel_Update(&LGUI->StartBevelPanel, &LInfo->StartBevel);
				E3dBevelPanel_Update(&LGUI->EndBevelPanel, &LInfo->EndBevel);

				XeToggleButton_SetState(LGUI->CloseStartW, LInfo->CloseStart, FALSE);
				XeToggleButton_SetState(LGUI->CloseEndW, LInfo->CloseEnd, FALSE);
				XeToggleButton_SetState(LGUI->CloseSidesW, LInfo->CloseSides, FALSE);

				XeToggleButton_SetState(LGUI->TrianglesOnCapsW, LInfo->TrianglesOnCaps, FALSE);
				XeToggleButton_SetState(LGUI->TrianglesOnSidesW, LInfo->TrianglesOnSides, FALSE);
				XeToggleButton_SetState(LGUI->CenteredW,  LInfo->CenterAlongPath, FALSE);
				XeToggleButton_SetState(LGUI->Profile3DW, LInfo->Profile3D, FALSE);

				XeToggleButton_SetState(LGUI->IncludeBevelsInLengthW, LInfo->IncludeBevelsInLength, FALSE);
				XeScale_SetValue_f32(LGUI->SmoothingAngleW, LInfo->SmoothingAngle, FALSE);

				XeScale_SetValue_Coord(LGUI->DepthW, LInfo->Depth, FALSE);
				XeScale_SetValue_Coord(LGUI->OffsetW, LInfo->Offset, FALSE);
				if(LGUI->TwistW)  XeScale_SetValue_Coord(LGUI->TwistW, LInfo->Twist,   FALSE);
				if(LGUI->ScaleXW) XeScale_SetValue_Coord(LGUI->ScaleXW, LInfo->Scale.X, FALSE);
				if(LGUI->ScaleYW) XeScale_SetValue_Coord(LGUI->ScaleYW, LInfo->Scale.Y, FALSE);
				XeScale_SetValue(LGUI->PathStepsW, LInfo->PathSteps, FALSE);

				E3dpAxisSelector_Set(&LGUI->AxisSelector,  LInfo->Axis.X, LInfo->Axis.Y, LInfo->Axis.Z);
			}
			else
			{
				XeWidget  LTopMatrixW = LDrawer->Drawer.DrawerW, LW;
				XeWidget  LIndentWs[16];int  LIndentN;      // For indenting the ScaleWidgets
				XeWidget  LTraverseWs[16];int  LTraverseN;  // For TextField traversal
				EArg      Args[256];int  ArgCnt;


				ENew(LGUI);LDrawer->ResourcesGUI = LGUI;

// Options
//
				{
					EXeStart;
					EXeSetArg(XeNOrientation, XeHORIZONTAL);
					EXeSetArg(XeNSpacingX, 12);EXeSetArg(XeNSpacingRight, 8);
					EXeSetArg(XeNColumns, 3);EXeSetArg(XeNRows, 2);
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					EXeSetArg(XeNResizableY, FALSE);
					XeWidget  LMatrixW = XeWidget_ManagedNew("LMatrixW", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);

					EXeStart;
					EXeSetArg(XeNShadowThickness, 0);
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNSet, LInfo->CloseStart);
					LGUI->CloseStartW = LW = XeWidget_ManagedNew("Close start", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_CloseStart, (EPointer)LPanel);

					EXeStart;
					EXeSetArg(XeNShadowThickness, 0);
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNSet, LInfo->CloseEnd);
					LGUI->CloseEndW = LW = XeWidget_ManagedNew("Close end", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_CloseEnd, (EPointer)LPanel);

					EXeStart;
					EXeSetArg(XeNShadowThickness, 0);
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNSet, LInfo->TrianglesOnCaps);
					XeToolTip8("Triangulate start and end caps.");
					LGUI->TrianglesOnCapsW = LW = XeWidget_ManagedNew("Triangles", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_TrianglesOnCaps, (EPointer)LPanel);


					EXeStart;
					EXeSetArg(XeNShadowThickness, 0);
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNSet, LInfo->CloseSides);
					LGUI->CloseSidesW = LW = XeWidget_ManagedNew("Close sides", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_CloseSides, (EPointer)LPanel);

					EXeStart;
					EXeSetArg(XeNShadowThickness, 0);
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNSet, LInfo->TrianglesOnSides);
					LGUI->TrianglesOnSidesW = LW = XeWidget_ManagedNew("Side triangles", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_TrianglesOnSides, (EPointer)LPanel);

					EXeStart;
					LW = XeWidget_ManagedWindowlessNew("Pad", XeCoreWidget_class, LMatrixW,  Args, ArgCnt);



					EXeStart;
					EXeSetArg(XeNShadowThickness, 0);
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNSet, LInfo->CenterAlongPath);
					XeToolTip8("Center along extrusion direction");
					LGUI->CenteredW = LW = XeWidget_ManagedNew("Centered", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);

					EXeStart;
					EXeSetArg(XeNShadowThickness, 0);
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNSet, LInfo->IncludeBevelsInLength);
					XeToolTip8("Include bevels in the length of extrusion ('Depth').");
					LGUI->IncludeBevelsInLengthW = LW = XeWidget_ManagedNew("Include bevels", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_IncludeBevelsInLength, (EPointer)LPanel);
				}


// Profile
//
				{
					EXeStart;
					EXeSetArg(XeNOrientation, XeHORIZONTAL);
					EXeSetArg(XeNMarginSizeY, 5);
					EXeSetArg(XeNSpacingX, 0);
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					EXeSetArg(XeNResizableY, FALSE);
					XeWidget  LMatrixW = XeWidget_ManagedNew("", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);

					EXeStart;
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					EXeSetArg(XeNMarginSizeX, 2);EXeSetArg(XeNMarginSizeY, 3);
					EXeSetArg(XeNResizableY, FALSE);
					XeWidget_ManagedNew("Profile", XeLabelWidget_class, LMatrixW,  Args, ArgCnt);

					EXeStart;
					EXeSetArg(XeNMarginSizeX, 0);EXeSetArg(XeNSpacingLeft, 6);
					EXeSetArg(XeNShadowThickness, 0);EXeSetArg(XeNLabelType, XeIMAGE);EXeSetArg(XeNImageSizeYPtr, &Xe_IconSize);EXeSetImages2(_Xe_Theme->Image_Find);
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					EXeSetArg(XeNResizableY, FALSE);
					XeToolTip8("Select the Splines and/or Faces that are used as profiles for this extrusion (so you can find them).");
					LW = XeWidget_ManagedNew("Show", XePushButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNActivateCallback, _DCB_ProfileShow, (EPointer)LPanel);

					EXeStart;
					EXeSetArg(XeNMarginSizeX, 0);
					EXeSetArg(XeNShadowThickness, 0);EXeSetArg(XeNLabelType, XeIMAGE);EXeSetArg(XeNImageSizeYPtr, &Xe_IconSize);EXeSetImages2(_Xe_Theme->Image_Replace);
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					EXeSetArg(XeNResizableY, FALSE);
					XeToolTip8("Pick a new profile Spline or Face.");
					LW = XeWidget_ManagedNew("Pick", XePushButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNActivateCallback, _DCB_ProfilePick, (EPointer)LPanel);

					EXeStart;
					EXeSetArg(XeNShadowThickness, 0);
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNSpacingLeft, 6);
					EXeSetArg(XeNSet, LInfo->Profile3D);
					XeToolTip8("Consider the Z coordinate of the profile Spline / Face.\nIf this is OFF, the caps will be flat, even if the profile Spline/Face has curvature in the Z direction.");
					LGUI->Profile3DW = LW = XeWidget_ManagedNew("3D", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);
				}


				LTraverseN = 0;

				EXeStart;
				EXeSetArg(XeNShadowThickness, 2);
				EXeSetArg(XeNShadowType, XeShadowETCHED_IN);
				EXeSetArg(XeNResizableY, FALSE);
				XeWidget_ManagedNew("", XeSeparatorWidget_class, LTopMatrixW,  Args, ArgCnt);
				E3dBevelPanel_CreateDrawer(&LGUI->StartBevelPanel, LTopMatrixW, "Bevel start", _CB_Bevel, (EPointer)LPanel, &LInfo->StartBevel);
				LTraverseWs[LTraverseN++] = LGUI->StartBevelPanel.RadiusW;
				LTraverseWs[LTraverseN++] = LGUI->StartBevelPanel.RoundingStepsW;

				EXeStart;
				EXeSetArg(XeNShadowThickness, 2);
				EXeSetArg(XeNShadowType, XeShadowETCHED_IN);
				EXeSetArg(XeNResizableY, FALSE);
				XeWidget_ManagedNew("", XeSeparatorWidget_class, LTopMatrixW,  Args, ArgCnt);
				E3dBevelPanel_CreateDrawer(&LGUI->EndBevelPanel, LTopMatrixW, "Bevel end", _CB_Bevel, (EPointer)LPanel, &LInfo->EndBevel);
				LTraverseWs[LTraverseN++] = LGUI->EndBevelPanel.RadiusW;
				LTraverseWs[LTraverseN++] = LGUI->EndBevelPanel.RoundingStepsW;

				EXeStart;
				EXeSetArg(XeNShadowThickness, 2);
				EXeSetArg(XeNShadowType, XeShadowETCHED_IN);
				EXeSetArg(XeNResizableY, FALSE);
				XeWidget_ManagedNew("", XeSeparatorWidget_class, LTopMatrixW,  Args, ArgCnt);

				LIndentN = 0;
				EXeStart;
				EXeSetArg(XeNTextFieldDigits, 5);EXeSetArg(XeNDecimalPoints, 4);
				EXeSetArg(XeNResizableY, FALSE);
				LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LGUI->DepthW = LW = XeScale_New_Coord(LTopMatrixW, "Depth",  Args, ArgCnt);
				XeScale_SetValueAndRanges_Coord(LW,  LInfo->Depth,  -1000000.0, 1000000.0,  -10.0, 10.0,  FALSE);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);

				EXeStart;
				EXeSetArg(XeNTextFieldDigits, 5);EXeSetArg(XeNDecimalPoints, 4);
				EXeSetArg(XeNResizableY, FALSE);
				LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LGUI->OffsetW = LW = XeScale_New_Coord(LTopMatrixW, "Offset",  Args, ArgCnt);
				XeScale_SetValueAndRanges_Coord(LW,  LInfo->Offset,  -1000000.0, 1000000.0,  -10.0, 10.0,  FALSE);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);

				EXeStart;
				EXeSetArg(XeNTextFieldDigits, 5);EXeSetArg(XeNDecimalPoints, 4);
				EXeSetArg(XeNResizableY, FALSE);
				LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LGUI->TwistW = LW = XeScale_New_Coord(LTopMatrixW, "Twist",  Args, ArgCnt);
				XeScale_SetValueAndRanges_Coord(LW,  LInfo->Twist,  -7200.0, 7200.0,  -360.0, 360.0,  FALSE);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);

				EXeStart;
				EXeSetArg(XeNTextFieldDigits, 5);EXeSetArg(XeNDecimalPoints, 4);
				EXeSetArg(XeNResizableY, FALSE);
				LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LGUI->ScaleXW = LW = XeScale_New_Coord(LTopMatrixW, "Scale X",  Args, ArgCnt);
				XeScale_SetValueAndRanges_Coord(LW,  LInfo->Scale.X,  0.0001, 1000.0,  0.1, 10.0,  FALSE);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);

				EXeStart;
				EXeSetArg(XeNTextFieldDigits, 5);EXeSetArg(XeNDecimalPoints, 4);
				EXeSetArg(XeNResizableY, FALSE);
				LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LGUI->ScaleYW = LW = XeScale_New_Coord(LTopMatrixW, "Scale Y",  Args, ArgCnt);
				XeScale_SetValueAndRanges_Coord(LW,  LInfo->Scale.Y,  0.0001, 1000.0,  0.1, 10.0,  FALSE);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);

				EXeStart;
				EXeSetArg(XeNTextFieldDigits, 5);
				EXeSetArg(XeNMinimum, 1);EXeSetArg(XeNMaximum, 500);
				EXeSetArg(XeNScaleMaximum, 100);
				EXeSetArg(XeNValue, LInfo->PathSteps);
				EXeSetArg(XeNResizableY, FALSE);
				LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LGUI->PathStepsW = LW = XeWidget_ManagedNew("Path steps", XeScaleWidget_class, LTopMatrixW,  Args, ArgCnt);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB_PathSteps, (EPointer)LPanel);

				EXeStart;
				EXeSetArg(XeNTextFieldDigits, 5);EXeSetArg(XeNDecimalPoints, 2);
				EXeSetArg(XeNResizableY, FALSE);
				LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LGUI->SmoothingAngleW = LW = XeScale_ManagedNew_f32(LTopMatrixW, "Smoothing angle",  Args, ArgCnt);
				XeScale_SetValueAndRange_f32(LW,  LInfo->SmoothingAngle,  0.0, 180.0,  FALSE);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB_SmoothingAngle, (EPointer)LPanel);

				EXeStart;
				EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
				EXeSetArg(XeNMarginSizeX, 2);EXeSetArg(XeNMarginSizeY, 3);
				EXeSetArg(XeNResizableY, FALSE);
				XeWidget_ManagedNew("Extrude along:", XeLabelWidget_class, LTopMatrixW,  Args, ArgCnt);

				E3dpAxisSelector_New(&LGUI->AxisSelector, NULL, LTopMatrixW, TRUE,  _CB_AxisSelector, (EPointer)LPanel,  LInfo->Axis.X, LInfo->Axis.Y, LInfo->Axis.Z);

				XeWidgets_IndentFromLeft(LIndentWs, LIndentN, 2);

// Set up TextField traversal
//
				XeTraversal_SetRing(LTraverseWs, LTraverseN);
			}
			if(LInfo->CenterAlongPath) Xe_SetValuesVa(LGUI->OffsetW,  XeNSensitive, FALSE,  NULL);else Xe_SetValuesVa(LGUI->OffsetW,  XeNSensitive, TRUE,  NULL);
		break;
	}
}


//========================================
// Rebuild() (create/change) method
//========================================
static E3dObject* _Update1(E3dObject* PObject, E3dObjectComponent* PComponentTemplate,  EUpdateFlags* PTriggeredUpdateFlags, EChangeFlags* PTriggeredSceneChangeFlags,  const EBool PCheckOnly)
{
	E3dMesh*   LMesh;
	E3dInfo*   LInfo;
	EBool      LNewObject;

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

	if(PObject == NULL)
	{
		E_New(E3dMesh, LMesh);
		PObject = (E3dObject*)LMesh;
		LInfo = (E3dInfo*)E3dObjectComponent_Add((E3dObject*)LMesh, _ObjectComponent_class);
		if(PComponentTemplate && PComponentTemplate->Class == _ObjectComponent_class) _Copy(PObject, PComponentTemplate, PObject, (E3dObjectComponent*)LInfo);

// Add output to the Profile Geometries
//
		EIndex  LProfileI = 0;for(;LProfileI < LInfo->ProfileGeometries_Count;LProfileI++)
		{
			EObject*  LProfileG = LInfo->ProfileGeometries[LProfileI];

			if(EObject_IsSubClass(LProfileG, E3dGeometry)) E3dBaseObject_AddOutput((E3dBaseObject*)LProfileG, &LInfo->InputInterface);
		}

		LNewObject = TRUE;
	}
	else
	{
		if(!EObject_IsSubClass(PObject, E3dMesh)) return(NULL);

		LMesh = (E3dMesh*)PObject;
		LInfo = (E3dInfo*)E3dObjectComponent_ByClass((E3dObject*)LMesh, _ObjectComponent_class, FALSE);
		if(!PCheckOnly && LInfo->AttributeChangeCount == 0 && LInfo->InputChangeCount == 0) return(NULL);    // If no attributes or inputs changed, bail

		if(PCheckOnly)
		{
			E3dExtrude_CheckBevelsAndProfiles((E3dObject*)LMesh, (E3dExtrudeSplineInfoCommon*)LInfo);
		}

		LNewObject = FALSE;
	}

//printf("%s() %s:%d  <%s> %p %p\n", __FUNCTION__, __FILE__, __LINE__, PObject ? PObject->Name : "-", LInfo->ProfileGeometries, LInfo->Profiles);fflush(stdout);


	if(LInfo->ProfileGeometries_Count && LInfo->ProfileGeometries == NULL) { LInfo->ProfileGeometries_Count = 0;return(NULL); }
	if(PCheckOnly || LInfo->ProfileGeometries_Count == 0) return(NULL);


// If any of the profiles is missing, bail
//
	{
		EIndex  LProfileI = 0;for(;LProfileI < LInfo->ProfileGeometries_Count;LProfileI++)
		{
			if(LInfo->ProfileGeometries[LProfileI] == NULL) return(NULL);
		}
	}

	if(LMesh)
	{
		E3dMesh_FreeCaches(LMesh);

		E3dMesh_EdgesFree(LMesh);

// Calculate number of Vertices
//
		EIndex  LPathStepsWithBevel = LInfo->PathSteps;
		if(LInfo->StartBevel.Type) LPathStepsWithBevel += LInfo->StartBevel.Profile->SNumOfLinearSegments;
		if(LInfo->EndBevel.Type) LPathStepsWithBevel += LInfo->EndBevel.Profile->SNumOfLinearSegments;
		const EIndex	LNumOfPathPoints = LPathStepsWithBevel + 1;

		EIndex  LNumOfVertices = 0;

		if(LInfo->Profiles)
		{
			EIndex  LProfileI = 0;for(;LProfileI < LInfo->ProfileGeometries_Count;LProfileI++)
			{
				E3dExtrudeSplineProfile*  LProfile = LInfo->Profiles[LProfileI];

				LNumOfVertices += LNumOfPathPoints * LProfile->NumOfPoints;
			}
		}
		else
		{
			printf("%s() %s:%d  %sProfiles is NULL%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, ECodeNormal);fflush(stdout);
		}

		if(LNumOfVertices == 0)
		{
			E3dMesh_PolyGroupsFree(LMesh);
			E3dMesh_VerticesFree(LMesh);

			if(PTriggeredUpdateFlags) *PTriggeredUpdateFlags = E3dUpdateALL & ~E3dUpdateTRIANGULATE;
			LInfo->AttributeChangeCount = LInfo->InputChangeCount = 0;
			return(PObject);
		}

// Allocate Vertices for the Mesh
//
		if(LNumOfVertices != LMesh->Vertices.Count)
		{
			E3dMesh_VerticesFree(LMesh);
			E3dMesh_VerticesAdd(LMesh, LNumOfVertices);
		}

		const EIndex  LNumOfPolyGroups = LInfo->ProfileGeometries_Count;

// Free unused PolyGroups
//
		if(LMesh->PolyGroups.Count > LNumOfPolyGroups)
		{
			EIndex  LProfileI = LNumOfPolyGroups;for(;LProfileI < LMesh->PolyGroups.Count;LProfileI++) E3dMesh_PolyGroupDel(LMesh, LMesh->PolyGroups.Items[LProfileI]);
		}

		{
			E3dVertex*  LVertex = LMesh->Vertices.Items;const E3dVertexSize  LVertexSize = LMesh->VertexSize;
			EIndex      LVertexOffset = 0;
			int         LPolyGroupC = 0;

			uint8_t     LCloseEnds = 0;
			if(LInfo->CloseStart) LCloseEnds |= ExtrCLOSE_START;
			if(LInfo->CloseEnd) LCloseEnds |= ExtrCLOSE_END;

			E3dMesh_EdgesFree(LMesh);  // Edges will be built in E3dExtrude_CreatePolygons

			EIndex  LProfileI = 0;for(;LProfileI < LInfo->ProfileGeometries_Count;LProfileI++)
			{
				E3dExtrudeSplineProfile*  LProfile = LInfo->Profiles[LProfileI];
				EObject*                  LProfileGeo = LInfo->ProfileGeometries[LProfileI];
				E3dFace*                  LProfileFace = LProfile->Face ? LProfile->Face : E_DynamicCast(LProfileGeo, E3dFace);
				E3dPolyGroup*             LPolyGroup;
				E3dPolygon*               LPolygons;
				const EIndex              LNumOfProfileHoles = LProfile->NumOfHoles;
				const EIndex              LNumOfProfilePoints = LProfile->NumOfPoints, LNumOfProfileSegments = LProfile->NumOfSegments;

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

				if(LMesh->PolyGroups.Count <= LPolyGroupC) LPolyGroup = E3dMesh_PolyGroupAdd(LMesh);
				else LPolyGroup = LMesh->PolyGroups.Items[LPolyGroupC];
				LPolyGroupC++;
				E3dPolyGroup_FreeContents(LPolyGroup);
				E3dPolyGroup_VertexAttributeAdd(LPolyGroup,  NULL, E3dVtxAttribute_Normal, 0,  NULL);
				E3dPolyGroup_VertexAttributeAdd(LPolyGroup,  NULL, E3dVtxAttribute_UV, 0,  NULL);    // Make sure that the VertexNodes have UVs (evaluator coordinates)

//printf("%s() %s:%d  %sLPathStepsWithBevel%s  %lld\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, (long long)LPathStepsWithBevel);fflush(stdout);
				EIndex  LNumOfPolygons = LPathStepsWithBevel * LNumOfProfileSegments;

				if(LInfo->TrianglesOnSides) LNumOfPolygons *= 2;

				E3dPolyGroup*  LProfilePolyGroup = LProfileFace->PolyGroup;if(LProfilePolyGroup)
				{
					{
						if(LProfileFace->PolyGroup->Polygons.Count == 0) E3dFace_Tesselate(LProfileFace);
						const EIndex   LNProfileTriangles = E3dPolyGroup_CountTriangles(LProfilePolyGroup, NULL);

						if(LCloseEnds & ExtrCLOSE_START)
						{
							if(LInfo->TrianglesOnCaps) LNumOfPolygons += LNProfileTriangles;
							else LNumOfPolygons += LProfilePolyGroup->Polygons.Count;
						}

						if(LCloseEnds & ExtrCLOSE_END)
						{
							if(LInfo->TrianglesOnCaps) LNumOfPolygons += LNProfileTriangles;
							else LNumOfPolygons += LProfilePolyGroup->Polygons.Count;
						}
					}

// Allocate Polygons for the PolyGroup
//
					if(LNumOfPolygons == LPolyGroup->Polygons.Count) LPolygons = LPolyGroup->Polygons.Items;
					else
					{
						if(LPolyGroup->Polygons.Items) E3dPolyGroup_FreePolygons(LPolyGroup);
						LPolygons = E3dPolygons_New(LNumOfPolygons);if(LPolygons)
						{
							LPolyGroup->Polygons.Items = LPolygons;LPolyGroup->Polygons.Count = LPolyGroup->Polygons.CountAllocated = LNumOfPolygons;
						}
					}

					if(LNumOfProfilePoints)
					{
						E3dCoord2       PProfile2DBBoxMin = E3dCoord2_s(0.0, 0.0);
						E3dCoord2       PProfile2DBBoxMax = E3dCoord2_s(0.0, 0.0);
						E3dCoordinate*  LUs;EIndex  LNumOfUs;
						E3dCoord2*      L2DVertices = E3d_UVLayout((E3dExtrudeSplineInfoCommon*)LInfo, LProfile, LProfileFace,  NULL, E3d_Abs(LInfo->Depth),  &PProfile2DBBoxMin, &PProfile2DBBoxMax,  &LUs, &LNumOfUs,  LCloseEnds);

						E3dPolygon*     LPolygon = LPolyGroup->Polygons.Items;
						if(EObject_IsSubClass(LProfileGeo, E3dSpline))
						{
							E3dCoordinate   LTotalVLength;EIndex	LTotalNumOfVs;
							E3dCoordinate*  LVs;

							if(LInfo->SidesUVOrientation == E3dXY_TO_UV)
							{
								LVs = E3dExtrude_FaceGetVs(LProfileFace, E3dV_SPACING / LInfo->NormSidesVLength,  &LTotalVLength, &LTotalNumOfVs);
								_ScaleAndBias(LVs, LTotalNumOfVs,  LInfo->NormSidesVLength, LInfo->NormSidesVStart);
							}
							else
							{
								LVs = E3dExtrude_FaceGetVs(LProfileFace, E3dU_SPACING / LInfo->NormSidesULength,  &LTotalVLength, &LTotalNumOfVs);
								_ScaleAndBias(LVs, LTotalNumOfVs,  LInfo->NormSidesULength, LInfo->NormSidesUStart);
							}

//printf("%s() %s:%d  LNumOfUs %d   %d %d %d\n", __FUNCTION__, __FILE__, __LINE__, LNumOfUs, ((E3dSpline*)LProfileGeo)->SNumOfLinearSegments, LNumOfProfilePoints, LNumOfProfileSegments);fflush(stdout);
//printf("%s() %s:%d LInfo->PathSteps %d LPathStepsWithBevel %d\n", __FUNCTION__, __FILE__, __LINE__, LInfo->PathSteps, LPathStepsWithBevel);fflush(stdout);

							LVertex = EStruct_ByIndex(LVertex, LVertexSize, _SetVertices((E3dGeometry*)LProfileGeo,  LVertex, LVertexSize,  LInfo, LNumOfProfilePoints));

							if(LInfo->CloseSides) LPolygon = E3dExtrude_CreatePolygons(LMesh, (E3dExtrudeSplineInfoCommon*)LInfo, (E3dSpline*)LProfileGeo, NULL,  LUs, LNumOfUs,  LVs, LNumOfProfilePoints, LNumOfProfileSegments,  LInfo->PathSteps, LPathStepsWithBevel, LVertexOffset, LPolygon, LPolyGroup,  LInfo->Depth >= 0.0, FALSE);

							EFree(LVs);
						}
						else if(EObject_IsSubClass(LProfileGeo, E3dFace))
						{
// If we just created a new Object (e.g. a Mesh), assign the Material of the Face to the PolyGroups.
// Otherwise leave the Material alone, because the user might have set it.
//
							if(LNewObject)
							{
								E_AssignToMember(LProfilePolyGroup->Material, LPolyGroup,Material);
							}

							LVertex = EStruct_ByIndex(LVertex, LVertexSize, _SetVertices((E3dGeometry*)LProfileFace,  LVertex, LVertexSize,  LInfo, LNumOfProfilePoints));

							{
								E3dCoordinate   LTotalVLength;EIndex  LTotalNumOfVs;
								E3dCoordinate*  LVs;

								if(LInfo->SidesUVOrientation == E3dXY_TO_UV)
								{
									LVs = E3dExtrude_FaceGetVs(LProfileFace, E3dV_SPACING / LInfo->NormSidesVLength,  &LTotalVLength, &LTotalNumOfVs);
									_ScaleAndBias(LVs, LTotalNumOfVs,  LInfo->NormSidesVLength, LInfo->NormSidesVStart);
								}
								else
								{
									LVs = E3dExtrude_FaceGetVs(LProfileFace, E3dU_SPACING / LInfo->NormSidesULength,  &LTotalVLength, &LTotalNumOfVs);
									_ScaleAndBias(LVs, LTotalNumOfVs,  LInfo->NormSidesULength, LInfo->NormSidesUStart);

/*
	{
	printf("%s() %s:%d LVs %d:", __FUNCTION__, __FILE__, __LINE__, (int)LTotalNumOfVs);fflush(stdout);
	EIndex  LI = 0;for(;LI < LTotalNumOfVs;LI++)
	{
		printf("  %f", LVs[LI]);fflush(stdout);
	}
	printf("\n");fflush(stdout);
	}
*/
								}

								E3dCoordinate*  LVPtr = LVs;

								EIndex  LVOffset = LVertexOffset;

								{
									E3dSpline*    LProfileSpline = LProfileFace->Exterior;
									const EIndex  LContourNumOfVSegments = LProfileSpline->SNumOfLinearSegments;
									EIndex        LContourNumOfVPoints = LContourNumOfVSegments;if(!LProfileSpline->Closed) LContourNumOfVPoints++;

									if(LInfo->CloseSides) LPolygon = E3dExtrude_CreatePolygons(LMesh, (E3dExtrudeSplineInfoCommon*)LInfo, LProfileFace->Exterior, NULL,  LUs, LNumOfUs,  LVPtr, LNumOfProfilePoints, LContourNumOfVSegments,  LInfo->PathSteps, LPathStepsWithBevel,  LVOffset, LPolygon, LPolyGroup,  LInfo->Depth >= 0.0, FALSE);
									LVPtr    += LContourNumOfVPoints;
									LVOffset += LContourNumOfVPoints;

//printf("%s() %s:%d   <%s> LContourNumOfVSegments %d  LContourNumOfVPoints %d\n", __FUNCTION__, __FILE__, __LINE__, LProfileFace->Name, (int)LContourNumOfVSegments, (int)LContourNumOfVPoints);fflush(stdout);
								}

								if(LNumOfProfileHoles)
								{
									EIndex  LHI = 0;for(;LHI < LNumOfProfileHoles;LHI++)
									{
										E3dSpline*    LProfileSpline = LProfileFace->Holes[LHI];
										const EIndex  LContourNumOfVSegments = LProfileSpline->SNumOfLinearSegments;
										EIndex        LContourNumOfVPoints = LContourNumOfVSegments;if(!LProfileSpline->Closed) LContourNumOfVPoints++;

										if(LInfo->CloseSides) LPolygon = E3dExtrude_CreatePolygons(LMesh, (E3dExtrudeSplineInfoCommon*)LInfo,  LProfileFace->Holes[LHI], NULL,
																												 LUs, LNumOfUs,  LVPtr, LNumOfProfilePoints, LContourNumOfVSegments,
																												 LInfo->PathSteps, LPathStepsWithBevel,  LVOffset, LPolygon, LPolyGroup,  LInfo->Depth >= 0.0, TRUE);
										LVPtr    += LContourNumOfVPoints;
										LVOffset += LContourNumOfVPoints;

//printf("%s() %s:%d   <%s> LContourNumOfVSegments %d  LContourNumOfVPoints %d\n", __FUNCTION__, __FILE__, __LINE__, LProfileFace->Name, (int)LContourNumOfVSegments, (int)LContourNumOfVPoints);fflush(stdout);
									}
								}

								EFree(LVs);
							}
						}

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

						if(LCloseEnds) LPolygon = E3dExtrude_CloseEnds((E3dExtrudeSplineInfoCommon*)LInfo,  LProfile, LProfileFace,  L2DVertices, &PProfile2DBBoxMin, &PProfile2DBBoxMax,  LMesh, LPolyGroup, LPolygon, LVertexOffset, LNumOfPathPoints, LNumOfProfilePoints, LCloseEnds, LInfo->Depth >= 0.0);
						EFreeChk(L2DVertices);
						EFree(LUs);

						LPolyGroup->SmoothingAngle = LInfo->SmoothingAngle;
						if(LInfo->SmoothingAngle > 0.0)
						{
							LPolyGroup->Flags |= E3dUSE_VERTEX_NORMALS;

							LPolyGroup->VertexNormalType = E3dNormalFROM_SMOOTHING_ANGLE;
						}
						else
						{
							LPolyGroup->VertexNormalType = 0;
							LPolyGroup->Flags &= ~E3dUSE_VERTEX_NORMALS;
						}

						LPolyGroup->Polygons.Count = LPolygon - LPolyGroup->Polygons.Items;
					}

					LVertexOffset += LNumOfPathPoints * LNumOfProfilePoints;
				}
			}
		}

#if E3dEXTRUDESPLINE_CREATE_EDGES
		const EBool   LCreateEdges = !LInfo->TrianglesOnSides;
#else
		const EBool   LCreateEdges = FALSE;
#endif

//E3dMesh_EdgeFlagsUpdate(LMesh);  // For debugging (to show selected Edges).

		if(LCreateEdges)
		{
			if(PTriggeredUpdateFlags) *PTriggeredUpdateFlags = E3dUpdateALL & (~(E3dUpdateTRIANGULATE|E3dUpdateEDGES));
		}
		else
		{
			if(PTriggeredUpdateFlags) *PTriggeredUpdateFlags = E3dUpdateALL & ~E3dUpdateTRIANGULATE;
		}

		LInfo->AttributeChangeCount = LInfo->InputChangeCount = 0;

//{ char	s[256];printf("%s() %s:%d  [%s]\n", __FUNCTION__, __FILE__, __LINE__, E3dSceneChangeFlags_ToString(LChanged, EfCOLOR,  s));fflush(stdout); }
	}

	return(PObject);
}


//================================================
// Menu/ToolBar callback
//================================================
static void _MCB(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*    LScene = E3d_Scene;
	const EBool  LShowOptions = ((XePushButtonCallbackStruct*)PCallData)->Reason == XeCR_ACTIVATE_OPTIONS;

	{
		EListAI_s(, E3dObjectPtr, LObjects);const EIndex  LNObjects = E3dScene_GetObjects(E3d_Scene, E3dItemMESH, E3dSELECTED, TRUE,  &LObjects);if(LNObjects > 0)
		{
			E3d_ExtrudeMesh(LObjects.Items, LNObjects,  LShowOptions);
			return;
		}
	}

	EListAI_s(, E3dObjectPtr, LObjects);const EIndex  LNObjects = E3dScene_GetObjects(E3d_Scene, E3dItemSPLINE|E3dItemFACE, E3dSELECTED, TRUE,  &LObjects);if(LNObjects > 0)
	{
		E3dInfo  LTInfo;E3dObjectComponent_InitStatic(&LTInfo, _ObjectComponent_class);
		EBool    LSuccess = FALSE;

		LTInfo.ProfileGeometries = (EObject**)LObjects.Items;LTInfo.ProfileGeometries_Count = LNObjects;

		E3dModel*  LOrigModel = E3dObject_GetSelectingModel(LObjects.Items[0], FALSE);

// Check if all Objects have the same Model
//
		{
			EIndex  LI = 1;for(;LI < LNObjects;LI++)
			{
				E3dModel*  LSelectingModel = E3dObject_GetSelectingModel(LObjects.Items[LI], FALSE);
				
				if(LSelectingModel != LOrigModel) { LOrigModel = NULL;break; }
			}
		}

		E3dNode*  LParentNode = NULL;

		if(LOrigModel == NULL)
		{
			EListAI(, E3dModel*, LModels);

			EIndex  LI = 0;for(;LI < LNObjects;LI++)
			{
				E3dModel*  LSelectingModel = E3dObject_GetSelectingModel(LObjects.Items[LI], FALSE);
				
				EList_PointerAddAChk(LModels, 16, LSelectingModel);
			}

			LParentNode = E3dNodeHrc_NearestCommonAncestor((E3dNode**)LModels, LModels_Count);
			EList_FreeAChk(LModels);
		}

		E3dObject*  LObject = E3dObject_CustomNew(LScene, LParentNode, LOrigModel, NULL, (E3dObjectComponent*)(&LTInfo), _NewObjectAddingMode, (E3dp_Prefs.Duplicate_Flags & E3dCopyMODEL_UNIQUE_NAME) != 0,  NULL);if(LObject)
		{
			{ E3dGeometry*  LGeometry = E_DynamicCast(LObject, E3dGeometry);if(LGeometry) LGeometry->AutoLOD = FALSE; }

			LSuccess = TRUE;
		  E3dScene_ObjectsUpdateModelList(LScene);
			E3dScene_LayoutOnSchematics(E3d_Scene, TRUE);
		}

		EList_ObjectsFree(LTInfo.ProfileGeometries);

		if(LSuccess)
		{
			if(LShowOptions) E3dObject_Edit(LObject, _ObjectComponent_class);

			E3dWindows_RedrawByChangeFlags(LScene, E3dChgMeshSHAPE|E3dChgHIERARCHY);
		}
	}
	else
	{
		E3dp_PrintMessage(ELogWARNING, 5000, "Select Splines, Face or Meshes to extrude");
	}
}


//========================================
// Entry point of the plugin
//========================================
int E_PluginInit(EPlugin* LPlugin)
{
	_Plugin = LPlugin;

	{
		E3dObjectComponentClass  LClass=
		{
			.Name=        "Extrude",
			.StructSize=  sizeof(E3dInfo),
			.Resources=   EListS_s(_ExtrudeResources),

			.Init=        _Init,
			.Copy=        _Copy,
			.Free=        _Free,
			.Update1=      _Update1,
			.GUI=         _GUI
		};
		_ObjectComponent_class = E3dObjectComponent_ClassRegister(&LClass);
	}

	if(_ObjectComponent_class)
	{
		EpM_CreateIconStates3_sp(Extrude);E_Assign(_Image_Extrude, _ObjectComponent_class->Icon);
		XePushButton_AddImg3(_ToolBarButtons[_ToolBarButtons_Count++], Extrude, "Tool->Surface", "Extrude", '\0', NULL, NULL, TRUE, "Extrude a Spline or a Face along the selected axis to create\na surface, or extrude a Mesh to give it wall thickness.\nRight button: dialog box for this tool", _MCB, (EPointer)0);
		_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Surface", "Extrude", '\0', NULL, NULL, TRUE, NULL, _MCB, (EPointer)0);
	}

	E3d_ExtrudeAlongSplineInit();
	E3d_ExtrudeMeshInit();

	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; }
		{ int  LI = 0;for(;LI < _ToolBarButtons_Count;LI++) { XeItemFree(_ToolBarButtons[LI]); } _ToolBarButtons_Count = 0; }
	}

	if(_ObjectComponent_class) { E3dObjectComponent_ClassDeactivate(_ObjectComponent_class);_ObjectComponent_class = NULL; }

	E3d_ExtrudeAlongSplineExit();
	E3d_ExtrudeMeshExit();

	EpIconFree3(Extrude);

	return(E_SUCCESS);
}
