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 "boardpainter.h"
#include "../../application.h"
#include "../../attribute/attributesubstitutor.h"
#include "../../export/graphicsexportsettings.h"
#include "../../font/strokefontpool.h"
#include "../../geometry/text.h"
#include "../../graphics/graphicslayer.h"
#include "../../graphics/graphicspainter.h"
#include "../../library/pkg/footprint.h"
#include "../project.h"
#include "board.h"
#include "items/bi_device.h"
#include "items/bi_footprint.h"
#include "items/bi_footprintpad.h"
#include "items/bi_hole.h"
#include "items/bi_netline.h"
#include "items/bi_netsegment.h"
#include "items/bi_plane.h"
#include "items/bi_polygon.h"
#include "items/bi_stroketext.h"
#include "items/bi_via.h"
#include <QtCore>
#include <QtGui>
/*******************************************************************************
* Namespace
******************************************************************************/
namespace librepcb {
/*******************************************************************************
* Constructors / Destructor
******************************************************************************/
BoardPainter::BoardPainter(const Board& board)
: mStrokeFont(board.getProject().getStrokeFonts().getFont(
board.getDefaultFontName())) // can throw
{
foreach (const BI_Device* device, board.getDeviceInstances()) {
const BI_Footprint& footprint = device->getFootprint();
Footprint fpt;
fpt.transform = Transform(*device);
foreach (const BI_FootprintPad* pad, footprint.getPads()) {
fpt.pads.append(pad->getLibPad());
}
for (const Polygon& polygon : footprint.getLibFootprint().getPolygons()) {
fpt.polygons.append(polygon);
}
for (const Circle& circle : footprint.getLibFootprint().getCircles()) {
fpt.circles.append(circle);
}
for (const Hole& hole : footprint.getLibFootprint().getHoles()) {
fpt.holes.append(hole);
}
foreach (const BI_StrokeText* text, footprint.getStrokeTexts()) {
StrokeText copy(text->getText());
copy.setText(
AttributeSubstitutor::substitute(copy.getText(), &footprint));
mStrokeTexts.append(copy);
}
mFootprints.append(fpt);
}
foreach (const BI_Plane* plane, board.getPlanes()) {
mPlanes.append(Plane{*plane->getLayerName(), plane->getFragments()});
}
foreach (const BI_Polygon* polygon, board.getPolygons()) {
mPolygons.append(polygon->getPolygon());
}
foreach (const BI_StrokeText* text, board.getStrokeTexts()) {
StrokeText copy(text->getText());
copy.setText(AttributeSubstitutor::substitute(copy.getText(), &board));
mStrokeTexts.append(copy);
}
foreach (const BI_Hole* hole, board.getHoles()) {
mHoles.append(hole->getHole());
}
foreach (const BI_NetSegment* segment, board.getNetSegments()) {
for (const BI_Via* via : segment->getVias()) {
mVias.append(via->getVia());
}
for (const BI_NetLine* netline : segment->getNetLines()) {
mTraces.append(Trace{
netline->getLayer().getName(), netline->getStartPoint().getPosition(),
netline->getEndPoint().getPosition(), netline->getWidth()});
}
}
}
BoardPainter::~BoardPainter() noexcept {
}
/*******************************************************************************
* General Methods
******************************************************************************/
void BoardPainter::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 traces.
foreach (const Trace& trace, content.traces) {
p.drawLine(trace.startPosition, trace.endPosition, *trace.width,
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 BoardPainter::initContentByLayer() const noexcept {
QMutexLocker lock(&mMutex);
if (mContentByLayer.isEmpty()) {
// Footprints.
foreach (const Footprint& footprint, mFootprints) {
// Footprint polygons.
foreach (Polygon polygon, footprint.polygons) {
polygon.setLayerName(footprint.transform.map(polygon.getLayerName()));
polygon.setPath(footprint.transform.map(polygon.getPath()));
mContentByLayer[*polygon.getLayerName()].polygons.append(polygon);
}
// Footprint circles.
foreach (Circle circle, footprint.circles) {
circle.setLayerName(footprint.transform.map(circle.getLayerName()));
circle.setCenter(footprint.transform.map(circle.getCenter()));
mContentByLayer[*circle.getLayerName()].circles.append(circle);
}
// Footprint holes.
foreach (Hole hole, footprint.holes) {
hole.setPosition(footprint.transform.map(hole.getPosition()));
mContentByLayer[GraphicsLayer::sBoardDrillsNpth].holes.append(hole);
}
// Footprint pads.
foreach (FootprintPad pad, footprint.pads) {
QPainterPath path = footprint.transform
.map(pad.getOutline()
.rotated(pad.getRotation())
.translated(pad.getPosition()))
.toQPainterPathPx();
path.setFillRule(Qt::OddEvenFill); // To subtract the hole!
path.addEllipse(
footprint.transform.map(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;
}
layer = footprint.transform.map(layer);
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(), footprint.transform.map(pad.getPosition()),
PositiveLength(*pad.getDrillDiameter()));
mContentByLayer[GraphicsLayer::sBoardDrillsNpth].padHoles.append(
hole);
}
}
}
// Planes.
foreach (const Plane& plane, mPlanes) {
foreach (const Path& path, plane.fragments) {
mContentByLayer[plane.layerName].areas.append(path.toQPainterPathPx());
}
}
// Vias.
foreach (const Via& via, mVias) {
mContentByLayer[GraphicsLayer::sBoardViasTht].areas.append(
via.toQPainterPathPx().translated(via.getPosition().toPxQPointF()));
}
// Traces.
foreach (const Trace& trace, mTraces) {
mContentByLayer[trace.layerName].traces.append(trace);
}
// Polygons.
foreach (const Polygon& polygon, mPolygons) {
mContentByLayer[*polygon.getLayerName()].polygons.append(polygon);
}
// Holes.
foreach (const Hole& hole, mHoles) {
mContentByLayer[GraphicsLayer::sBoardDrillsNpth].holes.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
76de63d66This file is pinned to the selected release. Download the original to open it in your design tool.