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 "projectloader.h"
#include "../application.h"
#include "../fileio/versionfile.h"
#include "../library/cmp/component.h"
#include "../library/dev/device.h"
#include "../library/dev/part.h"
#include "../library/pkg/package.h"
#include "../library/sym/symbol.h"
#include "../serialization/fileformatmigration.h"
#include "../types/pcbcolor.h"
#include "board/board.h"
#include "board/boarddesignrules.h"
#include "board/boardfabricationoutputsettings.h"
#include "board/drc/boarddesignrulechecksettings.h"
#include "board/items/bi_device.h"
#include "board/items/bi_hole.h"
#include "board/items/bi_netline.h"
#include "board/items/bi_netpoint.h"
#include "board/items/bi_netsegment.h"
#include "board/items/bi_pad.h"
#include "board/items/bi_plane.h"
#include "board/items/bi_polygon.h"
#include "board/items/bi_stroketext.h"
#include "board/items/bi_via.h"
#include "board/items/bi_zone.h"
#include "circuit/assemblyvariant.h"
#include "circuit/bus.h"
#include "circuit/circuit.h"
#include "circuit/componentinstance.h"
#include "circuit/componentsignalinstance.h"
#include "circuit/netclass.h"
#include "circuit/netsignal.h"
#include "erc/electricalrulecheck.h"
#include "project.h"
#include "projectlibrary.h"
#include "schematic/items/si_busjunction.h"
#include "schematic/items/si_buslabel.h"
#include "schematic/items/si_busline.h"
#include "schematic/items/si_bussegment.h"
#include "schematic/items/si_image.h"
#include "schematic/items/si_netlabel.h"
#include "schematic/items/si_netline.h"
#include "schematic/items/si_netpoint.h"
#include "schematic/items/si_netsegment.h"
#include "schematic/items/si_polygon.h"
#include "schematic/items/si_symbol.h"
#include "schematic/items/si_symbolpin.h"
#include "schematic/items/si_text.h"
#include "schematic/schematic.h"
#include <QtCore>
/*******************************************************************************
* Namespace
******************************************************************************/
namespace librepcb {
static const char* HTML_LOG_HEADER = R"(<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{TITLE}}</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; background: #f5f5f5; }
table { border-collapse: collapse; width: 100%; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
th, td { padding: 12px; border-bottom: 1px solid #ddd; width: auto; white-space: nowrap; text-align: center; vertical-align: middle; }
th { background: #e0e0e0; font-weight: bold; }
th:nth-child(3), td:nth-child(4) { text-align: left; width: 100%; white-space: normal; }
.footer { font-size: 10pt; font-style: italic; color: #505050; }
.note { background: #e6f3ff; }
.warning { background: #fff3cd; }
.critical { background: #f8d7da; }
.note td:nth-child(3)::before { content: "ℹ️"; }
.warning td:nth-child(3)::before { content: "⚠️"; }
.critical td:nth-child(3)::before { content: "❌"; }
</style>
</head>
<body>
<h1>🚀 {{TITLE}}</h1>
)";
static const char* HTML_LOG_TABLE_HEADER = R"( <table>
<thead>
<tr>
<th>{{HEADER_VERSION}}</th>
<th>{{HEADER_OCCURRENCES}}</th>
<th colspan="2">{{HEADER_MESSAGE}}</th>
</tr>
</thead>
<tbody>
)";
static const char* HTML_LOG_FOOTER = R"( </tbody>
</table>
<p class="footer">{{GENERATED_AT}}</p>
</body>
</html>)";
QByteArray ProjectLoader::MigrationLog::toHtml(
bool isTemporary) const noexcept {
const QHash<FileFormatMigration::Message::Severity, QString> classes = {
{FileFormatMigration::Message::Severity::Note, "note"},
{FileFormatMigration::Message::Severity::Warning, "warning"},
{FileFormatMigration::Message::Severity::Critical, "critical"},
};
QString html = HTML_LOG_HEADER;
if (isTemporary) {
html +=
" <div style=\"background:#d0d0d0;border: 1px solid "
"black;padding:8px;margin-bottom:15px;\">\n";
html += " <span style=\"color:blue;\">" %
tr("Note: This is a temporary file since the project has not been "
"saved to disk yet. When you save the project, this log will be "
"written to the projects '%1' directory.")
.arg("logs")
.toHtmlEscaped() %
"</span></br>\n";
html += " <span style=\"color:red;font-weight:bold;\">" %
tr("Attention: After saving the project, the file format migration "
"cannot be reverted. Check if everything looks good before saving "
"the project.")
.toHtmlEscaped() %
"</span>\n";
html += " </div>\n";
}
html += " <p>" %
tr("The project has been migrated from file format <strong>%1 to "
"%2</strong>, which emitted the following messages:")
.arg("v" % fromVersion.toStr(), "v" % toVersion.toStr()) %
"</p>\n";
html += HTML_LOG_TABLE_HEADER;
if (messages.isEmpty()) {
html += QString(" <tr>\n");
html +=
QString(
" <td colspan=\"4\" style=\"text-align:left;\">%1</td>\n")
.arg(tr("No messages emitted.").toHtmlEscaped());
html += " </tr>\n";
}
for (const FileFormatMigration::Message& m : messages) {
const QStringList cells = {
m.fromVersion.toStr() % " ⇨ " % m.toVersion.toStr(),
(m.affectedItems > 0) ? QString::number(m.affectedItems) : QString(),
QString(), // Severity icon from CSS
m.message,
};
html += QString(" <tr class=\"%1\">\n").arg(classes.value(m.severity));
for (const QString& s : cells) {
html += QString(" <td>%1</td>\n").arg(s.toHtmlEscaped());
}
html += " </tr>\n";
}
html += HTML_LOG_FOOTER;
html.replace("{{HEADER_VERSION}}", tr("Version"));
html.replace("{{HEADER_OCCURRENCES}}", tr("Occurrences"));
html.replace("{{HEADER_MESSAGE}}", tr("Message"));
html.replace("{{TITLE}}",
QString("Migration Log: %1").arg(projectName).toHtmlEscaped());
html.replace(
"{{GENERATED_AT}}",
QString("Generated by LibrePCB %1 at %2")
.arg(Application::getVersion())
.arg(dateTime.date().toString() % ", " % dateTime.time().toString())
.toHtmlEscaped());
return html.toUtf8();
}
/*******************************************************************************
* Constructors / Destructor
******************************************************************************/
ProjectLoader::ProjectLoader(QObject* parent) noexcept
: QObject(parent), mAutoAssignDeviceModels(false) {
}
ProjectLoader::~ProjectLoader() noexcept {
}
/*******************************************************************************
* General Methods
******************************************************************************/
std::unique_ptr<Project> ProjectLoader::open(
std::unique_ptr<TransactionalDirectory> directory,
const QString& filename) {
Q_ASSERT(directory);
mMigrationLog = std::nullopt;
QElapsedTimer timer;
timer.start();
const FilePath fp = directory->getAbsPath(filename);
qDebug().nospace() << "Open project " << fp.toNative() << "...";
// Check if the project file exists.
if (!directory->fileExists(filename)) {
throw RuntimeError(__FILE__, __LINE__,
tr("File does not exist: '%1'").arg(fp.toNative()));
}
// Read the file format version.
if (!directory->fileExists(".librepcb-project")) {
throw RuntimeError(__FILE__, __LINE__,
tr("Directory does not contain a LibrePCB project: '%1'")
.arg(directory->getAbsPath().toNative()));
}
const Version fileFormat =
VersionFile::fromByteArray(directory->read(".librepcb-project"))
.getVersion();
qDebug().noquote() << "Detected project file format:" << fileFormat.toStr();
// Check file format version.
if (fileFormat > Application::getFileFormatVersion()) {
throw RuntimeError(
__FILE__, __LINE__,
tr("This project was created with a newer application version.\n"
"You need at least LibrePCB %1 to open it.\n\n%2")
.arg(fileFormat.toPrettyStr(3))
.arg(fp.toNative()));
}
// Upgrade file format, if needed.
for (auto migration : FileFormatMigration::getMigrations(fileFormat)) {
if (!mMigrationLog) {
mMigrationLog = MigrationLog{filename,
QDateTime::currentDateTime(),
fileFormat,
Application::getFileFormatVersion(),
{}};
}
qInfo().nospace().noquote()
<< "Project file format is outdated, upgrading from v"
<< migration->getFromVersion().toStr() << " to v"
<< migration->getToVersion().toStr() << "...";
migration->upgradeProject(*directory, mMigrationLog->messages);
}
// Sort & save migration messages.
if (mMigrationLog) {
std::sort(mMigrationLog->messages.begin(), mMigrationLog->messages.end(),
[](const FileFormatMigration::Message& a,
const FileFormatMigration::Message& b) {
// It is most intuitive to have the oldest messages at top, and
// the newest messages at bottom since they might "depend" on
// each other.
if (a.toVersion != b.toVersion) {
return a.toVersion < b.toVersion;
} else if (a.severity != b.severity) {
return a.severity > b.severity;
} else {
return a.message < b.message;
}
});
const QByteArray html = mMigrationLog->toHtml(false);
const QString logFileName =
QString("logs/%1_migration_to_v%2.html")
.arg(mMigrationLog->dateTime.date().toString("yyyy-MM-dd"))
.arg(Application::getFileFormatVersion().toStr());
directory->write(logFileName, html);
}
// Load project.
std::unique_ptr<Project> p(new Project(std::move(directory), filename));
loadMetadata(*p);
loadSettings(*p);
loadOutputJobs(*p);
loadLibrary(*p);
loadCircuit(*p);
loadErc(*p);
loadSchematics(*p);
loadBoards(*p);
loadProjectUserSettings(*p);
// If the file format was migrated, clean up obsolete ERC messages.
if (mMigrationLog) {
qInfo() << "Running ERC to clean up obsolete message approvals...";
ElectricalRuleCheck erc(*p);
const RuleCheckMessageList msgs = erc.runChecks();
const QSet<SExpression> approvals = RuleCheckMessage::getAllApprovals(msgs);
p->setErcMessageApprovals(p->getErcMessageApprovals() & approvals);
}
// Make sure the files are formatted correctly. Also handle possible errors
// during serialization now instead of later.
if (mMigrationLog) {
p->save();
}
// Done!
qDebug() << "Successfully opened project in" << timer.elapsed() << "ms.";
return p;
}
/*******************************************************************************
* Private Methods
******************************************************************************/
void ProjectLoader::loadMetadata(Project& p) {
qDebug() << "Load project metadata...";
const QString fp = "project/metadata.lp";
const std::unique_ptr<const SExpression> root = SExpression::parse(
p.getDirectory().read(fp), p.getDirectory().getAbsPath(fp));
p.setUuid(deserialize<Uuid>(root->getChild("@0")));
p.setName(deserialize<ElementName>(root->getChild("name/@0")));
p.setAuthor(root->getChild("author/@0").getValue());
p.setVersion(deserialize<FileProofName>(root->getChild("version/@0")));
p.setCreated(deserialize<QDateTime>(root->getChild("created/@0")));
p.setAttributes(AttributeList(*root));
qDebug() << "Successfully loaded project metadata.";
}
void ProjectLoader::loadSettings(Project& p) {
qDebug() << "Load project settings...";
const QString fp = "project/settings.lp";
const std::unique_ptr<const SExpression> root = SExpression::parse(
p.getDirectory().read(fp), p.getDirectory().getAbsPath(fp));
{
QStringList l;
foreach (const SExpression* node,
root->getChild("library_locale_order").getChildren("locale")) {
l.append(node->getChild("@0").getValue());
}
p.setLocaleOrder(l);
}
{
QStringList l;
foreach (const SExpression* node,
root->getChild("library_norm_order").getChildren("norm")) {
l.append(node->getChild("@0").getValue());
}
p.setNormOrder(l);
}
{
QStringList l;
foreach (const SExpression* node,
root->getChild("custom_bom_attributes").getChildren("attribute")) {
l.append(node->getChild("@0").getValue());
}
p.setCustomBomAttributes(l);
}
p.setDefaultLockComponentAssembly(
deserialize<bool>(root->getChild("default_lock_component_assembly/@0")));
qDebug() << "Successfully loaded project settings.";
}
void ProjectLoader::loadOutputJobs(Project& p) {
qDebug() << "Load output jobs...";
const QString fp = "project/jobs.lp";
const std::unique_ptr<const SExpression> root = SExpression::parse(
p.getDirectory().read(fp), p.getDirectory().getAbsPath(fp));
p.getOutputJobs() = deserialize<OutputJobList>(*root);
qDebug() << "Successfully loaded output jobs.";
}
void ProjectLoader::loadLibrary(Project& p) {
qDebug() << "Load project library...";
loadLibraryElements<Symbol>(p, "sym", "symbols", &ProjectLibrary::addSymbol);
loadLibraryElements<Package>(p, "pkg", "packages",
&ProjectLibrary::addPackage);
loadLibraryElements<Component>(p, "cmp", "components",
&ProjectLibrary::addComponent);
loadLibraryElements<Device>(p, "dev", "devices", &ProjectLibrary::addDevice);
qDebug() << "Successfully loaded project library.";
}
template <typename ElementType>
void ProjectLoader::loadLibraryElements(
Project& p, const QString& dirname, const QString& type,
void (ProjectLibrary::*addFunction)(ElementType&)) {
// Search all subdirectories which have a valid UUID as directory name.
int count = 0;
foreach (const QString& sub, p.getLibrary().getDirectory().getDirs(dirname)) {
std::unique_ptr<TransactionalDirectory> dir(new TransactionalDirectory(
p.getLibrary().getDirectory(), dirname % "/" % sub));
// Check if directory is a valid library element.
if (!LibraryBaseElement::isValidElementDirectory<ElementType>(*dir, "")) {
qWarning() << "Invalid directory in project library, ignoring it:"
<< dir->getAbsPath().toNative();
continue;
}
// Load the library element.
ElementType* element =
ElementType::open(std::move(dir)).release(); // can throw
(p.getLibrary().*addFunction)(*element);
++count;
}
qDebug().nospace().noquote()
<< "Successfully loaded " << count << " " << type << ".";
}
void ProjectLoader::loadCircuit(Project& p) {
qDebug() << "Load circuit...";
const QString fp = "circuit/circuit.lp";
const std::unique_ptr<const SExpression> root = SExpression::parse(
p.getDirectory().read(fp), p.getDirectory().getAbsPath(fp));
// Load assembly variants.
foreach (const SExpression* node, root->getChildren("variant")) {
auto av = std::make_shared<AssemblyVariant>(*node);
p.getCircuit().addAssemblyVariant(av);
}
if (p.getCircuit().getAssemblyVariants().isEmpty()) {
throw RuntimeError(__FILE__, __LINE__, "Project has no assembly variants.");
}
// Load net classes.
foreach (const SExpression* node, root->getChildren("netclass")) {
NetClass* netclass = new NetClass(p.getCircuit(), *node);
p.getCircuit().addNetClass(*netclass);
}
// Load net signals.
foreach (const SExpression* node, root->getChildren("net")) {
const Uuid netclassUuid = deserialize<Uuid>(node->getChild("netclass/@0"));
NetClass* netclass = p.getCircuit().getNetClasses().value(netclassUuid);
if (!netclass) {
throw RuntimeError(
__FILE__, __LINE__,
QString("Inexistent net class: '%1'").arg(netclassUuid.toStr()));
}
NetSignal* netsignal = new NetSignal(
p.getCircuit(), deserialize<Uuid>(node->getChild("@0")), *netclass,
deserialize<CircuitIdentifier>(node->getChild("name/@0")),
deserialize<bool>(node->getChild("auto/@0")));
p.getCircuit().addNetSignal(*netsignal);
}
// Load buses.
foreach (const SExpression* node, root->getChildren("bus")) {
Bus* bus = new Bus(p.getCircuit(), deserialize<Uuid>(node->getChild("@0")),
deserialize<BusName>(node->getChild("name/@0")),
deserialize<bool>(node->getChild("auto/@0")),
deserialize<bool>(node->getChild("prefix_nets/@0")),
deserialize<std::optional<UnsignedLength>>(
node->getChild("max_trace_length_difference/@0")));
p.getCircuit().addBus(*bus);
}
// Load component instances.
foreach (const SExpression* node, root->getChildren("component")) {
const Uuid cmpUuid = deserialize<Uuid>(node->getChild("lib_component/@0"));
const Component* libCmp = p.getLibrary().getComponent(cmpUuid);
if (!libCmp) {
throw RuntimeError(
__FILE__, __LINE__,
QString("The component '%1' does not exist in the project's library.")
.arg(cmpUuid.toStr()));
}
ComponentInstance* cmp = new ComponentInstance(
p.getCircuit(), deserialize<Uuid>(node->getChild("@0")), *libCmp,
deserialize<Uuid>(node->getChild("lib_variant/@0")),
deserialize<CircuitIdentifier>(node->getChild("name/@0")));
cmp->setValue(node->getChild("value/@0").getValue());
cmp->setAttributes(AttributeList(*node));
cmp->setAssemblyOptions(ComponentAssemblyOptionList(*node));
cmp->setLockAssembly(deserialize<bool>(node->getChild("lock_assembly/@0")));
p.getCircuit().addComponentInstance(*cmp);
QSet<Uuid> loadedSignals;
foreach (const SExpression* child, node->getChildren("signal")) {
const Uuid cmpSigUuid = deserialize<Uuid>(child->getChild("@0"));
ComponentSignalInstance* cmpSig = cmp->getSignalInstance(cmpSigUuid);
if (!cmpSig) {
throw RuntimeError(__FILE__, __LINE__,
QString("Inexistent component signal: '%1'")
.arg(cmpSigUuid.toStr()));
}
if (loadedSignals.contains(cmpSigUuid)) {
throw RuntimeError(__FILE__, __LINE__,
QString("The signal '%1' is defined multiple times.")
.arg(cmpSigUuid.toStr()));
}
loadedSignals.insert(cmpSigUuid);
if (const std::optional<Uuid> netSignalUuid =
deserialize<std::optional<Uuid>>(child->getChild("net/@0"))) {
NetSignal* netSignal =
p.getCircuit().getNetSignals().value(*netSignalUuid);
if (!netSignal) {
throw RuntimeError(__FILE__, __LINE__,
QString("Inexistent net signal: '%1'")
.arg(netSignalUuid->toStr()));
}
cmpSig->setNetSignal(netSignal);
}
}
if (loadedSignals.count() != cmp->getSignals().count()) {
qCritical() << "Signal count mismatch:" << loadedSignals.count()
<< "!=" << cmp->getSignals().count();
throw RuntimeError(
__FILE__, __LINE__,
QString("The signal count of the component instance '%1' does "
"not match with the signal count of the component '%2'.")
.arg(cmp->getUuid().toStr())
.arg(libCmp->getUuid().toStr()));
}
}
qDebug() << "Successfully loaded circuit.";
}
void ProjectLoader::loadErc(Project& p) {
qDebug() << "Load ERC approvals...";
const QString fp = "circuit/erc.lp";
const std::unique_ptr<const SExpression> root = SExpression::parse(
p.getDirectory().read(fp), p.getDirectory().getAbsPath(fp));
// Load approvals.
QSet<SExpression> approvals;
foreach (const SExpression* node, root->getChildren("approved")) {
approvals.insert(*node);
}
p.setErcMessageApprovals(approvals);
qDebug() << "Successfully loaded ERC approvals.";
}
void ProjectLoader::loadSchematics(Project& p) {
qDebug() << "Load schematics...";
const QString fp = "schematics/schematics.lp";
const std::unique_ptr<const SExpression> indexRoot = SExpression::parse(
p.getDirectory().read(fp), p.getDirectory().getAbsPath(fp));
foreach (const SExpression* indexNode, indexRoot->getChildren("schematic")) {
loadSchematic(p, indexNode->getChild("@0").getValue());
}
qDebug() << "Successfully loaded" << p.getSchematics().count()
<< "schematics.";
}
void ProjectLoader::loadSchematic(Project& p, const QString& relativeFilePath) {
const FilePath fp = FilePath::fromRelative(p.getPath(), relativeFilePath);
std::unique_ptr<TransactionalDirectory> dir(new TransactionalDirectory(
p.getDirectory(), fp.getParentDir().toRelative(p.getPath())));
const std::unique_ptr<const SExpression> root =
SExpression::parse(dir->read(fp.getFilename()), fp);
Schematic* schematic =
new Schematic(p, std::move(dir), fp.getParentDir().getFilename(),
deserialize<Uuid>(root->getChild("@0")),
deserialize<ElementName>(root->getChild("name/@0")));
schematic->setGridInterval(
deserialize<PositiveLength>(root->getChild("grid/interval/@0")));
schematic->setGridUnit(
deserialize<LengthUnit>(root->getChild("grid/unit/@0")));
p.addSchematic(*schematic);
foreach (const SExpression* node, root->getChildren("symbol")) {
loadSchematicSymbol(*schematic, *node);
}
foreach (const SExpression* node, root->getChildren("bussegment")) {
loadSchematicBusSegment(*schematic, *node);
}
foreach (const SExpression* node, root->getChildren("netsegment")) {
loadSchematicNetSegment(*schematic, *node);
}
foreach (const SExpression* node, root->getChildren("polygon")) {
SI_Polygon* polygon = new SI_Polygon(*schematic, Polygon(*node));
schematic->addPolygon(*polygon);
}
foreach (const SExpression* node, root->getChildren("text")) {
SI_Text* text = new SI_Text(*schematic, Text(*node));
schematic->addText(*text);
}
foreach (const SExpression* node, root->getChildren("image")) {
SI_Image* image = new SI_Image(*schematic, Image(*node));
schematic->addImage(*image);
}
// Load user settings.
loadSchematicUserSettings(*schematic);
}
void ProjectLoader::loadSchematicSymbol(Schematic& s, const SExpression& node) {
const Uuid cmpUuid = deserialize<Uuid>(node.getChild("component/@0"));
ComponentInstance* cmp =
s.getProject().getCircuit().getComponentInstanceByUuid(cmpUuid);
if (!cmp) {
throw RuntimeError(
__FILE__, __LINE__,
QString("The component '%1' does not exist in the circuit.")
.arg(cmpUuid.toStr()));
}
SI_Symbol* symbol =
new SI_Symbol(s, deserialize<Uuid>(node.getChild("@0")), *cmp,
deserialize<Uuid>(node.getChild("lib_gate/@0")),
Point(node.getChild("position")),
deserialize<Angle>(node.getChild("rotation/@0")),
deserialize<bool>(node.getChild("mirror/@0")), false);
foreach (const SExpression* child, node.getChildren("text")) {
symbol->addText(*new SI_Text(s, Text(*child)));
}
s.addSymbol(*symbol);
}
void ProjectLoader::loadSchematicBusSegment(Schematic& s,
const SExpression& node) {
const Uuid busUuid = deserialize<Uuid>(node.getChild("bus/@0"));
Bus* bus = s.getProject().getCircuit().getBuses().value(busUuid);
if (!bus) {
throw RuntimeError(__FILE__, __LINE__,
QString("Inexistent bus: '%1'").arg(busUuid.toStr()));
}
SI_BusSegment* segment =
new SI_BusSegment(s, deserialize<Uuid>(node.getChild("@0")), *bus);
s.addBusSegment(*segment);
// Load junctions.
QList<SI_BusJunction*> junctions;
foreach (const SExpression* child, node.getChildren("junction")) {
SI_BusJunction* j =
new SI_BusJunction(*segment, deserialize<Uuid>(child->getChild("@0")),
Point(child->getChild("position")));
junctions.append(j);
}
// Load lines.
QList<SI_BusLine*> lines;
foreach (const SExpression* child, node.getChildren("line")) {
auto parseAnchor = [&](const SExpression& aNode) {
const Uuid junctionUuid =
deserialize<Uuid>(aNode.getChild("junction/@0"));
foreach (SI_BusJunction* j, junctions) {
if (j->getUuid() == junctionUuid) {
return j;
}
}
throw RuntimeError(
__FILE__, __LINE__,
QString("Bus junction '%1:%2' does not exist in schematic.")
.arg(segment->getUuid().toStr(), junctionUuid.toStr()));
};
SI_BusLine* line = new SI_BusLine(
*segment, deserialize<Uuid>(child->getChild("@0")),
*parseAnchor(child->getChild("from")),
*parseAnchor(child->getChild("to")),
deserialize<UnsignedLength>(child->getChild("width/@0")));
lines.append(line);
}
// Add junctions & lines.
segment->addJunctionsAndLines(junctions, lines);
// Load labels.
foreach (const SExpression* child, node.getChildren("label")) {
SI_BusLabel* l = new SI_BusLabel(*segment, NetLabel(*child));
segment->addLabel(*l);
}
}
void ProjectLoader::loadSchematicNetSegment(Schematic& s,
const SExpression& node) {
const Uuid netSignalUuid = deserialize<Uuid>(node.getChild("net/@0"));
NetSignal* netSignal =
s.getProject().getCircuit().getNetSignals().value(netSignalUuid);
if (!netSignal) {
throw RuntimeError(
__FILE__, __LINE__,
QString("Inexistent net signal: '%1'").arg(netSignalUuid.toStr()));
}
SI_NetSegment* netSegment =
new SI_NetSegment(s, deserialize<Uuid>(node.getChild("@0")), *netSignal);
s.addNetSegment(*netSegment);
// Load net points.
QList<SI_NetPoint*> netPoints;
foreach (const SExpression* child, node.getChildren("junction")) {
SI_NetPoint* netpoint =
new SI_NetPoint(*netSegment, deserialize<Uuid>(child->getChild("@0")),
Point(child->getChild("position")));
netPoints.append(netpoint);
}
// Load net lines.
QList<SI_NetLine*> netLines;
foreach (const SExpression* child, node.getChildren("line")) {
auto parseAnchor = [&](const SExpression& aNode) {
const NetLineAnchor anchor(aNode);
if (const std::optional<Uuid>& obj = anchor.tryGetJunction()) {
foreach (SI_NetPoint* netPoint, netPoints) {
if (netPoint->getUuid() == *obj) {
return static_cast<SI_NetLineAnchor*>(netPoint);
}
}
throw RuntimeError(
__FILE__, __LINE__,
QString("Net point '%1' does not exist in schematic.")
.arg(obj->toStr()));
} else if (const std::optional<NetLineAnchor::BusAnchor>& obj =
anchor.tryGetBusJunction()) {
SI_BusSegment* segment = s.getBusSegments().value(obj->segment);
if (!segment) {
throw RuntimeError(
__FILE__, __LINE__,
QString("Bus segment '%1' does not exist in schematic.")
.arg(obj->segment.toStr()));
}
SI_NetLineAnchor* anchor = segment->getJunctions().value(obj->junction);
if (!anchor) {
throw RuntimeError(
__FILE__, __LINE__,
QString("Bus junction '%1:%2' does not exist in schematic.")
.arg(obj->segment.toStr(), obj->junction.toStr()));
}
return anchor;
} else if (const std::optional<NetLineAnchor::PinAnchor>& obj =
anchor.tryGetPin()) {
SI_Symbol* symbol = s.getSymbols().value(obj->symbol);
if (!symbol) {
throw RuntimeError(__FILE__, __LINE__,
QString("Symbol '%1' does not exist in schematic.")
.arg(obj->symbol.toStr()));
}
SI_NetLineAnchor* anchor = symbol->getPin(obj->pin);
if (!anchor) {
throw RuntimeError(
__FILE__, __LINE__,
QString("Symbol pin '%1:%2' does not exist in schematic.")
.arg(obj->symbol.toStr(), obj->pin.toStr()));
}
return anchor;
} else {
throw RuntimeError(
__FILE__, __LINE__,
QString("Unknown net line anchor in net segment '%1'.")
.arg(netSegment->getUuid().toStr()));
}
};
SI_NetLine* netLine = new SI_NetLine(
*netSegment, deserialize<Uuid>(child->getChild("@0")),
*parseAnchor(child->getChild("from")),
*parseAnchor(child->getChild("to")),
deserialize<UnsignedLength>(child->getChild("width/@0")));
netLines.append(netLine);
}
// Add net points & net lines.
netSegment->addNetPointsAndNetLines(netPoints, netLines);
// Load net labels.
foreach (const SExpression* child, node.getChildren("label")) {
SI_NetLabel* netLabel = new SI_NetLabel(*netSegment, NetLabel(*child));
netSegment->addNetLabel(*netLabel);
}
}
void ProjectLoader::loadSchematicUserSettings(Schematic& s) {
try {
const QString fp = "settings.user.lp";
const std::unique_ptr<const SExpression> root = SExpression::parse(
s.getDirectory().read(fp), s.getDirectory().getAbsPath(fp));
} catch (const Exception&) {
// Project user settings are normally not put under version control and
// thus the likelihood of parse errors is higher (e.g. when switching to
// an older, now incompatible revision). To avoid frustration, we just
// ignore these errors and load the default settings instead...
qCritical() << "Could not load schematic user settings, defaults will be "
"used instead.";
}
}
void ProjectLoader::loadBoards(Project& p) {
qDebug() << "Load boards...";
const QString fp = "boards/boards.lp";
const std::unique_ptr<const SExpression> indexRoot = SExpression::parse(
p.getDirectory().read(fp), p.getDirectory().getAbsPath(fp));
foreach (const SExpression* node, indexRoot->getChildren("board")) {
loadBoard(p, node->getChild("@0").getValue());
}
qDebug() << "Successfully loaded" << p.getBoards().count() << "boards.";
}
void ProjectLoader::loadBoard(Project& p, const QString& relativeFilePath) {
const FilePath fp = FilePath::fromRelative(p.getPath(), relativeFilePath);
std::unique_ptr<TransactionalDirectory> dir(new TransactionalDirectory(
p.getDirectory(), fp.getParentDir().toRelative(p.getPath())));
const std::unique_ptr<const SExpression> root =
SExpression::parse(dir->read(fp.getFilename()), fp);
Board* board = new Board(p, std::move(dir), fp.getParentDir().getFilename(),
deserialize<Uuid>(root->getChild("@0")),
deserialize<ElementName>(root->getChild("name/@0")));
board->setDefaultFontName(root->getChild("default_font/@0").getValue());
board->setGridInterval(
deserialize<PositiveLength>(root->getChild("grid/interval/@0")));
board->setGridUnit(deserialize<LengthUnit>(root->getChild("grid/unit/@0")));
board->setPreferredFootprintTags(Board::PreferredFootprintTags(
root->getChild("preferred_footprint_tags")));
board->setInnerLayerCount(
deserialize<uint>(root->getChild("layers/inner/@0")));
board->setPcbThickness(
deserialize<PositiveLength>(root->getChild("thickness/@0")));
board->setSolderResist(
deserialize<const PcbColor*>(root->getChild("solder_resist/@0")));
board->setSilkscreenColor(
deserialize<const PcbColor&>(root->getChild("silkscreen/@0")));
{
QVector<const Layer*> layers;
foreach (const SExpression* child,
root->getChild("silkscreen_layers_top")
.getChildren(SExpression::Type::Token)) {
layers.append(deserialize<const Layer*>(*child));
}
board->setSilkscreenLayersTop(layers);
}
{
QVector<const Layer*> layers;
foreach (const SExpression* child,
root->getChild("silkscreen_layers_bot")
.getChildren(SExpression::Type::Token)) {
layers.append(deserialize<const Layer*>(*child));
}
board->setSilkscreenLayersBot(layers);
}
board->setDesignRules(BoardDesignRules(root->getChild("design_rules")));
{
const SExpression& node = root->getChild("design_rule_check");
const Version approvalsVersion =
deserialize<Version>(node.getChild("approvals_version/@0"));
QSet<SExpression> approvals;
foreach (const SExpression* child, node.getChildren("approved")) {
approvals.insert(*child);
}
board->setDrcSettings(BoardDesignRuleCheckSettings(node));
board->loadDrcMessageApprovals(approvalsVersion, approvals);
}
board->getFabricationOutputSettings() = BoardFabricationOutputSettings(
root->getChild("fabrication_output_settings"));
p.addBoard(*board);
foreach (const SExpression* node, root->getChildren("device")) {
loadBoardDeviceInstance(*board, *node);
}
foreach (const SExpression* node, root->getChildren("netsegment")) {
loadBoardNetSegment(*board, *node);
}
foreach (const SExpression* node, root->getChildren("plane")) {
loadBoardPlane(*board, *node);
}
foreach (const SExpression* node, root->getChildren("zone")) {
BI_Zone* zone = new BI_Zone(*board, BoardZoneData(*node));
board->addZone(*zone);
}
foreach (const SExpression* node, root->getChildren("polygon")) {
BI_Polygon* polygon = new BI_Polygon(*board, BoardPolygonData(*node));
board->addPolygon(*polygon);
}
foreach (const SExpression* node, root->getChildren("stroke_text")) {
BI_StrokeText* text = new BI_StrokeText(*board, BoardStrokeTextData(*node));
board->addStrokeText(*text);
}
foreach (const SExpression* node, root->getChildren("hole")) {
BI_Hole* hole = new BI_Hole(*board, BoardHoleData(*node));
board->addHole(*hole);
}
// Load user settings.
loadBoardUserSettings(*board);
}
void ProjectLoader::loadBoardDeviceInstance(Board& b, const SExpression& node) {
const Uuid cmpUuid = deserialize<Uuid>(node.getChild("@0"));
ComponentInstance* cmp =
b.getProject().getCircuit().getComponentInstanceByUuid(cmpUuid);
if (!cmp) {
throw RuntimeError(
__FILE__, __LINE__,
QString("The component instance '%1' does not exist in the circuit.")
.arg(cmpUuid.toStr()));
}
BI_Device* device =
new BI_Device(b, *cmp, deserialize<Uuid>(node.getChild("lib_device/@0")),
deserialize<Uuid>(node.getChild("lib_footprint/@0")),
Point(node.getChild("position")),
deserialize<Angle>(node.getChild("rotation/@0")),
deserialize<bool>(node.getChild("flip/@0")),
deserialize<bool>(node.getChild("lock/@0")),
deserialize<bool>(node.getChild("glue/@0")), false);
device->setAttributes(AttributeList(node));
{
// Load 3D package model. But after upgrading the project library, assign
// the default package model if no valid model is set on this device.
std::optional<Uuid> model =
deserialize<std::optional<Uuid>>(node.getChild("lib_3d_model/@0"));
if (mAutoAssignDeviceModels &&
((!model) || (!device->getLibPackage().getModels().contains(*model)) ||
(!device->getLibFootprint().getModels().contains(*model)))) {
model = device->getDefaultLibModelUuid();
}
device->setModel(model);
}
foreach (const SExpression* child, node.getChildren("stroke_text")) {
device->addStrokeText(*new BI_StrokeText(b, BoardStrokeTextData(*child)));
}
b.addDeviceInstance(*device);
}
void ProjectLoader::loadBoardNetSegment(Board& b, const SExpression& node) {
const std::optional<Uuid> netSignalUuid =
deserialize<std::optional<Uuid>>(node.getChild("net/@0"));
NetSignal* netSignal = netSignalUuid
? b.getProject().getCircuit().getNetSignals().value(*netSignalUuid)
: nullptr;
if (netSignalUuid && (!netSignal)) {
throw RuntimeError(
__FILE__, __LINE__,
QString("Inexistent net signal: '%1'").arg(netSignalUuid->toStr()));
}
BI_NetSegment* netSegment =
new BI_NetSegment(b, deserialize<Uuid>(node.getChild("@0")), netSignal);
b.addNetSegment(*netSegment);
// Load pads.
QList<BI_Pad*> pads;
foreach (const SExpression* child, node.getChildren("pad")) {
BI_Pad* pad = new BI_Pad(*netSegment, BoardPadData(*child));
pads.append(pad);
}
// Load vias.
QList<BI_Via*> vias;
foreach (const SExpression* child, node.getChildren("via")) {
BI_Via* via = new BI_Via(*netSegment, Via(*child));
vias.append(via);
}
// Load net points.
QList<BI_NetPoint*> netPoints;
foreach (const SExpression* child, node.getChildren("junction")) {
BI_NetPoint* netPoint =
new BI_NetPoint(*netSegment, deserialize<Uuid>(child->getChild("@0")),
Point(child->getChild("position")));
netPoints.append(netPoint);
}
// Load net lines.
QList<BI_NetLine*> netLines;
foreach (const SExpression* child, node.getChildren("trace")) {
auto parseAnchor = [&](const SExpression& aNode) {
const TraceAnchor anchor(aNode);
if (const std::optional<Uuid>& obj = anchor.tryGetJunction()) {
foreach (BI_NetPoint* netPoint, netPoints) {
if (netPoint->getUuid() == *obj) {
return static_cast<BI_NetLineAnchor*>(netPoint);
}
}
throw RuntimeError(
__FILE__, __LINE__,
QString("Net point '%1' does not exist in schematic.")
.arg(obj->toStr()));
} else if (const std::optional<Uuid>& obj = anchor.tryGetVia()) {
foreach (BI_Via* via, vias) {
if (via->getUuid() == *obj) {
return static_cast<BI_NetLineAnchor*>(via);
}
}
throw RuntimeError(
__FILE__, __LINE__,
QString("Via '%1' does not exist in board.").arg(obj->toStr()));
} else if (const std::optional<TraceAnchor::PadAnchor>& obj =
anchor.tryGetFootprintPad()) {
BI_Device* device = b.getDeviceInstanceByComponentUuid(obj->device);
if (!device) {
throw RuntimeError(
__FILE__, __LINE__,
QString("Device instance '%1' does not exist in board.")
.arg(obj->device.toStr()));
}
BI_NetLineAnchor* anchor = device->getPad(obj->pad);
if (!anchor) {
throw RuntimeError(
__FILE__, __LINE__,
QString("Footprint pad '%1:%2' does not exist in board.")
.arg(obj->device.toStr(), obj->pad.toStr()));
}
return anchor;
} else if (const std::optional<Uuid>& obj = anchor.tryGetPad()) {
foreach (BI_Pad* pad, pads) {
if (pad->getUuid() == *obj) {
return static_cast<BI_NetLineAnchor*>(pad);
}
}
throw RuntimeError(
__FILE__, __LINE__,
QString("Pad '%1' does not exist in board.").arg(obj->toStr()));
} else {
throw RuntimeError(
__FILE__, __LINE__,
QString("Unknown trace anchor in trace segment '%1'.")
.arg(netSegment->getUuid().toStr()));
}
};
BI_NetLine* netLine = new BI_NetLine(
*netSegment, deserialize<Uuid>(child->getChild("@0")),
*parseAnchor(child->getChild("from")),
*parseAnchor(child->getChild("to")),
deserialize<const Layer&>(child->getChild("layer/@0")),
deserialize<PositiveLength>(child->getChild("width/@0")));
netLines.append(netLine);
}
// Add vias, net points & net lines.
netSegment->addElements(pads, vias, netPoints, netLines);
}
void ProjectLoader::loadBoardPlane(Board& b, const SExpression& node) {
const std::optional<Uuid> netSignalUuid =
deserialize<std::optional<Uuid>>(node.getChild("net/@0"));
NetSignal* netSignal = netSignalUuid
? b.getProject().getCircuit().getNetSignals().value(*netSignalUuid)
: nullptr;
if (netSignalUuid && (!netSignal)) {
throw RuntimeError(
__FILE__, __LINE__,
QString("Inexistent net signal: '%1'").arg(netSignalUuid->toStr()));
}
BI_Plane* plane =
new BI_Plane(b, deserialize<Uuid>(node.getChild("@0")),
deserialize<const Layer&>(node.getChild("layer/@0")),
netSignal, Path(node));
plane->setMinWidth(
deserialize<UnsignedLength>(node.getChild("min_width/@0")));
plane->setMinClearanceToCopper(
deserialize<UnsignedLength>(node.getChild("min_copper_clearance/@0")));
plane->setMinClearanceToBoard(
deserialize<UnsignedLength>(node.getChild("min_board_clearance/@0")));
plane->setMinClearanceToNpth(
deserialize<UnsignedLength>(node.getChild("min_npth_clearance/@0")));
plane->setKeepIslands(deserialize<bool>(node.getChild("keep_islands/@0")));
plane->setPriority(deserialize<int>(node.getChild("priority/@0")));
plane->setConnectStyle(
deserialize<BI_Plane::ConnectStyle>(node.getChild("connect_style/@0")));
plane->setThermalGap(
deserialize<PositiveLength>(node.getChild("thermal_gap/@0")));
plane->setThermalSpokeWidth(
deserialize<PositiveLength>(node.getChild("thermal_spoke/@0")));
b.addPlane(*plane);
}
void ProjectLoader::loadBoardUserSettings(Board& b) {
try {
const QString fp = "settings.user.lp";
const std::unique_ptr<const SExpression> root = SExpression::parse(
b.getDirectory().read(fp), b.getDirectory().getAbsPath(fp));
// Layers.
QMap<QString, bool> layersVisibility;
for (const SExpression* node : root->getChildren("layer")) {
const QString name = node->getChild("@0").getValue();
layersVisibility[name] = deserialize<bool>(node->getChild("visible/@0"));
}
b.setLayersVisibility(layersVisibility);
// Planes visibility.
foreach (const SExpression* node, root->getChildren("plane")) {
const Uuid uuid = deserialize<Uuid>(node->getChild("@0"));
if (BI_Plane* plane = b.getPlanes().value(uuid)) {
plane->setVisible(deserialize<bool>(node->getChild("visible/@0")));
} else {
qWarning() << "Plane" << uuid.toStr()
<< "doesn't exist, could not restore its visibility.";
}
}
} catch (const Exception&) {
// Project user settings are normally not put under version control and
// thus the likelihood of parse errors is higher (e.g. when switching to
// an older, now incompatible revision). To avoid frustration, we just
// ignore these errors and load the default settings instead...
qCritical() << "Could not load board user settings, defaults will be "
"used instead.";
}
}
void ProjectLoader::loadProjectUserSettings(Project& p) {
try {
const QString fp = "project/settings.user.lp";
const std::unique_ptr<const SExpression> root = SExpression::parse(
p.getDirectory().read(fp), p.getDirectory().getAbsPath(fp));
} catch (const Exception&) {
// Project user settings are normally not put under version control and
// thus the likelihood of parse errors is higher (e.g. when switching to
// an older, now incompatible revision). To avoid frustration, we just
// ignore these errors and load the default settings instead...
qCritical() << "Could not load project user settings, defaults will be "
"used instead.";
}
}
/*******************************************************************************
* End of File
******************************************************************************/
} // namespace librepcb
94ec4b8feThis file is pinned to the selected release. Download the original to open it in your design tool.