Loading files
This 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 "boardeditorstate.h"
#include "../../../graphics/graphicslayer.h"
#include "../../../graphics/graphicslayerlist.h"
#include "../../../undostack.h"
#include "../../../widgets/graphicsview.h"
#include "../boardgraphicsscene.h"
#include "../graphicsitems/bgi_device.h"
#include "../graphicsitems/bgi_hole.h"
#include "../graphicsitems/bgi_netline.h"
#include "../graphicsitems/bgi_netpoint.h"
#include "../graphicsitems/bgi_pad.h"
#include "../graphicsitems/bgi_plane.h"
#include "../graphicsitems/bgi_polygon.h"
#include "../graphicsitems/bgi_stroketext.h"
#include "../graphicsitems/bgi_via.h"
#include "../graphicsitems/bgi_zone.h"
#include <librepcb/core/geometry/polygon.h>
#include <librepcb/core/project/board/board.h>
#include <librepcb/core/project/board/items/bi_device.h>
#include <librepcb/core/project/board/items/bi_hole.h>
#include <librepcb/core/project/board/items/bi_netline.h>
#include <librepcb/core/project/board/items/bi_netpoint.h>
#include <librepcb/core/project/board/items/bi_netsegment.h>
#include <librepcb/core/project/board/items/bi_pad.h>
#include <librepcb/core/project/board/items/bi_plane.h>
#include <librepcb/core/project/board/items/bi_polygon.h>
#include <librepcb/core/project/board/items/bi_stroketext.h>
#include <librepcb/core/project/board/items/bi_via.h>
#include <librepcb/core/project/board/items/bi_zone.h>
#include <librepcb/core/utils/toolbox.h>
#include <librepcb/core/workspace/workspace.h>
#include <librepcb/core/workspace/workspacesettings.h>
#include <QtCore>
/*******************************************************************************
* Namespace
******************************************************************************/
namespace librepcb {
namespace editor {
/*******************************************************************************
* Constructors / Destructor
******************************************************************************/
BoardEditorState::BoardEditorState(const Context& context,
QObject* parent) noexcept
: QObject(parent), mContext(context), mAdapter(context.adapter) {
}
BoardEditorState::~BoardEditorState() noexcept {
}
/*******************************************************************************
* Protected Methods
******************************************************************************/
BoardGraphicsScene* BoardEditorState::getActiveBoardScene() noexcept {
return mAdapter.fsmGetGraphicsScene();
}
bool BoardEditorState::getIgnoreLocks() const noexcept {
return mAdapter.fsmGetIgnoreLocks();
}
PositiveLength BoardEditorState::getGridInterval() const noexcept {
return mContext.board.getGridInterval();
}
const LengthUnit& BoardEditorState::getLengthUnit() const noexcept {
return mContext.board.getGridUnit();
}
QSet<const Layer*> BoardEditorState::getAllowedGeometryLayers() noexcept {
static const QSet<const Layer*> commonLayers = {
&Layer::boardSheetFrames(),
&Layer::boardOutlines(),
&Layer::boardCutouts(),
&Layer::boardPlatedCutouts(),
&Layer::boardMeasures(),
&Layer::boardAlignment(),
&Layer::boardDocumentation(),
&Layer::boardComments(),
&Layer::boardGuide(),
&Layer::topNames(),
&Layer::topValues(),
&Layer::topLegend(),
&Layer::topDocumentation(),
// &Layer::topPackageOutlines(), -> makes no sense in boards
// &Layer::topCourtyard(), -> makes no sense in boards
// &Layer::topHiddenGrabAreas(), -> makes no sense in boards
&Layer::topCopper(),
&Layer::topGlue(),
&Layer::topSolderPaste(),
&Layer::topStopMask(),
&Layer::botNames(),
&Layer::botValues(),
&Layer::botLegend(),
&Layer::botDocumentation(),
// &Layer::botPackageOutlines(), -> makes no sense in boards
// &Layer::botCourtyard(), -> makes no sense in boards
// &Layer::botHiddenGrabAreas(), -> makes no sense in boards
&Layer::botCopper(),
&Layer::botGlue(),
&Layer::botSolderPaste(),
&Layer::botStopMask(),
};
return commonLayers | mContext.board.getCopperLayers();
}
void BoardEditorState::makeLayerVisible(const ColorRole& role) noexcept {
if (std::shared_ptr<GraphicsLayer> l = mContext.layers.get(role)) {
if (l->isEnabled()) {
l->setVisible(true);
}
}
}
void BoardEditorState::abortBlockingToolsInOtherEditors() noexcept {
mAdapter.fsmAbortBlockingToolsInOtherEditors();
}
bool BoardEditorState::execCmd(UndoCommand* cmd) {
return mContext.undoStack.execCmd(cmd);
}
QWidget* BoardEditorState::parentWidget() noexcept {
return mAdapter.fsmGetParentWidget();
}
QList<std::shared_ptr<QGraphicsItem>> BoardEditorState::findItemsAtPos(
const Point& pos, FindFlags flags, const Layer* cuLayer,
const QSet<const NetSignal*>& netsignals,
const QVector<std::shared_ptr<QGraphicsItem>>& except) noexcept {
BoardGraphicsScene* scene = getActiveBoardScene();
if (!scene) {
return QList<std::shared_ptr<QGraphicsItem>>();
}
const QPointF posExact = pos.toPxQPointF();
const QPointF posOnGrid = pos.mappedToGrid(getGridInterval()).toPxQPointF();
const QPainterPath posArea = mAdapter.fsmCalcPosWithTolerance(pos, 1);
const QPainterPath posAreaLarge = mAdapter.fsmCalcPosWithTolerance(pos, 1.5);
// Note: The order of adding the items is very important (the top most item
// must appear as the first item in the list)! For that, we work with
// priorities (0 = highest priority):
//
// 0: vias
// 1: pads THT
// 5: holes
// 50: polygons/texts board layer
// 110: netpoints top
// 120: netlines top
// 130: planes/zones top
// 140: footprints top
// 150: pads top
// 160: polygons/texts top
// 210: netpoints inner
// 220: netlines inner
// 230: planes/zones inner
// 240: polygons/texts inner
// 310: netpoints bottom
// 320: netlines bottom
// 330: planes/zones bottom
// 340: footprints bottom
// 350: pads bottom
// 360: polygons/texts bottom
//
// So the system is:
// 0 for vias
// 5 for holes
// 10 for netpoints
// 20 for netlines
// 30 for planes/zones
// 40 for footprints
// 50 for pads
// 60 for polygons/texts
// +100 for top layer items
// +200 for inner layer items
// +300 for bottom layer items
//
// And for items not directly under the cursor, but very close to the cursor,
// add +1000. For items not under the cursor, but on the next grid interval,
// add +2000.
QMultiMap<std::pair<int, int>, std::shared_ptr<QGraphicsItem>> items;
std::optional<std::pair<int, int>> lowestPriority;
auto addItem = [&items, &lowestPriority](
const std::pair<int, int>& prio,
std::shared_ptr<QGraphicsItem> item) {
if ((!lowestPriority) || (prio < (*lowestPriority))) {
lowestPriority = prio;
}
items.insert(prio, item);
};
auto canSkip = [&lowestPriority, flags](const std::pair<int, int>& prio) {
return flags.testFlag(FindFlag::SkipLowerPriorityMatches) &&
lowestPriority && (prio > (*lowestPriority));
};
auto priorityFromLayer = [scene](const Layer& layer) {
if (layer.isTop()) {
return scene->isFlipped() ? 300 : 100;
} else if (layer.isInner()) {
return 200;
} else if (layer.isBottom()) {
return scene->isFlipped() ? 100 : 300;
} else {
return 0;
}
};
auto processItem = [&pos, &posExact, &posOnGrid, &posArea, &posAreaLarge,
flags, &except, &addItem, &canSkip](
std::shared_ptr<QGraphicsItem> itemToCheck,
std::shared_ptr<QGraphicsItem> itemToAdd,
const Point& nearestPos, int priority, bool large) {
if (except.contains(itemToAdd)) {
return;
}
auto prio = std::make_pair(priority, 0);
if (canSkip(prio)) {
return;
}
const QPainterPath grabArea = itemToCheck->mapToScene(itemToCheck->shape());
if (grabArea.isEmpty()) {
return;
}
const int distance = qRound((nearestPos - pos).getLength()->toPx());
prio = std::make_pair(priority, distance);
if (canSkip(prio)) {
return;
}
if (grabArea.contains(posExact)) {
addItem(prio, itemToAdd);
return;
}
prio = std::make_pair(priority + 1000, distance);
if (canSkip(prio)) {
return;
}
if ((flags & (FindFlag::AcceptNearMatch | FindFlag::AcceptNextGridMatch)) &&
grabArea.intersects(large ? posAreaLarge : posArea)) {
addItem(prio, itemToAdd);
return;
}
prio = std::make_pair(distance + 2000, priority); // Swapped order!
if (canSkip(prio)) {
return;
}
if ((flags & FindFlag::AcceptNextGridMatch) && (posOnGrid != posExact) &&
grabArea.contains(posOnGrid)) {
addItem(prio, itemToAdd);
return;
}
};
if (flags.testFlag(FindFlag::Holes)) {
for (auto it = scene->getHoles().begin(); it != scene->getHoles().end();
it++) {
processItem(it.value(), it.value(),
it.key()->getData().getPath()->getVertices().first().getPos(),
5, false);
}
}
if (flags.testFlag(FindFlag::Vias)) {
for (auto it = scene->getVias().begin(); it != scene->getVias().end();
it++) {
if (netsignals.isEmpty() ||
netsignals.contains(it.key()->getNetSegment().getNetSignal())) {
if ((!cuLayer) || (it.key()->getVia().isOnLayer(*cuLayer))) {
processItem(it.value(), it.value(), it.key()->getPosition(), 0,
false);
}
}
}
}
if (flags.testFlag(FindFlag::NetPoints)) {
for (auto it = scene->getNetPoints().begin();
it != scene->getNetPoints().end(); it++) {
if (netsignals.isEmpty() ||
netsignals.contains(it.key()->getNetSegment().getNetSignal())) {
const Layer* layer = it.key()->getLayerOfTraces();
if ((!cuLayer) || (&*cuLayer == layer)) {
processItem(it.value(), it.value(), it.key()->getPosition(),
10 + (layer ? priorityFromLayer(*layer) : 0), false);
}
}
}
}
if (flags.testFlag(FindFlag::NetLines)) {
for (auto it = scene->getNetLines().begin();
it != scene->getNetLines().end(); it++) {
if (netsignals.isEmpty() ||
netsignals.contains(it.key()->getNetSegment().getNetSignal())) {
const Layer& layer = it.key()->getLayer();
if ((!cuLayer) || (*cuLayer == layer)) {
processItem(
it.value(), it.value(),
Toolbox::nearestPointOnLine(pos.mappedToGrid(getGridInterval()),
it.key()->getP1().getPosition(),
it.key()->getP2().getPosition()),
20 + priorityFromLayer(layer), false);
}
}
}
}
if (flags.testFlag(FindFlag::Planes)) {
for (auto it = scene->getPlanes().begin(); it != scene->getPlanes().end();
it++) {
if (netsignals.isEmpty() ||
netsignals.contains(it.key()->getNetSignal())) {
if ((!cuLayer) || (*cuLayer == it.key()->getLayer())) {
processItem(
it.value(), it.value(),
it.key()->getOutline().calcNearestPointBetweenVertices(pos),
30 + priorityFromLayer(it.key()->getLayer()),
true); // Probably large grab area makes sense?
}
}
}
}
if (flags.testFlag(FindFlag::Zones)) {
for (auto it = scene->getZones().begin(); it != scene->getZones().end();
it++) {
if ((!cuLayer) || (it.key()->getData().getLayers().contains(&*cuLayer))) {
const QVector<const Layer*> layers =
Layer::sorted(it.key()->getData().getLayers());
int priority = 30;
if (!layers.isEmpty()) {
priority += priorityFromLayer(*layers.first());
}
processItem(
it.value(), it.value(),
it.key()->getData().getOutline().calcNearestPointBetweenVertices(
pos),
priority,
true); // Probably large grab area makes sense?
}
}
}
if (flags.testFlag(FindFlag::Devices)) {
for (auto it = scene->getDevices().begin(); it != scene->getDevices().end();
it++) {
processItem(it.value(), it.value(), it.key()->getPosition(),
40 + (it.key()->getMirrored() ? 300 : 100), false);
}
}
if (flags.testFlag(FindFlag::FootprintPads) ||
flags.testFlag(FindFlag::BoardPads)) {
for (auto it = scene->getPads().begin(); it != scene->getPads().end();
it++) {
if (((it.key()->getDevice() && flags.testFlag(FindFlag::FootprintPads)) ||
(it.key()->getNetSegment() &&
flags.testFlag(FindFlag::BoardPads))) &&
(netsignals.isEmpty() ||
netsignals.contains(it.key()->getNetSignal()))) {
if ((!cuLayer) || (it.key()->isOnLayer(*cuLayer))) {
// Give THT pads high priority to fix
// https://github.com/LibrePCB/LibrePCB/issues/1073.
const int priority = it.key()->getProperties().isTht()
? 1
: (50 + (it.key()->getMirrored() ? 300 : 100));
std::shared_ptr<QGraphicsItem> itemToAdd;
if (flags.testFlag(FindFlag::DevicesOfPads) &&
it.key()->getDevice()) {
// See https://github.com/LibrePCB/LibrePCB/issues/1531.
itemToAdd = it.value()->getDeviceGraphicsItem().lock();
} else {
itemToAdd = it.value();
}
if (itemToAdd) {
processItem(it.value(), itemToAdd, it.key()->getPosition(),
priority, false);
}
}
}
}
}
if (flags.testFlag(FindFlag::Polygons)) {
for (auto it = scene->getPolygons().begin();
it != scene->getPolygons().end(); it++) {
processItem(
it.value(), it.value(),
it.key()->getData().getPath().calcNearestPointBetweenVertices(pos),
60 + priorityFromLayer(it.key()->getData().getLayer()),
true); // Probably large grab area makes sense?
}
}
if (flags.testFlag(FindFlag::StrokeTexts)) {
for (auto it = scene->getStrokeTexts().begin();
it != scene->getStrokeTexts().end(); it++) {
processItem(it.value(), it.value(), it.key()->getData().getPosition(),
60 + priorityFromLayer(it.key()->getData().getLayer()),
false);
}
}
return items.values();
}
/*******************************************************************************
* End of File
******************************************************************************/
} // namespace editor
} // namespace librepcb
c730ec352This file is pinned to the selected release. Download the original to open it in your design tool.