//  machine.cpp - machine class implementation
//
//  $Id: machine.cpp 53 2014-02-07 19:39:59Z brian $ 
//
//  Copyright (c) 1999-2014 by Precision Micro Control Corp. All rights reserved.
//
#include "stdafx.h"

static char* MachineTypeSpecifier[MAX_MACHINE_TYPES];
static CString MachineTypeName[MAX_MACHINE_TYPES];

static char* ProgramStyleSpecifier[MAX_PROGRAM_STYLES];
static CString ProgramStyleName[MAX_PROGRAM_STYLES];

int iConfigFileNumber = 100;
int iWorkCoorFileNumber = 101;
int iToolDataFileNumber = 102;

IMPLEMENT_SERIAL(CMachine, CDocument, 0 /* schema number*/ )

CMachine::CMachine() :
	XAxis(XAXIS,'X'), YAxis(YAXIS,'Y'), ZAxis(ZAXIS,'Z'),
	UAxis(UAXIS,'U'), VAxis(VAXIS,'V'), WAxis(WAXIS,'W')
{
	TRACE("Constructor: CMachine\r\n");
	
	// Initialize application wide pointer to the machine
	theApp.SetMachine(this);
	
	SetMode(0);
	m_homed = false;
	
	ClearJogState(JOG_ALL_STATES);
	m_cutter_enable_jog = false;
	m_calibrating_joystick = false;	
	m_button_state = 0;
	SetPositionReadoutMode(SHOW_OPTIMAL);
	
	m_status_frame = NULL;		// Initialize to no status frame window or view
	m_status_view = NULL;
	m_output_frame = NULL;		// No output frame window or view
	m_output_view = NULL;
	m_mdi_frame = NULL;			// No MDI frame window
	m_homing_frame = NULL;		// No homing frame window or view
	m_homing_view = NULL;
	
	// Initialize machine type names and specifiers
	MachineTypeName[VMILL] = VMILL_NAME;
	MachineTypeName[HMILL] = HMILL_NAME;
	MachineTypeName[LATHE] = LATHE_NAME;
	MachineTypeSpecifier[VMILL] = VMILL_SPECIFIER;
	MachineTypeSpecifier[HMILL] = HMILL_SPECIFIER;
	MachineTypeSpecifier[LATHE] = LATHE_SPECIFIER;
	
	// Initialize program style names and specifiers
	ProgramStyleName[FANUC] = FANUC_NAME;
	ProgramStyleName[AMTICO] = AMTICO_NAME;
	ProgramStyleSpecifier[FANUC] = FANUC_SPECIFIER;
	ProgramStyleSpecifier[AMTICO] = AMTICO_SPECIFIER;
	
	// Initialize member Application Variables so they can save and restore themselves	
	MachineType.Init(DEFAULT_MACHINE_TYPE, GENERAL_SECTION, MACHINE_TYPE_ENTRY);
	ProgramStyle.Init(DEFAULT_PROGRAM_STYLE, GENERAL_SECTION, PROGRAM_STYLE_ENTRY);
	m_default_units.Init(DEFAULT_UNITS, GENERAL_SECTION, DEFAULT_UNITS_ENTRY);
	m_setup_precision.Init(DEFAULT_SETUP_PRECISION, GENERAL_SECTION, SETUP_PRECISION_ENTRY);
	PartPrgmEnglishDimUnit.Init(DEFAULT_PART_PRGM_ENGLISH_DIM_UNIT, GENERAL_SECTION, PART_PRGM_ENGLISH_DIM_ENTRY);
	PartPrgmEnglishPerUnit.Init(DEFAULT_PART_PRGM_ENGLISH_PER_UNIT, GENERAL_SECTION, PART_PRGM_ENGLISH_PER_ENTRY);
	PartPrgmMetricDimUnit.Init(DEFAULT_PART_PRGM_METRIC_DIM_UNIT, GENERAL_SECTION, PART_PRGM_METRIC_DIM_ENTRY);
	PartPrgmMetricPerUnit.Init(DEFAULT_PART_PRGM_METRIC_PER_UNIT, GENERAL_SECTION, PART_PRGM_METRIC_PER_ENTRY);
	
	m_feed_rate.Init(DEFAULT_DIM_UNIT, DEFAULT_PER_UNIT, 0.0, FEED_SECTION, FEED_RATE_ENTRY);
	m_feed_accel.Init(DEFAULT_DIM_UNIT, DEFAULT_PER_UNIT, 0.0, FEED_SECTION, FEED_ACCEL_ENTRY);
	m_feed_decel.Init(DEFAULT_DIM_UNIT, DEFAULT_PER_UNIT, 0.0, FEED_SECTION, FEED_DECEL_ENTRY);
	m_feed_rate_max.Init(DEFAULT_DIM_UNIT, DEFAULT_PER_UNIT, 0.0, FEED_SECTION, MAX_FEED_RATE_ENTRY);
	m_feed_angle_max.Init(DEGREE_UNIT, 10.0, FEED_SECTION, MAX_ANGLE_ENTRY);
	
	m_jog_max_rate.Init(DEFAULT_DIM_UNIT,DEFAULT_PER_UNIT ,0.0, JOGGING_SECTION,MAX_JOG_RATE_ENTRY);
	m_jog_accel.Init(DEFAULT_DIM_UNIT,DEFAULT_PER_UNIT, 0.0, JOGGING_SECTION,JOG_ACCEL_ENTRY);
	m_jog_decel.Init(DEFAULT_DIM_UNIT,DEFAULT_PER_UNIT, 0.0, JOGGING_SECTION,JOG_DECEL_ENTRY);
	m_joy_use_pc.Init(0,JOGGING_SECTION, USE_PC_JOYSTICK_ENTRY);
	m_joy_deadband.Init(5000,JOGGING_SECTION, JOYSTICK_DEADBAND_ENTRY);
	m_joy_x_center.Init(32767.0,JOGGING_SECTION, JOYSTICK_X_CENTER_ENTRY);
	m_joy_x_upper.Init(65535.0,JOGGING_SECTION, JOYSTICK_X_UPPER_ENTRY);
	m_joy_x_lower.Init(0.0,JOGGING_SECTION, JOYSTICK_X_LOWER_ENTRY);
	m_joy_x_invert.Init(0,JOGGING_SECTION, JOYSTICK_X_INVERT_ENTRY);
	m_joy_y_center.Init(32767.0,JOGGING_SECTION, JOYSTICK_Y_CENTER_ENTRY);
	m_joy_y_upper.Init(65535.0,JOGGING_SECTION, JOYSTICK_Y_UPPER_ENTRY);
	m_joy_y_lower.Init(0.0,JOGGING_SECTION, JOYSTICK_Y_LOWER_ENTRY);
	m_joy_y_invert.Init(0,JOGGING_SECTION, JOYSTICK_Y_INVERT_ENTRY);
	
	m_homing_macro.Init(0, HOMING_SECTION, HOMING_MACRO_ENTRY);
	m_homing_msg_suppress.Init(0, HOMING_SECTION,SUPPRESS_HOMING_MESSAGES_ENTRY);
	
	StartButtonChannel.Init(0 ,EXTERNAL_CONTROLS_SECTION,START_BUTTON_CHANNEL_ENTRY);
	BlockButtonChannel.Init(0, EXTERNAL_CONTROLS_SECTION,BLOCK_BUTTON_CHANNEL_ENTRY);
	FHoldButtonChannel.Init(0, EXTERNAL_CONTROLS_SECTION,FHOLD_BUTTON_CHANNEL_ENTRY);
	StartIndicatorChannel.Init(0, EXTERNAL_CONTROLS_SECTION,START_INDICATOR_CHANNEL_ENTRY);
	BlockIndicatorChannel.Init(0, EXTERNAL_CONTROLS_SECTION,BLOCK_INDICATOR_CHANNEL_ENTRY);
	FHoldIndicatorChannel.Init(0, EXTERNAL_CONTROLS_SECTION,FHOLD_INDICATOR_CHANNEL_ENTRY);
	
	EStopInputChannel.Init(0, EXTERNAL_CONTROLS_SECTION,E_STOP_INPUT_CHANNEL_ENTRY);
	ExtEStopInputChannel.Init(0, EXTERNAL_CONTROLS_SECTION,EXT_E_STOP_INPUT_CHANNEL_ENTRY);
	DriveFaultInputChannel.Init(0, EXTERNAL_CONTROLS_SECTION,DRIVE_FAULT_INPUT_CHANNEL_ENTRY);
	BlockSkipSwitchChannel.Init(0, EXTERNAL_CONTROLS_SECTION,BLOCK_SKIP_SWITCH_CHANNEL_ENTRY);
	DryrunSwitchChannel.Init(0, EXTERNAL_CONTROLS_SECTION,DRYRUN_SWITCH_CHANNEL_ENTRY);

	for (int i = FIRST_WORKCOOR; i <= LAST_WORKCOOR; i++)
		m_work_coords[WorkCoord::ConvertToIndex(i)].Init(i);

	for (int i = FIRST_TOOL; i <= LAST_TOOL; i++)
		m_tools[Tool::ConvertToIndex(i)].Init(i);
		
	DOffsetTable.Init('D');
	HOffsetTable.Init('H');

	MtnCtrlr.Open();		// Open binary and ASCII interfaces to motion controller
	
	// Issue Prompt Character and Echo oFf commands to controller
	MtnCtrlr.PutS("PC0,EF\r");
	
	// Issue Format command to erase all files on board
	MtnCtrlr.PutS("FO\r");

	// Disable display of text during G-code execution, individual
	//  displays can be reenabled depending on views open	
	DisableAllOutput();
	
	// Since all part files have been erased from controller,
	//  set flag to false
	SetPartLoadedFlag(false);
	m_part_program_name = "";
	SetPartRunningFlag(false);	
	SetPartSuspendedFlag(false);	
}


CMachine::~CMachine()
{
	char sCmdBuf[80];

	TRACE("Destructor: CMachine\r\n");
	
	// If a part program is currently running, get motion controller to
	//  terminate run
	if (GetPartRunningFlag() == TRUE)
	{
		MtnCtrlr.RunTerminate();
		while (!MtnCtrlr.Rdy())
			;
		SetPartRunningFlag(false);
		SetPartSuspendedFlag(false);		// Terminating a run also clears suspended state
	}

	// Disable display of certain text during G-code execution
	// leave controller in default state of just displaying block text
	DisableAllOutput();
	MtnCtrlr.DisplayBlockText(true);
	
	// Issue Prompt Character and Echo oN commands to controller
	sprintf(sCmdBuf,"PC62,EN\r");
	MtnCtrlr.PutS(sCmdBuf);
	
	while (MtnCtrlr.GetS(sCmdBuf, sizeof(sCmdBuf)))
		;

	MtnCtrlr.Close();			// Close motion controller for program termination
	
	theApp.SetMachine(NULL);	// Clear global machine pointer to let others
								//  know the machine doesn't exist anymore
}                          

void CMachine::Initialize()
{
	XAxis.Initialize(DEFAULT_X_CTRLR_AXIS);
	YAxis.Initialize(DEFAULT_Y_CTRLR_AXIS);
	ZAxis.Initialize(DEFAULT_Z_CTRLR_AXIS);
	UAxis.Initialize(DEFAULT_U_CTRLR_AXIS);
	VAxis.Initialize(DEFAULT_V_CTRLR_AXIS);
	WAxis.Initialize(DEFAULT_W_CTRLR_AXIS);
	
	m_spindle.Initialize(DEFAULT_S_CTRLR_AXIS);

	m_cutter.Initialize(DEFAULT_C_CTRLR_AXIS);
	
	for (int i = FIRST_WORKCOOR; i <= LAST_WORKCOOR; i++)
		m_work_coords[WorkCoord::ConvertToIndex(i)].Initialize();
	
	for (int i = FIRST_TOOL; i <= LAST_TOOL; i++)
		m_tools[Tool::ConvertToIndex(i)].Initialize();

	DOffsetTable.Initialize();
	HOffsetTable.Initialize();

	SetFeedRateOverride(1.0);
	SetRapidOverride(1.0);
	SetSpindleOverride(1.0);
	
	DisableBlockSkip();
	DisableDryrun();
}

BOOL CMachine::OnNewDocument()
{
	Initialize();
	
	InitializeController();		// Initialize motion controller with current setup
	
	if (!CDocument::OnNewDocument())
		return FALSE;

	return TRUE;
}

BOOL CMachine::OnOpenDocument(const char* file_path)
{
	TRACE("OnOpenDocument()\r\n");
	
	Initialize();
	
	RestoreGeneral(file_path);			// Restore general data from machine file
	RestoreFeed(file_path);				// Restore feed data from machine file
	
	// Load axis specific data from machine file
	XAxis.RestoreData(file_path);
	YAxis.RestoreData(file_path);
	ZAxis.RestoreData(file_path);
	UAxis.RestoreData(file_path);
	VAxis.RestoreData(file_path);
	WAxis.RestoreData(file_path);
	m_spindle.RestoreData(file_path);
	m_cutter.RestoreData(file_path);
	
	RestoreWorkCoor(file_path);			// Restore Work Coordinate data from machine file
	RestoreToolData(file_path);			// Restore Tool data from machine file
	RestoreOffsets(file_path);			// Restore Tool Offsets from machine file
	RestoreJogging(file_path);			// Restore Jogging data from machine file
	RestoreHoming(file_path);			// Restore Homing data from machine file
	RestoreExternalControls(file_path);	// Restore External Controls data from machine file
	
	if (!CDocument::OnOpenDocument(file_path))
		return FALSE;
	else
	{
		// Set path name for this document. This is normally done by CMultiDocTemplate::OpenDocumentFile() 
		// after return of this this function. Unfortunately, we need it done now for the call to 
		// InitializeController().
		SetPathName(file_path);
		
		// Initialize motion controller with current setup
		InitializeController();
		
		return TRUE;
	}
}

BOOL CMachine::OnSaveDocument(const char* file_path)
{
	SaveGeneral(file_path);				// Save general data in machine file
	SaveJogging(file_path);				// Save Jogging data in machine file
	SaveHoming(file_path);				// Save Homing data in machine file
	SaveExternalControls(file_path);	// Save External Controls data in machine file
	SaveFeed(file_path);				// Save feed data in machine file
	
	// Store Axis specific data in machine file
	XAxis.SaveData(file_path);
	YAxis.SaveData(file_path);
	ZAxis.SaveData(file_path);
	UAxis.SaveData(file_path);
	VAxis.SaveData(file_path);
	WAxis.SaveData(file_path);
	m_spindle.SaveData(file_path);
	m_cutter.SaveData(file_path);

	SaveWorkCoor(file_path);			// Save Work Coordinate data in machine file
	SaveToolData(file_path);			// Save Tool data in machine file
	SaveOffsets(file_path);				// Save Tool Offsets in machine file
	
	return TRUE;
}

void CMachine::SaveGeneral(const char* file_path)
{
	// Delete contents of the section from file in preparation for new data
	MachineType.Erase(file_path);

	MachineType.Save(file_path);
	ProgramStyle.Save(file_path);
	m_default_units.Save(file_path);
	m_setup_precision.Save(file_path);
	PartPrgmEnglishDimUnit.Save(file_path);
	PartPrgmEnglishPerUnit.Save(file_path);
	PartPrgmMetricDimUnit.Save(file_path);
	PartPrgmMetricPerUnit.Save(file_path);
}

void CMachine::RestoreGeneral(const char* file_path)
{
	// If Machine Type is not in machine file, Restore() will set it to 0
	MachineType.Restore(file_path);

	// If Program Style is not in machine file, Restore() will set it to 0
	ProgramStyle.Restore(file_path);

	// If Default Units is not in machine file, Restore() will set it to 0
	m_default_units.Restore(file_path);
	
	m_setup_precision.Restore(file_path);
	// If Setup Precision is not in machine file set to default
	if (GetSetupPrecision() <= 0)
		SetSetupPrecision(DEFAULT_SETUP_PRECISION);
	
	PartPrgmEnglishDimUnit.Restore(file_path);
	PartPrgmEnglishPerUnit.Restore(file_path);
	PartPrgmMetricDimUnit.Restore(file_path);
	PartPrgmMetricPerUnit.Restore(file_path);
}

void CMachine::SaveFeed(const char* file_path)
{
	// Delete contents of the section from file in preparation for new data
	m_feed_rate.Erase(file_path);

	m_feed_rate.Save(file_path, GetSetupPrecision());
	m_feed_accel.Save(file_path, GetSetupPrecision());
	m_feed_decel.Save(file_path, GetSetupPrecision());
	m_feed_rate_max.Save(file_path, GetSetupPrecision());
	m_feed_angle_max.Save(file_path, GetSetupPrecision());
}

void CMachine::RestoreFeed(const char* file_path)
{
	m_feed_rate.Restore(file_path);
	m_feed_accel.Restore(file_path);
	m_feed_decel.Restore(file_path);
	m_feed_rate_max.Restore(file_path);
	m_feed_angle_max.Restore(file_path);
}

void CMachine::SaveWorkCoor(const char* file_path)
{
	for (int i = FIRST_WORKCOOR; i <= LAST_WORKCOOR; i++)
		m_work_coords[WorkCoord::ConvertToIndex(i)].SaveData(file_path);
}

void CMachine::RestoreWorkCoor(const char* file_path)
{
	for (int i = FIRST_WORKCOOR; i <= LAST_WORKCOOR; i++)
		m_work_coords[WorkCoord::ConvertToIndex(i)].RestoreData(file_path);
}

void CMachine::SaveToolData(const char* file_path)
{
	for (int i = FIRST_TOOL; i <= LAST_TOOL; i++)
		m_tools[Tool::ConvertToIndex(i)].SaveData(file_path);
}

void CMachine::RestoreToolData(const char* file_path)
{
	for (int i = FIRST_TOOL; i <= LAST_TOOL; i++)
		m_tools[Tool::ConvertToIndex(i)].RestoreData(file_path);
}

void CMachine::SaveOffsets(const char* file_path)
{
	DOffsetTable.SaveData(file_path);
	HOffsetTable.SaveData(file_path);
}

void CMachine::RestoreOffsets(const char* file_path)
{
	DOffsetTable.RestoreData(file_path);
	HOffsetTable.RestoreData(file_path);
}

void CMachine::SaveJogging(const char* file_path)
{
	// Delete contents of the section from file in preparation for new data
	m_jog_max_rate.Erase(file_path);

	m_jog_max_rate.Save(file_path, GetSetupPrecision());
	m_jog_accel.Save(file_path, GetSetupPrecision());
	m_jog_decel.Save(file_path, GetSetupPrecision());
	m_joy_use_pc.Save(file_path);
	m_joy_deadband.Save(file_path);
	m_joy_x_center.Save(file_path, GetSetupPrecision());
	m_joy_x_upper.Save(file_path, GetSetupPrecision());
	m_joy_x_lower.Save(file_path, GetSetupPrecision());
	m_joy_x_invert.Save(file_path);
	m_joy_y_center.Save(file_path, GetSetupPrecision());
	m_joy_y_upper.Save(file_path, GetSetupPrecision());
	m_joy_y_lower.Save(file_path, GetSetupPrecision());
	m_joy_y_invert.Save(file_path);
}

void CMachine::RestoreJogging(const char* file_path)
{
	m_jog_max_rate.Restore(file_path);
	m_jog_accel.Restore(file_path);
	m_jog_decel.Restore(file_path);
	// If UsePCJoystick flag is not in machine file, Restore() will set it to 0
	m_joy_use_pc.Restore(file_path);
	m_joy_deadband.Restore(file_path);
	m_joy_x_center.Restore(file_path);
	m_joy_x_upper.Restore(file_path);
	m_joy_x_lower.Restore(file_path);
	m_joy_x_invert.Restore(file_path);
	m_joy_y_center.Restore(file_path);
	m_joy_y_upper.Restore(file_path);
	m_joy_y_lower.Restore(file_path);
	m_joy_y_invert.Restore(file_path);
	
	// Force joystick limits to reasonable numbers. Zero values will
	// occur if machine file doesn't contain joystick sections
	if ((double)m_joy_x_center == 0.0)
		m_joy_x_center = 32767.0;
	if ((double)m_joy_x_upper == 0.0)
		m_joy_x_upper = 65535.0;
	if ((double)m_joy_y_center == 0.0)
		m_joy_y_center = 32767.0;
	if ((double)m_joy_y_upper == 0.0)
		m_joy_y_upper = 65535.0;
}

void CMachine::SaveHoming(const char* file_path)
{
	// Delete contents of the section from file in preparation for new data
	m_homing_macro.Erase(file_path);

	m_homing_macro.Save(file_path);
	m_homing_msg_suppress.Save(file_path);
}

void CMachine::RestoreHoming(const char* file_path)
{
	m_homing_macro.Restore(file_path);
	// If SuppressHomingMessages flag is not in machine file, Restore() will set it to 0
	m_homing_msg_suppress.Restore(file_path);
}

void CMachine::SaveExternalControls(const char* file_path)
{
	// Delete contents of the section from file in preparation for new data
	StartButtonChannel.Erase(file_path);

	StartButtonChannel.Save(file_path);
	BlockButtonChannel.Save(file_path);
	FHoldButtonChannel.Save(file_path);
	StartIndicatorChannel.Save(file_path);
	BlockIndicatorChannel.Save(file_path);
	FHoldIndicatorChannel.Save(file_path);
	EStopInputChannel.Save(file_path);
	ExtEStopInputChannel.Save(file_path);
	DriveFaultInputChannel.Save(file_path);
	BlockSkipSwitchChannel.Save(file_path);
	DryrunSwitchChannel.Save(file_path);
}

void CMachine::RestoreExternalControls(const char* file_path)
{
	// If value is not in machine file, Restore() will set it to 0
	StartButtonChannel.Restore(file_path);
	BlockButtonChannel.Restore(file_path);
	FHoldButtonChannel.Restore(file_path);
	StartIndicatorChannel.Restore(file_path);
	BlockIndicatorChannel.Restore(file_path);
	FHoldIndicatorChannel.Restore(file_path);
	EStopInputChannel.Restore(file_path);
	ExtEStopInputChannel.Restore(file_path);
	DriveFaultInputChannel.Restore(file_path);
	BlockSkipSwitchChannel.Restore(file_path);
	DryrunSwitchChannel.Restore(file_path);
}

BEGIN_MESSAGE_MAP(CMachine, CDocument)
	//{{AFX_MSG_MAP(CMachine)
	ON_COMMAND(ID_FILE_LOAD, OnFileLoad)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

void CMachine::Serialize(CArchive& ar)
{
	// Document serialization not used, see OnOpenDocument and OnCloseDocument
	//  member functions
}

void CMachine::InitializeController(void)
{
	TRACE("InitializeController()\r\n");
	
	InitializeConfiguration();
	InitializeWorkCoordinates();
	InitializeToolData();
}

void CMachine::InitializeConfiguration(void)
{
	char machine_name[MCHFILENAMELENGTH];
	char cfg_file_name[CFGFILENAMELENGTH];

	BeginWaitCursor();				// Change cursor to hour glass

	// Get machine title (ie. file name), convert to machine name
	_snprintf(machine_name, sizeof(machine_name), "%s", (const char*)GetTitle());
	// Terminate title at period (if there is one)
	char* chr;
	if ((chr = strchr(machine_name, '.')) != NULL)
		*chr = '\0';
		
	// Convert machine name to configuration file name
	_snprintf(cfg_file_name, sizeof(cfg_file_name), "%s%s", machine_name, ".CFG");
	
	// Open configuration file for writing
	std::fstream cfg_file;
	cfg_file.open(cfg_file_name, std::ios::out);
	if (cfg_file.fail())
	{
		TRACE("Unable to open configuration file\r\n");
		cfg_file.clear();
		return;
	}
	else
	{
		// Place comment header in configuration file
		cfg_file << ";" << machine_name << " Configuration file" << std::endl;
		cfg_file << ";" << " generated: " << CTime::GetCurrentTime().Format("%B %d, %Y   %I:%M:%S%p") << std::endl;
		
		// Set precision of values placed in configuration file
		cfg_file.precision(GetSetupPrecision());
		
		// Specify machine type
		cfg_file << "MACHINETYPE=" << MachineTypeSpecifier[(int)MachineType] << std::endl;
		
		// Specify part program style (compatibility)
		cfg_file << "PROGRAMCOMP=" << ProgramStyleSpecifier[(int)ProgramStyle] << std::endl;
		
		// Load motion controller configuration into file
		ConfigureController(cfg_file);

		// Get axis and spindle objects to configure themselves	
		XAxis.Configure(cfg_file);
		YAxis.Configure(cfg_file);
		ZAxis.Configure(cfg_file);
		UAxis.Configure(cfg_file);
		VAxis.Configure(cfg_file);
		WAxis.Configure(cfg_file);
		
		m_spindle.Configure(cfg_file);
		
		m_cutter.Configure(cfg_file);
		
		// Place defaults units specifier as last line of file
		cfg_file << ";Default units specifier" << std::endl;
		cfg_file << (const char*)UnitsName[GetDefaultUnits()] << std::endl;
		
		// Close configuration file
		cfg_file.close();
	}
	
	// If a part program is currently running, get motion controller to
	//  terminate run
	if (GetPartRunningFlag() == true)
	{
		MtnCtrlr.RunTerminate();
		while(!MtnCtrlr.Rdy())
			;
		SetPartRunningFlag(false);
		SetPartSuspendedFlag(false);		// Terminating a run also clears suspended state
	}
	
	// Load configuration file on motion controller
	MtnCtrlr.LoadFile(cfg_file_name, iConfigFileNumber);
	
	// Issue Run Configuration File command to controller
	char cmd_buf[80];
	_snprintf(cmd_buf, sizeof(cmd_buf), "RCF%d\r", iConfigFileNumber);
	TRACE("Motion Controller Run Configuration...\r\n");
	//TRACE("%s", cmd_buf);
	MtnCtrlr.PutS(cmd_buf);
	
	// Read reply strings from board, print to trace window
	while (MtnCtrlr.GetS(cmd_buf, sizeof(cmd_buf)))
		TRACE("%s", cmd_buf);
	TRACE("DONE\r\n");
	
   EndWaitCursor();				// Return to regular cursor
}

void CMachine::ConfigureController(std::fstream& cfg_file)
{
	// Place comment in configuration file
	cfg_file << ";External controls configuration" << std::endl;
	// Configure external controls
	cfg_file << "ESTOPCHANNEL=" << (int)EStopInputChannel << std::endl;
	cfg_file << "EESTOPCHANNEL=" << (int)ExtEStopInputChannel << std::endl;
	cfg_file << "DRIVEFAULTCHANNEL=" << (int)DriveFaultInputChannel << std::endl;
	
	// Place comment in configuration file
	cfg_file << ";Feed configuration" << std::endl;
	
	// Setup for configuration data in both english and metric units
	for (int units = ENGLISH; units <= METRIC; units++)
	{
		DIM_UNIT DimUnit = UNDEFINED_DIM_UNIT;
		PER_UNIT PerUnit = UNDEFINED_PER_UNIT;

		// Specify units for succeeding configuration data
		cfg_file << (const char*) UnitsName[units] << std::endl;
		
		if (units == ENGLISH)
		{
			DimUnit = PartPrgmEnglishDimUnit.Get();
			PerUnit = PartPrgmEnglishPerUnit.Get();
		}
		else if (units == METRIC)
		{
			DimUnit = PartPrgmMetricDimUnit.Get();
			PerUnit = PartPrgmMetricPerUnit.Get();
		}
		
		cfg_file << "FEEDRATE=" << m_feed_rate.Get(DimUnit, PerUnit) << std::endl;
		cfg_file << "FEEDACCEL=" << m_feed_accel.Get(DimUnit, PerUnit) << std::endl;
		cfg_file << "FEEDDECEL=" << m_feed_decel.Get(DimUnit, PerUnit) << std::endl;
		cfg_file << "MAXRATE=" << m_feed_rate_max.Get(DimUnit, PerUnit) << std::endl;
		cfg_file << "INCREMENT=" << m_feed_angle_max.Get(DEGREE_UNIT) << std::endl;
	}
}

void CMachine::InitializeWorkCoordinates(void)
{
	char machine_name[MCHFILENAMELENGTH];
	char work_coord_file_name[CFGFILENAMELENGTH];

	// Get machine title (ie. file name), convert to machine name
	_snprintf(machine_name, sizeof(machine_name), "%s", (const char*)GetTitle());
	// Terminate title at period (if there is one)
	char* chr;
	if ((chr = strchr(machine_name, '.')) != NULL)
		*chr = '\0';
		
	// Convert machine name to configuration file name
	_snprintf(work_coord_file_name, sizeof(work_coord_file_name), "%s%s", machine_name, WORKCOORFILEEXT);

	// Open work coordinate file for writing
	std::fstream work_coor_file;
	work_coor_file.open(work_coord_file_name, std::ios::out);
	if (work_coor_file.fail())
	{
		TRACE("Unable to open work coordinate file\r\n");
		work_coor_file.clear();
		return;
	}
	else
	{
		// Place comment header in work coordinate file
		work_coor_file << ";" << machine_name << " Work Coordinate file" << std::endl;
		work_coor_file << ";" << " generated: " << CTime::GetCurrentTime().Format("%B %d, %Y   %I:%M:%S%p") << std::endl;
		
		// Set precision of values placed in work coordinate file
		work_coor_file.precision(GetSetupPrecision());
		
		// Get work coordinate objects to configure themselves	
		for (int i = FIRST_WORKCOOR; i <= LAST_WORKCOOR; i++)
			m_work_coords[WorkCoord::ConvertToIndex(i)].Configure(work_coor_file);
		
		// Close work coordinate file
		work_coor_file.close();
	}
	
	// If a part program is currently running, get motion controller to terminate run
	if (GetPartRunningFlag() == true)
	{
		MtnCtrlr.RunTerminate();
		while(!MtnCtrlr.Rdy());
		SetPartRunningFlag(false);
		SetPartSuspendedFlag(false);		// Terminating a run also clears suspended state
	}
	
	// Load work coordinate file on motion controller
	MtnCtrlr.LoadFile(work_coord_file_name, iWorkCoorFileNumber);
	
	// Issue Run Work coordinate File command to controller
	char cmd_buf[80];
	_snprintf(cmd_buf, sizeof(cmd_buf), "RWF%d\r", iWorkCoorFileNumber);
	TRACE("Work Coordinates Initialization...\r\n");
	//TRACE("%s",cmd_buf);
	MtnCtrlr.PutS(cmd_buf);
	
	// Read reply strings from board, print to trace window
	while (MtnCtrlr.GetS(cmd_buf, sizeof(cmd_buf)))
		TRACE("%s", cmd_buf);
	TRACE("DONE\r\n");
}

void CMachine::InitializeToolData(void)
{
	char machine_name[MCHFILENAMELENGTH];
	char tool_data_file_name[CFGFILENAMELENGTH];

	// Get machine title (ie. file name), convert to machine name
	_snprintf(machine_name, sizeof(machine_name), "%s", (const char*)GetTitle());
	// Terminate title at period (if there is one)
	char* chr;
	if ((chr = strchr(machine_name, '.')) != NULL)
		*chr = '\0';
		
	// Convert machine name to configuration file name
	_snprintf(tool_data_file_name, sizeof(tool_data_file_name), "%s%s", machine_name, TOOLDATAFILEEXT);

	// Open tool data file for writing
	std::fstream tool_data_file;
	tool_data_file.open(tool_data_file_name, std::ios::out);
	if (tool_data_file.fail())
	{
		TRACE("Unable to open tool data file\r\n");
		tool_data_file.clear();
		return;
	}
	else
	{
		// Place comment header in tool data file
		tool_data_file << ";" << machine_name << " Tool data file" << std::endl;
		tool_data_file << ";" << " generated: " << CTime::GetCurrentTime().Format("%B %d, %Y   %I:%M:%S%p") << std::endl;
		
		// Set precision of values placed in tool data file
		tool_data_file.precision(GetSetupPrecision());
		
		// Get tool objects to configure themselves	
		for (int i = FIRST_TOOL; i <= LAST_TOOL; i++)
			m_tools[Tool::ConvertToIndex(i)].Configure(tool_data_file);
			
		DOffsetTable.Configure(tool_data_file);
		HOffsetTable.Configure(tool_data_file);
			
		// Close tool data file
		tool_data_file.close();
	}

	// If a part program is currently running, get motion controller to terminate run
	if (GetPartRunningFlag() == true)
	{
		MtnCtrlr.RunTerminate();
		while(!MtnCtrlr.Rdy());
		SetPartRunningFlag(false);
		SetPartSuspendedFlag(false);		// Terminating a run also clears suspended state
	}
	
	// Load tool data file on motion controller
	MtnCtrlr.LoadFile(tool_data_file_name, iToolDataFileNumber);
	
	// Issue Run Tool data File command to controller
	char cmd_buf[80];
	_snprintf(cmd_buf, sizeof(cmd_buf), "RTF%d\r", iToolDataFileNumber);
	TRACE("Tool Data Initialization...\r\n");
	//TRACE("%s", cmd_buf);
	MtnCtrlr.PutS(cmd_buf);
	
	// Read reply strings from board, print to trace window
	while (MtnCtrlr.GetS(cmd_buf, sizeof(cmd_buf)))
		TRACE("%s", cmd_buf);
	TRACE("DONE\r\n");
}

void CMachine::OnFileLoad()
{
	FileLoad();
}

void CMachine::FileLoad(void)
{
	if (GetMode() != RUN_MODE)
	{
		CString Message;
		Message.LoadString(IDS_MACHINE_IN_RUN_MODE_TO_LOAD_FILE_MESSAGE);
		AfxMessageBox((const char *)Message,MB_OK | MB_APPLMODAL,0);
		return;
	}
	
	CString title("File Load");		// Window title
	CString filter;
	filter = "Part Files (*.prt)";
	filter += (char)'\0';			// next string
	filter += "*.prt";
	filter += (char)'\0';			// next string
	filter += "All Files (*.*)";
	filter += (char)'\0';			// next string
	filter += "*.*";
	filter += (char)'\0';			// last string
	
	char filename[_MAX_PATH];
	filename[0] = '\0';				// No default file name

	CFileDialog dlgFile(true);
	dlgFile.m_ofn.lpstrFilter = filter;
	dlgFile.m_ofn.hwndOwner = AfxGetMainWnd()->GetSafeHwnd();
	dlgFile.m_ofn.lpstrTitle = title;
	dlgFile.m_ofn.lpstrFile = filename;
	dlgFile.m_ofn.nMaxFile = sizeof(filename);
                                                           
	if (dlgFile.DoModal() != IDOK)
		return;
		
	BeginWaitCursor();				// Change cursor to hour glass
	TRACE("Loading: %s\r\n", filename);

	// Open file for reading
	std::fstream theFile;
	theFile.open(filename, std::ios::in);
	if (theFile.fail())
	{
		CString Message;
		Message.LoadString(IDS_UNABLE_TO_OPEN_FILE_MESSAGE);
		AfxMessageBox((const char *)Message, MB_OK | MB_APPLMODAL,0);
		theFile.clear();
		return;
	}
	else
	{
		// If a part program is currently running, get motion controller to
		//  terminate run
		if (GetPartRunningFlag() == true)
		{
			MtnCtrlr.RunTerminate();
			while (!MtnCtrlr.Rdy())
				;
			SetPartRunningFlag(false);
			SetPartSuspendedFlag(false);		// Terminating a run also clears suspended state
		}

		SetPartLoadedFlag(FALSE);
		
		// Issue Load Fanuc part program command to controller
		char cmd_buf[40];
		_snprintf(cmd_buf, sizeof(cmd_buf), "LOF\r");
		MtnCtrlr.PutS(cmd_buf);
			
		// Wait for controller to be ready fo file loading
		while (!MtnCtrlr.RdyFile())
			Sleep(25);

		int file_number = 0;
		bool first_line_recorded = false;

		// Copy file to motion controller
		do
		{
			// Get line of text from stream file, newline character is discarded by getline()
			char line_buf[PARTMAXLINELENGTH];
			theFile.getline(line_buf, sizeof(line_buf), '\n');
			if (!theFile.eof())
			{
				if (!first_line_recorded)		// Record first line of file for display
				{
					m_part_program_first_line = line_buf;
					first_line_recorded = true;					
				}				
														// Check for File number "Word" (ie. Onnnn or :nnnn or Nnnnn)
				if (!file_number && ((toupper(line_buf[0]) == 'O') || (line_buf[0] == ':') || (toupper(line_buf[0]) == 'N')))
					sscanf(&line_buf[1], "%d", &file_number);
		
				int len = strlen(line_buf);
				line_buf[len++]='\n';		// Append newline to strings
				line_buf[len++]='\0';
				MtnCtrlr.PutS(line_buf);		// Send string to motion controller
				//TRACE("%s\r", sLineBuf);
			}
		} while (!theFile.eof());
		
		// Close file
		theFile.close();
		// Terminate load of file
		_snprintf(cmd_buf, sizeof(cmd_buf), "\x01A");
		MtnCtrlr.PutS(cmd_buf);
		
		DWORD delay = GetTickCount();
		while (GetTickCount() < (delay + 500))
			;
		// Read any response from controller
		while (MtnCtrlr.GetS(cmd_buf, sizeof(cmd_buf)))
			;
		
		if (file_number)
		{
			SetPartLoadedFlag(true);
			m_part_program_name = filename;
			m_part_program_file_number = file_number;

			// Issue the RUF command to the motion controller with the file number as a 
			// negative value. This causes the controller to delay starting of execution 
			// until initiated by IST command.
			_snprintf(cmd_buf, sizeof(cmd_buf), "RUF%d\r", -file_number);
			MtnCtrlr.PutS(cmd_buf);
			SetPartRunningFlag(true);

			TRACE("Loaded File: %s\r\n", (const char *)m_part_program_name);

			if (m_output_frame && m_output_view)
			{
				m_output_view->Erase();	// Clear output view
				// Show first line of part program
				m_output_view->OutputText((const char *)m_part_program_first_line);
				m_output_view->OutputText("\r");
			}

			EndWaitCursor();				// Return to regular cursor
		}
		else
		{
			EndWaitCursor();
			CString Message;
			Message.LoadString(IDS_NO_PROGRAM_OR_SEQUENCE_NUMBER_MESSAGE);
			AfxMessageBox((const char *)Message,MB_OK | MB_APPLMODAL,0);
		}
	}	
}


void CMachine::FileRestart(void)
{
	if (GetMode() != RUN_MODE)
	{
		CString Message;
		Message.LoadString(IDS_MACHINE_IN_RUN_MODE_TO_RESTART_FILE_MESSAGE);
		AfxMessageBox((const char *)Message, MB_OK | MB_APPLMODAL,0);
		return;
	}
	
	if (GetPartLoadedFlag() == false)
	{
		// If no part file is currently loaded, call file load function
		FileLoad();
		return;
	}
	else
	{
	   BeginWaitCursor();				// Change cursor to hour glass
	   
		// Terminate running of currently loaded part file
		MtnCtrlr.RunTerminate();
		while(!MtnCtrlr.Rdy())
			;
		SetPartRunningFlag(false);
		SetPartSuspendedFlag(false);	// Terminating a run also clears suspended state
		
		char cmd_buf[40];
		_snprintf(cmd_buf, sizeof(cmd_buf), "RUF%d\r", -m_part_program_file_number);
		MtnCtrlr.PutS(cmd_buf);
		SetPartRunningFlag(true);
		
		TRACE("Restarted: %s\r\n", (const char *)m_part_program_name);
		
		if (m_output_frame && m_output_view)
		{
			m_output_view->Erase();		// Clear output view
			// Show first line of part program
			m_output_view->OutputText((const char *)m_part_program_first_line);
			m_output_view->OutputText("\r");
		}
		
	   EndWaitCursor();					// Return to regular cursor
	}	
}


// Load Edit View (ie. Part program)
void CMachine::LoadEditView(CEditView* edit_view)
{
	if (GetMode() != RUN_MODE)
	{
		CString Message;
		Message.LoadString(IDS_MACHINE_IN_RUN_MODE_TO_LOAD_FILE_MESSAGE);
		AfxMessageBox((const char *)Message, MB_OK | MB_APPLMODAL,0);
		return;
	}
	
   BeginWaitCursor();				// Change cursor to hour glass
   
	// If a part program is currently running, get motion controller to terminate run
	if (GetPartRunningFlag() == true)
	{
		MtnCtrlr.RunTerminate();
		while (!MtnCtrlr.Rdy())
			;
		SetPartRunningFlag(false);
		SetPartSuspendedFlag(false);		// Terminating a run also clears suspended state
	}

	SetPartLoadedFlag(FALSE);
	
	// Issue Load Fanuc part program command to controller
	char cmd_buf[40];
	_snprintf(cmd_buf, sizeof(cmd_buf), "LOF\r");
	MtnCtrlr.PutS(cmd_buf);
	
	// Wait for controller to be ready fo file loading
	while (!MtnCtrlr.RdyFile())
		Sleep(25);

	bool first_line_recorded = false;
	int file_number = 0;

	// Get text strings from edit view and send to motion controller
	int line_count = (edit_view->GetEditCtrl()).GetLineCount();
	for (int i = 0; i < line_count; i++)
	{
		char line_buf[PARTMAXLINELENGTH];

		int len = (edit_view->GetEditCtrl()).GetLine(i, line_buf, PARTMAXLINELENGTH);
		line_buf[len] = '\0';				// Terminate string with a NULL character
		
		if (!first_line_recorded)			// Record first line of file for display
		{
			m_part_program_first_line = line_buf;
			first_line_recorded = true;					
		}
		
		// Check for File number "Word" (ie. Onnnn or :nnnn or Nnnnn)
		if (!file_number && ((toupper(line_buf[0]) == 'O') || (line_buf[0] == ':') || (toupper(line_buf[0]) == 'N')))
			sscanf(&line_buf[1], "%d", &file_number);
		
		line_buf[len++] = '\n';				// Append newline to strings
		line_buf[len++] = '\0';
		MtnCtrlr.PutS(line_buf);			// Send string to motion controller
		//TRACE("%s\r", line_buf);
	}
	
	_snprintf(cmd_buf, sizeof(cmd_buf), "\x01A");		// Terminate load of file
	MtnCtrlr.PutS(cmd_buf);

	DWORD delay = GetTickCount();
	while (GetTickCount() < (delay + 500))
		;
	
	// Read any response from controller
	while (MtnCtrlr.GetS(cmd_buf, sizeof(cmd_buf)))
		;
	
	if (file_number)
	{
		SetPartLoadedFlag(true);
		m_part_program_name = edit_view->GetDocument()->GetPathName();
		m_part_program_file_number = file_number;
		
		// Issue the RUF command to the motion controller with the file number as a negative value. This
		// causes the controller to delay starting of execution until initiated by IST command.
		_snprintf(cmd_buf, sizeof(cmd_buf), "RUF%d\r", -file_number);
		MtnCtrlr.PutS(cmd_buf);
		SetPartRunningFlag(true);
		
		TRACE("Loaded Edit View: %s\r\n", (const char *)m_part_program_name);
		
		if (m_output_frame && m_output_view)
		{
			m_output_view->Erase();	// Clear output view
			// Show first line of part program
			m_output_view->OutputText((const char *)m_part_program_first_line);
			m_output_view->OutputText("\r");
		}
		EndWaitCursor();				// Return to regular cursor
	}
	else
	{
		EndWaitCursor();				// Return to regular cursor
		CString Message;
		Message.LoadString(IDS_NO_PROGRAM_OR_SEQUENCE_NUMBER_MESSAGE);
		AfxMessageBox((const char *)Message, MB_OK | MB_APPLMODAL,0);
	}
}

void CMachine::Start(void)
{
	switch (GetMode()) 
	{
		case RUN_MODE:
			if (RunReady())
				MtnCtrlr.InitStart();
			break;
			
		case HOME_MODE:
			if (m_homing_view)
			{
				CString Message;
				m_homing_view->Erase();
				Message.LoadString(IDS_HOMING_MESSAGE);
				m_homing_view->OutputText((const char *)Message);
			}
			if ((int)m_homing_macro)
			{
				// Issue Macro Call command to controller
				char cmd_buf[40];
				_snprintf(cmd_buf, sizeof(cmd_buf), "MC%d\r", (int)m_homing_macro);
				MtnCtrlr.PutS(cmd_buf);
			}
			m_homed = true;
			break;
	}
}

void CMachine::Block(void)
{
	switch (GetMode()) 
	{
		case RUN_MODE:
			if (RunReady())
			{
				MtnCtrlr.InitBlockByBlock();
			}
			break;
	}
}

void CMachine::FHold(void)
{
	switch (GetMode()) 
	{
		case RUN_MODE:
			if (RunReady())
			{
				MtnCtrlr.InitFeedHold();
			}
			break;
	}
}

bool CMachine::RunReady(void)
{
	if (GetPartLoadedFlag() == false)
	{
		CString Message;
		Message.LoadString(IDS_NO_PART_PROGRAM_IS_LOADED_MESSAGE);
		AfxMessageBox((const char *)Message, MB_OK | MB_APPLMODAL,0);
		return false;
	}
	
	if (!(MtnCtrlr.GetRunStatus() & (READY | RUNNING)))
	{
		CString Message;
		Message.LoadString(IDS_PART_PROGRAM_MUST_BE_RESTARTED_MESSAGE);
		AfxMessageBox((const char *)Message, MB_OK | MB_APPLMODAL,0);
		return false;
	}
	
	return true;
}	

void CMachine::XZero(void)
{
	if (GetMode() == JOG_MODE)
	{
		XAxis.SetProgramZero();
		InitializeConfiguration();
		SetModifiedFlag();			// Mark document as modified			
	}
}

void CMachine::YZero(void)
{
	if (GetMode() == JOG_MODE)
	{
		YAxis.SetProgramZero();
		InitializeConfiguration();
		SetModifiedFlag();			// Mark document as modified			
	}
}

void CMachine::ZZero()
{
	if (GetMode() == JOG_MODE)
	{
		ZAxis.SetProgramZero();
		InitializeConfiguration();
		SetModifiedFlag();			// Mark document as modified			
	}
}

void CMachine::JogUpdate(void)
{
	double dJogRate;
	double dJoyXPos;
	double dJoyYPos;
	double dJoyZPos;

	if (m_joy_use_pc && !m_calibrating_joystick)
	{
		JOYINFO joy_info;
		if (joyGetPos(JOYSTICKID1, &joy_info) == JOYERR_NOERROR)
		{
			// If joystick button 2 isn't pressed, jogging will be on X and Y
			if (!(joy_info.wButtons & JOY_BUTTON2))
			{
				dJoyXPos = LinearizeXJoystick(&joy_info);
				dJoyYPos = LinearizeYJoystick(&joy_info);
				
				// Pressing joystick button 1 will reduce joystick reading by a factor of 10
				if (joy_info.wButtons & JOY_BUTTON1)
				{
					dJoyXPos /= 10.0;
					dJoyYPos /= 10.0;
				}
				
				dJoyZPos = 0.0;
			}
			else		// Z activate button pressed, use joystick Y axis for Z axis
			{
				dJoyZPos = LinearizeYJoystick(&joy_info);
				
				// Pressing joystick button 1 will reduce joystick reading by a factor of 10
				if (joy_info.wButtons & JOY_BUTTON1)
					dJoyZPos /= 10.0;
				
				// X and Y joystick controls will be zero when moving Z
				dJoyXPos = 0.0;
				dJoyYPos = 0.0;
			}
			
			// If machine type is a lathe, use joystick X for machine Z, joystick Y for machine X
			if ((int)MachineType == LATHE)
			{
				double temp = dJoyZPos;
				dJoyZPos = dJoyXPos;
				dJoyXPos = dJoyYPos;
				dJoyYPos = temp;
			}
		}
		else
			// Error reading joystick, set conversion values to zero
			dJoyXPos = dJoyYPos = dJoyZPos = 0.0;
	}
	else
		// No joystick control or calibration in process, set readings to 0
		dJoyXPos = dJoyYPos = dJoyZPos = 0.0;
		

	// Determine current units used for machine
	DIM_UNIT DimUnit;
	PER_UNIT PerUnit;
	if (GetDefaultUnits() == ENGLISH)
	{
		DimUnit = PartPrgmEnglishDimUnit.Get();
		PerUnit = PartPrgmEnglishPerUnit.Get();
	}
	else
	{
		DimUnit = PartPrgmMetricDimUnit.Get();
		PerUnit = PartPrgmMetricPerUnit.Get();
	}

	int old_button_state = m_button_state;		// Record old button state so that transitions can be detected
	m_button_state = theApp.GetControlPanel()->GetButtonState();		// Get current button state
	
	unsigned int jog_state = GetJogState();

	if (XAxis.GetAxis())
	{
		// X Plus Jogging
		bool position_nonzero = (dJoyXPos > 0.0);
		if ((m_button_state & JOG_XPLUS_DOWN) || position_nonzero)
		{
			// The following conditions will be met if machine is currently not jogging
			// any other axis or jogging this motion is in a stopping state, or the joystick
			// is active in this direction
			if (!(jog_state & (JOG_ALL_STATES & ~JOG_XPLUS_STOPPING)) || position_nonzero)
			{
				// If cutter axis is present, and it's enabled during jogging, axis
				// motion in this direction won't be initiated until all other motion has
				// stopped and the cutter is in position
				if (!m_cutter.GetAxis() ||
						(!m_cutter_enable_jog && PositionCutter(CUTTER_UNDEF_DIR)) ||	// this term raises cutter
							(!(jog_state & (JOG_ALL_STATES & ~JOG_XPLUS_STATES)) && PositionCutter(CUTTER_XPLUS_DIR)))
				{
					// Initate jogging motion in this direction
					dJogRate = m_jog_max_rate.Get(DimUnit, PerUnit);	// Get jogging maximum rate
					if (position_nonzero)								// Scale it by joystick reading
						dJogRate *= dJoyXPos;
					XAxis.SetVelocity(dJogRate);
					XAxis.Direction(MC_DIR_POSITIVE);
					XAxis.Go();
					SetJogState(JOG_XPLUS_ACTIVE);
					ClearJogState(JOG_XPLUS_STOPPING);
				}
			}
		}
		else
		{
			if (jog_state & JOG_XPLUS_ACTIVE)
			{
				XAxis.Stop();
				SetJogState(JOG_XPLUS_STOPPING);
				ClearJogState(JOG_XPLUS_ACTIVE);
			}
			else if (jog_state & JOG_XPLUS_STOPPING)
			{
				if (XAxis.DecodeStatus(XAxis.GetStatus(),MC_STAT_TRAJ))
					ClearJogState(JOG_XPLUS_STOPPING);
			}
		}
	
		// X Minus Jogging	
		position_nonzero = (dJoyXPos < 0.0);
		if ((m_button_state & JOG_XMINUS_DOWN) || position_nonzero)
		{
			if (!(jog_state & (JOG_ALL_STATES & ~JOG_XMINUS_STOPPING)) || position_nonzero)
			{
				if (!m_cutter.GetAxis() ||
						(!m_cutter_enable_jog && PositionCutter(CUTTER_UNDEF_DIR)) ||	// this term raises cutter
							(!(jog_state & (JOG_ALL_STATES & ~JOG_XMINUS_STATES)) &&  PositionCutter(CUTTER_XMINUS_DIR)))
				{
					dJogRate = m_jog_max_rate.Get(DimUnit,PerUnit);
					if (position_nonzero)
						dJogRate *= -dJoyXPos;
					XAxis.SetVelocity(dJogRate);
					XAxis.Direction(MC_DIR_NEGATIVE);
					XAxis.Go();
					SetJogState(JOG_XMINUS_ACTIVE);
					ClearJogState(JOG_XMINUS_STOPPING);
				}
			}
		}
		else
		{
			if (jog_state & JOG_XMINUS_ACTIVE)
			{
				XAxis.Stop();
				SetJogState(JOG_XMINUS_STOPPING);
				ClearJogState(JOG_XMINUS_ACTIVE);
			}
			else if (jog_state & JOG_XMINUS_STOPPING)
			{
				if (XAxis.DecodeStatus(XAxis.GetStatus(),MC_STAT_TRAJ))
					ClearJogState(JOG_XMINUS_STOPPING);
			}
		}
	}
	
	if (YAxis.GetAxis())
	{
		// Y Plus Jogging	
		bool position_nonzero = (dJoyYPos > 0.0);
		if ((m_button_state & JOG_YPLUS_DOWN) || position_nonzero)
		{
			if (!(jog_state & (JOG_ALL_STATES & ~JOG_YPLUS_STOPPING)) || position_nonzero)
			{
				if (!m_cutter.GetAxis() ||
						(!m_cutter_enable_jog && PositionCutter(CUTTER_UNDEF_DIR)) ||	// this term raises cutter
							(!(jog_state & (JOG_ALL_STATES & ~JOG_YPLUS_STATES)) &&  PositionCutter(CUTTER_YPLUS_DIR)))
				{
					dJogRate = m_jog_max_rate.Get(DimUnit,PerUnit);
					if (position_nonzero)
						dJogRate *= dJoyYPos;
					YAxis.SetVelocity(dJogRate);
					YAxis.Direction(MC_DIR_POSITIVE);
					YAxis.Go();
					SetJogState(JOG_YPLUS_ACTIVE);
					ClearJogState(JOG_YPLUS_STOPPING);
				}
			}
		}
		else
		{
			if (jog_state & JOG_YPLUS_ACTIVE)
			{
				YAxis.Stop();
				SetJogState(JOG_YPLUS_STOPPING);
				ClearJogState(JOG_YPLUS_ACTIVE);
			}
			else if (jog_state & JOG_YPLUS_STOPPING)
			{
				if (YAxis.DecodeStatus(YAxis.GetStatus(),MC_STAT_TRAJ))
					ClearJogState(JOG_YPLUS_STOPPING);
			}
		}
	
		// Y Minus Jogging	
		position_nonzero = (dJoyYPos < 0.0);
		if ((m_button_state & JOG_YMINUS_DOWN) || position_nonzero)
		{
			if (!(jog_state & (JOG_ALL_STATES & ~JOG_YMINUS_STOPPING)) || position_nonzero)
			{
				if (!m_cutter.GetAxis() ||
						(!m_cutter_enable_jog && PositionCutter(CUTTER_UNDEF_DIR)) ||	// this term raises cutter
							(!(jog_state & (JOG_ALL_STATES & ~JOG_YMINUS_STATES)) &&  PositionCutter(CUTTER_YMINUS_DIR)))
				{
					dJogRate = m_jog_max_rate.Get(DimUnit,PerUnit);
					if (position_nonzero)
						dJogRate *= -dJoyYPos;
					YAxis.SetVelocity(dJogRate);
					YAxis.Direction(MC_DIR_NEGATIVE);
					YAxis.Go();
					SetJogState(JOG_YMINUS_ACTIVE);
					ClearJogState(JOG_YMINUS_STOPPING);
				}
			}
		}
		else
		{
			if (jog_state & JOG_YMINUS_ACTIVE)
			{
				YAxis.Stop();
				SetJogState(JOG_YMINUS_STOPPING);
				ClearJogState(JOG_YMINUS_ACTIVE);
			}
			else if (jog_state & JOG_YMINUS_STOPPING)
			{
				if (YAxis.DecodeStatus(YAxis.GetStatus(),MC_STAT_TRAJ))
					ClearJogState(JOG_YMINUS_STOPPING);
			}
		}
	}
	
	if (ZAxis.GetAxis())
	{
		// Z Plus Jogging	
		bool position_nonzero = (dJoyZPos > 0.0);
		if ((m_button_state & JOG_ZPLUS_DOWN) || position_nonzero)
		{
			if (!(jog_state & (JOG_ALL_STATES & ~JOG_ZPLUS_STOPPING)) || position_nonzero)
			{
				// Z axis motion won't be initiated until all other motion has
				// stopped
				if (!(jog_state & ~JOG_ZPLUS_STATES))
				{
					dJogRate = m_jog_max_rate.Get(DimUnit,PerUnit);
					if (position_nonzero)
						dJogRate *= dJoyZPos;
					ZAxis.SetVelocity(dJogRate);
					ZAxis.Direction(MC_DIR_POSITIVE);
					ZAxis.Go();
					SetJogState(JOG_ZPLUS_ACTIVE);
					ClearJogState(JOG_ZPLUS_STOPPING);
				}
			}
		}
		else
		{
			if (jog_state & JOG_ZPLUS_ACTIVE)
			{
				ZAxis.Stop();
				SetJogState(JOG_ZPLUS_STOPPING);
				ClearJogState(JOG_ZPLUS_ACTIVE);
			}
			else if (jog_state & JOG_ZPLUS_STOPPING)
			{
				if (XAxis.DecodeStatus(XAxis.GetStatus(),MC_STAT_TRAJ))
					ClearJogState(JOG_ZPLUS_STOPPING);
			}
		}
	
		// Z Minus Jogging	
		position_nonzero = (dJoyZPos<0.0);
		if ((m_button_state & JOG_ZMINUS_DOWN) || position_nonzero)
		{
			if (!(jog_state & (JOG_ALL_STATES & ~JOG_ZMINUS_STOPPING)) || position_nonzero)
			{
				if (!(jog_state & ~JOG_ZMINUS_STATES))
				{
					dJogRate = m_jog_max_rate.Get(DimUnit,PerUnit);
					if (position_nonzero)
						dJogRate *= -dJoyZPos;
					ZAxis.SetVelocity(dJogRate);
					ZAxis.Direction(MC_DIR_NEGATIVE);
					ZAxis.Go();
					SetJogState(JOG_ZMINUS_ACTIVE);
					ClearJogState(JOG_ZMINUS_STOPPING);
				}
			}
		}
		else
		{
			if (jog_state & JOG_ZMINUS_ACTIVE)
			{
				ZAxis.Stop();
				SetJogState(JOG_ZMINUS_STOPPING);
				ClearJogState(JOG_ZMINUS_ACTIVE);
			}
			else if (jog_state & JOG_ZMINUS_STOPPING)
			{
				if (ZAxis.DecodeStatus(ZAxis.GetStatus(),MC_STAT_TRAJ))
					ClearJogState(JOG_ZMINUS_STOPPING);
			}
		}
	}
	
	if (m_cutter.GetAxis())
	{
		// Sense when Z plus button pressed to raise cutter
		if (!(old_button_state & JOG_ZPLUS_DOWN) && (m_button_state & JOG_ZPLUS_DOWN))
			m_cutter.Raise();
			
		// Sense when Z minus button presses to lower cutter
		if (!(old_button_state & JOG_ZMINUS_DOWN) && (m_button_state & JOG_ZMINUS_DOWN))
			m_cutter.Lower();
	}
}

// This function is called to position cutter. It can be called multiple times
// with the same parameter value, and it will return FALSE until the cutter
// is in position and lowered
bool CMachine::PositionCutter(double angle)
{
	bool ready = false;

	if (m_cutter_enable_jog)	
	{
		double target = m_cutter.GetCommandTarget();	// Get target position of cutter that is maintained 
														// external to motion controller. This ensures value 
														// is exactly equal to target of last move command.
		double target_rotation = floor(target);
		double target_angle = target - target_rotation;
		
		if (target_angle != angle)
		{
			target = target_rotation + angle;
			m_cutter.Raise();
			m_cutter.MoveAbsolute(target);
			m_cutter.SetCommandTarget(target);			// Record target position external to controller
		}
		
		if (m_cutter.DecodeStatus(m_cutter.GetStatus(), MC_STAT_TRAJ))
		{
			// When cutter has finished positioning move, lower it and return true to caller
			if (m_cutter.GetState() != CUTTER_STATE_LOWERED)
				m_cutter.Lower();
			ready = true;								// Ready for jogging motion
		}
	}
	else
	{
		// If Cutter is not enabled while jogging, make sure it is raised if user
		// is trying to jog machine (ie. cutter down button may have been pressed)
		if (m_cutter.GetState() != CUTTER_STATE_RAISED)
			m_cutter.Raise();
		ready = true;
	}
	return ready;
}

double CMachine::LinearizeXJoystick(JOYINFO* joy_info)
{
	double joy_x_pos = (double)joy_info->wXpos - (double)m_joy_x_center;
	double deadband = ((int)m_joy_deadband) / 100.0 * 32767.0;
	if (joy_x_pos > 0.0)
	{
		joy_x_pos -= deadband;
		joy_x_pos /= ((double)m_joy_x_upper - (double)m_joy_x_center - deadband);
		if (joy_x_pos < 0.0)
			joy_x_pos = 0.0;
		else if (joy_x_pos > 1.0)
			joy_x_pos = 1.0;
	}
	else
	{
		joy_x_pos += deadband;
		joy_x_pos /= ((double)m_joy_x_center - (double)m_joy_x_lower - deadband);
		if (joy_x_pos > 0.0)
			joy_x_pos = 0.0;
		else if (joy_x_pos < -1.0)
			joy_x_pos = -1.0;
	}
	// Invert joystick reading if necessary
	if ((int)m_joy_x_invert)
		joy_x_pos = -joy_x_pos;
		
	return joy_x_pos;
}

double CMachine::LinearizeYJoystick(JOYINFO* joy_info)
{
	double joy_y_pos = (double)joy_info->wYpos - (double)m_joy_y_center;
	double deadband = ((int)m_joy_deadband) / 100.0 * 32767.0;
	if (joy_y_pos > 0.0)
	{
		joy_y_pos -= deadband;
		joy_y_pos /= ((double)m_joy_y_upper - (double)m_joy_y_center - deadband);
		if (joy_y_pos < 0.0)
			joy_y_pos = 0.0;
		else if (joy_y_pos > 1.0)
			joy_y_pos = 1.0;
	}
	else
	{
		joy_y_pos += deadband;
		joy_y_pos /= ((double)m_joy_y_center - (double)m_joy_y_lower - deadband);
		if (joy_y_pos > 0.0)
			joy_y_pos = 0.0;
		else if (joy_y_pos < -1.0)
			joy_y_pos = -1.0;
	}
	// Invert joystick reading if necessary
	if ((int)m_joy_y_invert)
		joy_y_pos = -joy_y_pos;
		
	return joy_y_pos;
}

void CMachine::CalibrateJoystick(void)
{
	if (!m_joy_use_pc)
		return;

	// Take a reading from joystick to verify its' operation
	JOYINFO JoyInfo;
	UINT joy_error;
	if ((joy_error = joyGetPos(JOYSTICKID1, &JoyInfo)) != JOYERR_NOERROR)
	{
		CString Message;
		switch (joy_error) 
		{
			case MMSYSERR_NODRIVER:
				Message.LoadString(IDS_JOYSTICK_DRIVER_NOT_PRESENT_MESSAGE);
				AfxMessageBox((const char *)Message, MB_OK | MB_APPLMODAL, 0);
				break;

			case JOYERR_PARMS:
				Message.LoadString(IDS_JOYSTICK_DEVICE_ID_IS_INVALID_MESSAGE);
				AfxMessageBox((const char *)Message, MB_OK | MB_APPLMODAL, 0);
				break;

			case JOYERR_UNPLUGGED:
				Message.LoadString(IDS_JOYSTICK_NOT_CONNECTED_MESSAGE);
				AfxMessageBox((const char *)Message, MB_OK | MB_APPLMODAL, 0);
				break;
		}
		return;
	}
	
	m_calibrating_joystick = true;
		
	CString Prompt;
	Prompt.LoadString(IDS_JOYSTICK_CENTER_PROMPT);
	AfxMessageBox((const char *)Prompt, MB_OK | MB_APPLMODAL,0);
	joyGetPos(JOYSTICKID1, &JoyInfo);
	m_joy_x_center = JoyInfo.wXpos;
	m_joy_y_center = JoyInfo.wYpos;
	
	Prompt.LoadString(IDS_JOYSTICK_UPPER_LEFT_PROMPT);
	AfxMessageBox((const char *)Prompt, MB_OK | MB_APPLMODAL,0);
	joyGetPos(JOYSTICKID1, &JoyInfo);
	m_joy_x_lower = JoyInfo.wXpos;
	m_joy_y_upper = JoyInfo.wYpos;
	TRACE("Joy X:%d  Y:%d\r\n", JoyInfo.wXpos, JoyInfo.wYpos);
	
	Prompt.LoadString(IDS_JOYSTICK_LOWER_RIGHT_PROMPT);
	AfxMessageBox((const char *)Prompt, MB_OK | MB_APPLMODAL,0);
	joyGetPos(JOYSTICKID1, &JoyInfo);
	m_joy_x_upper = JoyInfo.wXpos;
	m_joy_y_lower = JoyInfo.wYpos;
	TRACE("Joy X:%d  Y:%d\r\n", JoyInfo.wXpos, JoyInfo.wYpos);
	
	// Check for inverted joystick
	if ((double)m_joy_x_upper > (double)m_joy_x_lower)
		m_joy_x_invert = 0;
	else
	{
		// Set flag to indicate joystick is inverted, reverse limit values so linearization 
		// algorithim works. Inversion will be applied after linearization
		m_joy_x_invert = 1;
		double temp = (double)m_joy_x_upper;
		m_joy_x_upper = (double)m_joy_x_lower;
		m_joy_x_lower = temp;
	}
		
	if ((double)m_joy_y_upper > (double)m_joy_y_lower)
		m_joy_y_invert = 0;
	else
	{
		m_joy_y_invert = 1;
		double temp = (double)m_joy_y_upper;
		m_joy_y_upper = (double)m_joy_y_lower;
		m_joy_y_lower = temp;
	}
	// Don't accept a center position that's outside upper and lower limits
	if (((double)m_joy_x_center > (double)m_joy_x_upper) || ((double)m_joy_x_lower < (double)m_joy_x_center))
		m_joy_x_center = ((double)m_joy_x_upper + (double)m_joy_x_lower) / 2.0;
	if (((double)m_joy_y_center > (double)m_joy_y_upper) || ((double)m_joy_y_lower < (double)m_joy_y_center))
		m_joy_y_center = ((double)m_joy_y_upper + (double)m_joy_y_lower) / 2.0;
		
	SetModifiedFlag();		// Mark document as modified, so that calibration will be saved in machine file
	TRACE("XLower:%f  XCenter:%f  XUpper:%f  Invert:%d\r\n", (double)m_joy_x_lower, (double)m_joy_x_center, (double)m_joy_x_upper, (int)m_joy_x_invert);
	TRACE("YLower:%f  YCenter:%f  YUpper:%f  Invert:%d\r\n", (double)m_joy_y_lower, (double)m_joy_y_center, (double)m_joy_y_upper, (int)m_joy_y_invert);
	m_calibrating_joystick = false;
}

void CMachine::UpdateCalibrateJoystick(CCmdUI* pCmdUI)
{
	pCmdUI->Enable(m_joy_use_pc);
}

void CMachine::ViewStatus(void)
{
	if (m_status_frame == NULL)	// Make sure status view isn't already open
	{
		CDocTemplate* pTemplate = theApp.GetStatusDisplayTemplate();
		ASSERT_VALID(pTemplate);
		CFrameWnd* pFrame = pTemplate->CreateNewFrame(this, NULL);
		if (pFrame == NULL)
		{
			TRACE0("Warning: failed to create status display frame\n");
			return;     // command failed
		}
		m_status_frame = pFrame;
		pTemplate->InitialUpdateFrame(pFrame, this, MAKE_VIEW_VISIBLE);
		// Save pointer to view	
		m_status_view = reinterpret_cast<CStatusDisplay*>(pFrame->GetActiveView());
	}
	else
		m_status_frame->DestroyWindow();
}

void CMachine::ViewProgram(void)
{
	// First test for an open output view, and currently displaying part program. This 
	// implies output view should be closed
	if ((m_output_frame != NULL) && (GetPartProgramViewMode() == DISPLAY_PROGRAM))
	{
		m_output_frame->DestroyWindow();
		DisableAllOutput();
	}
	else
	{
		if (m_output_frame == NULL)
			OpenOutputView();	
	
		DisableAllOutput();
		// Enable part program output from motion controller	
		//MtnCtrlr.EnableDisplayFileNumber();		// Include file number
		//MtnCtrlr.EnableDisplayFilePosition();		// Include file position
		MtnCtrlr.DisplayBlockText(true);
		SetPartProgramViewMode(DISPLAY_PROGRAM);
	}
}

void CMachine::ViewMessages(void)
{
	// First test for an open output view, and currently displaying messages program. This 
	// implies output view should be closed
	if ((m_output_frame != NULL) && (GetPartProgramViewMode() == DISPLAY_MESSAGES))
	{
		m_output_frame->DestroyWindow();
		DisableAllOutput();
	}
	else
	{
		if (m_output_frame==NULL)
			OpenOutputView();	
	
		DisableAllOutput();
		// Enable message output from motion controller	
		MtnCtrlr.DisplayUserOutput(true);
		SetPartProgramViewMode(DISPLAY_MESSAGES);
	}
}

void CMachine::OpenOutputView(void)
{
	if (m_output_frame == NULL)		// If output view isn't already open, create it
	{
		CDocTemplate* pTemplate = theApp.GetOutputDisplayTemplate();
		ASSERT_VALID(pTemplate);
		CFrameWnd* pFrame = pTemplate->CreateNewFrame(this, NULL);
		if (pFrame == NULL)
		{
			TRACE0("Warning: failed to create output display frame\n");
			return;					// command failed
		}
		m_output_frame = pFrame;
		pTemplate->InitialUpdateFrame(pFrame, this, MAKE_VIEW_VISIBLE);
		// Save pointer to view	
		m_output_view = reinterpret_cast<COutputDisplay*>(pFrame->GetActiveView());
		// Set pointer to function that will be called when view is closed
	}
	m_output_view->Erase();		// Clear output View
}

void CMachine::CloseOutputView(void)
{
	m_output_frame = NULL;
	m_output_view = NULL;
	DisableAllOutput();
}

void CMachine::DisableAllOutput(void)
{
	// Disable text output from motion controller	
	MtnCtrlr.DisplayFileNumber(false);
	MtnCtrlr.DisplayFilePosition(false);
	MtnCtrlr.DisplayBlockText(false);
	MtnCtrlr.DisplayUserOutput(false);
}

void CMachine::OutputText(LPCSTR string)
{
	// Based on current mode, determine where to send text
	if ((GetMode() == RUN_MODE) && (m_output_view != NULL))
		m_output_view->OutputText(string);	// If in Run mode and output view open, send text to that view
	else if ((GetMode() == HOME_MODE) && (m_homing_view != NULL))
		m_homing_view->OutputText(string);	// If in Homing mode and homing view open, send text to that view
}

void CMachine::SetupGeneral(void)
{
	CSetupGeneral dlg;
	char units[UNITTITLESTRINGLENGTH];
	
	// Initialize dialog data
	dlg.m_MachineType = MachineTypeName[(int)MachineType];
	dlg.m_ProgramStyle = ProgramStyleName[(int)ProgramStyle];
	dlg.m_DefaultUnits = UnitsName[GetDefaultUnits()];
	
	dlg.m_PartPrgmEnglishDimUnit = PartPrgmEnglishDimUnit.GetTitleS(units);
	dlg.m_PartPrgmEnglishPerUnit = PartPrgmEnglishPerUnit.GetTitleS(units);
	dlg.m_PartPrgmMetricDimUnit = PartPrgmMetricDimUnit.GetTitleS(units);
	dlg.m_PartPrgmMetricPerUnit = PartPrgmMetricPerUnit.GetTitleS(units);

	// Invoke the dialog box
	if (dlg.DoModal() == IDOK)
	{
		// Retrieve the dialog data
		
		// Determine machine type
		if (dlg.m_MachineType == MachineTypeName[VMILL])
			MachineType = VMILL;
		else if (dlg.m_MachineType == MachineTypeName[HMILL])
			MachineType = HMILL;
		else if (dlg.m_MachineType == MachineTypeName[LATHE])
			MachineType = LATHE;
		
		// Determine part program style (compatibility)
		if (dlg.m_ProgramStyle == ProgramStyleName[FANUC])
			ProgramStyle = FANUC;
		else if (dlg.m_ProgramStyle == ProgramStyleName[AMTICO])
			ProgramStyle = AMTICO;
		
		// Determine default units
		if (dlg.m_DefaultUnits == UnitsName[ENGLISH])
			SetDefaultUnits(ENGLISH);
		else if (dlg.m_DefaultUnits == UnitsName[METRIC])
			SetDefaultUnits(METRIC);

		PartPrgmEnglishDimUnit.SetTitleS((const char*)dlg.m_PartPrgmEnglishDimUnit);
		PartPrgmEnglishPerUnit.SetTitleS((const char*)dlg.m_PartPrgmEnglishPerUnit);
		PartPrgmMetricDimUnit.SetTitleS((const char*)dlg.m_PartPrgmMetricDimUnit);
		PartPrgmMetricPerUnit.SetTitleS((const char*)dlg.m_PartPrgmMetricPerUnit);
			
		InitializeConfiguration();
		SetModifiedFlag();				// Mark document as modified			
	}
}

void CMachine::SetupAxes(void)
{
	CSetupAxes dlg;
	char dimension[VALUESTRINGLENGTH];
	
	// Initialize dialog data
	dlg.m_XAxisNum = XAxis.GetAxis();
	dlg.m_YAxisNum = YAxis.GetAxis();
	dlg.m_ZAxisNum = ZAxis.GetAxis();
	dlg.m_UAxisNum = UAxis.GetAxis();
	dlg.m_VAxisNum = VAxis.GetAxis();
	dlg.m_WAxisNum = WAxis.GetAxis();
	
	dlg.m_XSlaveNum = XAxis.GetSlaveAxis();
	dlg.m_YSlaveNum = YAxis.GetSlaveAxis();
	dlg.m_ZSlaveNum = ZAxis.GetSlaveAxis();
	dlg.m_USlaveNum = UAxis.GetSlaveAxis();
	dlg.m_VSlaveNum = VAxis.GetSlaveAxis();
	dlg.m_WSlaveNum = WAxis.GetSlaveAxis();
	
	dlg.m_XScale = XAxis.Scale.GetS(dimension, GetSetupPrecision());
	dlg.m_YScale = YAxis.Scale.GetS(dimension, GetSetupPrecision());
	dlg.m_ZScale = ZAxis.Scale.GetS(dimension, GetSetupPrecision());
	dlg.m_UScale = UAxis.Scale.GetS(dimension, GetSetupPrecision());
	dlg.m_VScale = VAxis.Scale.GetS(dimension, GetSetupPrecision());
	dlg.m_WScale = WAxis.Scale.GetS(dimension, GetSetupPrecision());

	// Invoke the dialog box
	if (dlg.DoModal() == IDOK)
	{
		// retrieve the dialog data
		XAxis.SetAxis(dlg.m_XAxisNum);
		YAxis.SetAxis(dlg.m_YAxisNum);
		ZAxis.SetAxis(dlg.m_ZAxisNum);
		UAxis.SetAxis(dlg.m_UAxisNum);
		VAxis.SetAxis(dlg.m_VAxisNum);
		WAxis.SetAxis(dlg.m_WAxisNum);
		
		XAxis.SetSlaveAxis(dlg.m_XSlaveNum);
		YAxis.SetSlaveAxis(dlg.m_YSlaveNum);
		ZAxis.SetSlaveAxis(dlg.m_ZSlaveNum);
		UAxis.SetSlaveAxis(dlg.m_USlaveNum);
		VAxis.SetSlaveAxis(dlg.m_VSlaveNum);
		WAxis.SetSlaveAxis(dlg.m_WSlaveNum);
		
		XAxis.Scale.SetS((const char *)dlg.m_XScale);
		YAxis.Scale.SetS((const char *)dlg.m_YScale);
		ZAxis.Scale.SetS((const char *)dlg.m_ZScale);
		UAxis.Scale.SetS((const char *)dlg.m_UScale);
		VAxis.Scale.SetS((const char *)dlg.m_VScale);
		WAxis.Scale.SetS((const char *)dlg.m_WScale);
		
		InitializeConfiguration();
		SetModifiedFlag();				// Mark document as modified			
	}
}

void CMachine::SetupFeed(void)
{
	CSetupFeedDlg dlg;
	char dimension[VALUESTRINGLENGTH];
	
	// Initialize dialog data
	dlg.m_FeedRate = m_feed_rate.GetS(dimension, GetSetupPrecision());
	dlg.m_FeedAccel = m_feed_accel.GetS(dimension, GetSetupPrecision());
	dlg.m_FeedDecel = m_feed_decel.GetS(dimension, GetSetupPrecision());
	dlg.m_MaxFeedRate = m_feed_rate_max.GetS(dimension, GetSetupPrecision());
	dlg.m_MaxAngle = m_feed_angle_max.GetS(dimension, GetSetupPrecision());

	// Invoke the dialog box
	if (dlg.DoModal() == IDOK)
	{
		m_feed_rate.SetS((const char *)dlg.m_FeedRate);
		m_feed_accel.SetS((const char *)dlg.m_FeedAccel);
		m_feed_decel.SetS((const char *)dlg.m_FeedDecel);
		m_feed_rate_max.SetS((const char *)dlg.m_MaxFeedRate);
		m_feed_angle_max.SetS((const char *)dlg.m_MaxAngle);
					
		InitializeConfiguration();
		SetModifiedFlag();				// Mark document as modified			
	}
}

void CMachine::SetupSpindle(void)
{
	CSetupSpindleDlg dlg;
	char dimension[VALUESTRINGLENGTH];
	
	// Initialize dialog data
	dlg.m_SpindleAxisNum = m_spindle.GetCtrlrAxis();
	
	if ((int)m_spindle.Mode == 1)
		dlg.m_SpindleMode = "SERVO";
	else if ((int)m_spindle.Mode == 2)
		dlg.m_SpindleMode = "DRIVE";
		
	if ((int)m_spindle.Polarity == 1)
		dlg.m_SpindlePolarity = "NORMAL";
	else if ((int)m_spindle.Polarity == -1)
		dlg.m_SpindlePolarity = "REVERSE";
		
	dlg.m_SpindleScale = m_spindle.EncoderScale.GetS(dimension, GetSetupPrecision());
	dlg.m_SpindleConstant = m_spindle.SpeedConstant.GetS(dimension, GetSetupPrecision());
	dlg.m_SpindleSpeed = m_spindle.DefaultSpeed.GetS(dimension, GetSetupPrecision());
	dlg.m_SpindleAccel = m_spindle.Accel.GetS(dimension, GetSetupPrecision());
	dlg.m_SpindleDecel = m_spindle.Decel.GetS(dimension, GetSetupPrecision());

	// Invoke the dialog box
	if (dlg.DoModal() == IDOK)
	{
		// retrieve the dialog data
		m_spindle.SetCtrlrAxis(dlg.m_SpindleAxisNum);
		
		if (strcmp(dlg.m_SpindleMode,"SERVO")==0)
			m_spindle.Mode = 1;
		else if (strcmp(dlg.m_SpindleMode,"DRIVE")==0)
			m_spindle.Mode = 2;
		
		if (strcmp(dlg.m_SpindlePolarity,"NORMAL")==0)
			m_spindle.Polarity = 1;
		else if (strcmp(dlg.m_SpindlePolarity,"REVERSE")==0)
			m_spindle.Polarity = -1;
		
		m_spindle.EncoderScale.SetS((const char *)dlg.m_SpindleScale);
		m_spindle.SpeedConstant.SetS((const char *)dlg.m_SpindleConstant);
		m_spindle.DefaultSpeed.SetS((const char *)dlg.m_SpindleSpeed);
		m_spindle.Accel.SetS((const char *)dlg.m_SpindleAccel);
		m_spindle.Decel.SetS((const char *)dlg.m_SpindleDecel);
		
		InitializeConfiguration();
		SetModifiedFlag();				// Mark document as modified			
	}
}

void CMachine::SetupCutter(void)
{
	CSetupCutterDlg dlg;
	char dimension[VALUESTRINGLENGTH];
	
	// Initialize dialog data
	dlg.m_CutterAxisNum = m_cutter.GetAxis();
	
	dlg.m_CutterScale = m_cutter.Scale.GetS(dimension, GetSetupPrecision());
	dlg.m_CutterOffset = m_cutter.Offset.GetS(dimension, GetSetupPrecision());
	dlg.m_CutterRaiseMCode = (int)m_cutter.RaiseMCode;
	dlg.m_CutterLowerMCode = (int)m_cutter.LowerMCode;
	dlg.m_CutterRaiseMacro = (int)m_cutter.RaiseMacro;
	dlg.m_CutterLowerMacro = (int)m_cutter.LowerMacro;
	dlg.m_CutterSpeed = m_cutter.Speed.GetS(dimension, GetSetupPrecision());
	dlg.m_CutterAccel = m_cutter.Accel.GetS(dimension, GetSetupPrecision());
	dlg.m_CutterDecel = m_cutter.Decel.GetS(dimension, GetSetupPrecision());

	// Invoke the dialog box
	if (dlg.DoModal() == IDOK)
	{
		// retrieve the dialog data
		m_cutter.SetAxis(dlg.m_CutterAxisNum);
		
		m_cutter.Scale.SetS((const char *)dlg.m_CutterScale);
		m_cutter.Offset.SetS((const char *)dlg.m_CutterOffset);
		m_cutter.RaiseMCode = dlg.m_CutterRaiseMCode;
		m_cutter.LowerMCode = dlg.m_CutterLowerMCode;
		m_cutter.RaiseMacro = dlg.m_CutterRaiseMacro;
		m_cutter.LowerMacro = dlg.m_CutterLowerMacro;
		m_cutter.Speed.SetS((const char *)dlg.m_CutterSpeed);
		m_cutter.Accel.SetS((const char *)dlg.m_CutterAccel);
		m_cutter.Decel.SetS((const char *)dlg.m_CutterDecel);
		
		InitializeConfiguration();
		SetModifiedFlag();				// Mark document as modified			
	}
}

void CMachine::SetupRapid(void)
{
	CSetupRapidDlg dlg;
	char dimension[VALUESTRINGLENGTH];
	
	// Initialize dialog data
	dlg.m_XRapidRate = XAxis.RapidRate.GetS(dimension, GetSetupPrecision());
	dlg.m_YRapidRate = YAxis.RapidRate.GetS(dimension, GetSetupPrecision());
	dlg.m_ZRapidRate = ZAxis.RapidRate.GetS(dimension, GetSetupPrecision());
	dlg.m_URapidRate = UAxis.RapidRate.GetS(dimension, GetSetupPrecision());
	dlg.m_VRapidRate = VAxis.RapidRate.GetS(dimension, GetSetupPrecision());
	dlg.m_WRapidRate = WAxis.RapidRate.GetS(dimension, GetSetupPrecision());
	
	dlg.m_XRapidAccel = XAxis.RapidAccel.GetS(dimension, GetSetupPrecision());
	dlg.m_YRapidAccel = YAxis.RapidAccel.GetS(dimension, GetSetupPrecision());
	dlg.m_ZRapidAccel = ZAxis.RapidAccel.GetS(dimension, GetSetupPrecision());
	dlg.m_URapidAccel = UAxis.RapidAccel.GetS(dimension, GetSetupPrecision());
	dlg.m_VRapidAccel = VAxis.RapidAccel.GetS(dimension, GetSetupPrecision());
	dlg.m_WRapidAccel = WAxis.RapidAccel.GetS(dimension, GetSetupPrecision());
	
	dlg.m_XRapidDecel = XAxis.RapidDecel.GetS(dimension, GetSetupPrecision());
	dlg.m_YRapidDecel = YAxis.RapidDecel.GetS(dimension, GetSetupPrecision());
	dlg.m_ZRapidDecel = ZAxis.RapidDecel.GetS(dimension, GetSetupPrecision());
	dlg.m_URapidDecel = UAxis.RapidDecel.GetS(dimension, GetSetupPrecision());
	dlg.m_VRapidDecel = VAxis.RapidDecel.GetS(dimension, GetSetupPrecision());
	dlg.m_WRapidDecel = WAxis.RapidDecel.GetS(dimension, GetSetupPrecision());
	

	// Invoke the dialog box
	if (dlg.DoModal() == IDOK)
	{
		// retrieve the dialog data
		XAxis.RapidRate.SetS((const char *)dlg.m_XRapidRate);
		YAxis.RapidRate.SetS((const char *)dlg.m_YRapidRate);
		ZAxis.RapidRate.SetS((const char *)dlg.m_ZRapidRate);
		UAxis.RapidRate.SetS((const char *)dlg.m_URapidRate);
		VAxis.RapidRate.SetS((const char *)dlg.m_VRapidRate);
		WAxis.RapidRate.SetS((const char *)dlg.m_WRapidRate);
			                                                
		XAxis.RapidAccel.SetS((const char *)dlg.m_XRapidAccel);
		YAxis.RapidAccel.SetS((const char *)dlg.m_YRapidAccel);
		ZAxis.RapidAccel.SetS((const char *)dlg.m_ZRapidAccel);
		UAxis.RapidAccel.SetS((const char *)dlg.m_URapidAccel);
		VAxis.RapidAccel.SetS((const char *)dlg.m_VRapidAccel);
		WAxis.RapidAccel.SetS((const char *)dlg.m_WRapidAccel);
			                                                
		XAxis.RapidDecel.SetS((const char *)dlg.m_XRapidDecel);
		YAxis.RapidDecel.SetS((const char *)dlg.m_YRapidDecel);
		ZAxis.RapidDecel.SetS((const char *)dlg.m_ZRapidDecel);
		UAxis.RapidDecel.SetS((const char *)dlg.m_URapidDecel);
		VAxis.RapidDecel.SetS((const char *)dlg.m_VRapidDecel);
		WAxis.RapidDecel.SetS((const char *)dlg.m_WRapidDecel);
			                                                
		InitializeConfiguration();
		SetModifiedFlag();				// Mark document as modified			
	}
}

void CMachine::SetupLimits(void)
{
	CSetupLimitDlg dlg;
	char dimension[VALUESTRINGLENGTH];
	
	// Initialize dialog data
	dlg.m_XLowerLimit = XAxis.LowerLimit.GetS(dimension, GetSetupPrecision());
	dlg.m_YLowerLimit = YAxis.LowerLimit.GetS(dimension, GetSetupPrecision());
	dlg.m_ZLowerLimit = ZAxis.LowerLimit.GetS(dimension, GetSetupPrecision());
	dlg.m_ULowerLimit = UAxis.LowerLimit.GetS(dimension, GetSetupPrecision());
	dlg.m_VLowerLimit = VAxis.LowerLimit.GetS(dimension, GetSetupPrecision());
	dlg.m_WLowerLimit = WAxis.LowerLimit.GetS(dimension, GetSetupPrecision());

	dlg.m_XUpperLimit = XAxis.UpperLimit.GetS(dimension, GetSetupPrecision());
	dlg.m_YUpperLimit = YAxis.UpperLimit.GetS(dimension, GetSetupPrecision());
	dlg.m_ZUpperLimit = ZAxis.UpperLimit.GetS(dimension, GetSetupPrecision());
	dlg.m_UUpperLimit = UAxis.UpperLimit.GetS(dimension, GetSetupPrecision());
	dlg.m_VUpperLimit = VAxis.UpperLimit.GetS(dimension, GetSetupPrecision());
	dlg.m_WUpperLimit = WAxis.UpperLimit.GetS(dimension, GetSetupPrecision());

	// Invoke the dialog box
	if (dlg.DoModal() == IDOK)
	{
		// retrieve the dialog data
		XAxis.LowerLimit.SetS((const char *)dlg.m_XLowerLimit);
		YAxis.LowerLimit.SetS((const char *)dlg.m_YLowerLimit);
		ZAxis.LowerLimit.SetS((const char *)dlg.m_ZLowerLimit);
		UAxis.LowerLimit.SetS((const char *)dlg.m_ULowerLimit);
		VAxis.LowerLimit.SetS((const char *)dlg.m_VLowerLimit);
		WAxis.LowerLimit.SetS((const char *)dlg.m_WLowerLimit);
			
		XAxis.UpperLimit.SetS((const char *)dlg.m_XUpperLimit);
		YAxis.UpperLimit.SetS((const char *)dlg.m_YUpperLimit);
		ZAxis.UpperLimit.SetS((const char *)dlg.m_ZUpperLimit);
		UAxis.UpperLimit.SetS((const char *)dlg.m_UUpperLimit);
		VAxis.UpperLimit.SetS((const char *)dlg.m_VUpperLimit);
		WAxis.UpperLimit.SetS((const char *)dlg.m_WUpperLimit);
			
		InitializeConfiguration();
		SetModifiedFlag();				// Mark document as modified			
	}
}

void CMachine::SetupMachineCoordinates()
{
	CSetupMachineCoorDlg dlg;
	char dimension[VALUESTRINGLENGTH];
	
	// Initialize dialog data
	dlg.m_XMachineOffset = XAxis.MachineOffset.GetS(dimension, GetSetupPrecision());
	dlg.m_YMachineOffset = YAxis.MachineOffset.GetS(dimension, GetSetupPrecision());
	dlg.m_ZMachineOffset = ZAxis.MachineOffset.GetS(dimension, GetSetupPrecision());
	dlg.m_UMachineOffset = UAxis.MachineOffset.GetS(dimension, GetSetupPrecision());
	dlg.m_VMachineOffset = VAxis.MachineOffset.GetS(dimension, GetSetupPrecision());
	dlg.m_WMachineOffset = WAxis.MachineOffset.GetS(dimension, GetSetupPrecision());

	// Invoke the dialog box
	if (dlg.DoModal() == IDOK)
	{
		// retrieve the dialog data
		XAxis.MachineOffset.SetS((const char *)dlg.m_XMachineOffset);
		YAxis.MachineOffset.SetS((const char *)dlg.m_YMachineOffset);
		ZAxis.MachineOffset.SetS((const char *)dlg.m_ZMachineOffset);
		UAxis.MachineOffset.SetS((const char *)dlg.m_UMachineOffset);
		VAxis.MachineOffset.SetS((const char *)dlg.m_VMachineOffset);
		WAxis.MachineOffset.SetS((const char *)dlg.m_WMachineOffset);
			
		InitializeConfiguration();
		SetModifiedFlag();				// Mark document as modified			
	}
}

void CMachine::SetupProgramCoordinates(void)
{
	CSetupProgramCoorDlg dlg;
	char dimension[VALUESTRINGLENGTH];
	
	// Initialize dialog data
	dlg.m_XProgramOffset = XAxis.ProgramOffset.GetS(dimension, GetSetupPrecision());
	dlg.m_YProgramOffset = YAxis.ProgramOffset.GetS(dimension, GetSetupPrecision());
	dlg.m_ZProgramOffset = ZAxis.ProgramOffset.GetS(dimension, GetSetupPrecision());
	dlg.m_UProgramOffset = UAxis.ProgramOffset.GetS(dimension, GetSetupPrecision());
	dlg.m_VProgramOffset = VAxis.ProgramOffset.GetS(dimension, GetSetupPrecision());
	dlg.m_WProgramOffset = WAxis.ProgramOffset.GetS(dimension, GetSetupPrecision());

	// Invoke the dialog box
	if (dlg.DoModal() == IDOK)
	{
		// retrieve the dialog data
		XAxis.ProgramOffset.SetS((const char *)dlg.m_XProgramOffset);
		YAxis.ProgramOffset.SetS((const char *)dlg.m_YProgramOffset);
		ZAxis.ProgramOffset.SetS((const char *)dlg.m_ZProgramOffset);
		UAxis.ProgramOffset.SetS((const char *)dlg.m_UProgramOffset);
		VAxis.ProgramOffset.SetS((const char *)dlg.m_VProgramOffset);
		WAxis.ProgramOffset.SetS((const char *)dlg.m_WProgramOffset);
			
		InitializeConfiguration();
		SetModifiedFlag();				// Mark document as modified			
	}
}

void CMachine::SetupWorkCoordinates(void)
{
	CSetupWorkCoorDlg dlg;
	char szWorkCoorSetup[INTSTRINGLENGTH];

	// Copy work coordinate table to temporary for dialog editing
	for (int i = FIRST_WORKCOOR; i <= LAST_WORKCOOR; i++)
		dlg.WorkCoor[WorkCoord::ConvertToIndex(i)] = m_work_coords[WorkCoord::ConvertToIndex(i)];
		
	// Initialize dialog data
	sprintf(szWorkCoorSetup, "%d", iWorkCoorSetup);
	dlg.m_WorkCoor = szWorkCoorSetup;
	
	dlg.UpdateVariables(iWorkCoorSetup);

	// Invoke the dialog box
	if (dlg.DoModal() == IDOK)
	{
		// retrieve the dialog data
		dlg.RetrieveVariables(iWorkCoorSetup);
			
		// Update work coordinate table from temporary
		for (int i = FIRST_WORKCOOR; i <= LAST_WORKCOOR; i++)
			m_work_coords[WorkCoord::ConvertToIndex(i)] = dlg.WorkCoor[WorkCoord::ConvertToIndex(i)];
		
		InitializeWorkCoordinates();
		SetModifiedFlag();				// Mark document as modified			
	}
}

void CMachine::SetupTools(void)
{
	CSetupToolDlg dlg;
	char szToolSetup[INTSTRINGLENGTH];

	// Copy tool table to temporary for dialog editing
	for (int i = FIRST_TOOL; i <= LAST_TOOL; i++)
		dlg.m_tools[Tool::ConvertToIndex(i)] = m_tools[Tool::ConvertToIndex(i)];
		
	// Initialize dialog data
	sprintf(szToolSetup, "%d", iToolSetup);
	dlg.m_Tool = szToolSetup;
	
	dlg.UpdateVariables(iToolSetup);

	// Invoke the dialog box
	if (dlg.DoModal() == IDOK)
	{
		// retrieve the dialog data
		dlg.RetrieveVariables(iToolSetup);
			
		// Update tool table from temporary
		for (int i = FIRST_TOOL; i <= LAST_TOOL; i++)
			m_tools[Tool::ConvertToIndex(i)] = dlg.m_tools[Tool::ConvertToIndex(i)];
		
		InitializeToolData();
		SetModifiedFlag();			// Mark document as modified			
	}
}

void CMachine::SetupOffsets(void)
{
	CSetupOffsetsDlg dlg;
	char szOffsetNumber[INTSTRINGLENGTH];

	// Copy offset tables to temporary for dialog editing
	dlg.DOffsetTable = (const OffsetTable&)DOffsetTable;
	dlg.HOffsetTable = (const OffsetTable&)HOffsetTable;
		
	// Initialize dialog data
	sprintf(szOffsetNumber, "%d", iOffsetNumber);
	dlg.m_OffsetNumber = szOffsetNumber;
	
	dlg.UpdateVariables(iOffsetNumber);

	// Invoke the dialog box
	if (dlg.DoModal() == IDOK)
	{
		// retrieve the dialog data
		dlg.RetrieveVariables(iOffsetNumber);
			
		// Update work coordinate table from temporary
		DOffsetTable = dlg.DOffsetTable;
		HOffsetTable = dlg.HOffsetTable;
		
		InitializeToolData();
		SetModifiedFlag();			// Mark document as modified			
	}
}

void CMachine::SetupJogging(void)
{
	CSetupJoggingDlg dlg;
	char dimension[VALUESTRINGLENGTH];
	
	dlg.m_MaxJogRate = m_jog_max_rate.GetS(dimension, GetSetupPrecision());
	dlg.m_JogAccel = m_jog_accel.GetS(dimension, GetSetupPrecision());
	dlg.m_JogDecel = m_jog_decel.GetS(dimension, GetSetupPrecision());
	
	dlg.m_UsePCJoystick = (int)m_joy_use_pc;
	dlg.m_JoystickDeadband = (int)m_joy_deadband;
	
	// Invoke the dialog box
	if (dlg.DoModal() == IDOK)
	{
		m_jog_max_rate.SetS((const char *)dlg.m_MaxJogRate);
		m_jog_accel.SetS((const char *)dlg.m_JogAccel);
		m_jog_decel.SetS((const char *)dlg.m_JogDecel);
		
		m_joy_use_pc = dlg.m_UsePCJoystick;
		m_joy_deadband = dlg.m_JoystickDeadband;
		
		SetModifiedFlag();			// Mark document as modified
		
		// If currently in jog mode, make current jogging settings active
		if (GetMode() == JOG_MODE)
			ModeJog();
	}
}

void CMachine::SetupHoming(void)
{
	CSetupHomingDlg dlg;
	
	dlg.m_HomingMacro = (int)m_homing_macro;
	dlg.m_SuppressHomingMessages = (int)m_homing_msg_suppress;

	// Invoke the dialog box
	if (dlg.DoModal() == IDOK)
	{
		m_homing_macro = dlg.m_HomingMacro;
		m_homing_msg_suppress = dlg.m_SuppressHomingMessages;
		
		SetModifiedFlag();			// Mark document as modified
		
		if (GetMode() == HOME_MODE)
		{
			if (!(int)m_homing_msg_suppress)
			{
				if (!m_homing_view)
					OpenHomingView();
			}
			else
			{
				if (m_homing_view)
					CloseHomingView();
			}
		}
	}
}

void CMachine::SetupExternalControls()
{
	CSetupExtControlsDlg dlg;
	
	dlg.m_StartButtonChannel = (int)StartButtonChannel;
	dlg.m_BlockButtonChannel = (int)BlockButtonChannel;
	dlg.m_FHoldButtonChannel = (int)FHoldButtonChannel;
	dlg.m_StartIndicatorChannel = (int)StartIndicatorChannel;
	dlg.m_BlockIndicatorChannel = (int)BlockIndicatorChannel;
	dlg.m_FHoldIndicatorChannel = (int)FHoldIndicatorChannel;
	dlg.m_EStopInputChannel = (int)EStopInputChannel;
	dlg.m_ExtEStopInputChannel = (int)ExtEStopInputChannel;
	dlg.m_DriveFaultInputChannel = (int)DriveFaultInputChannel;
	dlg.m_BlockSkipSwitchChannel = (int)BlockSkipSwitchChannel;
	dlg.m_DryrunSwitchChannel = (int)DryrunSwitchChannel;

	// Invoke the dialog box
	if (dlg.DoModal() == IDOK)
	{
		StartButtonChannel = dlg.m_StartButtonChannel;
		BlockButtonChannel = dlg.m_BlockButtonChannel;
		FHoldButtonChannel = dlg.m_FHoldButtonChannel;
		StartIndicatorChannel = dlg.m_StartIndicatorChannel;
		BlockIndicatorChannel = dlg.m_BlockIndicatorChannel;
		FHoldIndicatorChannel = dlg.m_FHoldIndicatorChannel;
		EStopInputChannel = dlg.m_EStopInputChannel;
		ExtEStopInputChannel = dlg.m_ExtEStopInputChannel;
		DriveFaultInputChannel = dlg.m_DriveFaultInputChannel;
		BlockSkipSwitchChannel = dlg.m_BlockSkipSwitchChannel;
		DryrunSwitchChannel = dlg.m_DryrunSwitchChannel;
		
		InitializeConfiguration();
		SetModifiedFlag();				// Mark document as modified
	}
}

void CMachine::SetupSave(void)
{
	CString FileName;
	
	FileName = this->GetPathName();
	if (FileName != "")
	{
		// If machine setup (document) is already associated with a file, save to that file
		TRACE("Save Setup: %s\r\n",(const char *)FileName);
		OnSaveDocument((const char*)FileName);
		SetModifiedFlag(FALSE);			// Mark document as clean			
	}
	else
	{
		// If no file is associated with machine setup, use Save As function
		SetupSaveAs();
		// Test whether Save As was successful
		FileName = this->GetPathName();
		if (FileName != "")
			SetModifiedFlag(FALSE);		// Mark document as clean			
	}
}

void CMachine::SetupSaveAs(void)
{
	CFileDialog dlgFile(TRUE);
	CString FileName;
	CString title;
	CString strFilter;
	
	title="Save Setup As";				// Window title

	strFilter =  "Machine Files (*.mch)";
	strFilter += (char)'\0';			// next string
	strFilter += "*.mch";
	strFilter += (char)'\0';			// next string
	strFilter += "All Files (*.*)";
	strFilter += (char)'\0';			// next string
	strFilter += "*.*";
	strFilter += (char)'\0';			// last string
	
	FileName = (char)'\0';				// No default file name
	
	dlgFile.m_ofn.lpstrFilter = strFilter;
	dlgFile.m_ofn.hwndOwner = AfxGetMainWnd()->GetSafeHwnd();
	dlgFile.m_ofn.lpstrTitle = title;
	dlgFile.m_ofn.lpstrFile = FileName.GetBuffer(100);	// Allocate a buffer for the file name
                                                           
	if (dlgFile.DoModal() != IDOK)
		return;
		
	FileName.ReleaseBuffer();		// Release the file name buffer

	TRACE("Save Setup As: %s\r\n", (const char *)FileName);
	OnSaveDocument((const char *)FileName);
	
	SetPathName((const char *)FileName);
	SetTitle((const char *)FileName);
}

void CMachine::ModeRun(void)
{
	// Make sure machine has been homed
	if (!m_homed)
	{
		CString Message;
		Message.LoadString(IDS_MACHINE_HOMED_BEFORE_RUN_MODE_MESSAGE);
		AfxMessageBox((const char *)Message, MB_OK | MB_APPLMODAL,0);
		return;
	}
	
	// Switching to Run mode, end current mode
	if (GetMode() != RUN_MODE)
	{
		EndMode(GetMode());
		SetMode(RUN_MODE);
	}
}

void CMachine::ModeMdi(void)
{
	// Make sure machine has been homed
	if (!m_homed)
	{
		CString Message;
		Message.LoadString(IDS_MACHINE_HOMED_BEFORE_MDI_MODE_MESSAGE);
		AfxMessageBox((const char *)Message, MB_OK | MB_APPLMODAL,0);
		return;
	}
		
	// Switching to MDI mode, end current mode
	if (GetMode()!=MDI_MODE)
	{
		EndMode(GetMode());
			
		// Create MDI panel
		CDocTemplate* pTemplate = theApp.GetMDIPanelTemplate();
		ASSERT_VALID(pTemplate);
		CFrameWnd* pFrame = pTemplate->CreateNewFrame(this, NULL);
		if (pFrame == NULL)
		{
			TRACE0("Warning: failed to create new frame\n");
			return;     // command failed
		}
	
		pTemplate->InitialUpdateFrame(pFrame, this);
		m_mdi_frame = pFrame;
		SetMode(MDI_MODE);
	}
}

void CMachine::ModeJog(void)
{
	DIM_UNIT DimUnit;
	PER_UNIT PerUnit;

	if (GetDefaultUnits() == ENGLISH)
	{
		DimUnit = PartPrgmEnglishDimUnit.Get();
		PerUnit = PartPrgmEnglishPerUnit.Get();
	}
	else
	{
		DimUnit = PartPrgmMetricDimUnit.Get();
		PerUnit = PartPrgmMetricPerUnit.Get();
	}
	
	// Switching to Jog mode, end current mode
	if (GetMode() != JOG_MODE)
		EndMode(GetMode());

	if (!(GetJogState() & JOG_ALL_STATES))
	{
		if (m_cutter.GetAxis())
		{
			// When entering Jog mode, need to establish cutter state (raised or lowered). Raising
			// it is the safe thing to do. This is also necessary when accepting Jogging Setup
			// dialog (ie. Enable Cutter option may have been changed)
			m_cutter.Raise();
			
			m_cutter.SetVelocityOverride(1.0);
			m_cutter.SetVelocity(m_cutter.Speed.Get(REVOLUTION_UNIT, PerUnit));
			m_cutter.SetAcceleration(m_cutter.Accel.Get(REVOLUTION_UNIT, PerUnit));
			m_cutter.SetDeceleration(m_cutter.Decel.Get(REVOLUTION_UNIT, PerUnit));
			m_cutter.SetOperatingMode(0,MC_MODE_POSITION);
			m_cutter.EnableAxis(TRUE);
			// While jogging, cutter target position will be  maintained external to controller. Initialize
			// external variable when entering Jog mode
			m_cutter.SetCommandTarget(m_cutter.GetTarget());
		}
		
		if (XAxis.GetAxis())
		{
			XAxis.SetVelocityOverride(1.0);
			XAxis.SetVelocity(m_jog_max_rate.Get(DimUnit, PerUnit));
			XAxis.SetAcceleration(m_jog_accel.Get(DimUnit, PerUnit));
			XAxis.SetDeceleration(m_jog_decel.Get(DimUnit, PerUnit));
			XAxis.SetOperatingMode(0, MC_MODE_VELOCITY);
			XAxis.EnableAxis(TRUE);
		}
		if (YAxis.GetAxis())
		{
			YAxis.SetVelocityOverride(1.0);
			YAxis.SetVelocity(m_jog_max_rate.Get(DimUnit, PerUnit));
			YAxis.SetAcceleration(m_jog_accel.Get(DimUnit, PerUnit));
			YAxis.SetDeceleration(m_jog_decel.Get(DimUnit, PerUnit));
			YAxis.SetOperatingMode(0, MC_MODE_VELOCITY);
			YAxis.EnableAxis(TRUE);
		}
		if (ZAxis.GetAxis())
		{
			ZAxis.SetVelocityOverride(1.0);
			ZAxis.SetVelocity(m_jog_max_rate.Get(DimUnit, PerUnit));
			ZAxis.SetAcceleration(m_jog_accel.Get(DimUnit, PerUnit));
			ZAxis.SetDeceleration(m_jog_decel.Get(DimUnit, PerUnit));
			ZAxis.SetOperatingMode(0, MC_MODE_VELOCITY);
			ZAxis.EnableAxis(TRUE);
		}
	}
		
	SetMode(JOG_MODE);
}

void CMachine::ModeHome(void)
{
	// Switching to Homing, end current mode
	if (GetMode() != HOME_MODE)
	{
		EndMode(GetMode());
		
		if (!(int)m_homing_msg_suppress)
			OpenHomingView();
			
		SetMode(HOME_MODE);
	}
	
	if (!(int)m_homing_msg_suppress)
	{
		CString Prompt;
		m_homing_view->Erase();
		Prompt.LoadString(IDS_HOMING_START_PROMPT);
		m_homing_view->OutputText((const char *)Prompt);
	}
}

void CMachine::OpenHomingView(void)
{
	if (m_homing_frame == NULL)	// If homing view isn't already open, create it
	{
		CDocTemplate* pTemplate = theApp.GetHomingDisplayTemplate();
		ASSERT_VALID(pTemplate);
		CFrameWnd* pFrame = pTemplate->CreateNewFrame(this, NULL);
		if (pFrame == NULL)
		{
			TRACE0("Warning: failed to create homing display frame\n");
			return;     // command failed
		}
		m_homing_frame = pFrame;
		pTemplate->InitialUpdateFrame(pFrame, this, MAKE_VIEW_VISIBLE);
		// Save pointer to view	
		m_homing_view = reinterpret_cast<HomingView*>(pFrame->GetActiveView());
	}
}

// Switching to new mode, terminate operations of current mode
void CMachine::EndMode(int mode)
{
	if (mode == RUN_MODE)
	{
		if (GetPartRunningFlag() == TRUE)
		{
			MtnCtrlr.RunTerminate();
			while(!MtnCtrlr.Rdy())
				;
			SetPartRunningFlag(FALSE);
			SetPartSuspendedFlag(FALSE);		// Terminating a run also clears suspended state
		}
	}
	else if (mode == MDI_MODE)					// If currently in MDI mode, need to close MDI Frame
	{
		if (m_mdi_frame)						// Check that it hasn't already been closed
			m_mdi_frame->DestroyWindow();		// Close frame associated with MDI mode
	}
	else if (mode == HOME_MODE)	
	{
		// If currently in Homing mode, need to close Homing Frame
		if (m_homing_frame)						// Check that it hasn't already been closed
			m_homing_frame->DestroyWindow();
		InitializeConfiguration();				// Use Initialize configuration function to put motors back
												//   in proper configuration
	}
	else if (mode==JOG_MODE)
	{
												// Raise cutter if present
		if (m_cutter.GetAxis())
			MtnCtrlr.GenerateTask((int)m_cutter.RaiseMacro);

		// Test for X axis currently being jogged
		if (GetJogState() & (JOG_XPLUS_ACTIVE | JOG_XMINUS_ACTIVE))
		{
			// Issue stop command to axis
			XAxis.Stop();
			SetJogState(JOG_XPLUS_STOPPING);
			ClearJogState(JOG_XPLUS_ACTIVE);
			ClearJogState(JOG_XMINUS_ACTIVE);
		}
		if (GetJogState() & (JOG_XPLUS_STOPPING | JOG_XMINUS_STOPPING))
		{
			BeginWaitCursor();
			// Wait for axis to stop
			while (!XAxis.DecodeStatus(XAxis.GetStatus(),MC_STAT_TRAJ))
				;
			ClearJogState(JOG_XPLUS_STOPPING);
			ClearJogState(JOG_XMINUS_STOPPING);
			EndWaitCursor();
		}
		// Test for Y axis currently being jogged
		if (GetJogState() & (JOG_YPLUS_ACTIVE | JOG_YMINUS_ACTIVE))
		{
			// Issue stop command to axis
			YAxis.Stop();
			SetJogState(JOG_YPLUS_STOPPING);
			ClearJogState(JOG_YPLUS_ACTIVE);
			ClearJogState(JOG_YMINUS_ACTIVE);
		}
		if (GetJogState() & (JOG_YPLUS_STOPPING | JOG_YMINUS_STOPPING))
		{
			BeginWaitCursor();
			// Wait for axis to stop
			while (!YAxis.DecodeStatus(YAxis.GetStatus(),MC_STAT_TRAJ))
				;
			ClearJogState(JOG_YPLUS_STOPPING);
			ClearJogState(JOG_YMINUS_STOPPING);
			EndWaitCursor();
		}
		// Test for Z axis currently being jogged
		if (GetJogState() & (JOG_ZPLUS_ACTIVE | JOG_ZMINUS_ACTIVE))
		{
			// Issue stop command to axis
			ZAxis.Stop();
			SetJogState(JOG_ZPLUS_STOPPING);
			ClearJogState(JOG_ZPLUS_ACTIVE);
			ClearJogState(JOG_ZMINUS_ACTIVE);
		}
		if (GetJogState() & (JOG_ZPLUS_STOPPING | JOG_ZMINUS_STOPPING))
		{
			BeginWaitCursor();
			// Wait for axis to stop
			while (!ZAxis.DecodeStatus(ZAxis.GetStatus(),MC_STAT_TRAJ))
				;
			ClearJogState(JOG_ZPLUS_STOPPING);
			ClearJogState(JOG_ZMINUS_STOPPING);
			EndWaitCursor();
		}
	}
}

void CMachine::OptionsBlockskip(void)
{
	if (GetBlockSkipStatus() == FALSE)
		EnableBlockSkip();
	else
		DisableBlockSkip();
}

void CMachine::UpdateOptionsBlockskip(CCmdUI* pCmdUI)
{
	pCmdUI->Enable((int)BlockSkipSwitchChannel==0);
	pCmdUI->SetCheck(GetBlockSkipStatus()==TRUE);
}

void CMachine::OptionsDryrun(void)
{
	if (GetDryrunStatus()==FALSE)
		EnableDryrun();
	else
		DisableDryrun();
}

void CMachine::UpdateOptionsDryrun(CCmdUI* pCmdUI)
{
	pCmdUI->Enable((int)DryrunSwitchChannel == 0);
	pCmdUI->SetCheck(GetDryrunStatus() == TRUE);
}
