Loading files
c136b73e0This file is pinned to the selected release. Download the original to open it in your design tool.
/*
* 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 "footprintpainter.h"
#include "../../application.h"
#include "../../export/graphicsexportsettings.h"
#include "../../geometry/text.h"
#include "../../graphics/graphicslayer.h"
#include "../../graphics/graphicspainter.h"
#include "../../utils/transform.h"
#include "footprint.h"
#include <QtCore>
#include <QtGui>
/*******************************************************************************
* Namespace
******************************************************************************/
namespace librepcb {
/*******************************************************************************
* Constructors / Destructor
******************************************************************************/
FootprintPainter::FootprintPainter(const Footprint& footprint) noexcept
: mStrokeFont(qApp->getDefaultStrokeFont()) {
for (const FootprintPad& pad : footprint.getPads()) {
mPads.append(pad);
}
for (const Polygon& polygon : footprint.getPolygons()) {
mPolygons.append(polygon);
}
for (const Circle& circle : footprint.getCircles()) {
mCircles.append(circle);
}
for (const StrokeText& text : footprint.getStrokeTexts()) {
mStrokeTexts.append(text);
}
for (const Hole& hole : footprint.getHoles()) {
mHoles.append(hole);
}
}
FootprintPainter::~FootprintPainter() noexcept {
}
/*******************************************************************************
* General Methods
******************************************************************************/
void FootprintPainter::paint(QPainter& painter,
const GraphicsExportSettings& settings) const
noexcept {
// Determine what to paint on which layer.
initContentByLayer();
// Draw pad circles only if holes are enabled, but pads not.
const bool drawPadHoles =
settings.getLayerPaintOrder().contains(GraphicsLayer::sBoardDrillsNpth) &&
(!settings.getLayerPaintOrder().contains(GraphicsLayer::sBoardPadsTht));
// Draw each layer in reverse order for correct stackup.
GraphicsPainter p(painter);
p.setMinLineWidth(settings.getMinLineWidth());
foreach (const QString& layer, settings.getLayerPaintOrder()) {
LayerContent content = mContentByLayer.value(layer);
// Draw areas.
foreach (const QPainterPath& area, content.areas) {
p.drawPath(area, Length(0), QColor(), settings.getColor(layer));
}
// Draw polygons.
foreach (const Polygon& polygon, content.polygons) {
p.drawPolygon(polygon.getPath(), *polygon.getLineWidth(),
settings.getColor(layer),
settings.getFillColor(layer, polygon.isFilled(),
polygon.isGrabArea()));
}
// Draw circles.
foreach (const Circle& circle, content.circles) {
p.drawCircle(
circle.getCenter(), *circle.getDiameter(), *circle.getLineWidth(),
settings.getColor(layer),
settings.getFillColor(layer, circle.isFilled(), circle.isGrabArea()));
}
// Draw holes.
foreach (const Hole& hole, content.holes) {
p.drawCircle(hole.getPosition(), *hole.getDiameter(), Length(0),
settings.getColor(layer), QColor());
}
// Draw pad holes.
if (drawPadHoles) {
foreach (const Hole& hole, content.padHoles) {
p.drawCircle(hole.getPosition(), *hole.getDiameter(), Length(0),
settings.getColor(layer), QColor());
}
}
// Draw invisible texts to make them selectable and searchable in PDF and
// SVG output.
foreach (const Text& text, content.texts) {
p.drawText(text.getPosition(), text.getRotation(), *text.getHeight(),
text.getAlign(), text.getText(),
qApp->getDefaultMonospaceFont(), Qt::transparent, true,
settings.getMirror());
}
}
}
/*******************************************************************************
* Private Methods
******************************************************************************/
void FootprintPainter::initContentByLayer() const noexcept {
QMutexLocker lock(&mMutex);
if (mContentByLayer.isEmpty()) {
// Footprint polygons.
foreach (Polygon polygon, mPolygons) {
mContentByLayer[*polygon.getLayerName()].polygons.append(polygon);
}
// Footprint circles.
foreach (Circle circle, mCircles) {
mContentByLayer[*circle.getLayerName()].circles.append(circle);
}
// Footprint holes.
foreach (Hole hole, mHoles) {
mContentByLayer[GraphicsLayer::sBoardDrillsNpth].holes.append(hole);
}
// Footprint pads.
foreach (FootprintPad pad, mPads) {
QPainterPath path = pad.getOutline()
.rotated(pad.getRotation())
.translated(pad.getPosition())
.toQPainterPathPx();
path.setFillRule(Qt::OddEvenFill); // To subtract the hole!
path.addEllipse(pad.getPosition().toPxQPointF(),
pad.getDrillDiameter()->toPx() / 2,
pad.getDrillDiameter()->toPx() / 2);
QString layer;
switch (pad.getBoardSide()) {
case FootprintPad::BoardSide::TOP:
layer = GraphicsLayer::sTopCopper;
break;
case FootprintPad::BoardSide::BOTTOM:
layer = GraphicsLayer::sBotCopper;
break;
default:
layer = GraphicsLayer::sBoardPadsTht;
break;
}
mContentByLayer[layer].areas.append(path);
// Also add the hole for THT pads.
if ((pad.getBoardSide() == FootprintPad::BoardSide::THT) &&
(pad.getDrillDiameter() > 0)) {
Hole hole(pad.getUuid(), pad.getPosition(),
PositiveLength(*pad.getDrillDiameter()));
mContentByLayer[GraphicsLayer::sBoardDrillsNpth].padHoles.append(hole);
}
}
// Texts.
foreach (StrokeText text, mStrokeTexts) {
Transform transform(text);
foreach (Path path, transform.map(text.generatePaths(mStrokeFont))) {
mContentByLayer[*text.getLayerName()].polygons.append(
Polygon(text.getUuid(), text.getLayerName(), text.getStrokeWidth(),
false, false, path));
}
Angle rotation = transform.map(Angle::deg0());
Alignment align =
text.getMirrored() ? text.getAlign().mirroredV() : text.getAlign();
Length totalHeight = *text.getHeight() + *text.getStrokeWidth();
totalHeight += totalHeight / 2; // Correction factor from TTF to stroke.
Length baseline = totalHeight / 4; // Baseline correction TTF -> stroke.
Point baselineOffset;
if (align.getV() == VAlign::bottom()) {
baselineOffset.setY(-baseline);
} else if (align.getV() == VAlign::top()) {
baselineOffset.setY(baseline);
}
baselineOffset.rotate(rotation);
mContentByLayer[*text.getLayerName()].texts.append(
Text(text.getUuid(), text.getLayerName(), text.getText(),
text.getPosition() + baselineOffset, rotation,
PositiveLength(totalHeight), align));
}
}
}
/*******************************************************************************
* End of File
******************************************************************************/
} // namespace librepcb