Loading files
/*
* LibrePCB - Professional EDA for everyone!
* Copyright (C) 2013 LibrePCB Developers, see AUTHORS.md for contributors.
* https://librepcb.org/
*
* 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/>.
*/
/*******************************************************************************
* Includes
******************************************************************************/
#include "project.h"
#include "../application.h"
#include "../exceptions.h"
#include "../fileio/directorylock.h"
#include "../fileio/fileutils.h"
#include "../fileio/versionfile.h"
#include "../font/strokefontpool.h"
#include "../serialization/sexpression.h"
#include "board/board.h"
#include "board/items/bi_polygon.h"
#include "circuit/circuit.h"
#include "circuit/netclass.h"
#include "projectlibrary.h"
#include "schematic/schematic.h"
#include <QtCore>
/*******************************************************************************
* Namespace
******************************************************************************/
namespace librepcb {
/*******************************************************************************
* Constructors / Destructor
******************************************************************************/
Project::Project(std::unique_ptr<TransactionalDirectory> directory,
const QString& filename)
: QObject(nullptr),
mDirectory(std::move(directory)),
mFilename(filename),
mUuid(Uuid::createRandom()),
mName("Unnamed"),
mAuthor(),
mVersion("v1"),
mCreated(QDateTime::currentDateTime()),
mDateTime(QDateTime::currentDateTime()),
mLocaleOrder(),
mNormOrder(),
mCustomBomAttributes(),
mDefaultLockComponentAssembly(false),
mPrimaryBoard(nullptr) {
// Check if the file extension is correct
if (!mFilename.endsWith(".lpp")) {
throw RuntimeError(__FILE__, __LINE__,
tr("The suffix of the project file must be \"lpp\"!"));
}
// Load stroke fonts.
mStrokeFontPool.reset(new StrokeFontPool(
TransactionalDirectory(*mDirectory, "resources/fontobene")));
// Load project library.
mProjectLibrary.reset(
new ProjectLibrary(std::unique_ptr<TransactionalDirectory>(
new TransactionalDirectory(*mDirectory, "library"))));
// Initialize circuit.
mCircuit.reset(new Circuit(*this));
}
Project::~Project() noexcept {
// free the allocated memory in the reverse order of their allocation
// delete all boards and schematics (and catch all thrown exceptions)
foreach (Board* board, mBoards) {
try {
removeBoard(*board, true);
} catch (...) {
}
}
qDeleteAll(mRemovedBoards);
mRemovedBoards.clear();
foreach (Schematic* schematic, mSchematics) {
try {
removeSchematic(*schematic, true);
} catch (...) {
}
}
qDeleteAll(mRemovedSchematics);
mRemovedSchematics.clear();
qDebug().nospace() << "Closed project " << getFilepath().toNative() << ".";
}
/*******************************************************************************
* Setters
******************************************************************************/
void Project::setUuid(const Uuid& newUuid) noexcept {
if (newUuid != mUuid) {
mUuid = newUuid;
emit attributesChanged();
}
}
void Project::setName(const ElementName& newName) noexcept {
if (newName != mName) {
mName = newName;
emit attributesChanged();
}
}
void Project::setAuthor(const QString& newAuthor) noexcept {
if (newAuthor != mAuthor) {
mAuthor = newAuthor;
emit attributesChanged();
}
}
void Project::setVersion(const FileProofName& newVersion) noexcept {
if (newVersion != mVersion) {
mVersion = newVersion;
emit attributesChanged();
}
}
void Project::setCreated(const QDateTime& newCreated) noexcept {
if (newCreated != mCreated) {
mCreated = newCreated;
emit attributesChanged();
}
}
void Project::updateDateTime() noexcept {
mDateTime = QDateTime::currentDateTime();
emit attributesChanged();
}
void Project::setAttributes(const AttributeList& newAttributes) noexcept {
if (newAttributes != mAttributes) {
mAttributes = newAttributes;
emit attributesChanged();
}
}
void Project::setLocaleOrder(const QStringList& newLocales) noexcept {
if (newLocales != mLocaleOrder) {
mLocaleOrder = newLocales;
emit attributesChanged();
}
}
void Project::setNormOrder(const QStringList& newNorms) noexcept {
if (newNorms != mNormOrder) {
mNormOrder = newNorms;
emit attributesChanged();
emit normOrderChanged();
}
}
void Project::setCustomBomAttributes(const QStringList& newKeys) noexcept {
if (newKeys != mCustomBomAttributes) {
mCustomBomAttributes = newKeys;
}
}
void Project::setDefaultLockComponentAssembly(bool newLock) noexcept {
mDefaultLockComponentAssembly = newLock;
}
bool Project::setErcMessageApprovals(
const QSet<SExpression>& approvals) noexcept {
if (approvals != mErcMessageApprovals) {
mErcMessageApprovals = approvals;
emit ercMessageApprovalsChanged(mErcMessageApprovals);
return true;
} else {
return false;
}
}
bool Project::setErcMessageApproved(const SExpression& approval,
bool approved) noexcept {
if (approved && (!mErcMessageApprovals.contains(approval))) {
mErcMessageApprovals.insert(approval);
emit ercMessageApprovalsChanged(mErcMessageApprovals);
return true;
} else if ((!approved) && mErcMessageApprovals.contains(approval)) {
mErcMessageApprovals.remove(approval);
emit ercMessageApprovalsChanged(mErcMessageApprovals);
return true;
}
return false;
}
/*******************************************************************************
* Schematic Methods
******************************************************************************/
int Project::getSchematicIndex(const Schematic& schematic) const noexcept {
return mSchematics.indexOf(const_cast<Schematic*>(&schematic));
}
Schematic* Project::getSchematicByUuid(const Uuid& uuid) const noexcept {
foreach (Schematic* schematic, mSchematics) {
if (schematic->getUuid() == uuid) return schematic;
}
return nullptr;
}
Schematic* Project::getSchematicByName(const QString& name) const noexcept {
foreach (Schematic* schematic, mSchematics) {
if (schematic->getName() == name) return schematic;
}
return nullptr;
}
void Project::addSchematic(Schematic& schematic, int newIndex) {
if ((mSchematics.contains(&schematic)) || (&schematic.getProject() != this)) {
throw LogicError(__FILE__, __LINE__);
}
if (getSchematicByUuid(schematic.getUuid())) {
throw RuntimeError(
__FILE__, __LINE__,
QString("There is already a schematic with the UUID \"%1\"!")
.arg(schematic.getUuid().toStr()));
}
if (getSchematicByName(*schematic.getName())) {
throw RuntimeError(__FILE__, __LINE__,
tr("There is already a schematic with the name \"%1\"!")
.arg(*schematic.getName()));
}
foreach (const Schematic* s, mSchematics) {
if (s->getDirectoryName() == schematic.getDirectoryName()) {
throw RuntimeError(
__FILE__, __LINE__,
tr("There is already a schematic with the directory name \"%1\"!")
.arg(schematic.getDirectoryName()));
}
}
if ((newIndex < 0) || (newIndex > mSchematics.count())) {
newIndex = mSchematics.count();
}
schematic.addToProject(); // can throw
mSchematics.insert(newIndex, &schematic);
if (mRemovedSchematics.contains(&schematic)) {
mRemovedSchematics.removeOne(&schematic);
}
emit schematicAdded(newIndex);
emit attributesChanged();
}
void Project::removeSchematic(Schematic& schematic, bool deleteSchematic) {
if ((!mSchematics.contains(&schematic)) ||
(mRemovedSchematics.contains(&schematic))) {
throw LogicError(__FILE__, __LINE__);
}
if ((!deleteSchematic) && (!schematic.isEmpty())) {
throw RuntimeError(__FILE__, __LINE__,
tr("There are still elements in the schematic \"%1\"!")
.arg(*schematic.getName()));
}
int index = getSchematicIndex(schematic);
Q_ASSERT(index >= 0);
schematic.removeFromProject(); // can throw
mSchematics.removeAt(index);
emit schematicRemoved(index);
emit attributesChanged();
if (deleteSchematic) {
delete &schematic;
} else {
mRemovedSchematics.append(&schematic);
}
}
/*******************************************************************************
* Board Methods
******************************************************************************/
int Project::getBoardIndex(const Board& board) const noexcept {
return mBoards.indexOf(const_cast<Board*>(&board));
}
Board* Project::getBoardByUuid(const Uuid& uuid) const noexcept {
foreach (Board* board, mBoards) {
if (board->getUuid() == uuid) return board;
}
return nullptr;
}
Board* Project::getBoardByName(const QString& name) const noexcept {
foreach (Board* board, mBoards) {
if (board->getName() == name) return board;
}
return nullptr;
}
void Project::addBoard(Board& board, int newIndex) {
if ((mBoards.contains(&board)) || (&board.getProject() != this)) {
throw LogicError(__FILE__, __LINE__);
}
if (getBoardByUuid(board.getUuid())) {
throw RuntimeError(__FILE__, __LINE__,
QString("There is already a board with the UUID \"%1\"!")
.arg(board.getUuid().toStr()));
}
if (getBoardByName(*board.getName())) {
throw RuntimeError(__FILE__, __LINE__,
tr("There is already a board with the name \"%1\"!")
.arg(*board.getName()));
}
foreach (const Board* b, mBoards) {
if (b->getDirectoryName() == board.getDirectoryName()) {
throw RuntimeError(
__FILE__, __LINE__,
tr("There is already a board with the directory name \"%1\"!")
.arg(board.getDirectoryName()));
}
}
if ((newIndex < 0) || (newIndex > mBoards.count())) {
newIndex = mBoards.count();
}
board.addToProject(); // can throw
mBoards.insert(newIndex, &board);
if (mRemovedBoards.contains(&board)) {
mRemovedBoards.removeOne(&board);
}
emit boardAdded(newIndex);
updatePrimaryBoard();
emit attributesChanged();
}
void Project::removeBoard(Board& board, bool deleteBoard) {
if ((!mBoards.contains(&board)) || (mRemovedBoards.contains(&board))) {
throw LogicError(__FILE__, __LINE__);
}
int index = getBoardIndex(board);
Q_ASSERT(index >= 0);
board.removeFromProject(); // can throw
mBoards.removeAt(index);
emit boardRemoved(index);
updatePrimaryBoard();
emit attributesChanged();
if (deleteBoard) {
delete &board;
} else {
mRemovedBoards.append(&board);
}
}
/*******************************************************************************
* General Methods
******************************************************************************/
void Project::save() {
qDebug() << "Save project files to transactional file system...";
// Version file.
mDirectory->write(
".librepcb-project",
VersionFile(Application::getFileFormatVersion()).toByteArray());
// Project file.
mDirectory->write(mFilename, "LIBREPCB-PROJECT");
// Metadata.
{
std::unique_ptr<SExpression> root =
SExpression::createList("librepcb_project_metadata");
root->appendChild(mUuid);
root->ensureLineBreak();
root->appendChild("name", mName);
root->ensureLineBreak();
root->appendChild("author", mAuthor);
root->ensureLineBreak();
root->appendChild("version", mVersion);
root->ensureLineBreak();
root->appendChild("created", mCreated);
root->ensureLineBreak();
mAttributes.serialize(*root);
root->ensureLineBreak();
mDirectory->write("project/metadata.lp", root->toByteArray());
}
// Settings.
{
std::unique_ptr<SExpression> root =
SExpression::createList("librepcb_project_settings");
root->ensureLineBreak();
{
SExpression& node = root->appendList("library_locale_order");
foreach (const QString& locale, mLocaleOrder) {
node.ensureLineBreak();
node.appendChild("locale", locale);
}
node.ensureLineBreak();
}
root->ensureLineBreak();
{
SExpression& node = root->appendList("library_norm_order");
foreach (const QString& norm, mNormOrder) {
node.ensureLineBreak();
node.appendChild("norm", norm);
}
node.ensureLineBreak();
}
root->ensureLineBreak();
{
SExpression& node = root->appendList("custom_bom_attributes");
foreach (const QString& key, mCustomBomAttributes) {
node.ensureLineBreak();
node.appendChild("attribute", key);
}
node.ensureLineBreak();
}
root->ensureLineBreak();
root->appendChild("default_lock_component_assembly",
mDefaultLockComponentAssembly);
root->ensureLineBreak();
mDirectory->write("project/settings.lp", root->toByteArray());
}
// User settings.
{
std::unique_ptr<SExpression> root =
SExpression::createList("librepcb_project_user_settings");
root->ensureLineBreak();
mDirectory->write("project/settings.user.lp", root->toByteArray());
}
// Output jobs.
{
std::unique_ptr<SExpression> root =
SExpression::createList("librepcb_jobs");
root->ensureLineBreak();
mOutputJobs.serialize(*root);
root->ensureLineBreak();
mDirectory->write("project/jobs.lp", root->toByteArray());
}
// Circuit.
{
std::unique_ptr<SExpression> root =
SExpression::createList("librepcb_circuit");
mCircuit->serialize(*root);
mDirectory->write("circuit/circuit.lp", root->toByteArray());
}
// ERC.
{
std::unique_ptr<SExpression> root = SExpression::createList("librepcb_erc");
foreach (const SExpression& node,
Toolbox::sortedQSet(mErcMessageApprovals)) {
root->ensureLineBreak();
root->appendChild(node);
}
root->ensureLineBreak();
mDirectory->write("circuit/erc.lp", root->toByteArray());
}
// Schematics.
{
std::unique_ptr<SExpression> root =
SExpression::createList("librepcb_schematics");
foreach (Schematic* schematic, mSchematics) {
root->ensureLineBreak();
root->appendChild(
"schematic",
"schematics/" + schematic->getDirectoryName() + "/schematic.lp");
schematic->save();
}
root->ensureLineBreak();
mDirectory->write("schematics/schematics.lp", root->toByteArray());
}
// Boards.
{
std::unique_ptr<SExpression> root =
SExpression::createList("librepcb_boards");
foreach (Board* board, mBoards) {
root->ensureLineBreak();
root->appendChild("board",
"boards/" + board->getDirectoryName() + "/board.lp");
board->save();
}
root->ensureLineBreak();
mDirectory->write("boards/boards.lp", root->toByteArray());
}
// Update the datetime attribute of the project.
updateDateTime();
}
/*******************************************************************************
* Static Methods
******************************************************************************/
std::unique_ptr<Project> Project::create(
std::unique_ptr<TransactionalDirectory> directory,
const QString& filename) {
Q_ASSERT(directory);
qDebug().nospace() << "Create project "
<< directory->getAbsPath(filename).toNative() << "...";
// Check if there isn't already a project in the selected directory.
if (directory->fileExists(".librepcb-project") ||
directory->fileExists(filename)) {
throw RuntimeError(
__FILE__, __LINE__,
QString(tr("The directory \"%1\" already contains a LibrePCB project."))
.arg(directory->getAbsPath().toNative()));
}
// Populate with stroke fonts.
TransactionalDirectory fontobeneDir(*directory, "resources/fontobene");
FilePath src = Application::getResourcesDir().getPathTo("fontobene");
foreach (const FilePath& fp,
FileUtils::getFilesInDirectory(src, {"*.bene"})) {
if (fp.getSuffix() == "bene") {
fontobeneDir.write(fp.getFilename(), FileUtils::readFile(fp));
}
}
// Create empty project.
std::unique_ptr<Project> p(new Project(std::move(directory), filename));
// Add default assembly variant with name "Std".
p->getCircuit().addAssemblyVariant(std::make_shared<AssemblyVariant>(
Uuid::createRandom(), FileProofName("Std"), "Standard assembly"));
// Add default netclass with name "default".
{
NetClass* netclass = new NetClass(p->getCircuit(), Uuid::createRandom(),
ElementName("default"));
p->getCircuit().addNetClass(*netclass);
}
// Done!
return p;
}
bool Project::isFilePathInsideProjectDirectory(const FilePath& fp) noexcept {
FilePath parent = fp.getParentDir();
if (isProjectDirectory(parent)) {
return true;
} else if (parent.isValid() && !parent.isRoot()) {
return isFilePathInsideProjectDirectory(parent);
} else {
return false;
}
}
bool Project::isProjectFile(const FilePath& file) noexcept {
return file.getSuffix() == "lpp" && file.isExistingFile() &&
isProjectDirectory(file.getParentDir());
}
bool Project::isProjectDirectory(const FilePath& dir) noexcept {
return dir.getPathTo(".librepcb-project").isExistingFile();
}
Version Project::getProjectFileFormatVersion(const FilePath& dir) {
QByteArray content = FileUtils::readFile(dir.getPathTo(".librepcb-project"));
VersionFile file = VersionFile::fromByteArray(content);
return file.getVersion();
}
/*******************************************************************************
* Private Methods
******************************************************************************/
void Project::updatePrimaryBoard() {
Board* primary = mBoards.value(0);
if (mPrimaryBoard != primary) {
mPrimaryBoard = primary;
primaryBoardChanged(mPrimaryBoard);
}
}
/*******************************************************************************
* End of File
******************************************************************************/
} // namespace librepcb
2955876e1This file is pinned to the selected release. Download the original to open it in your design tool.