file.cpp

Wed, 10 Apr 2013 01:51:35 +0300

author
Santeri Piippo <crimsondusk64@gmail.com>
date
Wed, 10 Apr 2013 01:51:35 +0300
changeset 87
8dbf0fdf3374
parent 79
f8917e9d07f6
child 94
a9e67f6e610e
permissions
-rw-r--r--

Split some stuff out of the edit tool bar into move and object tool bars. The object tool bar is now located on the left side of the screen as the top one is running out of space.

/*
 *  LDForge: LDraw parts authoring CAD
 *  Copyright (C) 2013 Santeri `arezey` Piippo
 *  
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <vector>

#include "common.h"
#include "config.h"
#include "file.h"
#include "misc.h"
#include "bbox.h"
#include "gui.h"

cfg (str, io_ldpath, "");

// =============================================================================
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// =============================================================================
OpenFile* findLoadedFile (str zName) {
	for (OpenFile* file : g_LoadedFiles)
		if (file->zFileName == zName)
			return file;
	
	return nullptr;
}

// =============================================================================
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// =============================================================================
FILE* openLDrawFile (str path, bool bSubDirectories) {
	str zTruePath = path;
	
#ifndef WIN32
	zTruePath.replace ("\\", "/");
#endif // WIN32
	
	FILE* fp = fopen (path.chars (), "r");
	str zFilePath;
	
	if (fp != nullptr)
		return fp;
	
	if (~io_ldpath.value) {
		// Try with just the LDraw path first
		zFilePath = str::mkfmt ("%s" DIRSLASH "%s",
			io_ldpath.value.chars(), zTruePath.chars());
		printf ("try %s\n", zFilePath.chars());
		
		fp = fopen (zFilePath, "r");
		if (fp != nullptr)
			return fp;
		
		if (bSubDirectories) {
			char const* saSubdirectories[] = {
				"parts",
				"p",
			};
			
			for (char const* sSubdir : saSubdirectories) {
				zFilePath = str::mkfmt ("%s" DIRSLASH "%s" DIRSLASH "%s",
					io_ldpath.value.chars(), sSubdir, zTruePath.chars());
				printf ("try %s\n", zFilePath.chars());
				
				fp = fopen (zFilePath.chars (), "r");
				
				if (fp)
					return fp;
			}
		}
	}
	
	return nullptr;
}

// =============================================================================
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// =============================================================================
OpenFile* openDATFile (str path) {
	logf ("Opening %s...\n", path.chars());
	
	// Convert the file name to lowercase since some parts contain uppercase
	// file names. I'll assume here that the library will always use lowercase
	// file names for the actual parts..
	FILE* fp = openLDrawFile (-path, true);
	
	if (!fp) {
		logf (LOG_Error, "Couldn't open %s: %s\n", path.chars (), strerror (errno));
		return nullptr;
	}
	
	OpenFile* load = new OpenFile;
	ulong numWarnings = 0;
	
	load->zFileName = path;
	
	vector<str> lines;
	
	{
		char line[1024];
		while (fgets (line, sizeof line, fp)) {
			// Trim the trailing newline
			str zLine = line;
			while (zLine[~zLine - 1] == '\n' || zLine[~zLine - 1] == '\r')
				zLine -= 1;
			
			lines.push_back (zLine);
		}
	}
	
	fclose (fp);
	
	for (str line : lines) {
		LDObject* obj = parseLine (line);
		load->objects.push_back (obj);
		
		// Check for parse errors and warn about tthem
		if (obj->getType() == OBJ_Gibberish) {
			logf (LOG_Warning, "Couldn't parse line #%lu: %s\n",
				(&line - &(lines[0])),
				static_cast<LDGibberish*> (obj)->zReason.chars());
			
			logf (LOG_Warning, "- Line was: %s\n", line.chars());
			numWarnings++;
		}
	}
	
	g_LoadedFiles.push_back (load);
	
	logf (LOG_Success, "File %s parsed successfully (%lu warning%s).\n",
		path.chars(), numWarnings, PLURAL (numWarnings));
	
	return load;
}

// =============================================================================
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// =============================================================================
// Clear everything from the model
void OpenFile::close () {
	for (LDObject* obj : objects)
		delete obj;
	
	// Clear the cache as well
	for (LDObject* obj : objCache)
		delete obj;
	
	delete this;
}

// =============================================================================
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// =============================================================================
void closeAll () {
	if (!g_LoadedFiles.size())
		return;
	
	// Remove all loaded files and the objects they contain
	for (OpenFile* file : g_LoadedFiles)
		file->close ();
	
	// Clear the array
	g_LoadedFiles.clear();
	g_CurrentFile = NULL;
	
	g_ForgeWindow->refresh ();
}

// =============================================================================
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// =============================================================================
void newFile () {
	// Create a new anonymous file and set it to our current
	closeAll ();
	
	OpenFile* f = new OpenFile;
	f->zFileName = "";
	g_LoadedFiles.push_back (f);
	g_CurrentFile = f;
	
	g_BBox.calculate();
	g_ForgeWindow->refresh ();
}

// =============================================================================
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// =============================================================================
void openMainFile (str zPath) {
	closeAll ();
	
	OpenFile* pFile = openDATFile (zPath);
	
	if (!pFile)
		return;
	
	g_CurrentFile = pFile;
	
	// Recalculate the bounding box
	g_BBox.calculate();
	
	// Rebuild the object tree view now.
	g_ForgeWindow->refresh ();
}

// =============================================================================
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// =============================================================================
bool OpenFile::save (str zPath) {
	if (!~zPath)
		zPath = zFileName;
	
	FILE* fp = fopen (zPath, "w");
	if (!fp)
		return false;
	
	// Write all entries now
	for (LDObject* obj : objects) {
		// LDraw requires lines to have DOS line endings
		str zLine = str::mkfmt ("%s\r\n", obj->getContents ().chars ());
		
		fwrite (zLine.chars(), 1, ~zLine, fp);
	}
	
	fclose (fp);
	return true;
}

#define CHECK_TOKEN_COUNT(N) \
	if (tokens.size() != N) \
		return new LDGibberish (zLine, "Bad amount of tokens");

#define CHECK_TOKEN_NUMBERS(MIN,MAX) \
	for (ushort i = MIN; i <= MAX; ++i) \
		if (!isNumber (tokens[i])) \
			return new LDGibberish (zLine, str::mkfmt ("Token #%u was `%s`, expected a number", \
				(i + 1), tokens[i].chars()));

// =============================================================================
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// =============================================================================
static vertex parseVertex (vector<str>& s, const ushort n) {
	// Disable the locale while parsing the line or atof's behavior changes
	// between locales (i.e. fails to read decimals properly). That is
	// quite undesired...
	setlocale (LC_NUMERIC, "C");
	
	vertex v;
	v.x = atof (s[n]);
	v.y = atof (s[n + 1]);
	v.z = atof (s[n + 2]);
	
	return v;
}

// =============================================================================
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// =============================================================================
LDObject* parseLine (str zLine) {
	vector<str> tokens = zLine.split (" ", true);
	
	if (!tokens.size ()) {
		// Line was empty, or only consisted of whitespace
		return new LDEmpty;
	}
	
	if (~tokens[0] != 1)
		return new LDGibberish (zLine, "Illogical line code");
	
	const char c = tokens[0][0];
	switch (c - '0') {
	case 0:
		{
			// Comment
			str zComment;
			for (uint i = 1; i < tokens.size(); ++i) {
				zComment += tokens[i];
				
				if (i != tokens.size() - 1)
					zComment += ' ';
			}
			
			// Handle BFC statements
			if (tokens.size() > 2 && tokens[1] == "BFC") {
				for (short i = 0; i < NUM_BFCStatements; ++i)
					if (zComment == str::mkfmt ("BFC %s", LDBFC::saStatements [i]))
						return new LDBFC (i);
				
				// MLCAD is notorious for stuffing these statements in parts it
				// creates. The above block only handles valid statements, so we
				// need to handle MLCAD-style invertnext separately.
				if (zComment == "BFC CERTIFY INVERTNEXT")
					return new LDBFC (BFC_InvertNext);
			}
			
			if (tokens.size() > 2 && tokens[1] == "!LDFORGE") {
				// Handle LDForge-specific types, they're embedded into comments
				
				if (tokens[2] == "VERTEX") {
					// Vertex (0 !LDFORGE VERTEX)
					CHECK_TOKEN_COUNT (7)
					CHECK_TOKEN_NUMBERS (3, 6)
					
					LDVertex* obj = new LDVertex;
					obj->dColor = atol (tokens[3]);
					obj->vPosition.x = atof (tokens[4]);
					obj->vPosition.y = atof (tokens[5]);
					obj->vPosition.z = atof (tokens[6]);
					
					return obj;
				}
			}
			
			LDComment* obj = new LDComment;
			obj->zText = zComment;
			return obj;
		}
	
	case 1:
		{
			// Subfile
			CHECK_TOKEN_COUNT (15)
			CHECK_TOKEN_NUMBERS (1, 13)
			
			// Try open the file
			OpenFile* pFile = loadSubfile (tokens[14]);
			
			// If we cannot open the file, mark it an error
			if (!pFile)
				return new LDGibberish (zLine, "Could not open referred file");
			
			LDSubfile* obj = new LDSubfile;
			obj->dColor = atol (tokens[1]);
			obj->vPosition = parseVertex (tokens, 2); // 2 - 4
			
			for (short i = 0; i < 9; ++i)
				obj->mMatrix[i] = atof (tokens[i + 5]); // 5 - 13
			
			obj->zFileName = tokens[14];
			obj->pFile = pFile;
			return obj;
		}
	
	case 2:
		{
			CHECK_TOKEN_COUNT (8)
			CHECK_TOKEN_NUMBERS (1, 7)
			
			// Line
			LDLine* obj = new LDLine;
			obj->dColor = atol (tokens[1]);
			for (short i = 0; i < 2; ++i)
				obj->vaCoords[i] = parseVertex (tokens, 2 + (i * 3)); // 2 - 7
			return obj;
		}
	
	case 3:
		{
			CHECK_TOKEN_COUNT (11)
			CHECK_TOKEN_NUMBERS (1, 10)
			
			// Triangle
			LDTriangle* obj = new LDTriangle;
			obj->dColor = atol (tokens[1]);
			
			for (short i = 0; i < 3; ++i)
				obj->vaCoords[i] = parseVertex (tokens, 2 + (i * 3)); // 2 - 10
			
			return obj;
		}
	
	case 4:
		{
			CHECK_TOKEN_COUNT (14)
			CHECK_TOKEN_NUMBERS (1, 13)
			
			// Quadrilateral
			LDQuad* obj = new LDQuad;
			obj->dColor = atol (tokens[1]);
			
			for (short i = 0; i < 4; ++i)
				obj->vaCoords[i] = parseVertex (tokens, 2 + (i * 3)); // 2 - 13
			
			return obj;
		}
	
	case 5:
		{
			CHECK_TOKEN_COUNT (14)
			CHECK_TOKEN_NUMBERS (1, 13)
			
			// Conditional line
			LDCondLine* obj = new LDCondLine;
			obj->dColor = atol (tokens[1]);
			
			for (short i = 0; i < 4; ++i)
				obj->vaCoords[i] = parseVertex (tokens, 2 + (i * 3)); // 2 - 13
			
			return obj;
		}
	
	default: // Strange line we couldn't parse
		return new LDGibberish (zLine, "Unknown line code number");
	}
}

// =============================================================================
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// =============================================================================
OpenFile* loadSubfile (str zFile) {
	// Try open the file
	OpenFile* pFile = findLoadedFile (zFile);
	if (!pFile)
		pFile = openDATFile (zFile);
	
	return pFile;
}

// =============================================================================
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// =============================================================================
void reloadAllSubfiles () {
	if (!g_CurrentFile)
		return;
	
	// First, close all but the current open file.
	for (OpenFile* file : g_LoadedFiles)
		if (file != g_CurrentFile)
			file->close ();
	
	g_LoadedFiles.clear ();
	g_LoadedFiles.push_back (g_CurrentFile);
	
	// Go through all objects in the current file and reload the subfiles
	for (LDObject* obj : g_CurrentFile->objects) {
		if (obj->getType() == OBJ_Subfile) {
			// Note: ref->pFile is invalid right now since all subfiles were closed.
			LDSubfile* ref = static_cast<LDSubfile*> (obj);
			OpenFile* pFile = loadSubfile (ref->zFileName);
			
			if (pFile)
				ref->pFile = pFile;
			else {
				// Couldn't load the file, mark it an error
				ref->replace (new LDGibberish (ref->getContents (), "Could not open referred file"));
			}
		}
		
		// Reparse gibberish files. It could be that they are invalid because
		// the file could not be opened. Circumstances may be different now.
		if (obj->getType() == OBJ_Gibberish)
			obj->replace (parseLine (static_cast<LDGibberish*> (obj)->zContents));
	}
}

// =============================================================================
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// =============================================================================
void OpenFile::addObject (LDObject* obj) {
	if (this != g_CurrentFile) {
		objects.insert (objects.end (), obj);
		return;
	}
	
	const ulong ulSpot = g_ForgeWindow->getInsertionPoint ();
	objects.insert (objects.begin() + ulSpot, obj);
}

// =============================================================================
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// =============================================================================
void OpenFile::forgetObject (LDObject* obj) {
	// Find the index for the given object
	ulong ulIndex;
	for (ulIndex = 0; ulIndex < (ulong)objects.size(); ++ulIndex)
		if (objects[ulIndex] == obj)
			break; // found it
	
	if (ulIndex >= objects.size ())
		return; // was not found
	
	// Erase it from memory
	objects.erase (objects.begin() + ulIndex);
}

mercurial