Loading files
bae9c0e4eThis 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 "packagecheck.h"
#include "../../geometry/polygon.h"
#include "../../utils/toolbox.h"
#include "../../utils/transform.h"
#include "package.h"
#include "packagecheckmessages.h"
#include <QtCore>
/*******************************************************************************
* Namespace
******************************************************************************/
namespace librepcb {
/*******************************************************************************
* Constructors / Destructor
******************************************************************************/
PackageCheck::PackageCheck(const Package& package) noexcept
: LibraryElementCheck(package), mPackage(package) {
}
PackageCheck::~PackageCheck() noexcept {
}
/*******************************************************************************
* General Methods
******************************************************************************/
RuleCheckMessageList PackageCheck::runChecks() const {
RuleCheckMessageList msgs = LibraryElementCheck::runChecks();
checkAssemblyType(msgs);
checkDuplicatePadNames(msgs);
checkMissingFootprint(msgs);
checkAmbiguousFootprintTags(msgs);
checkMissingTexts(msgs);
checkWrongTextLayers(msgs);
checkPackageOutlines(msgs);
checkCourtyards(msgs);
checkOriginInCenter(msgs);
checkPadsPackagePadUuid(msgs);
checkPadsClearanceToPads(msgs);
checkPadsClearanceToLegend(msgs);
checkPadsAnnularRing(msgs);
checkPadsConnectionPoint(msgs);
checkCustomPadOutline(msgs);
checkStopMaskOnPads(msgs);
checkSolderPasteOnPads(msgs);
checkCopperClearanceOnPads(msgs);
checkPadFunctions(msgs);
checkHolesStopMask(msgs);
checkLineWidths(msgs);
checkZones(msgs);
checkFootprintModels(msgs);
return msgs;
}
/*******************************************************************************
* Protected Methods
******************************************************************************/
void PackageCheck::checkAssemblyType(MsgList& msgs) const {
// Check for deprecated assembly type.
if (mPackage.getAssemblyType(false) == Package::AssemblyType::Auto) {
msgs.append(std::make_shared<MsgDeprecatedAssemblyType>());
}
// Check if the assembly type looks reasonable (only possible if there is at
// least one footprint).
if ((!mPackage.getFootprints().isEmpty()) &&
(mPackage.getAssemblyType(false) != Package::AssemblyType::Auto) &&
(mPackage.getAssemblyType(false) != mPackage.guessAssemblyType())) {
msgs.append(std::make_shared<MsgSuspiciousAssemblyType>());
}
}
void PackageCheck::checkDuplicatePadNames(MsgList& msgs) const {
QSet<CircuitIdentifier> padNames;
for (const PackagePad& pad : mPackage.getPads()) {
if (padNames.contains(pad.getName())) {
msgs.append(std::make_shared<MsgDuplicatePadName>(pad));
} else {
padNames.insert(pad.getName());
}
}
}
void PackageCheck::checkMissingFootprint(MsgList& msgs) const {
if (mPackage.getFootprints().isEmpty()) {
msgs.append(std::make_shared<MsgMissingFootprint>());
}
}
void PackageCheck::checkAmbiguousFootprintTags(MsgList& msgs) const {
QSet<QSet<Tag>> tagSets;
for (const Footprint& footprint : mPackage.getFootprints()) {
if (tagSets.contains(footprint.getTags())) {
msgs.append(std::make_shared<MsgAmbiguousFootprintTags>());
return;
} else {
tagSets.insert(footprint.getTags());
}
}
}
void PackageCheck::checkMissingTexts(MsgList& msgs) const {
for (auto itFtp = mPackage.getFootprints().begin();
itFtp != mPackage.getFootprints().end(); ++itFtp) {
QHash<QString, QVector<std::shared_ptr<const StrokeText>>> texts;
for (auto it = (*itFtp).getStrokeTexts().begin();
it != (*itFtp).getStrokeTexts().end(); ++it) {
texts[(*it).getText()].append(it.ptr());
}
if (texts.value("{{NAME}}").isEmpty()) {
msgs.append(std::make_shared<MsgMissingFootprintName>(itFtp.ptr()));
}
if (texts.value("{{VALUE}}").isEmpty()) {
msgs.append(std::make_shared<MsgMissingFootprintValue>(itFtp.ptr()));
}
}
}
void PackageCheck::checkWrongTextLayers(MsgList& msgs) const {
QHash<QString, const Layer*> textLayers = {
std::make_pair("{{NAME}}", &Layer::topNames()),
std::make_pair("{{VALUE}}", &Layer::topValues()),
};
for (auto itFtp = mPackage.getFootprints().begin();
itFtp != mPackage.getFootprints().end(); ++itFtp) {
for (auto it = (*itFtp).getStrokeTexts().begin();
it != (*itFtp).getStrokeTexts().end(); ++it) {
const Layer* expectedLayer = textLayers.value((*it).getText());
if (expectedLayer && (&(*it).getLayer() != expectedLayer)) {
msgs.append(std::make_shared<MsgWrongFootprintTextLayer>(
itFtp.ptr(), it.ptr(), *expectedLayer));
}
}
}
}
void PackageCheck::checkPackageOutlines(MsgList& msgs) const {
if (mPackage.getAssemblyType(false) != Package::AssemblyType::None) {
for (auto itFtp = mPackage.getFootprints().begin();
itFtp != mPackage.getFootprints().end(); ++itFtp) {
int count = 0;
for (auto it = (*itFtp).getPolygons().begin();
it != (*itFtp).getPolygons().end(); ++it) {
if (it->getLayer().isPackageOutline()) {
++count;
}
}
for (auto it = (*itFtp).getCircles().begin();
it != (*itFtp).getCircles().end(); ++it) {
if (it->getLayer().isPackageOutline()) {
++count;
}
}
if (count == 0) {
msgs.append(std::make_shared<MsgMissingPackageOutline>(itFtp.ptr()));
}
}
}
}
void PackageCheck::checkCourtyards(MsgList& msgs) const {
if (mPackage.getAssemblyType(false) != Package::AssemblyType::None) {
for (auto itFtp = mPackage.getFootprints().begin();
itFtp != mPackage.getFootprints().end(); ++itFtp) {
int count = 0;
for (auto it = (*itFtp).getPolygons().begin();
it != (*itFtp).getPolygons().end(); ++it) {
if (it->getLayer().isPackageCourtyard()) {
++count;
}
}
for (auto it = (*itFtp).getCircles().begin();
it != (*itFtp).getCircles().end(); ++it) {
if (it->getLayer().isPackageCourtyard()) {
++count;
}
}
if (count == 0) {
msgs.append(std::make_shared<MsgMissingCourtyard>(itFtp.ptr()));
}
}
}
}
void PackageCheck::checkOriginInCenter(MsgList& msgs) const {
for (auto itFtp = mPackage.getFootprints().begin();
itFtp != mPackage.getFootprints().end(); ++itFtp) {
// Specify a relative and an absolute tolerance.
qreal toleranceFactor = 0.01;
Length toleranceAbs(100000);
// Allow higher tolerance for THT parts.
if (mPackage.getAssemblyType(false) != Package::AssemblyType::Smt) {
toleranceFactor = 0.05;
toleranceAbs = Length(635000);
}
// Helper to increase tolerance.
auto increaseToleranceFactor = [&](qreal newFactor) {
toleranceFactor = std::max(newFactor, toleranceFactor);
};
// Consider mainly the package outlines since this is the most reliable
// source for this check.
QSet<Length> x, y;
for (const Circle& c : (*itFtp).getCircles()) {
if (c.getLayer() == Layer::topPackageOutlines()) {
const Length r = c.getDiameter() / 2;
x.insert(c.getCenter().getX() - r);
x.insert(c.getCenter().getX() + r);
y.insert(c.getCenter().getY() - r);
y.insert(c.getCenter().getY() + r);
}
}
for (const Polygon& p : (*itFtp).getPolygons()) {
if (p.getLayer() == Layer::topPackageOutlines()) {
if (p.getPath().isCurved()) {
const QRectF rectPx =
p.getPath().toClosedPath().toQPainterPathPx().boundingRect();
const Point topLeft = Point::fromPx(rectPx.topLeft());
const Point botRight = Point::fromPx(rectPx.bottomRight());
x.insert(topLeft.getX());
x.insert(botRight.getX());
y.insert(topLeft.getY());
y.insert(botRight.getY());
increaseToleranceFactor(0.15); // Relax check due to complex shape.
} else {
for (const Vertex& v : p.getPath().getVertices()) {
x.insert(v.getPos().getX());
y.insert(v.getPos().getY());
}
if (p.getPath().toOpenPath().cleaned().getVertices().count() != 4) {
increaseToleranceFactor(0.1); // Relax check due to complex shape.
}
}
}
}
// Only if we didn't find a package body, take more objects into account
// but allow much larger tolerance.
if (x.isEmpty() || y.isEmpty()) {
increaseToleranceFactor(0.3);
for (const FootprintPad& pad : (*itFtp).getPads()) {
x.insert(pad.getPosition().getX());
y.insert(pad.getPosition().getY());
}
for (const Circle& c : (*itFtp).getCircles()) {
if (c.getLayer() == Layer::topDocumentation()) {
const Length r = c.getDiameter() / 2;
x.insert(c.getCenter().getX() - r);
x.insert(c.getCenter().getX() + r);
y.insert(c.getCenter().getY() - r);
y.insert(c.getCenter().getY() + r);
}
}
for (const Polygon& p : (*itFtp).getPolygons()) {
if (p.getLayer() == Layer::topDocumentation()) {
const QRectF rectPx =
p.getPath().toClosedPath().toQPainterPathPx().boundingRect();
const Point topLeft = Point::fromPx(rectPx.topLeft());
const Point botRight = Point::fromPx(rectPx.bottomRight());
x.insert(topLeft.getX());
x.insert(botRight.getX());
y.insert(topLeft.getY());
y.insert(botRight.getY());
}
}
}
// If there is no boundary, abort.
if (x.isEmpty() || y.isEmpty()) {
continue;
}
// Calculate and check center.
const Length minX = *std::min_element(x.begin(), x.end());
const Length maxX = *std::max_element(x.begin(), x.end());
const Length minY = *std::min_element(y.begin(), y.end());
const Length maxY = *std::max_element(y.begin(), y.end());
const Point center((minX + maxX) / 2, (minY + maxY) / 2);
const Length width = maxX - minX;
const Length height = maxY - minY;
const Length tolX = std::max(width.scaled(toleranceFactor), toleranceAbs);
const Length tolY = std::max(height.scaled(toleranceFactor), toleranceAbs);
if ((center.getX().abs() > tolX) || (center.getY().abs() > tolY)) {
msgs.append(
std::make_shared<MsgFootprintOriginNotInCenter>(itFtp.ptr(), center));
}
}
}
void PackageCheck::checkPadsPackagePadUuid(MsgList& msgs) const {
for (auto itFtp = mPackage.getFootprints().begin();
itFtp != mPackage.getFootprints().end(); ++itFtp) {
std::shared_ptr<const Footprint> footprint = itFtp.ptr();
for (auto itPad = (*itFtp).getPads().begin();
itPad != (*itFtp).getPads().end(); ++itPad) {
std::shared_ptr<const FootprintPad> pad = itPad.ptr();
if ((pad->getPackagePadUuid()) &&
(!mPackage.getPads().find(*pad->getPackagePadUuid()))) {
msgs.append(std::make_shared<MsgInvalidPadConnection>(footprint, pad));
}
}
}
}
void PackageCheck::checkPadsClearanceToPads(MsgList& msgs) const {
const Length clearance = *mPackage.getMinCopperClearance();
const Length tolerance(10); // 0.01 µm, to avoid rounding issues
// Check all footprints.
for (auto itFtp = mPackage.getFootprints().begin();
itFtp != mPackage.getFootprints().end(); ++itFtp) {
std::shared_ptr<const Footprint> footprint = itFtp.ptr();
// Check all pads.
for (auto itPad1 = (*itFtp).getPads().begin();
itPad1 != (*itFtp).getPads().end(); ++itPad1) {
std::shared_ptr<const FootprintPad> pad1 = itPad1.ptr();
std::shared_ptr<const PackagePad> pkgPad1 = pad1->getPackagePadUuid()
? mPackage.getPads().find(*pad1->getPackagePadUuid())
: nullptr;
const Transform pad1Transform(pad1->getPosition(), pad1->getRotation());
const Length pad1Clearance =
std::max(clearance, *pad1->getCopperClearance()) - tolerance;
const QPainterPath pad1CopperPx =
pad1Transform.mapPx(pad1->getGeometry().toFilledQPainterPathPx());
const QPainterPath pad1ClearancePx =
pad1Transform.mapPx(pad1->getGeometry()
.withOffset(pad1Clearance)
.toFilledQPainterPathPx());
// Compare with all pads *after* pad1 to avoid duplicate messages!
// So, don't initialize the iterator with begin() but with pad1 + 1.
auto itPad2 = itPad1;
for (++itPad2; itPad2 != (*itFtp).getPads().end(); ++itPad2) {
std::shared_ptr<const FootprintPad> pad2 = itPad2.ptr();
std::shared_ptr<const PackagePad> pkgPad2 = pad2->getPackagePadUuid()
? mPackage.getPads().find(*pad2->getPackagePadUuid())
: nullptr;
const Transform pad2Transform(pad2->getPosition(), pad2->getRotation());
const Length pad2Clearance =
std::max(clearance, *pad2->getCopperClearance()) - tolerance;
const QPainterPath pad2CopperPx =
pad2Transform.mapPx(pad2->getGeometry().toFilledQPainterPathPx());
const QPainterPath pad2ClearancePx =
pad2Transform.mapPx(pad2->getGeometry()
.withOffset(pad2Clearance)
.toFilledQPainterPathPx());
// Only warn if both pads have copper on the same board side.
if ((pad1->getComponentSide() == pad2->getComponentSide()) ||
(pad1->isTht()) || (pad2->isTht())) {
// Only warn if both pads have different net signal, or one of them
// is unconnected (an unconnected pad is considered as a different
// net signal).
if ((pad1->getPackagePadUuid() != pad2->getPackagePadUuid()) ||
(!pad1->getPackagePadUuid()) || (!pad2->getPackagePadUuid())) {
// Now check if the clearance is really too small.
if (pad1CopperPx.intersects(pad2CopperPx)) {
msgs.append(std::make_shared<MsgOverlappingPads>(
footprint, pad1, pkgPad1 ? *pkgPad1->getName() : QString(),
pad2, pkgPad2 ? *pkgPad2->getName() : QString()));
} else if (pad1ClearancePx.intersects(pad2CopperPx) ||
pad1CopperPx.intersects(pad2ClearancePx)) {
msgs.append(std::make_shared<MsgPadClearanceViolation>(
footprint, pad1, pkgPad1 ? *pkgPad1->getName() : QString(),
pad2, pkgPad2 ? *pkgPad2->getName() : QString(), clearance));
}
}
}
}
}
}
}
void PackageCheck::checkPadsClearanceToLegend(MsgList& msgs) const {
for (auto itFtp = mPackage.getFootprints().begin();
itFtp != mPackage.getFootprints().end(); ++itFtp) {
std::shared_ptr<const Footprint> footprint = itFtp.ptr();
QPainterPath topLegend;
QPainterPath botLegend;
for (const Polygon& polygon : footprint->getPolygons()) {
QPen pen(Qt::NoPen);
if (polygon.getLineWidth() > 0) {
pen.setStyle(Qt::SolidLine);
pen.setWidthF(polygon.getLineWidth()->toPx());
}
QBrush brush(Qt::NoBrush);
if (polygon.isFilled() && polygon.getPath().isClosed()) {
brush.setStyle(Qt::SolidPattern);
}
QPainterPath area = Toolbox::shapeFromPath(
polygon.getPath().toQPainterPathPx(), pen, brush);
if (polygon.getLayer() == Layer::topLegend()) {
topLegend.addPath(area);
} else if (polygon.getLayer() == Layer::botLegend()) {
botLegend.addPath(area);
}
}
for (auto it = (*itFtp).getPads().begin(); it != (*itFtp).getPads().end();
++it) {
std::shared_ptr<const FootprintPad> pad = it.ptr();
std::shared_ptr<const PackagePad> pkgPad = pad->getPackagePadUuid()
? mPackage.getPads().find(*pad->getPackagePadUuid())
: nullptr;
Length clearance(150000); // 150 µm
Length tolerance(10); // 0.01 µm, to avoid rounding issues
const Transform transform(pad->getPosition(), pad->getRotation());
const QPainterPath stopMask =
transform.mapPx(pad->getGeometry()
.withOffset(clearance - tolerance)
.toFilledQPainterPathPx());
if (pad->isOnLayer(Layer::topCopper()) &&
stopMask.intersects(topLegend)) {
msgs.append(std::make_shared<MsgPadOverlapsWithLegend>(
footprint, pad, pkgPad ? *pkgPad->getName() : QString(),
clearance));
} else if (pad->isOnLayer(Layer::botCopper()) &&
stopMask.intersects(botLegend)) {
msgs.append(std::make_shared<MsgPadOverlapsWithLegend>(
footprint, pad, pkgPad ? *pkgPad->getName() : QString(),
clearance));
}
}
}
}
void PackageCheck::checkPadsAnnularRing(MsgList& msgs) const {
const Length annularRing(150000); // 150 µm
const Length tolerance(10); // 0.01 µm, to avoid rounding issues
// Check all footprints.
for (auto itFtp = mPackage.getFootprints().begin();
itFtp != mPackage.getFootprints().end(); ++itFtp) {
std::shared_ptr<const Footprint> footprint = itFtp.ptr();
// Check all pads.
for (auto itPad = (*itFtp).getPads().begin();
itPad != (*itFtp).getPads().end(); ++itPad) {
std::shared_ptr<const FootprintPad> pad = itPad.ptr();
std::shared_ptr<const PackagePad> pkgPad = pad->getPackagePadUuid()
? mPackage.getPads().find(*pad->getPackagePadUuid())
: nullptr;
const QPainterPath padPathPx =
pad->getGeometry().toFilledQPainterPathPx();
// Check all holes.
bool emitError = false;
bool emitWarning = false;
for (auto itHole1 = (*itPad).getHoles().begin();
itHole1 != (*itPad).getHoles().end(); ++itHole1) {
std::shared_ptr<const PadHole> hole1 = itHole1.ptr();
const QVector<Path> hole1Paths =
hole1->getPath()->toOutlineStrokes(hole1->getDiameter());
const QVector<Path> hole1PathsWithAnnular =
hole1->getPath()->toOutlineStrokes(
hole1->getDiameter() +
PositiveLength((annularRing * 2) - tolerance));
const QPainterPath hole1PathPx =
Path::toQPainterPathPx(hole1Paths, true);
const QPainterPath hole1PathPxWithAnnular =
Path::toQPainterPathPx(hole1PathsWithAnnular, true);
// Check annular rings.
if (!padPathPx.contains(hole1PathPx)) {
emitError = true;
} else if (!padPathPx.contains(hole1PathPxWithAnnular)) {
emitWarning = true;
} else {
// Compare with all holes *after* hole1 to avoid redundant checks.
// So, don't initialize the iterator with begin() but with hole1 + 1.
auto itHole2 = itHole1;
for (++itHole2; itHole2 != (*itPad).getHoles().end(); ++itHole2) {
std::shared_ptr<const PadHole> hole2 = itHole2.ptr();
const QVector<Path> hole2Paths =
hole2->getPath()->toOutlineStrokes(hole2->getDiameter());
const QPainterPath hole2PathPx =
Path::toQPainterPathPx(hole2Paths, true);
// Now check if the annular ring is really too small.
if (hole1PathPx.intersects(hole2PathPx)) {
emitError = true;
} else if (hole1PathPxWithAnnular.intersects(hole2PathPx)) {
emitWarning = true;
}
}
}
}
// Only show one message even if there are multiple violations.
if (emitError) {
msgs.append(std::make_shared<MsgPadHoleOutsideCopper>(
footprint, pad, pkgPad ? *pkgPad->getName() : QString()));
} else if (emitWarning) {
msgs.append(std::make_shared<MsgPadAnnularRingViolation>(
footprint, pad, pkgPad ? *pkgPad->getName() : QString(),
annularRing));
}
}
}
}
void PackageCheck::checkPadsConnectionPoint(MsgList& msgs) const {
for (auto itFtp = mPackage.getFootprints().begin();
itFtp != mPackage.getFootprints().end(); ++itFtp) {
std::shared_ptr<const Footprint> footprint = itFtp.ptr();
for (auto itPad = (*itFtp).getPads().begin();
itPad != (*itFtp).getPads().end(); ++itPad) {
std::shared_ptr<const FootprintPad> pad = itPad.ptr();
std::shared_ptr<const PackagePad> pkgPad = pad->getPackagePadUuid()
? mPackage.getPads().find(*pad->getPackagePadUuid())
: nullptr;
const QPainterPath allowedArea = pad->isTht()
? pad->getGeometry().toHolesQPainterPathPx()
: pad->getGeometry().toFilledQPainterPathPx();
if (!allowedArea.contains(QPointF(0, 0))) {
msgs.append(std::make_shared<MsgPadOriginOutsideCopper>(
footprint, pad, pkgPad ? *pkgPad->getName() : QString()));
}
}
}
}
void PackageCheck::checkCustomPadOutline(MsgList& msgs) const {
for (auto itFtp = mPackage.getFootprints().begin();
itFtp != mPackage.getFootprints().end(); ++itFtp) {
std::shared_ptr<const Footprint> footprint = itFtp.ptr();
for (auto itPad = (*itFtp).getPads().begin();
itPad != (*itFtp).getPads().end(); ++itPad) {
std::shared_ptr<const FootprintPad> pad = itPad.ptr();
std::shared_ptr<const PackagePad> pkgPad = pad->getPackagePadUuid()
? mPackage.getPads().find(*pad->getPackagePadUuid())
: nullptr;
if ((pad->getShape() == Pad::Shape::Custom) &&
(!PadGeometry::isValidCustomOutline(pad->getCustomShapeOutline()))) {
msgs.append(std::make_shared<MsgInvalidCustomPadOutline>(
footprint, pad, pkgPad ? *pkgPad->getName() : QString()));
} else if ((pad->getShape() != Pad::Shape::Custom) &&
(!pad->getCustomShapeOutline().getVertices().isEmpty())) {
msgs.append(std::make_shared<MsgUnusedCustomPadOutline>(
footprint, pad, pkgPad ? *pkgPad->getName() : QString()));
}
}
}
}
void PackageCheck::checkStopMaskOnPads(MsgList& msgs) const {
for (auto itFtp = mPackage.getFootprints().begin();
itFtp != mPackage.getFootprints().end(); ++itFtp) {
std::shared_ptr<const Footprint> footprint = itFtp.ptr();
for (auto itPad = (*itFtp).getPads().begin();
itPad != (*itFtp).getPads().end(); ++itPad) {
std::shared_ptr<const FootprintPad> pad = itPad.ptr();
std::shared_ptr<const PackagePad> pkgPad = pad->getPackagePadUuid()
? mPackage.getPads().find(*pad->getPackagePadUuid())
: nullptr;
if (!pad->getStopMaskConfig().isEnabled()) {
msgs.append(std::make_shared<MsgPadStopMaskOff>(
footprint, pad, pkgPad ? *pkgPad->getName() : QString()));
} else if (pad->getFunctionIsFiducial() &&
(!pad->getStopMaskConfig().getOffset())) {
msgs.append(
std::make_shared<MsgFiducialStopMaskNotSet>(footprint, pad));
}
}
}
}
void PackageCheck::checkSolderPasteOnPads(MsgList& msgs) const {
for (auto itFtp = mPackage.getFootprints().begin();
itFtp != mPackage.getFootprints().end(); ++itFtp) {
std::shared_ptr<const Footprint> footprint = itFtp.ptr();
for (auto itPad = (*itFtp).getPads().begin();
itPad != (*itFtp).getPads().end(); ++itPad) {
std::shared_ptr<const FootprintPad> pad = itPad.ptr();
std::shared_ptr<const PackagePad> pkgPad = pad->getPackagePadUuid()
? mPackage.getPads().find(*pad->getPackagePadUuid())
: nullptr;
if ((!pad->isTht()) && pad->getFunctionNeedsSoldering() &&
(!pad->getSolderPasteConfig().isEnabled())) {
msgs.append(std::make_shared<MsgSmtPadWithoutSolderPaste>(
footprint, pad, pkgPad ? *pkgPad->getName() : QString()));
} else if ((!pad->isTht()) && (!pad->getFunctionNeedsSoldering()) &&
pad->getSolderPasteConfig().isEnabled()) {
msgs.append(std::make_shared<MsgSmtPadWithSolderPaste>(
footprint, pad, pkgPad ? *pkgPad->getName() : QString()));
} else if (pad->isTht() && pad->getSolderPasteConfig().isEnabled()) {
msgs.append(std::make_shared<MsgThtPadWithSolderPaste>(
footprint, pad, pkgPad ? *pkgPad->getName() : QString()));
}
}
}
}
void PackageCheck::checkCopperClearanceOnPads(MsgList& msgs) const {
for (auto itFtp = mPackage.getFootprints().begin();
itFtp != mPackage.getFootprints().end(); ++itFtp) {
std::shared_ptr<const Footprint> footprint = itFtp.ptr();
for (auto itPad = (*itFtp).getPads().begin();
itPad != (*itFtp).getPads().end(); ++itPad) {
std::shared_ptr<const FootprintPad> pad = itPad.ptr();
std::shared_ptr<const PackagePad> pkgPad = pad->getPackagePadUuid()
? mPackage.getPads().find(*pad->getPackagePadUuid())
: nullptr;
const auto stopMaskOffset = pad->getStopMaskConfig().getOffset();
if ((!pad->getFunctionIsFiducial()) && (pad->getCopperClearance() > 0)) {
msgs.append(std::make_shared<MsgPadWithCopperClearance>(
footprint, pad, pkgPad ? *pkgPad->getName() : QString()));
} else if (pad->getFunctionIsFiducial() && stopMaskOffset &&
(pad->getCopperClearance() < (*stopMaskOffset))) {
msgs.append(std::make_shared<MsgFiducialClearanceLessThanStopMask>(
footprint, pad));
}
}
}
}
void PackageCheck::checkPadFunctions(MsgList& msgs) const {
const QSet<Pad::Function> thtFuncs = {
Pad::Function::StandardPad,
Pad::Function::PressFitPad,
};
const QSet<Pad::Function> smtFuncs = {
Pad::Function::StandardPad, Pad::Function::ThermalPad,
Pad::Function::BgaPad, Pad::Function::EdgeConnectorPad,
Pad::Function::TestPad, Pad::Function::LocalFiducial,
Pad::Function::GlobalFiducial,
};
for (auto itFtp = mPackage.getFootprints().begin();
itFtp != mPackage.getFootprints().end(); ++itFtp) {
std::shared_ptr<const Footprint> footprint = itFtp.ptr();
for (auto itPad = (*itFtp).getPads().begin();
itPad != (*itFtp).getPads().end(); ++itPad) {
std::shared_ptr<const FootprintPad> pad = itPad.ptr();
std::shared_ptr<const PackagePad> pkgPad = pad->getPackagePadUuid()
? mPackage.getPads().find(*pad->getPackagePadUuid())
: nullptr;
const bool isTht = pad->isTht();
const bool isConnected = pad->getPackagePadUuid().has_value();
if (pad->getFunction() == Pad::Function::Unspecified) {
msgs.append(std::make_shared<MsgUnspecifiedPadFunction>(
footprint, pad, pkgPad ? *pkgPad->getName() : QString()));
} else if ((isTht && (!thtFuncs.contains(pad->getFunction()))) ||
((!isTht) && (!smtFuncs.contains(pad->getFunction()))) ||
(isConnected == pad->getFunctionIsFiducial())) {
msgs.append(std::make_shared<MsgSuspiciousPadFunction>(
footprint, pad, pkgPad ? *pkgPad->getName() : QString()));
}
}
}
}
void PackageCheck::checkHolesStopMask(MsgList& msgs) const {
for (auto itFtp = mPackage.getFootprints().begin();
itFtp != mPackage.getFootprints().end(); ++itFtp) {
std::shared_ptr<const Footprint> footprint = itFtp.ptr();
for (auto itHole = (*itFtp).getHoles().begin();
itHole != (*itFtp).getHoles().end(); ++itHole) {
std::shared_ptr<const Hole> hole = itHole.ptr();
if (!hole->getStopMaskConfig().isEnabled()) {
msgs.append(std::make_shared<MsgHoleWithoutStopMask>(footprint, hole));
}
}
}
}
void PackageCheck::checkLineWidths(MsgList& msgs) const {
const QList<const Layer*> silkscreenLayers = {
&Layer::topLegend(), &Layer::botLegend(), &Layer::topNames(),
&Layer::botNames(), &Layer::topValues(), &Layer::botValues(),
};
const Length minSilkscreenWidth(150000); // See BoardDesignRuleCheckSettings.
for (auto itFtp = mPackage.getFootprints().begin();
itFtp != mPackage.getFootprints().end(); ++itFtp) {
std::shared_ptr<const Footprint> footprint = itFtp.ptr();
for (auto itPolygon = (*itFtp).getPolygons().begin();
itPolygon != (*itFtp).getPolygons().end(); ++itPolygon) {
std::shared_ptr<const Polygon> polygon = itPolygon.ptr();
if (silkscreenLayers.contains(&polygon->getLayer()) &&
(polygon->getLineWidth() < minSilkscreenWidth) &&
(!polygon->isFilled())) {
msgs.append(std::make_shared<MsgMinimumWidthViolation>(
footprint, polygon, minSilkscreenWidth));
}
}
for (auto itCircle = (*itFtp).getCircles().begin();
itCircle != (*itFtp).getCircles().end(); ++itCircle) {
std::shared_ptr<const Circle> circle = itCircle.ptr();
if (silkscreenLayers.contains(&circle->getLayer()) &&
(circle->getLineWidth() < minSilkscreenWidth) &&
(!circle->isFilled())) {
msgs.append(std::make_shared<MsgMinimumWidthViolation>(
footprint, circle, minSilkscreenWidth));
}
}
for (auto itText = (*itFtp).getStrokeTexts().begin();
itText != (*itFtp).getStrokeTexts().end(); ++itText) {
std::shared_ptr<const StrokeText> text = itText.ptr();
if (silkscreenLayers.contains(&text->getLayer()) &&
(text->getStrokeWidth() < minSilkscreenWidth)) {
msgs.append(std::make_shared<MsgMinimumWidthViolation>(
footprint, text, minSilkscreenWidth));
}
}
}
}
void PackageCheck::checkZones(MsgList& msgs) const {
for (auto itFtp = mPackage.getFootprints().begin();
itFtp != mPackage.getFootprints().end(); ++itFtp) {
std::shared_ptr<const Footprint> footprint = itFtp.ptr();
for (auto itZone = (*itFtp).getZones().begin();
itZone != (*itFtp).getZones().end(); ++itZone) {
std::shared_ptr<const Zone> zone = itZone.ptr();
if ((!zone->getLayers()) || (!zone->getRules()) ||
((zone->getLayers() == Zone::Layers(Zone::Layer::Inner)) &&
(!zone->getRules().testFlag(Zone::Rule::NoCopper)) &&
(!zone->getRules().testFlag(Zone::Rule::NoPlanes)))) {
msgs.append(std::make_shared<MsgUselessZone>(footprint, zone));
}
}
}
}
void PackageCheck::checkFootprintModels(MsgList& msgs) const {
if (mPackage.getAssemblyType(true) != Package::AssemblyType::None) {
const QSet<Uuid> packageModels = mPackage.getModels().getUuidSet();
for (auto itFtp = mPackage.getFootprints().begin();
itFtp != mPackage.getFootprints().end(); ++itFtp) {
std::shared_ptr<const Footprint> footprint = itFtp.ptr();
const QSet<Uuid> uuids = footprint->getModels() & packageModels;
if (uuids.isEmpty()) {
msgs.append(std::make_shared<MsgMissingFootprintModel>(footprint));
}
}
}
}
/*******************************************************************************
* End of File
******************************************************************************/
} // namespace librepcb