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 "boardplanefragmentsbuilder.h"
#include "../../graphics/graphicslayer.h"
#include "../../library/pkg/footprint.h"
#include "../../library/pkg/footprintpad.h"
#include "../../utils/clipperhelpers.h"
#include "../../utils/transform.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_netpoint.h"
#include "items/bi_netsegment.h"
#include "items/bi_plane.h"
#include "items/bi_polygon.h"
#include "items/bi_via.h"
#include <QtCore>
/*******************************************************************************
* Namespace
******************************************************************************/
namespace librepcb {
/*******************************************************************************
* Constructors / Destructor
******************************************************************************/
BoardPlaneFragmentsBuilder::BoardPlaneFragmentsBuilder(BI_Plane& plane) noexcept
: mPlane(plane) {
}
BoardPlaneFragmentsBuilder::~BoardPlaneFragmentsBuilder() noexcept {
}
/*******************************************************************************
* General Methods
******************************************************************************/
QVector<Path> BoardPlaneFragmentsBuilder::buildFragments() noexcept {
try {
mResult.clear();
addPlaneOutline();
clipToBoardOutline();
subtractOtherObjects();
ensureMinimumWidth();
flattenResult();
if (!mPlane.getKeepOrphans()) {
removeOrphans();
}
return ClipperHelpers::convert(mResult);
} catch (const Exception& e) {
qCritical() << "Failed to build plane fragments, leaving plane empty:"
<< e.getMsg();
return QVector<Path>();
}
}
/*******************************************************************************
* Private Methods
******************************************************************************/
void BoardPlaneFragmentsBuilder::addPlaneOutline() {
mResult.push_back(ClipperHelpers::convert(mPlane.getOutline().toClosedPath(),
maxArcTolerance()));
}
void BoardPlaneFragmentsBuilder::clipToBoardOutline() {
// determine board area
ClipperLib::Paths boardArea;
ClipperLib::Clipper boardAreaClipper;
foreach (const BI_Polygon* polygon, mPlane.getBoard().getPolygons()) {
if (polygon->getPolygon().getLayerName() == GraphicsLayer::sBoardOutlines) {
ClipperLib::Path path = ClipperHelpers::convert(
polygon->getPolygon().getPath(), maxArcTolerance());
boardAreaClipper.AddPath(path, ClipperLib::ptSubject, true);
}
}
foreach (const BI_Device* device, mPlane.getBoard().getDeviceInstances()) {
Transform transform(*device);
for (const Polygon& polygon : device->getLibFootprint().getPolygons()) {
if (polygon.getLayerName() == GraphicsLayer::sBoardOutlines) {
Path path = transform.map(polygon.getPath());
ClipperLib::Path clipperPath =
ClipperHelpers::convert(path, maxArcTolerance());
boardAreaClipper.AddPath(clipperPath, ClipperLib::ptSubject, true);
}
}
}
boardAreaClipper.Execute(ClipperLib::ctXor, boardArea, ClipperLib::pftEvenOdd,
ClipperLib::pftEvenOdd);
// perform clearance offset
ClipperHelpers::offset(boardArea, -mPlane.getMinClearance(),
maxArcTolerance()); // can throw
// if we have no board area, abort here
if (boardArea.empty()) return;
// clip result to board area
ClipperLib::Clipper clip;
clip.AddPaths(mResult, ClipperLib::ptSubject, true);
clip.AddPaths(boardArea, ClipperLib::ptClip, true);
clip.Execute(ClipperLib::ctIntersection, mResult, ClipperLib::pftNonZero,
ClipperLib::pftNonZero);
}
void BoardPlaneFragmentsBuilder::subtractOtherObjects() {
ClipperLib::Clipper c;
c.AddPaths(mResult, ClipperLib::ptSubject, true);
// subtract other planes
foreach (const BI_Plane* plane, mPlane.getBoard().getPlanes()) {
if (plane == &mPlane) continue;
if (*plane < mPlane) continue; // ignore planes with lower priority
if (plane->getLayerName() != mPlane.getLayerName()) continue;
if (&plane->getNetSignal() == &mPlane.getNetSignal()) continue;
ClipperLib::Paths paths =
ClipperHelpers::convert(plane->getFragments(), maxArcTolerance());
ClipperHelpers::offset(paths, *mPlane.getMinClearance(),
maxArcTolerance()); // can throw
c.AddPaths(paths, ClipperLib::ptClip, true);
}
// subtract holes and pads from devices
foreach (const BI_Device* device, mPlane.getBoard().getDeviceInstances()) {
Transform transform(*device);
for (const Hole& hole :
device->getFootprint().getLibFootprint().getHoles()) {
Point pos = transform.map(hole.getPosition());
PositiveLength dia(hole.getDiameter() + mPlane.getMinClearance() * 2);
Path path = Path::circle(dia).translated(pos);
c.AddPath(ClipperHelpers::convert(path, maxArcTolerance()),
ClipperLib::ptClip, true);
}
foreach (const BI_FootprintPad* pad, device->getFootprint().getPads()) {
if (!pad->isOnLayer(*mPlane.getLayerName())) continue;
if (pad->getCompSigInstNetSignal() == &mPlane.getNetSignal()) {
ClipperLib::Path path =
ClipperHelpers::convert(pad->getSceneOutline(), maxArcTolerance());
mConnectedNetSignalAreas.push_back(path);
}
c.AddPath(createPadCutOut(*pad), ClipperLib::ptClip, true);
}
}
// subtract board holes
for (const BI_Hole* hole : mPlane.getBoard().getHoles()) {
PositiveLength dia(hole->getHole().getDiameter() +
mPlane.getMinClearance() * 2);
Path path = Path::circle(dia).translated(hole->getHole().getPosition());
c.AddPath(ClipperHelpers::convert(path, maxArcTolerance()),
ClipperLib::ptClip, true);
}
// subtract net segment items
foreach (const BI_NetSegment* netsegment,
mPlane.getBoard().getNetSegments()) {
// subtract vias
foreach (const BI_Via* via, netsegment->getVias()) {
if (&netsegment->getNetSignal() == &mPlane.getNetSignal()) {
ClipperLib::Path path = ClipperHelpers::convert(
via->getVia().getSceneOutline(), maxArcTolerance());
mConnectedNetSignalAreas.push_back(path);
}
c.AddPath(createViaCutOut(*via), ClipperLib::ptClip, true);
}
// subtract netlines
foreach (const BI_NetLine* netline, netsegment->getNetLines()) {
if (netline->getLayer().getName() != mPlane.getLayerName()) continue;
if (&netsegment->getNetSignal() == &mPlane.getNetSignal()) {
ClipperLib::Path path = ClipperHelpers::convert(
netline->getSceneOutline(), maxArcTolerance());
mConnectedNetSignalAreas.push_back(path);
} else {
ClipperLib::Path path = ClipperHelpers::convert(
netline->getSceneOutline(*mPlane.getMinClearance()),
maxArcTolerance());
c.AddPath(path, ClipperLib::ptClip, true);
}
}
}
c.Execute(ClipperLib::ctDifference, mResult, ClipperLib::pftEvenOdd,
ClipperLib::pftNonZero);
}
void BoardPlaneFragmentsBuilder::ensureMinimumWidth() {
Length delta = mPlane.getMinWidth() / 2;
ClipperHelpers::offset(mResult, -delta, maxArcTolerance()); // can throw
ClipperHelpers::offset(mResult, delta, maxArcTolerance()); // can throw
}
void BoardPlaneFragmentsBuilder::flattenResult() {
// convert paths to tree
ClipperLib::PolyTree tree;
ClipperLib::Clipper c;
c.AddPaths(mResult, ClipperLib::ptSubject, true);
c.Execute(ClipperLib::ctXor, tree, ClipperLib::pftEvenOdd,
ClipperLib::pftEvenOdd);
// convert tree to simple paths with cut-ins
mResult = ClipperHelpers::flattenTree(tree); // can throw
}
void BoardPlaneFragmentsBuilder::removeOrphans() {
mResult.erase(std::remove_if(
mResult.begin(), mResult.end(),
[this](const ClipperLib::Path& p) {
ClipperLib::Paths intersections;
ClipperLib::Clipper c;
c.AddPaths(mConnectedNetSignalAreas,
ClipperLib::ptSubject, true);
c.AddPath(p, ClipperLib::ptClip, true);
c.Execute(ClipperLib::ctIntersection, intersections,
ClipperLib::pftNonZero, ClipperLib::pftNonZero);
return intersections.empty();
}),
mResult.end());
}
/*******************************************************************************
* Helper Methods
******************************************************************************/
ClipperLib::Path BoardPlaneFragmentsBuilder::createPadCutOut(
const BI_FootprintPad& pad) const noexcept {
bool differentNetSignal =
(pad.getCompSigInstNetSignal() != &mPlane.getNetSignal());
if ((mPlane.getConnectStyle() == BI_Plane::ConnectStyle::None) ||
differentNetSignal) {
return ClipperHelpers::convert(
pad.getSceneOutline(*mPlane.getMinClearance()), maxArcTolerance());
} else {
return ClipperLib::Path();
}
}
ClipperLib::Path BoardPlaneFragmentsBuilder::createViaCutOut(
const BI_Via& via) const noexcept {
bool differentNetSignal =
(&via.getNetSignalOfNetSegment() != &mPlane.getNetSignal());
if ((mPlane.getConnectStyle() == BI_Plane::ConnectStyle::None) ||
differentNetSignal) {
return ClipperHelpers::convert(
via.getVia().getSceneOutline(*mPlane.getMinClearance()),
maxArcTolerance());
} else {
return ClipperLib::Path();
}
}
/*******************************************************************************
* End of File
******************************************************************************/
} // namespace librepcb