void VoxCad::SetupPhysicsWindow(void)
{
	PhysicsDockWidget = new QDockWidget(this);
	PhysicsDockWidget->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
	PhysicsDlg = new Dlg_Physics(&MainSim, PhysicsDockWidget);
	PhysicsDockWidget->setWidget(PhysicsDlg);
    PhysicsDockWidget->setWindowTitle("Physics Settings");
	PhysicsDockWidget->setVisible(false);


	connect(PhysicsDockWidget->toggleViewAction(), SIGNAL(triggered(bool)), this, SLOT(PhysicsMode(bool)));
	connect(&MainSim, SIGNAL(UpdateText(QString)), PhysicsDlg, SLOT(SetStatusText(QString)));
	connect(&MainSim, SIGNAL(ReqGLUpdate()), this, SLOT(ReqGLUpdateAll()));
	connect(&MainSim, SIGNAL(ReqAddPlotPoint(double)), PhysicsDlg, SLOT(AddPlotPoint(double)), Qt::DirectConnection); 

	connect(&MainSim, SIGNAL(StartExternalGLUpdate()), GLWindow, SLOT(StartAutoRedraw()));
	connect(&MainSim, SIGNAL(StopExternalGLUpdate()), GLWindow, SLOT(StopAutoRedraw()));


	//connect(FEAInfoDlg, SIGNAL(RequestUpdateGL()), this, SLOT(ReqGLUpdateAll()));
	//connect(FEAInfoDlg, SIGNAL(GetCurIndex(int*)), this, SLOT(GetCurGLSelected(int*)));
	//connect(FEAInfoDlg, SIGNAL(GetFEAInfoString(QString*)), &MainFEA, SLOT(GetFEAInfoString(QString*)));
	//connect(FEAInfoDlg, SIGNAL(GetFEAInfoString(int, QString*)), &MainFEA, SLOT(GetFEAInfoString(int, QString*)));
	//connect(FEAInfoDlg, SIGNAL(DoneAnalyzing()), this, SLOT(ForceViewMode(void))); 

	addDockWidget(Qt::RightDockWidgetArea, PhysicsDockWidget);
}
Exemple #2
0
void QVX_Sim::EndRecording(void) //stops recording
{
	if (!Recording) return; //if already not recording, don't do anything

	DtThaw();
	GLUpdateEveryNFrame = -1; //make sure update every frame is turned off...
	emit StopExternalGLUpdate(); //stop external display that might be at different speed
	emit StartExternalGLUpdate(DEFAULT_DISPLAY_UPDATE_MS); //start default display rate again

	emit ResetGLWindow();

	Recording = false;
}
Exemple #3
0
void VoxCad::SetupTensileWindow(void)
{
	TensileDockWidget = new QDockWidget(this);
	TensileDockWidget->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
	TensileDlg = new Dlg_Tensile(&MainEnv, PhysicsDockWidget);
	TensileDockWidget->setWidget(TensileDlg);
    TensileDockWidget->setWindowTitle("Tensile Testing");
	TensileDockWidget->setVisible(false);

	connect(TensileDlg, SIGNAL(DoneTensileTesting()), this, SLOT(ViewMode()));
	connect(&TensileDlg->Tester, SIGNAL(StartExternalGLUpdate(int)), GLWindow, SLOT(StartAutoRedraw(int)));
	connect(&TensileDlg->Tester, SIGNAL(StopExternalGLUpdate()), GLWindow, SLOT(StopAutoRedraw()));

	addDockWidget(Qt::RightDockWidgetArea, TensileDockWidget);
}
void VoxCad::SetupPhysicsWindow(void)
{
	PhysicsDockWidget = new QDockWidget(this);
	PhysicsDockWidget->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
	PhysicsDlg = new Dlg_Physics(&MainSim, PhysicsDockWidget);
	PhysicsDockWidget->setWidget(PhysicsDlg);
    PhysicsDockWidget->setWindowTitle("Physics Settings");
	PhysicsDockWidget->setVisible(false);


	connect(PhysicsDockWidget->toggleViewAction(), SIGNAL(triggered(bool)), this, SLOT(PhysicsMode(bool)));
	connect(&MainSim, SIGNAL(UpdateText(QString)), PhysicsDlg, SLOT(SetStatusText(QString)));
	connect(&MainSim, SIGNAL(ReqGLUpdate()), this, SLOT(ReqGLUpdateAll()));
	connect(&MainSim, SIGNAL(ReqUiUpdate()), PhysicsDlg, SLOT(UpdateUI()));
	connect(&MainSim, SIGNAL(ReqGLDrawingStatus(bool*)), GLWindow, SLOT(IsDrawing(bool*)));


	connect(&MainSim, SIGNAL(IsStatusTextVisible(bool*)), PhysicsDlg, SLOT(IsOutputVisible(bool*)), Qt::DirectConnection); 
	connect(&MainSim, SIGNAL(IsPlotVisible(bool*)), PhysicsDlg, SLOT(IsPlotVisible(bool*)), Qt::DirectConnection); 
	connect(&MainSim, SIGNAL(GetPlotRqdStats(char*)), this, SLOT(GetPlotRqdDataType(char*)), Qt::DirectConnection); 
	connect(&MainSim, SIGNAL(ReqAddPlotPoint(double)), PhysicsDlg, SLOT(AddPlotPoint(double)), Qt::DirectConnection); 
	connect(&MainSim, SIGNAL(SimEndedInternally(QString)), PhysicsDlg, SLOT(UpdateUI())); 
//	connect(&MainSim, SIGNAL(SimEndedInternally(QString)), this, SLOT(ReqGLUpdateAll())); 


	connect(&MainSim, SIGNAL(StartExternalGLUpdate(int)), GLWindow, SLOT(StartAutoRedraw(int)));
	connect(&MainSim, SIGNAL(StopExternalGLUpdate()), GLWindow, SLOT(StopAutoRedraw()));
	connect(GLWindow, SIGNAL(FindCamTarget(Vec3D<>*)), &MainSim, SLOT(GetCoM(Vec3D<>*)));
	connect(GLWindow, SIGNAL(WantAutoSaveFrames(bool*)), &MainSim, SLOT(WantFramesAutoSaved(bool*)));
	connect(GLWindow, SIGNAL(GetFrameFilePath(QString*)), &MainSim, SLOT(AutoSavePath(QString*)));

	connect(&MainSim, SIGNAL(ResizeGLWindow(int, int)), this, SLOT(ResizeGlWindowArea(int, int)));
	connect(&MainSim, SIGNAL(ResetGLWindow()), this, SLOT(ResetGlWindowArea()));


	//connect(FEAInfoDlg, SIGNAL(RequestUpdateGL()), this, SLOT(ReqGLUpdateAll()));
	//connect(FEAInfoDlg, SIGNAL(GetCurIndex(int*)), this, SLOT(GetCurGLSelected(int*)));
	//connect(FEAInfoDlg, SIGNAL(GetFEAInfoString(QString*)), &MainFEA, SLOT(GetFEAInfoString(QString*)));
	//connect(FEAInfoDlg, SIGNAL(GetFEAInfoString(int, QString*)), &MainFEA, SLOT(GetFEAInfoString(int, QString*)));
	//connect(FEAInfoDlg, SIGNAL(DoneAnalyzing()), this, SLOT(ForceViewMode(void))); 

	addDockWidget(Qt::RightDockWidgetArea, PhysicsDockWidget);
}
void QVX_TensileTest::RunTensileTest(QString* pDispMessage)
{
	QFile File(OutFilePath);
		
	if (!File.open(QIODevice::WriteOnly | QIODevice::Text)) {
		ProgressMessage = "Could not open file. Aborting.";
		return;
	}
	QTextStream out(&File); 

//	double CurMaxDisp = 9e9;
	std::string IntMsg;

	vfloat StepPercAmt = 1.0/NumStep;
	int VoxCount = NumVox();

	int count = 0;
	int MinimumPerStep = 5;

	int NumBCs = pEnv->GetNumBCs();
	for (int j=0; j<NumBCs; j++){
		if (pEnv->GetBC(j)->Displace.Length2() == 0 ) continue; //if zero displacement, continue
		out << "Disp (m)" << "\t" << "Force (N)" << "\t";
		if (IsBasicTensile){out << "Strain (%)" << "\t" << "Stress (MPa)" << "\t" << "Modulus (MPa)" << "\t";}
	}
	
	out << "\n";

	double LastStress; //so we can calculate modulus at each point...
	double LastStrain;

	for (int i=0; i<NumStep; i++){
		ProgressMessage = "Performing tensile test...";
		for (int j=0; j<VoxCount; j++) VoxArray[j].ScaleExternalInputs((i+1)*StepPercAmt);
		//wiat to settle between timesteps...
		int LastBroken = -1;
		ClearHistories(); //needed so it doesn't immediately "converge" on the next time step

		while (NumBroken() != LastBroken){ //if one breaks, repeat the settling undtil we're done breaking...
			LastBroken = NumBroken();
			EnableFailure(false);
	
//			SetSlowDampZ(CurDamp);

			count = 0;
//			bool LastUnder = false; //were we under the threshhold last time?
//			while (!LastUnder || CurMaxDisp > ConvThresh){
			while (!StopConditionMet()){
	//			if (CurMaxDisp < ConvThresh) LastUnder = true;

				for (int i=0; i<MinimumPerStep; i++){
					if (CancelFlag) break;

					if (!TimeStep(&IntMsg)){ //always do at least 5 steps...
						ProgressMessage = "Tensile test failed. \n \n" + IntMsg;
						CancelFlag = true; //get outta here!
					}
//					CurMaxDisp = SS.NormObjDisp;
				}
				if (CancelFlag) break;
				
				count+=MinimumPerStep;
				if (count > 20000){
					QString Num = QString::number(MaxMoveHistory[0]*1000);
					ProgressMessage = "Simulation not converging at " + Num.toStdString() + ". \n Retry with larger threshold.";
				}
			}
			if (CancelFlag) break;

			EnableFailure(true); //do one step to apply breaking and re-settle as needed...
			if (!TimeStep(&IntMsg)){
				ProgressMessage = "Tensile test failed. \n \n" + IntMsg;
				CancelFlag = true; //get outta here!
			}
		}

		for (int j=0; j<NumBCs; j++){
			CVX_FRegion* pThisBC = pEnv->GetBC(j);
			if (pThisBC->Displace.Length2() != 0 ){ //if non-zero displacement
				double CurDisp = pThisBC->Displace.Length()*(i+1.0)/((double)NumStep);
				double tmp2 = -GetSumForceDir(pThisBC);
				out << CurDisp << "\t" << tmp2 << "\t";
				if (IsBasicTensile){ //only two materials, only one with displacement, so we should only ever enter here once!!
					double ThisStress = tmp2/CSArea;
					double ThisStrain = CurDisp/IniLength;
					out << ThisStrain*100 << "\t" << ThisStress/1e6 << "\t";
					if (i!=0) out << (ThisStress-LastStress)/(ThisStrain-LastStrain)/1e6 << "\t";
					else out << "" << "\t";
					LastStress = ThisStress;
					LastStrain = ThisStrain;
				}
			}
		}
		
		out << "\n";

//		for (int k=0; k<VoxArray.size(); k++){
//			VoxArray[k].ExternalDisp *= (i+2.0)/(i+1.0);
//		}

		CurTick = i+1;
	}
	int stop = 1;

	
	File.close();
	ProgressMessage = ""; //flag to not display message boc on return...
	TestRunning = false;
	emit StopExternalGLUpdate();

}
Exemple #6
0
void QVX_Sim::BeginRecording(void) //opens record dialog and begins recording
{
	if (Recording) return; //if we're already recording, don't do anything!

	bool WasPaused = Paused; //pause the sim and remeber whether it was paused or not...
	if (Running && !WasPaused) SimPauseToggle();

	Dlg_VideoCapture VidCapDlg(this);
	if (StopConditionType == SC_MAX_TIME_STEPS) {VidCapDlg.StopEnabled=true; VidCapDlg.CurStopSettings = SS_TIME_STEPS; VidCapDlg.sNumStep = qRound(StopConditionValue);}
	if (StopConditionType == SC_MAX_SIM_TIME) {VidCapDlg.StopEnabled=true; VidCapDlg.CurStopSettings = SS_SIM_TIME; VidCapDlg.sSimTime = StopConditionValue;}
	if (StopConditionType == SC_TEMP_CYCLES) {VidCapDlg.StopEnabled=true; VidCapDlg.CurStopSettings = SS_TEMP_CYCLES; VidCapDlg.sNumCyc = qRound(StopConditionValue);}

	VidCapDlg.UpdateUI();
	VidCapDlg.exec();

	if (VidCapDlg.AcceptedDialog){
		if (VidCapDlg.ResetSimOnBegin) ResetSim();

		emit StopExternalGLUpdate(); //stop external update!
		GLUpdateEveryNFrame = -1; //make sure update every frame is turned off...

		double OutFps = VidCapDlg.OutputFps;
		double OutSpdFctr = VidCapDlg.OutputSpeedFactor;

		switch(VidCapDlg.CurVidSetting){
			case VS_DISPLAY: StartExternalGLUpdate(1000*OutSpdFctr/OutFps); break;
			case VS_SIMULATION: {
					DtFreeze(); //this could be bad for sims with non-linear materials that change dramatically in their stability
					double VideoDt = OutSpdFctr/OutFps; //what the dt step for video should be (in sec)
					if (dt > VideoDt){
						dt = VideoDt; //if simulation timesteps are greater than desired video framerate slow down simulation timesteps
						GLUpdateEveryNFrame=1;
					}
					else { //round dt down so that VideoDt is an even multiple...
						GLUpdateEveryNFrame = (int)(VideoDt/dt) + 1;
						dt = VideoDt/((double)GLUpdateEveryNFrame);
					}

				}
				break;
			case VS_EVERY: GLUpdateEveryNFrame = 1; break;
			default: break;
		}

		if (VidCapDlg.StopEnabled){
			switch (VidCapDlg.CurStopSettings){
				case SS_TIME_STEPS: StopConditionType = SC_MAX_TIME_STEPS; StopConditionValue = (vfloat)VidCapDlg.sNumStep; break;
				case SS_SIM_TIME: StopConditionType = SC_MAX_SIM_TIME; StopConditionValue = (vfloat)VidCapDlg.sSimTime; break;
				case SS_TEMP_CYCLES: StopConditionType = SC_TEMP_CYCLES; StopConditionValue = (vfloat)VidCapDlg.sNumCyc; break;
				default: StopConditionType = SC_NONE;
			}
		}
		else StopConditionType = SC_NONE;

		//reset naming for current files in the selected folder
		VideoOutputFolder=VidCapDlg.CurFolder;
		CurVideoFrameNumber = 0;

		emit ResizeGLWindow(VidCapDlg.WidthPix, VidCapDlg.HeightPix);

		Recording = true;

		if (!Running){ RequestBeginSim(); Running = true;}
		else SimPauseToggle(); //always unpause the simulation if we began recording
	}
	else { //rejected (canceled) return to running or not state.
		if (Running && !WasPaused) SimPauseToggle(); //unpause the simulation if it was running before
	}
}
Exemple #7
0
void QVX_Sim::SimLoop(QString* pSimMessage)
{
	std::string tmp; //need to link this up to get info back...
	if (!Import(NULL, NULL, &tmp)) return;

	int Count = 0; //Possibly redundant with internal sim count.
	int LastCount = 0;
	bool IsStillDrawing;
	Vec3D<> CurSelPos;
	QString PosLabel;
	bool InternalEnding = false;
	QString Message, DispMessage;
	std::string RetMsg;
	QTime tLastPlot; //plot point add every...
	QTime tLastStatus; //status text box updated...
//	QTime tLastStatCalc; //simulation max/mins calculated every...
	tLastPlot.start();
	tLastStatus.start();
//	tLastStatCalc.start();

	emit ReqUiUpdate(); //for slider ranges that depend on dt or other sim params
	emit StartExternalGLUpdate(DEFAULT_DISPLAY_UPDATE_MS);
	StopSim = false;
	Running = true;
	Paused = false;

	//initialize the COM at the start of the simulation	
	IniCM=GetCM();
	Vec3D<> LastCM = IniCM;

	int StatusNumber = 1, PlotPointNumber = 1;
	double UpStatEv = 500; //Updates status pane every X ms
	int UpPlotEv = 30; //updates plot every X ms when not plotting every point.
	char PlotDataTypes;
	bool PlotVis, StatusVis;

	while (true){ //do this step...
		if (StopConditionMet()){InternalEnding = true; StatToCalc=CALCSTAT_ALL; UpdateStats(); RetMsg+="Simulation stop condition reached.\n";break;}//if stop condition met...
		
		//figure out what stats we need to calculate
		StatToCalc=CALCSTAT_NONE;

		emit IsPlotVisible(&PlotVis);
		bool PlottingPoint = (LogEvery?true:tLastPlot.elapsed() > PlotPointNumber*UpPlotEv) && PlotVis;
		if (PlottingPoint){ //ensure we calculate the info we want to plot
			emit GetPlotRqdStats(&PlotDataTypes);
			StatToCalc |= PlotDataTypes;
		}
		emit IsStatusTextVisible(&StatusVis);
		bool UpdatingStatus = (tLastStatus.elapsed() > StatusNumber*UpStatEv) && StatusVis;
		if (UpdatingStatus){StatToCalc |= CALCSTAT_COM;} //calc any data we need in the text status box

		bool DrawingGLLocal = (GLUpdateEveryNFrame != -1 && Count%GLUpdateEveryNFrame==0);
		if (DrawingGLLocal || NeedStatsUpdate){ //calc any data we need to draw the opengl view...
			StatToCalc |= StatRqdToDraw();
			NeedStatsUpdate = false;
			if (LockCoMToCenter) StatToCalc |= CALCSTAT_COM;
		}


//		if (tLastStatCalc.elapsed() > StatCalcNumber*30){CalcStat=true; StatCalcNumber++; /*tLastStatCalc.restart();*/}

		if (!TimeStep(&RetMsg)){InternalEnding = true; break;}//if something happened in this timestep

		if (DrawingGLLocal){
			IsStillDrawing = true;
			ReqGLDrawingStatus(&IsStillDrawing);
			while (IsStillDrawing){ //wait to finish drawing the previous timestep before continuing
				LOCALSLEEP(1);
				ReqGLDrawingStatus(&IsStillDrawing); //are 
			}
			emit ReqGLUpdate(); 
		} 

		if (StopSim) break;
		while(Paused){
			ActuallyPaused = true;
			if (StopSim) break; //kick out of the loop if we've stopped...
			LOCALSLEEP(100);
		}

		if(UpdatingStatus){
//			emit IsStatusTextVisible...
			if (CurXSel != -1){PosLabel = "Vox "+QString::number(XtoSIndexMap[CurXSel]) + "(sim)"; CurSelPos = VoxArray[XtoSIndexMap[CurXSel]].GetCurPos()*1000; }//position in mm
			else {PosLabel = "CoM "; CurSelPos = SS.CurCM*1000;} //center of mass in mm if nothing else selected
			Message = "Time " + QString::number(CurTime, 'g', 3) + " Sec" + 
					"\nStep Num " + QString::number(CurStepCount) + 
					"\nTime Step = " + QString::number(dt, 'g', 3)+ " Sec" + 
					"\nDisplay Rate  = "+ QString::number((Count-LastCount)/(UpStatEv/1000.0), 'g', 3)+" Steps/sec" +
					"\n" + PosLabel + " X:"+ QString::number(CurSelPos.x, 'g', 3)+"  Y:"+ QString::number(CurSelPos.y, 'g', 3)+"  Z:"+ QString::number(CurSelPos.z, 'g', 3)+ " mm" +
					"\nCoM Displacement = "+ QString::number((SS.CurCM-IniCM).Length()*1000, 'g', 3)+" mm" +
					"\nCoM Velocity = "+ QString::number((SS.CurCM-LastCM).Length()*1000/(dt*(Count-LastCount)), 'g', 3)+" mm/s\n";
					
			if (pEnv->IsFloorEnabled()) Message += "Voxel touching ground: " + QString::number(GetNumTouchingFloor()) + "\n";

					Message += "\n";
					LastCM = SS.CurCM;
			
			if (LogEvery) ApproxMSperLog = (double)tLastStatus.elapsed()/(Count-LastCount); //only update ms per step if its not fixed by the timer
			LastCount = Count;

			emit UpdateText(Message);
			StatusNumber++;
		}

		if (PlottingPoint){
			if (LogEvery) emit ReqAddPlotPoint(GetCurTime());
			else {
				ApproxMSperLog = UpPlotEv;
				emit ReqAddPlotPoint(GetCurTime());
				PlotPointNumber++;
			}
		}

//		if (StatCalcNumber == INT_MAX){StatCalcNumber=1; tLastStatCalc.restart();} //avoid int rollover for our counters
		if (StatusNumber == INT_MAX){StatusNumber=1; tLastStatus.restart();}
		if (PlotPointNumber == INT_MAX){PlotPointNumber=1; tLastPlot.restart();}


		Count++;
	}

	emit StopExternalGLUpdate();

//	emit SetExternalGLUpdate(false);
	Running = false;
	Paused = false;

	Message = "Time " + QString::number(CurTime, 'g', 3) + " Sec" + 
		"\nStep Num " + QString::number(CurStepCount) + 
		"\nTime Step = " + QString::number(dt, 'g', 3)+ " Sec" + 
		"\n" + PosLabel + " X:"+ QString::number(CurSelPos.x, 'g', 3)+"  Y:"+ QString::number(CurSelPos.y, 'g', 3)+"  Z:"+ QString::number(CurSelPos.z, 'g', 3)+ " mm" +
		"\nCOM_Dist = "+ QString::number((SS.CurCM-IniCM).Length()*1000, 'g', 3)+" mm\n\n";

	RetMsg += "Simulation stopped.\n";
	Message += RetMsg.c_str();
	DispMessage = Message + "\nFinal Conditions:\nStep = " + QString::number(CurStepCount) + "\nTime Step = " + QString::number(dt)+"\nKinetic Energy = " + QString::number(SS.TotalObjKineticE)+"\n" ;
	emit UpdateText(DispMessage);

	if (InternalEnding)	emit SimEndedInternally(RetMsg.c_str());

//	Running = false;



}
void QVX_TensileTest::RunTensileTest(QString* pDispMessage)
{
	QFile File(OutFilePath);
		
	if (!File.open(QIODevice::WriteOnly | QIODevice::Text)) {
		ProgressMessage = "Could not open file. Aborting.";
		return;
	}
	QTextStream out(&File); 

//	double CurMaxDisp = 9e9;
	std::string IntMsg;

	vfloat StepPercAmt = 1.0/NumStep;
	int VoxCount = NumVox();

	int count = 0;
	int MinimumPerStep = 5;

	int NumBCs = pEnv->GetNumBCs();
	for (int j=0; j<NumBCs; j++){
		if (pEnv->GetBC(j)->Displace.Length2() == 0 ) continue; //if zero displacement, continue
		out << "Disp (m)" << "\t" << "Force (N)" << "\t";
		if (IsBasicTensile){out << "Strain (%)" << "\t" << "Stress (MPa)" << "\t" << "Modulus (MPa)" << "\t";}
	}
	
	out << "\n";

	double LastStress; //so we can calculate modulus at each point for simple tensile...
	double LastStrain;
	double MaxMotion = -FLT_MAX; //the stop condition will be a threshold of this in auto mode
	double LastMotion = 0;
	bool FoundMaxMotion = false;
	if (AutoConverge){ ConvThresh = 0; SetStopConditionValue(ConvThresh);}//don't stop until we get a max

	for (int i=0; i<NumStep; i++){
		ProgressMessage = "Performing tensile test...";
		for (int j=0; j<VoxCount; j++) VoxArray[j].ScaleExternalInputs((i+1)*StepPercAmt);
		//wiat to settle between timesteps...
		int LastBroken = -1;
		ClearHistories(); //needed so it doesn't immediately "converge" on the next time step


		while (NumBroken() != LastBroken){ //if one breaks, repeat the settling until we're done breaking...
			LastBroken = NumBroken();
			EnableFeature(VXSFEAT_FAILURE, false);

		//	EnableFailure(false);
	
//			SetSlowDampZ(CurDamp);

			count = 0;
//			bool LastUnder = false; //were we under the threshhold last time?
//			while (!LastUnder || CurMaxDisp > ConvThresh){
			while (!StopConditionMet()){
	//			if (CurMaxDisp < ConvThresh) LastUnder = true;


				for (int i=0; i<MinimumPerStep; i++){
					if (CancelFlag) break;

					if (!TimeStep(&IntMsg)){ //always do at least 5 steps...
						ProgressMessage = "Tensile test failed. \n \n" + IntMsg;
						CancelFlag = true; //get outta here!
					}
//					CurMaxDisp = SS.NormObjDisp;
				}
				if (CancelFlag) break;
				
				if (!FoundMaxMotion && AutoConverge){
					if (MotionZeroed){ //find first max motion (actually max KE, but close enough)
						FoundMaxMotion = true;
						ConvThresh = LastMotion / AutoConvergeExp;
						SetStopConditionValue(ConvThresh);
						//ProgressMessage = "Auto convergence threshhold calculated: " + QString::number(ConvThresh).toStdString();
					}
					else LastMotion = SS.MaxVoxVel*dt;
				}

				if (count%100==0){
					ProgressMessage = "Performing tensile test...";
					if (AutoConverge && !FoundMaxMotion) ProgressMessage += "\nDetermining convergence threshhold.";
					//else ProgressMessage += "\nMotion at " + QString::number(MaxMoveHistory[0]*1000000, 'g', 3).toStdString() + "/" + QString::number(ConvThresh, 'g', 3).toStdString();
					else ProgressMessage += "\nStep " + QString::number(count, 'g', 3).toStdString() +/*", Max Displacement " + QString::number(SS.MaxVoxDisp, 'g', 3).toStdString() + */", Convergence threshhold " + QString::number(ConvThresh, 'g', 3).toStdString();

					if (count > 20000){
						ProgressMessage += "\nSimulation not converging.\nConsider retrying with a larger threshold. (Currently " + QString::number(MaxMoveHistory[0]*1000, 'g', 3).toStdString() +")";
					}	
				}

				count+=MinimumPerStep;

			}
			if (CancelFlag) break;

			EnableFeature(VXSFEAT_FAILURE, true);
//			EnableFailure(true); //do one step to apply breaking and re-settle as needed...
			if (!TimeStep(&IntMsg)){
				ProgressMessage = "Tensile test failed. \n \n" + IntMsg;
				CancelFlag = true; //get outta here!
			}
		}

		for (int j=0; j<NumBCs; j++){
			CVX_FRegion* pThisBC = pEnv->GetBC(j);
			if (pThisBC->Displace.Length2() != 0 ){ //if non-zero displacement
				double CurDisp = pThisBC->Displace.Length()*(i+1.0)/((double)NumStep);
				double tmp2 = -GetSumForceDir(pThisBC);
				out << CurDisp << "\t" << tmp2 << "\t";
				if (IsBasicTensile){ //only two materials, only one with displacement, so we should only ever enter here once!!
					double ThisStress = tmp2/CSArea;
					double ThisStrain = CurDisp/IniLength;
					out << ThisStrain*100 << "\t" << ThisStress/1e6 << "\t";
					if (i!=0) out << (ThisStress-LastStress)/(ThisStrain-LastStrain)/1e6 << "\t";
					else out << "" << "\t";
					LastStress = ThisStress;
					LastStrain = ThisStrain;
				}
			}
		}
		
		out << "\n";

//		for (int k=0; k<VoxArray.size(); k++){
//			VoxArray[k].ExternalDisp *= (i+2.0)/(i+1.0);
//		}

		CurTick = i+1;
	}
	int stop = 1;

	
	File.close();
	ProgressMessage = ""; //flag to not display message boc on return...
	TestRunning = false;
	emit StopExternalGLUpdate();

}