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 "outputjobrunner.h"
#include "../3d/stepexport.h"
#include "../application.h"
#include "../attribute/attributesubstitutor.h"
#include "../export/bomcsvwriter.h"
#include "../export/graphicsexport.h"
#include "../export/graphicsexportsettings.h"
#include "../export/pickplacecsvwriter.h"
#include "../fileio/csvfile.h"
#include "../fileio/fileutils.h"
#include "../fileio/outputdirectorywriter.h"
#include "../fileio/transactionalfilesystem.h"
#include "../job/archiveoutputjob.h"
#include "../job/board3doutputjob.h"
#include "../job/bomoutputjob.h"
#include "../job/copyoutputjob.h"
#include "../job/gerberexcellonoutputjob.h"
#include "../job/gerberx3outputjob.h"
#include "../job/graphicsoutputjob.h"
#include "../job/lppzoutputjob.h"
#include "../job/netlistoutputjob.h"
#include "../job/pickplaceoutputjob.h"
#include "../job/projectjsonoutputjob.h"
#include "../utils/toolbox.h"
#include "board/board.h"
#include "board/boardd356netlistexport.h"
#include "board/boardfabricationoutputsettings.h"
#include "board/boardgerberexport.h"
#include "board/boardpainter.h"
#include "board/boardpickplacegenerator.h"
#include "board/realisticboardpainter.h"
#include "bomgenerator.h"
#include "circuit/circuit.h"
#include "project.h"
#include "projectattributelookup.h"
#include "projectjsonexport.h"
#include "schematic/schematicpainter.h"
#include <QtCore>
/*******************************************************************************
* Namespace
******************************************************************************/
namespace librepcb {
/*******************************************************************************
* Constructors / Destructor
******************************************************************************/
OutputJobRunner::OutputJobRunner(Project& project) noexcept
: QObject(nullptr), mProject(project), mWriter() {
setOutputDirectory(mProject.getCurrentOutputDir());
}
OutputJobRunner::~OutputJobRunner() noexcept {
}
/*******************************************************************************
* Getters
******************************************************************************/
const FilePath& OutputJobRunner::getOutputDirectory() const noexcept {
return mWriter->getDirectoryPath();
}
const QMultiHash<Uuid, FilePath>& OutputJobRunner::getWrittenFiles()
const noexcept {
return mWriter->getWrittenFiles();
}
/*******************************************************************************
* Setters
******************************************************************************/
void OutputJobRunner::setOutputDirectory(const FilePath& fp) noexcept {
mWriter.reset(new OutputDirectoryWriter(fp));
connect(mWriter.data(), &OutputDirectoryWriter::aboutToWriteFile, this,
&OutputJobRunner::aboutToWriteFile);
connect(mWriter.data(), &OutputDirectoryWriter::aboutToRemoveFile, this,
&OutputJobRunner::aboutToRemoveFile);
}
/*******************************************************************************
* General Methods
******************************************************************************/
void OutputJobRunner::run(const QVector<std::shared_ptr<OutputJob>>& jobs) {
mWriter->loadIndex(); // can throw
foreach (const auto& job, jobs) {
emit jobStarted(job);
run(*job); // can throw
qApp->processEvents(); // Avoid freeze due to blocking loop.
}
mWriter->storeIndex(); // can throw
}
QList<FilePath> OutputJobRunner::findUnknownFiles(
const QSet<Uuid>& knownJobs) const {
return mWriter->findUnknownFiles(knownJobs);
}
void OutputJobRunner::removeUnknownFiles(const QList<FilePath>& files) {
mWriter->removeUnknownFiles(files);
}
GraphicsExport::Pages OutputJobRunner::buildPages(
const GraphicsOutputJob& job) {
GraphicsExport::Pages pages;
foreach (const GraphicsOutputJob::Content& content, job.getContent()) {
std::shared_ptr<GraphicsExportSettings> settings =
std::make_shared<GraphicsExportSettings>();
tl::optional<QPageSize> pageSize;
if (content.pageSizeKey) {
for (int i = 0; i < QPageSize::LastPageSize; ++i) {
const QPageSize::PageSizeId id = static_cast<QPageSize::PageSizeId>(i);
if (QPageSize::key(id) == content.pageSizeKey) {
pageSize = QPageSize(id);
break;
}
}
if (!pageSize) {
throw RuntimeError(
__FILE__, __LINE__,
QString("Unsupported page size: '%1'").arg(*content.pageSizeKey));
}
}
settings->setPageSize(pageSize);
settings->setOrientation(content.orientation);
settings->setMarginLeft(content.marginLeft);
settings->setMarginTop(content.marginTop);
settings->setMarginRight(content.marginRight);
settings->setMarginBottom(content.marginBottom);
settings->setRotate(content.rotate);
settings->setMirror(content.mirror);
settings->setScale(content.scale);
settings->setPixmapDpi(static_cast<int>(content.pixmapDpi));
settings->setBlackWhite(content.monochrome);
settings->setBackgroundColor(content.backgroundColor);
settings->setMinLineWidth(content.minLineWidth);
QList<std::pair<QString, QColor>> layers;
foreach (const auto& pair, settings->getColors()) {
if (content.layers.contains(pair.first)) {
layers.append(std::make_pair(pair.first, content.layers[pair.first]));
}
}
settings->setColors(layers);
const QList<Board*> boards = getBoards(content.boards, false);
const QVector<std::shared_ptr<AssemblyVariant>> assemblyVariants =
getAssemblyVariants(content.assemblyVariants, false);
if (content.type == GraphicsOutputJob::Content::Type::Schematic) {
foreach (auto av, assemblyVariants) {
Q_UNUSED(av); // TODO
foreach (const Board* board, boards) {
Q_UNUSED(board); // TODO
foreach (const Schematic* schematic, mProject.getSchematics()) {
std::shared_ptr<GraphicsPagePainter> painter =
std::make_shared<SchematicPainter>(*schematic);
pages.append(std::make_pair(painter, settings));
}
}
}
} else if (content.type == GraphicsOutputJob::Content::Type::Board) {
foreach (const Board* board, boards) {
foreach (auto av, assemblyVariants) {
Q_UNUSED(av); // TODO
std::shared_ptr<GraphicsPagePainter> painter;
// New option "realistic" was added after the v1.0.0 release, thus
// not officially supported in file format v1.0. Should probably be
// migrated to a new content type in file format v2, maybe with
// some configuration options.
if (content.options.contains("realistic")) {
painter = std::make_shared<RealisticBoardPainter>(
board->buildScene3D(tl::nullopt));
} else {
painter = std::make_shared<BoardPainter>(*board);
}
pages.append(std::make_pair(painter, settings));
}
}
} else if (content.type ==
GraphicsOutputJob::Content::Type::AssemblyGuide) {
throw RuntimeError(__FILE__, __LINE__,
"Assembly guide output jobs are not supported yet, "
"you need to use a more recent release of LibrePCB.");
} else {
throw LogicError(__FILE__, __LINE__,
"Unknown graphics output job content.");
}
}
return pages;
}
/*******************************************************************************
* Private Methods
******************************************************************************/
void OutputJobRunner::run(const OutputJob& job) {
const int countBefore = mWriter->getWrittenFiles().count(job.getUuid());
if (auto ptr = dynamic_cast<const BomOutputJob*>(&job)) {
runImpl(*ptr);
} else if (auto ptr = dynamic_cast<const GraphicsOutputJob*>(&job)) {
runImpl(*ptr);
} else if (auto ptr = dynamic_cast<const GerberExcellonOutputJob*>(&job)) {
runImpl(*ptr);
} else if (auto ptr = dynamic_cast<const PickPlaceOutputJob*>(&job)) {
runImpl(*ptr);
} else if (auto ptr = dynamic_cast<const GerberX3OutputJob*>(&job)) {
runImpl(*ptr);
} else if (auto ptr = dynamic_cast<const NetlistOutputJob*>(&job)) {
runImpl(*ptr);
} else if (auto ptr = dynamic_cast<const Board3DOutputJob*>(&job)) {
runImpl(*ptr);
} else if (auto ptr = dynamic_cast<const ProjectJsonOutputJob*>(&job)) {
runImpl(*ptr);
} else if (auto ptr = dynamic_cast<const LppzOutputJob*>(&job)) {
runImpl(*ptr);
} else if (auto ptr = dynamic_cast<const CopyOutputJob*>(&job)) {
runImpl(*ptr);
} else if (auto ptr = dynamic_cast<const ArchiveOutputJob*>(&job)) {
runImpl(*ptr);
} else {
throw LogicError(
__FILE__, __LINE__,
tr("Unknown output job type '%1'.").arg(job.getType()) % " " %
tr("You may need a more recent LibrePCB version to run this job."));
}
const int countAfter = mWriter->getWrittenFiles().count(job.getUuid());
mWriter->removeObsoleteFiles(job.getUuid()); // can throw
if (countAfter <= countBefore) {
emit warning(
tr("No output files were generated, check the job configuration."));
}
}
void OutputJobRunner::runImpl(const GraphicsOutputJob& job) {
// Build pages.
const GraphicsExport::Pages pages = buildPages(job);
// Determine lookup objects.
QSet<Board*> allBoards;
QSet<std::shared_ptr<AssemblyVariant>> allAssemblyVariants;
foreach (const GraphicsOutputJob::Content& content, job.getContent()) {
allBoards |= Toolbox::toSet(getBoards(content.boards, false));
allAssemblyVariants |=
Toolbox::toSet(getAssemblyVariants(content.assemblyVariants, false));
}
// Determine output path.
std::shared_ptr<AssemblyVariant> av = (allAssemblyVariants.count() == 1)
? (*allAssemblyVariants.begin())
: nullptr;
const ProjectAttributeLookup lookup =
((allBoards.count() == 1) && (*allBoards.begin()))
? ProjectAttributeLookup(**allBoards.begin(), av)
: ProjectAttributeLookup(mProject, av);
const FilePath fp = mWriter->beginWritingFile(
job.getUuid(),
AttributeSubstitutor::substitute(
job.getOutputPath(), lookup, [&](const QString& str) {
return FilePath::cleanFileName(
str, FilePath::ReplaceSpaces | FilePath::KeepCase);
})); // can throw
// Determine document name.
QString docName = *job.getDocumentTitle();
if (docName.isEmpty()) {
docName = "{{PROJECT}} {{VERSION}}";
}
docName = AttributeSubstitutor::substitute(docName, lookup).simplified();
// Perform the export.
GraphicsExport graphicsExport;
graphicsExport.setDocumentName(docName);
graphicsExport.startExport(pages, fp);
const GraphicsExport::Result result = graphicsExport.waitForFinished();
foreach (const FilePath& writtenFile, result.writtenFiles) {
if (writtenFile != fp) {
// Track additional files.
mWriter->beginWritingFile(
job.getUuid(),
writtenFile.toRelative(mWriter->getDirectoryPath())); // can throw
}
}
if (!result.errorMsg.isEmpty()) {
throw RuntimeError(__FILE__, __LINE__, result.errorMsg);
}
}
void OutputJobRunner::runImpl(const GerberExcellonOutputJob& job) {
// Build settings.
BoardFabricationOutputSettings settings;
settings.setOutputBasePath(mWriter->getDirectoryPath().toStr() % "/" %
job.getOutputPath());
settings.setSuffixDrills(job.getSuffixDrills());
settings.setSuffixDrillsNpth(job.getSuffixDrillsNpth());
settings.setSuffixDrillsPth(job.getSuffixDrillsPth());
settings.setSuffixDrillsBlindBuried(job.getSuffixDrillsBlindBuried());
settings.setSuffixOutlines(job.getSuffixOutlines());
settings.setSuffixCopperTop(job.getSuffixCopperTop());
settings.setSuffixCopperInner(job.getSuffixCopperInner());
settings.setSuffixCopperBot(job.getSuffixCopperBot());
settings.setSuffixSolderMaskTop(job.getSuffixSolderMaskTop());
settings.setSuffixSolderMaskBot(job.getSuffixSolderMaskBot());
settings.setSuffixSilkscreenTop(job.getSuffixSilkscreenTop());
settings.setSuffixSilkscreenBot(job.getSuffixSilkscreenBot());
settings.setSuffixSolderPasteTop(job.getSuffixSolderPasteTop());
settings.setSuffixSolderPasteBot(job.getSuffixSolderPasteBot());
settings.setMergeDrillFiles(job.getMergeDrillFiles());
settings.setUseG85SlotCommand(job.getUseG85SlotCommand());
settings.setEnableSolderPasteTop(job.getEnableSolderPasteTop());
settings.setEnableSolderPasteBot(job.getEnableSolderPasteBot());
// Determine boards.
const QList<Board*> boards = getBoards(job.getBoards());
// Perform export:
foreach (const Board* board, boards) {
BoardGerberExport grbExport(*board);
grbExport.setRemoveObsoleteFiles(false); // must be done by this runner!
grbExport.setBeforeWriteCallback([this, &job](const FilePath& fp) {
mWriter->beginWritingFile(job.getUuid(),
fp.toRelative(mWriter->getDirectoryPath()));
});
grbExport.exportPcbLayers(settings); // can throw
}
}
void OutputJobRunner::runImpl(const PickPlaceOutputJob& job) {
const QList<Board*> boards = getBoards(job.getBoards());
const QVector<std::shared_ptr<AssemblyVariant>> assemblyVariants =
getAssemblyVariants(job.getAssemblyVariants());
QVector<std::pair<PickPlaceCsvWriter::BoardSide, QString>> sides;
if (job.getCreateTop()) {
sides.append(std::make_pair(PickPlaceCsvWriter::BoardSide::Top,
job.getOutputPathTop()));
}
if (job.getCreateBottom()) {
sides.append(std::make_pair(PickPlaceCsvWriter::BoardSide::Bottom,
job.getOutputPathBottom()));
}
if (job.getCreateBoth()) {
sides.append(std::make_pair(PickPlaceCsvWriter::BoardSide::Both,
job.getOutputPathBoth()));
}
QSet<PickPlaceDataItem::Type> typeFilter;
if (job.getTechnologies().testFlag(PickPlaceOutputJob::Technology::Tht)) {
typeFilter.insert(PickPlaceDataItem::Type::Tht);
}
if (job.getTechnologies().testFlag(PickPlaceOutputJob::Technology::Smt)) {
typeFilter.insert(PickPlaceDataItem::Type::Smt);
}
if (job.getTechnologies().testFlag(PickPlaceOutputJob::Technology::Mixed)) {
typeFilter.insert(PickPlaceDataItem::Type::Mixed);
}
if (job.getTechnologies().testFlag(
PickPlaceOutputJob::Technology::Fiducial)) {
typeFilter.insert(PickPlaceDataItem::Type::Fiducial);
}
if (job.getTechnologies().testFlag(PickPlaceOutputJob::Technology::Other)) {
typeFilter.insert(PickPlaceDataItem::Type::Other);
}
if (typeFilter.isEmpty()) {
emit warning(
tr("No technologies selected, thus the output files won't "
"contain any entries."));
}
foreach (const Board* board, boards) {
foreach (const std::shared_ptr<AssemblyVariant>& av, assemblyVariants) {
BoardPickPlaceGenerator gen(*board, av->getUuid());
std::shared_ptr<PickPlaceData> data = gen.generate();
foreach (const auto& pair, sides) {
const FilePath fp = mWriter->beginWritingFile(
job.getUuid(),
AttributeSubstitutor::substitute(
pair.second, ProjectAttributeLookup(*board, av),
[&](const QString& str) {
return FilePath::cleanFileName(
str, FilePath::ReplaceSpaces | FilePath::KeepCase);
})); // can throw
if (fp.getSuffix().toLower() == "csv") {
PickPlaceCsvWriter writer(*data);
writer.setIncludeMetadataComment(job.getIncludeComment());
writer.setBoardSide(pair.first);
writer.setTypeFilter(typeFilter);
std::shared_ptr<CsvFile> csv = writer.generateCsv(); // can throw
csv->saveToFile(fp); // can throw
} else {
throw RuntimeError(__FILE__, __LINE__,
QString("Unsupported pick&place format: '%1'")
.arg(fp.getSuffix()));
}
}
}
}
}
void OutputJobRunner::runImpl(const GerberX3OutputJob& job) {
const QList<Board*> boards = getBoards(job.getBoards());
const QVector<std::shared_ptr<AssemblyVariant>> assemblyVariants =
getAssemblyVariants(job.getAssemblyVariants());
QVector<std::pair<BoardGerberExport::BoardSide, QString>> sides;
if (job.getCreateTop()) {
sides.append(std::make_pair(BoardGerberExport::BoardSide::Top,
job.getOutputPathTop()));
}
if (job.getCreateBottom()) {
sides.append(std::make_pair(BoardGerberExport::BoardSide::Bottom,
job.getOutputPathBottom()));
}
foreach (const Board* board, boards) {
foreach (const std::shared_ptr<AssemblyVariant>& av, assemblyVariants) {
foreach (const auto& pair, sides) {
const FilePath fp = mWriter->beginWritingFile(
job.getUuid(),
AttributeSubstitutor::substitute(
pair.second, ProjectAttributeLookup(*board, av),
[&](const QString& str) {
return FilePath::cleanFileName(
str, FilePath::ReplaceSpaces | FilePath::KeepCase);
})); // can throw
BoardGerberExport gen(*board);
gen.exportComponentLayer(pair.first, av->getUuid(), fp); // can throw
}
}
}
}
void OutputJobRunner::runImpl(const NetlistOutputJob& job) {
const QList<Board*> boards = getBoards(job.getBoards());
foreach (const Board* board, boards) {
const FilePath fp = mWriter->beginWritingFile(
job.getUuid(),
AttributeSubstitutor::substitute(
job.getOutputPath(), ProjectAttributeLookup(*board, nullptr),
[&](const QString& str) {
return FilePath::cleanFileName(
str, FilePath::ReplaceSpaces | FilePath::KeepCase);
})); // can throw
if (fp.getSuffix().toLower() == "d356") {
BoardD356NetlistExport exp(*board);
FileUtils::writeFile(fp, exp.generate()); // can throw
} else {
throw RuntimeError(
__FILE__, __LINE__,
QString("Unsupported netlist format: '%1'").arg(fp.getSuffix()));
}
}
}
void OutputJobRunner::runImpl(const BomOutputJob& job) {
const QList<Board*> boards = getBoards(job.getBoards(), false);
const QVector<std::shared_ptr<AssemblyVariant>> assemblyVariants =
getAssemblyVariants(job.getAssemblyVariants());
foreach (const Board* board, boards) {
foreach (const std::shared_ptr<AssemblyVariant>& av, assemblyVariants) {
const ProjectAttributeLookup lookup = board
? ProjectAttributeLookup(*board, av)
: ProjectAttributeLookup(mProject, av);
const FilePath fp = mWriter->beginWritingFile(
job.getUuid(),
AttributeSubstitutor::substitute(
job.getOutputPath(), lookup, [&](const QString& str) {
return FilePath::cleanFileName(
str, FilePath::ReplaceSpaces | FilePath::KeepCase);
})); // can throw
BomGenerator gen(mProject);
gen.setAdditionalAttributes(job.getCustomAttributes());
std::shared_ptr<Bom> bom = gen.generate(board, av->getUuid());
if (fp.getSuffix().toLower() == "csv") {
BomCsvWriter writer(*bom);
std::shared_ptr<CsvFile> csv = writer.generateCsv();
csv->saveToFile(fp);
} else {
throw RuntimeError(
__FILE__, __LINE__,
QString("Unsupported BOM format: '%1'").arg(fp.getSuffix()));
}
}
}
}
void OutputJobRunner::runImpl(const Board3DOutputJob& job) {
const QList<Board*> boards = getBoards(job.getBoards());
const QVector<std::shared_ptr<AssemblyVariant>> assemblyVariants =
getAssemblyVariants(job.getAssemblyVariants(), false);
foreach (const Board* board, boards) {
foreach (const std::shared_ptr<AssemblyVariant>& av, assemblyVariants) {
const FilePath fp = mWriter->beginWritingFile(
job.getUuid(),
AttributeSubstitutor::substitute(
job.getOutputPath(), ProjectAttributeLookup(*board, av),
[&](const QString& str) {
return FilePath::cleanFileName(
str, FilePath::ReplaceSpaces | FilePath::KeepCase);
})); // can throw
std::shared_ptr<SceneData3D> data = board->buildScene3D(
av ? tl::make_optional(av->getUuid()) : tl::nullopt);
if ((fp.getSuffix().toLower() == "step") ||
(fp.getSuffix().toLower() == "stp")) {
StepExport stepExport;
stepExport.start(data, fp);
const QString errorMsg = stepExport.waitForFinished();
if (!errorMsg.isEmpty()) {
throw RuntimeError(__FILE__, __LINE__, errorMsg);
}
} else {
throw RuntimeError(
__FILE__, __LINE__,
QString("Unsupported netlist format: '%1'").arg(fp.getSuffix()));
}
}
}
}
void OutputJobRunner::runImpl(const ProjectJsonOutputJob& job) {
// Determine output file.
const FilePath fp = mWriter->beginWritingFile(
job.getUuid(),
AttributeSubstitutor::substitute(
job.getOutputPath(), ProjectAttributeLookup(mProject, nullptr),
[&](const QString& str) {
return FilePath::cleanFileName(
str, FilePath::ReplaceSpaces | FilePath::KeepCase);
})); // can throw
// Export JSON.
ProjectJsonExport jsonExport;
FileUtils::writeFile(fp, jsonExport.toUtf8(mProject)); // can throw
}
void OutputJobRunner::runImpl(const LppzOutputJob& job) {
// Determine output file.
const FilePath fp = mWriter->beginWritingFile(
job.getUuid(),
AttributeSubstitutor::substitute(
job.getOutputPath(), ProjectAttributeLookup(mProject, nullptr),
[&](const QString& str) {
return FilePath::cleanFileName(
str, FilePath::ReplaceSpaces | FilePath::KeepCase);
})); // can throw
// Usually we save the project to the transactional file system (but not to
// the disk!) before exporting the *.lppz since the user probably expects
// that the current state of the project gets exported. However, if the
// file format is unstable (i.e. on development branches), this would lead
// in a *.lppz of an unstable file format, which is not really useful (most
// *.lppz readers will not support an unstable file format). Therefore we
// don't save the project on development branches. Note that unfortunately
// this doesn't work if there are any changes in the project and an autosave
// was already performed, but it is almost impossible to fix this issue :-(
if (Application::isFileFormatStable()) {
mProject.save(); // can throw
}
// Export project to ZIP, but without the output directory since this can
// be quite large and usually does not make sense, especially since *.lppz
// files might even be stored in this directory as well because they are
// output files.
auto filter = [](const QString& filePath) {
return !filePath.startsWith("output/");
};
mProject.getDirectory().getFileSystem()->exportToZip(fp,
filter); // can throw
}
void OutputJobRunner::runImpl(const CopyOutputJob& job) {
const QList<Board*> boards = getBoards(job.getBoards(), false);
const QVector<std::shared_ptr<AssemblyVariant>> assemblyVariants =
getAssemblyVariants(job.getAssemblyVariants(), false);
foreach (const Board* board, boards) {
foreach (const std::shared_ptr<AssemblyVariant>& av, assemblyVariants) {
const ProjectAttributeLookup lookup = board
? ProjectAttributeLookup(*board, av)
: ProjectAttributeLookup(mProject, av);
QString inputPath = AttributeSubstitutor::substitute(
job.getInputPath(), lookup, [&](const QString& str) {
return FilePath::cleanFileName(
str, FilePath::ReplaceSpaces | FilePath::KeepCase);
});
const FilePath outputFp = mWriter->beginWritingFile(
job.getUuid(),
AttributeSubstitutor::substitute(
job.getOutputPath(), lookup, [&](const QString& str) {
return FilePath::cleanFileName(
str, FilePath::ReplaceSpaces | FilePath::KeepCase);
})); // can throw
// The input file must be located within the project to keep the project
// self-contained, thus we can load it from the transactional filesystem.
// This also ensures that the job works for *.lppz projects.
// For compatibility, we need to normalize the specified file path.
const FilePath inputFp = mProject.getPath().getPathTo(inputPath);
if ((!QDir::isRelativePath(inputPath)) ||
(!inputFp.isLocatedInDir(mProject.getPath()))) {
throw RuntimeError(
__FILE__, __LINE__,
tr("The input file must be located within the project directory, "
"specified by a relative file path."));
}
inputPath = inputFp.toRelative(mProject.getPath());
// Copy file.
QByteArray content =
mProject.getDirectory().read(inputPath); // can throw
if (job.getSubstituteVariables()) {
content = AttributeSubstitutor::substitute(content, lookup).toUtf8();
}
FileUtils::writeFile(outputFp, content); // can throw
}
}
}
void OutputJobRunner::runImpl(const ArchiveOutputJob& job) {
// Determine output file.
const FilePath fp = mWriter->beginWritingFile(
job.getUuid(),
AttributeSubstitutor::substitute(
job.getOutputPath(), ProjectAttributeLookup(mProject, nullptr),
[&](const QString& str) {
return FilePath::cleanFileName(
str, FilePath::ReplaceSpaces | FilePath::KeepCase);
})); // can throw
// Collect input files.
std::shared_ptr<TransactionalFileSystem> fs =
TransactionalFileSystem::openRW(FilePath::getRandomTempPath());
for (auto it = job.getInputJobs().begin(); it != job.getInputJobs().end();
++it) {
if (!mWriter->getWrittenFiles().contains(it.key())) {
throw RuntimeError(
__FILE__, __LINE__,
tr("The archive job depends on files from another job which was not "
"run yet. Note that archive jobs can only depend on jobs further "
"ahead in the list so you might need to reorder them."));
}
foreach (const FilePath& inputFp,
mWriter->getWrittenFiles().values(it.key())) {
fs->write(it.value() % "/" % inputFp.getFilename(),
FileUtils::readFile(inputFp)); // can throw
}
}
if (job.getInputJobs().isEmpty()) {
emit warning(
tr("No input jobs selected, thus the resulting archive will "
"be empty."));
}
// Export depending on file extension.
if (fp.getSuffix().toLower() == "zip") {
fs->exportToZip(fp); // can throw
} else {
throw RuntimeError(
__FILE__, __LINE__,
QString("Unsupported archive format: '%1'").arg(fp.getSuffix()));
}
}
QList<Board*> OutputJobRunner::getBoards(
const OutputJob::ObjectSet<tl::optional<Uuid>>& set,
bool includeNullInAll) const {
QList<Board*> result;
if (set.isAll()) {
if (includeNullInAll) {
result.append(nullptr);
}
result += mProject.getBoards();
} else if (set.isDefault()) {
result.append(mProject.getBoardByIndex(0));
} else {
QSet<tl::optional<Uuid>> remainingUuids = set.getSet();
if (set.getSet().contains(tl::nullopt)) {
result.append(nullptr);
remainingUuids.remove(tl::nullopt);
}
foreach (auto board, mProject.getBoards()) {
if (remainingUuids.contains(board->getUuid())) {
result.append(board);
remainingUuids.remove(board->getUuid());
}
}
foreach (const auto& uuid, remainingUuids) {
Q_ASSERT(uuid);
throw RuntimeError(
__FILE__, __LINE__,
QString("Board does not exist: %1").arg(uuid->toStr()));
}
}
return result;
}
QList<Board*> OutputJobRunner::getBoards(
const OutputJob::ObjectSet<Uuid>& set) const {
QList<Board*> result;
if (set.isAll()) {
result = mProject.getBoards();
} else if (set.isDefault()) {
if (auto board = mProject.getBoardByIndex(0)) {
result.append(board);
}
} else {
QSet<Uuid> remainingUuids = set.getSet();
foreach (auto board, mProject.getBoards()) {
if (remainingUuids.contains(board->getUuid())) {
result.append(board);
remainingUuids.remove(board->getUuid());
}
}
foreach (const auto& uuid, remainingUuids) {
throw RuntimeError(__FILE__, __LINE__,
QString("Board does not exist: %1").arg(uuid.toStr()));
}
}
return result;
}
QVector<std::shared_ptr<AssemblyVariant>> OutputJobRunner::getAssemblyVariants(
const OutputJob::ObjectSet<tl::optional<Uuid>>& set,
bool includeNullInAll) const {
QVector<std::shared_ptr<AssemblyVariant>> result;
if (set.isAll()) {
if (includeNullInAll) {
result.append(nullptr);
}
result += mProject.getCircuit().getAssemblyVariants().values();
} else if (set.isDefault()) {
result.append(mProject.getCircuit().getAssemblyVariants().value(0));
} else {
QSet<tl::optional<Uuid>> remainingUuids = set.getSet();
if (set.getSet().contains(tl::nullopt)) {
result.append(nullptr);
remainingUuids.remove(tl::nullopt);
}
foreach (auto av, mProject.getCircuit().getAssemblyVariants().values()) {
if (remainingUuids.contains(av->getUuid())) {
result.append(av);
remainingUuids.remove(av->getUuid());
}
}
foreach (const auto& uuid, remainingUuids) {
Q_ASSERT(uuid);
throw RuntimeError(
__FILE__, __LINE__,
QString("Assembly variant does not exist: %1").arg(uuid->toStr()));
}
}
return result;
}
QVector<std::shared_ptr<AssemblyVariant>> OutputJobRunner::getAssemblyVariants(
const OutputJob::ObjectSet<Uuid>& set) const {
QVector<std::shared_ptr<AssemblyVariant>> result;
if (set.isAll()) {
result = mProject.getCircuit().getAssemblyVariants().values();
} else if (set.isDefault()) {
if (auto av = mProject.getCircuit().getAssemblyVariants().value(0)) {
result.append(av);
}
} else {
QSet<Uuid> remainingUuids = set.getSet();
foreach (auto av, mProject.getCircuit().getAssemblyVariants().values()) {
if (remainingUuids.contains(av->getUuid())) {
result.append(av);
remainingUuids.remove(av->getUuid());
}
}
foreach (const auto& uuid, remainingUuids) {
throw RuntimeError(
__FILE__, __LINE__,
QString("Assembly variant does not exist: %1").arg(uuid.toStr()));
}
}
return result;
}
/*******************************************************************************
* End of File
******************************************************************************/
} // namespace librepcb
c74bd0883This file is pinned to the selected release. Download the original to open it in your design tool.