Skip to content

Commit

Permalink
Merge pull request #567 from LibrePCB/board-print-support
Browse files Browse the repository at this point in the history
Board editor: Add PDF export and print support
(cherry picked from commit 54c6274)
  • Loading branch information
ubruhin committed Nov 17, 2019
1 parent 88c9584 commit deee42c
Show file tree
Hide file tree
Showing 14 changed files with 231 additions and 93 deletions.
2 changes: 1 addition & 1 deletion apps/EagleImport/EagleImport.pro
Expand Up @@ -10,7 +10,7 @@ TARGET = eagle-import
# Use common project definitions
include(../../common.pri)

QT += core widgets xml network
QT += core widgets xml network printsupport

LIBS += \
-L$${DESTDIR} \
Expand Down
2 changes: 1 addition & 1 deletion apps/WorkspaceLibraryUpdater/WorkspaceLibraryUpdater.pro
Expand Up @@ -10,7 +10,7 @@ TARGET = workspace-library-updater
# Use common project definitions
include(../../common.pri)

QT += core widgets xml sql network
QT += core widgets xml sql network printsupport

LIBS += \
-L$${DESTDIR} \
Expand Down
2 changes: 1 addition & 1 deletion libs/librepcb/common/common.pro
Expand Up @@ -15,7 +15,7 @@ DEFINES += BUILD_OUTPUT_DIRECTORY="\\\"$${OUTPUT_DIR_ABS}\\\""
DEFINES += SHARE_DIRECTORY_SOURCE="\\\"$${SHARE_DIR_ABS}\\\""
DEFINES += GIT_COMMIT_SHA="\\\"$(shell git -C \""$$_PRO_FILE_PWD_"\" rev-parse --verify HEAD)\\\""

QT += core widgets xml opengl network sql
QT += core widgets xml opengl network sql printsupport

CONFIG += staticlib

Expand Down
1 change: 1 addition & 0 deletions libs/librepcb/common/graphics/holegraphicsitem.cpp
Expand Up @@ -52,6 +52,7 @@ HoleGraphicsItem::HoleGraphicsItem(Hole& hole,

// add origin cross
mOriginCrossGraphicsItem.reset(new OriginCrossGraphicsItem(this));
mOriginCrossGraphicsItem->setVisibleInPrintOutput(true);
mOriginCrossGraphicsItem->setRotation(Angle::deg45());
mOriginCrossGraphicsItem->setSize(positiveToUnsigned(mHole.getDiameter()) +
UnsignedLength(500000));
Expand Down
29 changes: 25 additions & 4 deletions libs/librepcb/common/graphics/origincrossgraphicsitem.cpp
Expand Up @@ -22,6 +22,7 @@
******************************************************************************/
#include "origincrossgraphicsitem.h"

#include <QPrinter>
#include <QtCore>
#include <QtWidgets>

Expand All @@ -38,6 +39,7 @@ OriginCrossGraphicsItem::OriginCrossGraphicsItem(QGraphicsItem* parent) noexcept
: QGraphicsItem(parent),
mLayer(nullptr),
mSize(0),
mVisibleInPrintOutput(false), // don't show origin crosses by default
mOnLayerEditedSlot(*this, &OriginCrossGraphicsItem::layerEdited) {
mPen.setWidth(0);
mPenHighlighted.setWidth(0);
Expand Down Expand Up @@ -84,6 +86,11 @@ void OriginCrossGraphicsItem::setLayer(const GraphicsLayer* layer) noexcept {
}
}

void OriginCrossGraphicsItem::setVisibleInPrintOutput(bool visible) noexcept {
mVisibleInPrintOutput = visible;
update();
}

/*******************************************************************************
* Inherited from QGraphicsItem
******************************************************************************/
Expand All @@ -92,11 +99,25 @@ void OriginCrossGraphicsItem::paint(QPainter* painter,
const QStyleOptionGraphicsItem* option,
QWidget* widget) noexcept {
Q_UNUSED(widget);
if (option->state.testFlag(QStyle::State_Selected)) {
painter->setPen(mPenHighlighted);
} else {
painter->setPen(mPen);

const bool isSelected = option->state.testFlag(QStyle::State_Selected);
const bool deviceIsPrinter =
(dynamic_cast<QPrinter*>(painter->device()) != nullptr);

if (deviceIsPrinter && (!mVisibleInPrintOutput)) {
return;
}

QPen pen = isSelected ? mPenHighlighted : mPen;

// When printing, enforce a minimum line width to make sure the line will be
// visible (too thin lines will not be visible).
qreal minPrintLineWidth = Length(100000).toPx();
if (deviceIsPrinter && (pen.widthF() < minPrintLineWidth)) {
pen.setWidthF(minPrintLineWidth);
}

painter->setPen(pen);
painter->drawLine(mLineH);
painter->drawLine(mLineV);
}
Expand Down
2 changes: 2 additions & 0 deletions libs/librepcb/common/graphics/origincrossgraphicsitem.h
Expand Up @@ -54,6 +54,7 @@ class OriginCrossGraphicsItem final : public QGraphicsItem {
void setRotation(const Angle& rot) noexcept;
void setSize(const UnsignedLength& size) noexcept;
void setLayer(const GraphicsLayer* layer) noexcept;
void setVisibleInPrintOutput(bool visible) noexcept;

// Inherited from QGraphicsItem
QRectF boundingRect() const noexcept override { return mBoundingRect; }
Expand All @@ -79,6 +80,7 @@ class OriginCrossGraphicsItem final : public QGraphicsItem {
QLineF mLineV;
QRectF mBoundingRect;
QPainterPath mShape;
bool mVisibleInPrintOutput;

// Slots
GraphicsLayer::OnEditedSlot mOnLayerEditedSlot;
Expand Down
23 changes: 17 additions & 6 deletions libs/librepcb/common/graphics/primitivecirclegraphicsitem.cpp
Expand Up @@ -24,6 +24,7 @@

#include "../toolbox.h"

#include <QPrinter>
#include <QtCore>
#include <QtWidgets>

Expand Down Expand Up @@ -109,13 +110,23 @@ void PrimitiveCircleGraphicsItem::paint(QPainter* painter,
const QStyleOptionGraphicsItem* option,
QWidget* widget) noexcept {
Q_UNUSED(widget);
if (option->state.testFlag(QStyle::State_Selected)) {
painter->setPen(mPenHighlighted);
painter->setBrush(mBrushHighlighted);
} else {
painter->setPen(mPen);
painter->setBrush(mBrush);

const bool isSelected = option->state.testFlag(QStyle::State_Selected);
const bool deviceIsPrinter =
(dynamic_cast<QPrinter*>(painter->device()) != nullptr);

QPen pen = isSelected ? mPenHighlighted : mPen;
QBrush brush = isSelected ? mBrushHighlighted : mBrush;

// When printing, enforce a minimum line width to make sure the line will be
// visible (too thin lines will not be visible).
qreal minPrintLineWidth = Length(100000).toPx();
if (deviceIsPrinter && (pen.widthF() < minPrintLineWidth)) {
pen.setWidthF(minPrintLineWidth);
}

painter->setPen(pen);
painter->setBrush(brush);
painter->drawEllipse(mCircleRect);
}

Expand Down
23 changes: 17 additions & 6 deletions libs/librepcb/common/graphics/primitivepathgraphicsitem.cpp
Expand Up @@ -24,6 +24,7 @@

#include "../toolbox.h"

#include <QPrinter>
#include <QtCore>
#include <QtWidgets>

Expand Down Expand Up @@ -116,13 +117,23 @@ void PrimitivePathGraphicsItem::paint(QPainter* painter,
const QStyleOptionGraphicsItem* option,
QWidget* widget) noexcept {
Q_UNUSED(widget);
if (option->state.testFlag(QStyle::State_Selected)) {
painter->setPen(mPenHighlighted);
painter->setBrush(mBrushHighlighted);
} else {
painter->setPen(mPen);
painter->setBrush(mBrush);

const bool isSelected = option->state.testFlag(QStyle::State_Selected);
const bool deviceIsPrinter =
(dynamic_cast<QPrinter*>(painter->device()) != nullptr);

QPen pen = isSelected ? mPenHighlighted : mPen;
QBrush brush = isSelected ? mBrushHighlighted : mBrush;

// When printing, enforce a minimum line width to make sure the line will be
// visible (too thin lines will not be visible).
qreal minPrintLineWidth = Length(100000).toPx();
if (deviceIsPrinter && (pen.widthF() < minPrintLineWidth)) {
pen.setWidthF(minPrintLineWidth);
}

painter->setPen(pen);
painter->setBrush(brush);
painter->drawPath(mPainterPath);
}

Expand Down
29 changes: 29 additions & 0 deletions libs/librepcb/project/boards/board.cpp
Expand Up @@ -835,6 +835,35 @@ void Board::save() {
}
}

void Board::print(QPrinter& printer) {
clearSelection();

// Adjust layer colors
ScopeGuardList sgl;
foreach (GraphicsLayer* layer, mLayerStack->getAllLayers()) {
QColor color = layer->getColor();
sgl.add([layer, color]() { layer->setColor(color); }); // restore color
int h = color.hsvHue();
int s = color.hsvSaturation();
int v = color.value() / 2; // avoid white colors
int a = (color.alpha() / 2) + 127; // avoid transparent colors
layer->setColor(QColor::fromHsv(h, s, v, a));
}

QPainter painter(&printer);
renderToQPainter(painter, printer.resolution()); // can throw
}

void Board::renderToQPainter(QPainter& painter, int dpi) const {
QRectF sceneRect = mGraphicsScene->itemsBoundingRect();
QRectF printerRect(
qreal(0), qreal(0),
Length::fromPx(sceneRect.width()).toInch() * dpi, // can throw
Length::fromPx(sceneRect.height()).toInch() * dpi); // can throw
mGraphicsScene->render(&painter, printerRect, sceneRect,
Qt::IgnoreAspectRatio);
}

void Board::showInView(GraphicsView& view) noexcept {
view.setScene(mGraphicsScene.data());
}
Expand Down
18 changes: 18 additions & 0 deletions libs/librepcb/project/boards/board.h
Expand Up @@ -34,6 +34,7 @@
#include <librepcb/common/units/all_length_units.h>
#include <librepcb/common/uuid.h>

#include <QPrinter>
#include <QtCore>
#include <QtWidgets>

Expand Down Expand Up @@ -226,6 +227,23 @@ class Board final : public QObject,
void addToProject();
void removeFromProject();
void save();
/**
* @brief Print board to a QPrinter (printer or file)
*
* This is a helper function to print the board into w QPrinter. It does
* following:
*
* - Clear selection (#clearSelection())
* - Adjust layer colors to avoid too bright (almost invisible) elements
* - Render the board with scale 1:1 (#renderToQPainter())
* - Revert layer colors
*
* @param printer The QPrinter where to print the board
*
* @throw Exception On error
*/
void print(QPrinter& printer);
void renderToQPainter(QPainter& painter, int dpi) const;
void showInView(GraphicsView& view) noexcept;
void saveViewSceneRect(const QRectF& rect) noexcept { mViewRect = rect; }
const QRectF& restoreViewSceneRect() const noexcept { return mViewRect; }
Expand Down
17 changes: 9 additions & 8 deletions libs/librepcb/project/boards/graphicsitems/bgi_plane.cpp
Expand Up @@ -97,18 +97,19 @@ void BGI_Plane::paint(QPainter* painter, const QStyleOptionGraphicsItem* option,
Q_UNUSED(widget);

const bool selected = mPlane.isSelected();
// const bool deviceIsPrinter = (dynamic_cast<QPrinter*>(painter->device()) !=
// 0);
const bool deviceIsPrinter =
(dynamic_cast<QPrinter*>(painter->device()) != nullptr);
const qreal lod =
option->levelOfDetailFromTransform(painter->worldTransform());
Q_UNUSED(option);

if (mLayer && mLayer->isVisible()) {
// draw outline
painter->setPen(
QPen(mLayer->getColor(selected), 3 / lod, Qt::DashLine, Qt::RoundCap));
painter->setBrush(Qt::NoBrush);
painter->drawPath(mOutline);
// draw outline only on screen, not for print or PDF export
if (!deviceIsPrinter) {
painter->setPen(QPen(mLayer->getColor(selected), 3 / lod, Qt::DashLine,
Qt::RoundCap));
painter->setBrush(Qt::NoBrush);
painter->drawPath(mOutline);
}

// draw plane only if plane should be visible
if (mPlane.isVisible()) {
Expand Down
66 changes: 61 additions & 5 deletions libs/librepcb/projecteditor/boardeditor/boardeditor.cpp
Expand Up @@ -38,6 +38,7 @@
#include <librepcb/common/dialogs/boarddesignrulesdialog.h>
#include <librepcb/common/dialogs/filedialog.h>
#include <librepcb/common/dialogs/gridsettingsdialog.h>
#include <librepcb/common/fileio/fileutils.h>
#include <librepcb/common/graphics/graphicsview.h>
#include <librepcb/common/gridproperties.h>
#include <librepcb/common/undostack.h>
Expand All @@ -49,13 +50,15 @@
#include <librepcb/project/boards/cmd/cmdboardremove.h>
#include <librepcb/project/boards/items/bi_plane.h>
#include <librepcb/project/circuit/circuit.h>
#include <librepcb/project/metadata/projectmetadata.h>
#include <librepcb/project/project.h>
#include <librepcb/project/settings/projectsettings.h>
#include <librepcb/workspace/library/workspacelibrarydb.h>
#include <librepcb/workspace/settings/workspacesettings.h>
#include <librepcb/workspace/workspace.h>

#include <QtCore>
#include <QtPrintSupport>
#include <QtWidgets>

/*******************************************************************************
Expand Down Expand Up @@ -448,15 +451,68 @@ void BoardEditor::on_actionGrid_triggered() {
}
}

void BoardEditor::on_actionPrint_triggered() {
try {
Board* board = getActiveBoard();
if (!board) {
throw Exception(__FILE__, __LINE__, tr("No board selected."));
}
QPrinter printer(QPrinter::HighResolution);
printer.setPaperSize(QPrinter::A4);
printer.setOrientation(QPrinter::Landscape);
printer.setCreator(QString("LibrePCB %1").arg(qApp->applicationVersion()));
printer.setDocName(*mProject.getMetadata().getName());
QPrintDialog printDialog(&printer, this);
printDialog.setOption(QAbstractPrintDialog::PrintSelection, false);
printDialog.setMinMax(1, 1);
if (printDialog.exec() == QDialog::Accepted) {
board->print(printer); // can throw
}
} catch (Exception& e) {
QMessageBox::warning(this, tr("Error"), e.getMsg());
}
}

void BoardEditor::on_actionExportAsPdf_triggered() {
try {
QString filename = FileDialog::getSaveFileName(this, tr("PDF Export"),
QDir::homePath(), "*.pdf");
Board* board = getActiveBoard();
if (!board) {
throw Exception(__FILE__, __LINE__, tr("No board selected."));
}
QString projectName =
FilePath::cleanFileName(*mProject.getMetadata().getName(),
FilePath::ReplaceSpaces | FilePath::KeepCase);
QString projectVersion =
FilePath::cleanFileName(mProject.getMetadata().getVersion(),
FilePath::ReplaceSpaces | FilePath::KeepCase);
QString relativePath =
QString("output/%1/%2_Board.pdf").arg(projectVersion, projectName);
FilePath defaultFilePath = mProject.getPath().getPathTo(relativePath);
QDir().mkpath(defaultFilePath.getParentDir().toStr());
QString filename = FileDialog::getSaveFileName(
this, tr("PDF Export"), defaultFilePath.toNative(), "*.pdf");
if (filename.isEmpty()) return;
if (!filename.endsWith(".pdf")) filename.append(".pdf");
// FilePath filepath(filename);
// mProject.exportSchematicsAsPdf(filepath); // this method can throw an
// exception
FilePath filepath(filename);

// Create output directory first because QPrinter silently fails if it
// doesn't exist.
FileUtils::makePath(filepath.getParentDir()); // can throw

// Print (use local block scope to ensure the PDF is fully written & closed
// after leaving the block - without this, opening the PDF could fail)
{
QPrinter printer(QPrinter::HighResolution);
printer.setPaperSize(QPrinter::A4);
printer.setOrientation(QPrinter::Landscape);
printer.setOutputFormat(QPrinter::PdfFormat);
printer.setCreator(
QString("LibrePCB %1").arg(qApp->applicationVersion()));
printer.setOutputFileName(filepath.toStr());
board->print(printer); // can throw
}

QDesktopServices::openUrl(QUrl::fromLocalFile(filepath.toStr()));
} catch (Exception& e) {
QMessageBox::warning(this, tr("Error"), e.getMsg());
}
Expand Down
1 change: 1 addition & 0 deletions libs/librepcb/projecteditor/boardeditor/boardeditor.h
Expand Up @@ -100,6 +100,7 @@ private slots:
void on_actionCopyBoard_triggered();
void on_actionRemoveBoard_triggered();
void on_actionGrid_triggered();
void on_actionPrint_triggered();
void on_actionExportAsPdf_triggered();
void on_actionGenerateFabricationData_triggered();
void on_actionGenerateBom_triggered();
Expand Down

0 comments on commit deee42c

Please sign in to comment.