Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
8a92ff80bThis 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 "cmdboardspecctraimport.h"
#include "cmdboardnetsegmentadd.h"
#include "cmdboardnetsegmentaddelements.h"
#include "cmdboardnetsegmentremove.h"
#include "cmddeviceinstanceeditall.h"
#include <librepcb/core/project/board/board.h>
#include <librepcb/core/project/board/boarddesignrules.h>
#include <librepcb/core/project/board/boardnetsegmentsplitter.h>
#include <librepcb/core/project/board/items/bi_device.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_via.h>
#include <librepcb/core/project/circuit/circuit.h>
#include <librepcb/core/project/circuit/componentinstance.h>
#include <librepcb/core/project/circuit/componentsignalinstance.h>
#include <librepcb/core/project/circuit/netsignal.h>
#include <librepcb/core/project/project.h>
#include <librepcb/core/serialization/sexpression.h>
#include <librepcb/core/types/layer.h>
#include <librepcb/core/utils/messagelogger.h>
#include <librepcb/core/utils/scopeguard.h>
#include <QtCore>
/*******************************************************************************
* Namespace
******************************************************************************/
namespace librepcb {
namespace editor {
// Allow some procentual deviation due to floating point inaccuracy:
// 25nm <= 0.001% <= 1um
static bool fuzzyCompare(const Point& exact, const Point& imported) {
const Length maxDim = std::max(imported.getX(), imported.getY());
const Length epsilon = qBound(Length(25), maxDim / 100000, Length(1000));
return (exact - imported).getLength() < epsilon;
}
static bool fuzzyCompare(const Angle& a, const Angle& b) {
return (a - b).mappedTo180deg().abs() < Angle(100); // 100 micro degrees.
}
static CmdBoardSpecctraImport::Side parseSide(const SExpression& node) {
if (node.getValue() == "front") {
return CmdBoardSpecctraImport::Side::Front;
} else if (node.getValue() == "back") {
return CmdBoardSpecctraImport::Side::Back;
} else {
throw RuntimeError(__FILE__, __LINE__,
QString("Invalid board side: %1").arg(node.getValue()));
}
}
static Length parseLength(const SExpression& node, double resolution) {
bool ok;
double value = node.getValue().toDouble(&ok);
if (!ok)
throw RuntimeError(__FILE__, __LINE__,
QString("Invalid number: %1").arg(node.getValue()));
return Length::fromMm(value / resolution);
}
static Angle parseAngle(const SExpression& node) {
Angle angle = Angle::fromDeg(deserialize<double>(node));
const Angle multiple = Angle::fromDeg("1");
if (fuzzyCompare(Angle::deg0(), angle % multiple)) {
angle = angle.rounded(multiple);
}
return angle;
}
static double getResolution(const SExpression& node, QString& logStr) {
const SExpression& resolutionNode = node.getChild("resolution/@1");
double resolution = deserialize<double>(resolutionNode);
const QString unit = node.getChild("resolution/@0").getValue();
if (unit == "um") {
resolution /= 1000;
} else if (unit != "mm") {
throw RuntimeError(__FILE__, __LINE__,
QString("Unsupported unit: '%1'").arg(unit));
}
logStr = QString("1/%1 %2").arg(resolutionNode.getValue()).arg(unit);
return resolution;
}
CmdBoardSpecctraImport::CmdBoardSpecctraImport(
Board& board, const SExpression& root,
std::shared_ptr<MessageLogger> logger)
: UndoCommandGroup(tr("Import From Specctra Session")),
mProject(board.getProject()),
mCircuit(mProject.getCircuit()),
mBoard(board),
mLogger(logger) {
// Check file type.
if (root.getName() != "session") {
throw RuntimeError(
__FILE__, __LINE__,
tr("The specified file is not a Specctra session (SES)."));
}
// Check parser.
QString hostCad;
if (const auto child = root.tryGetChild("routes/parser/host_cad/@0")) {
hostCad = child->getValue();
}
QString hostVersion;
if (const auto child = root.tryGetChild("routes/parser/host_version/@0")) {
hostVersion = child->getValue();
}
if (hostCad.isEmpty()) {
mLogger->warning(
"Specctra session doesn't specify host CAD, compatibility is unknown.");
} else if ((hostCad != qApp->applicationName()) ||
(hostVersion != qApp->applicationVersion())) {
mLogger->warning(
QString(
"Specctra session originates from %1, compatibility is unknown.")
.arg(hostCad + " " + hostVersion));
}
// Parse placement.
if (const SExpression* child = root.tryGetChild("placement")) {
QString logRes;
const double resolution = getResolution(*child, logRes);
mLogger->debug("Placement resolution: " % logRes);
mComponents = QList<ComponentOut>();
for (const SExpression* cmpNode : child->getChildren("component")) {
QList<const SExpression*> childs = cmpNode->getChildren("place");
if (childs.count() != 1) {
throw RuntimeError(__FILE__, __LINE__,
"Unexpected component placement count.");
}
const SExpression* node = childs.first();
QString name = node->getChild("@0").getValue();
Point pos(parseLength(node->getChild("@1"), resolution),
parseLength(node->getChild("@2"), resolution));
Side side = parseSide(node->getChild("@3"));
Angle rot = parseAngle(node->getChild("@4"));
mComponents->append(ComponentOut{name, pos, side, rot});
}
} else {
mLogger->warning(
"Specctra session doesn't contain component placement data.");
}
const SExpression& routesNode = root.getChild("routes");
QString logRes;
const double resolution = getResolution(routesNode, logRes);
mLogger->debug("Routing resolution: " % logRes);
// Parse pad stacks.
for (const SExpression* padStackNode :
routesNode.getChild("library_out").getChildren("padstack")) {
QString name = padStackNode->getChild("@0").getValue();
if (mPadStacks.contains(name)) {
throw RuntimeError(
__FILE__, __LINE__,
QString("Pad stack '%1' defined multiple times.").arg(name));
}
QSet<Length> diameters;
QList<const Layer*> layers;
for (const SExpression* shapeNode :
padStackNode->getChildren(SExpression::Type::List)) {
for (const SExpression* childNode :
shapeNode->getChildren(SExpression::Type::List)) {
if (childNode->getName() != "circle") {
throw RuntimeError(__FILE__, __LINE__,
QString("Unsupported pad stack shape '%1'.")
.arg(shapeNode->getName()));
}
layers.append(deserialize<const Layer*>(childNode->getChild("@0")));
diameters.insert(parseLength(childNode->getChild("@1"), resolution));
}
}
if (diameters.count() != 1) {
throw RuntimeError(__FILE__, __LINE__, "Unsupported complex pad stack.");
}
if (layers.count() < 2) {
throw RuntimeError(__FILE__, __LINE__, "Too few layers in pad stack.");
}
std::sort(layers.begin(), layers.end(), [](const Layer* a, const Layer* b) {
return a->getCopperNumber() < b->getCopperNumber();
});
for (const Layer* layer : mBoard.getCopperLayers()) {
if ((layer->getCopperNumber() > layers.first()->getCopperNumber()) &&
(layer->getCopperNumber() < layers.last()->getCopperNumber()) &&
(!layers.contains(layer))) {
throw RuntimeError(__FILE__, __LINE__, "Missing layers in pad stack.");
}
}
mPadStacks[name] =
PadStackOut{layers.first(), layers.last(), *diameters.begin()};
}
// Parse networks.
for (const SExpression* netNode :
routesNode.getChild("network_out").getChildren("net")) {
NetOut net{netNode->getChild("@0").getValue(), {}, {}};
for (const SExpression* viaNode : netNode->getChildren("via")) {
QString padStackId = viaNode->getChild("@0").getValue();
if (!mPadStacks.contains(padStackId)) {
throw RuntimeError(
__FILE__, __LINE__,
QString("Pad stack '%1' not found.").arg(padStackId));
}
Point pos(parseLength(viaNode->getChild("@1"), resolution),
parseLength(viaNode->getChild("@2"), resolution));
net.vias.append(ViaOut{padStackId, pos});
}
for (const SExpression* wireNode : netNode->getChildren("wire")) {
for (const SExpression* pathNode : wireNode->getChildren("path")) {
QList<const SExpression*> childs =
pathNode->getChildren(SExpression::Type::Token);
if ((childs.count() < 2) || ((childs.count() % 2) != 0)) {
throw RuntimeError(__FILE__, __LINE__,
"Unexpected number of vertices in path element.");
}
WireOut wire{deserialize<const Layer*>(*childs.at(0)),
parseLength(*childs.at(1), resolution),
{}};
if (wire.width <= 0) {
mLogger->warning("Skippted wire with zero width.");
continue;
}
for (int i = 3; i < childs.count(); i += 2) {
Length x = parseLength(*childs.at(i - 1), resolution);
Length y = parseLength(*childs.at(i), resolution);
wire.path.addVertex(Point(x, y));
}
if (wire.path.getVertices().count() < 2) {
throw RuntimeError(__FILE__, __LINE__,
"Path contains too few vertices.");
}
net.wires.append(wire);
}
}
mNets.append(net);
}
mLogger->debug(tr("Specctra session file parsed successfully."));
}
CmdBoardSpecctraImport::~CmdBoardSpecctraImport() noexcept {
}
/*******************************************************************************
* Inherited from UndoCommand
******************************************************************************/
bool CmdBoardSpecctraImport::performExecute() {
// if an error occurs, undo all already executed child commands
auto undoScopeGuard = scopeGuard([&]() { performUndo(); });
// Memorize current board to allow reusing their properties.
struct OldJunction {
Uuid uuid;
Point pos;
const Layer* layer; // May be nullptr.
};
struct OldTrace {
Uuid uuid;
Point p1;
Point p2;
const Layer* layer;
Length width;
};
struct OldSegment {
Uuid uuid;
QPointer<NetSignal> net;
QList<OldJunction> junctions;
QList<OldTrace> traces;
QList<Via> vias;
QList<BoardPadData> pads;
};
QList<OldSegment> oldSegments;
foreach (const BI_NetSegment* seg, mBoard.getNetSegments()) {
OldSegment oldSeg{seg->getUuid(), seg->getNetSignal(), {}, {}, {}, {}};
foreach (const BI_NetPoint* np, seg->getNetPoints()) {
oldSeg.junctions.append(OldJunction{np->getUuid(), np->getPosition(),
np->getLayerOfTraces()});
}
foreach (const BI_NetLine* nl, seg->getNetLines()) {
oldSeg.traces.append(OldTrace{nl->getUuid(), nl->getP1().getPosition(),
nl->getP2().getPosition(), &nl->getLayer(),
*nl->getWidth()});
}
foreach (const BI_Via* via, seg->getVias()) {
oldSeg.vias.append(via->getVia());
}
foreach (const BI_Pad* pad, seg->getPads()) {
oldSeg.pads.append(pad->getProperties());
}
oldSegments.append(oldSeg);
}
// Helper functions to find corresponding old objects for new objects.
QSet<Uuid> reusedUuids;
int newUuids = 0;
auto findNetPoint = [&](const NetSignal* net, const Point& pos,
const Layer* layer) {
for (const OldSegment& seg : oldSegments) {
if (seg.net == net) {
for (const OldJunction& np : seg.junctions) {
if (fuzzyCompare(np.pos, pos) && (np.layer == layer) &&
(!reusedUuids.contains(np.uuid))) {
reusedUuids.insert(np.uuid);
return std::make_optional(np);
}
}
}
}
++newUuids;
return std::optional<OldJunction>();
};
auto findNetLineImpl = [&](const NetSignal* net, Point p1, Point p2,
const Layer& layer,
const std::optional<Length>& width) {
if (p1 > p2) std::swap(p1, p2);
for (const OldSegment& seg : oldSegments) {
if (seg.net == net) {
for (const OldTrace& nl : seg.traces) {
Point nlP1 = nl.p1;
Point nlP2 = nl.p2;
if (nlP1 > nlP2) std::swap(nlP1, nlP2);
if (fuzzyCompare(nlP1, p1) && fuzzyCompare(nlP2, p2) &&
(nl.layer == &layer) && ((!width) || (nl.width == *width)) &&
(!reusedUuids.contains(nl.uuid))) {
reusedUuids.insert(nl.uuid);
return std::make_optional(nl);
}
}
}
}
return std::optional<OldTrace>();
};
auto findNetLine = [&](const NetSignal* net, Point p1, Point p2,
const Layer& layer, const Length& width) {
// First try to match including trace width, then ignore it because it
// might have been changed during the DSN -> SES roundtrip.
if (auto nl = findNetLineImpl(net, p1, p2, layer, width)) {
return nl;
} else if (auto nl = findNetLineImpl(net, p1, p2, layer, std::nullopt)) {
return nl;
} else {
++newUuids;
return std::optional<OldTrace>();
}
};
auto findVia = [&](const NetSignal* net, const Point& pos, const Layer& start,
const Layer& end) {
for (const OldSegment& seg : oldSegments) {
if (seg.net == net) {
for (const Via& via : seg.vias) {
if (fuzzyCompare(via.getPosition(), pos) &&
(via.getStartLayer() == start) && (via.getEndLayer() == end) &&
(!reusedUuids.contains(via.getUuid()))) {
reusedUuids.insert(via.getUuid());
return std::make_optional(via);
}
}
}
}
++newUuids;
return std::optional<Via>();
};
auto anyRefInSegment = [](const OldSegment& seg, const QSet<Uuid>& refs) {
foreach (const OldJunction& np, seg.junctions) {
if (refs.contains(np.uuid)) {
return true;
}
}
foreach (const OldTrace& nl, seg.traces) {
if (refs.contains(nl.uuid)) {
return true;
}
}
foreach (const Via& via, seg.vias) {
if (refs.contains(via.getUuid())) {
return true;
}
}
foreach (const BoardPadData& pad, seg.pads) {
if (refs.contains(pad.getUuid())) {
return true;
}
}
return false;
};
auto findNetSegment = [&](const NetSignal* net, const QSet<Uuid>& refs) {
for (const OldSegment& seg : oldSegments) {
if (seg.net == net) {
if (anyRefInSegment(seg, refs) && (!reusedUuids.contains(seg.uuid))) {
reusedUuids.insert(seg.uuid);
return std::make_optional(seg);
}
}
}
++newUuids;
return std::optional<OldSegment>();
};
//////////////////////////////////////////////////////////////////////////////
// Delete all net segments, they will be re-created from scratch below.
foreach (auto seg, mBoard.getNetSegments()) {
execNewChildCmd(new CmdBoardNetSegmentRemove(*seg)); // can throw
}
// Update devices placement.
QSet<QString> importedComponents;
QSet<Uuid> updatedComponents;
if (mComponents) {
for (const auto& item : *mComponents) {
if (item.name == "BOARD") {
continue; // Currently board-level pads cannot be edited.
}
importedComponents.insert(item.name);
ComponentInstance* cmp = mCircuit.getComponentInstanceByName(item.name);
BI_Device* dev = cmp
? mBoard.getDeviceInstanceByComponentUuid(cmp->getUuid())
: nullptr;
if ((!cmp) || (!dev)) {
mLogger->warning(
tr("Component '%1' from Specctra session does not exist "
"in this board.")
.arg(item.name));
continue;
}
if (((item.side == Side::Front) && (dev->getMirrored())) ||
((item.side == Side::Back) && (!dev->getMirrored()))) {
mLogger->warning(
tr("Component '%1' has been flipped, which is not supported yet.")
.arg(item.name));
continue;
}
std::unique_ptr<CmdDeviceInstanceEditAll> cmd(
new CmdDeviceInstanceEditAll(*dev));
if (!fuzzyCompare(dev->getPosition(), item.pos)) {
cmd->setPosition(item.pos, false);
updatedComponents.insert(cmp->getUuid());
}
if (!fuzzyCompare(dev->getRotation(), item.rot)) {
cmd->setRotation(item.rot, false);
updatedComponents.insert(cmp->getUuid());
}
execNewChildCmd(cmd.release());
}
// Warn about missing components.
for (const auto dev : mBoard.getDeviceInstances()) {
const QString name = *dev->getComponentInstance().getName();
// Footprints without pads are discarded by Freerouting as they are not
// relevant, thus ignore them.
if ((!importedComponents.contains(name)) && (!dev->getPads().isEmpty())) {
mLogger->warning(
tr("The component '%1' does not exist in the Specctra session.")
.arg(name));
}
}
}
// Find the corresponding net signal for each imported net. In addition,
// explicitly add any net that has board-level pads in it if they haven't
// been imported anymore. The external router may have removed such nets if
// they didn't contain traces, which would cause board-level pads to be
// accidentally removed.
QSet<NetSignal*> importedNets;
QVector<NetOutProcessed> nets;
for (const auto& net : mNets) {
NetSignal* netSignal = mCircuit.getNetSignalByName(net.netName);
// ATTENTION: The ~anonymous~ comes from our own Specctra export!
if ((!netSignal) && (!net.netName.startsWith("~anonymous~"))) {
mLogger->warning(tr("The net '%1' from Specctra session does not exist "
"in this project, skipping it.")
.arg(net.netName));
continue;
}
nets.append(NetOutProcessed{netSignal, net.vias, net.wires});
importedNets.insert(netSignal);
}
for (const auto& ns : oldSegments) {
if ((!importedNets.contains(ns.net)) && (!ns.pads.isEmpty())) {
nets.append(NetOutProcessed{ns.net, {}, {}});
importedNets.insert(ns.net);
}
}
// Import nets.
for (const NetOutProcessed& net : nets) {
// Helper data to memorize anchors.
struct AnchorData {
Point pos;
const Layer* startLayer;
const Layer* endLayer;
TraceAnchor anchor;
};
QList<AnchorData> anchors;
auto findAnchor = [&anchors](const Point& pos, const Layer* layer) {
const int layerNumber = layer->getCopperNumber();
for (auto& anchor : anchors) {
if ((anchor.pos == pos) &&
(layerNumber >= anchor.startLayer->getCopperNumber()) &&
(layerNumber <= anchor.endLayer->getCopperNumber())) {
return std::make_optional(anchor.anchor);
}
}
return std::optional<TraceAnchor>();
};
// Add anchors for each pad corresponding to imported wire coordinates.
QList<Point> wireCoordinates;
QHash<const Layer*, QList<Point>> wireCoordinatesPerLayer;
for (const auto& wire : net.wires) {
for (const auto& vertex : wire.path.getVertices()) {
wireCoordinates.append(vertex.getPos());
wireCoordinatesPerLayer[wire.layer].append(vertex.getPos());
}
}
auto addPadAnchor = [&](const Uuid& uuid, Point pos, bool tht,
const Layer& solderLayer, const BI_Device* device) {
QList<Point>& coordinates =
tht ? wireCoordinates : wireCoordinatesPerLayer[&solderLayer];
if (!coordinates.contains(pos)) {
// Find another coordinate which is very close (rounding errors).
// In some tests, errors were up to 70 nm!
std::sort(coordinates.begin(), coordinates.end(),
[&pos](const Point& a, const Point& b) {
return (a - pos).getLength() < (b - pos).getLength();
});
if ((!coordinates.isEmpty()) &&
fuzzyCompare(pos, coordinates.first())) {
pos = coordinates.first();
} else {
return;
}
}
const Layer* startLayer = tht ? &Layer::topCopper() : &solderLayer;
const Layer* endLayer = tht ? &Layer::botCopper() : &solderLayer;
if (device) {
anchors.append(
AnchorData{pos, startLayer, endLayer,
TraceAnchor::footprintPad(
device->getComponentInstanceUuid(), uuid)});
} else {
anchors.append(
AnchorData{pos, startLayer, endLayer, TraceAnchor::pad(uuid)});
}
};
for (const ComponentSignalInstance* cmpSig :
(net.netSignal ? net.netSignal->getComponentSignals()
: QList<ComponentSignalInstance*>{})) {
for (const BI_Pad* pad : cmpSig->getRegisteredFootprintPads()) {
addPadAnchor(pad->getUuid(), pad->getPosition(),
pad->getProperties().isTht(), pad->getSolderLayer(),
pad->getDevice());
}
}
// Define net segments with BoardNetSegmentSplitter.
BoardNetSegmentSplitter splitter;
for (const auto& ns : oldSegments) {
if (ns.net == net.netSignal) {
for (const BoardPadData& pad : ns.pads) {
splitter.addPad(pad, false);
const Layer& solderLayer =
(pad.getComponentSide() == Pad::ComponentSide::Top)
? Layer::topCopper()
: Layer::botCopper();
addPadAnchor(pad.getUuid(), pad.getPosition(), pad.isTht(),
solderLayer, nullptr);
}
}
}
for (const auto& via : net.vias) {
const PadStackOut& padStack = mPadStacks.value(via.padStackId);
const std::optional<Via> oldVia = findVia(
net.netSignal, via.pos, *padStack.startLayer, *padStack.endLayer);
const Uuid uuid = oldVia ? oldVia->getUuid() : Uuid::createRandom();
// Note: How can we know the drill diameter??? Use this logic for now:
// - If position & size not modified, keep original drill diameter too
// - Try to extract drill diameter from pad stack ID
// - If this didn't work, use automatic drill diameter as fallback
// Not sure if this order makes sense. Maybe the pad stack ID should have
// highest priority?
std::optional<PositiveLength> drillDiameter;
if (oldVia) {
drillDiameter = oldVia->getDrillDiameter();
} else if (auto autoAndDia = extractViaDrillDiameter(via.padStackId)) {
drillDiameter = autoAndDia->first
? std::nullopt
: std::make_optional(autoAndDia->second);
} else {
// Automatic drill from design rules.
}
// Determine if the size is manually set or automatic.
std::optional<PositiveLength> size = drillDiameter
? std::make_optional(PositiveLength(padStack.diameter))
: std::nullopt; // Automatic drill with manual size is not allowed!
if (drillDiameter && size) {
const PositiveLength autoSize = Via::calcSizeFromRules(
*drillDiameter, mBoard.getDesignRules().getViaAnnularRing());
const auto autoAndDia = extractViaSize(via.padStackId);
if (autoAndDia && autoAndDia->first && (size == autoSize)) {
size = std::nullopt; // Automatic size is correct, let's take it.
}
}
// For the exposure config, use a similar mechanism like for the drill.
MaskConfig exposureConfig = MaskConfig::automatic();
if (oldVia) {
exposureConfig = oldVia->getExposureConfig();
} else if (auto cfg = extractViaExposureConfig(via.padStackId)) {
exposureConfig = *cfg;
}
splitter.addVia(Via(uuid, *padStack.startLayer, *padStack.endLayer,
oldVia ? oldVia->getPosition() : via.pos,
drillDiameter, size, exposureConfig),
false);
anchors.append(AnchorData{via.pos, padStack.startLayer, padStack.endLayer,
TraceAnchor::via(uuid)});
}
auto getOrCreateAnchor = [&](const Point& pos, const Layer* layer) {
if (auto anchor = findAnchor(pos, layer)) {
return *anchor;
} else {
// Create new junction.
const std::optional<OldJunction> oldNp =
findNetPoint(net.netSignal, pos, layer);
const Uuid uuid = oldNp ? oldNp->uuid : Uuid::createRandom();
splitter.addJunction(Junction(uuid, oldNp ? oldNp->pos : pos));
const TraceAnchor a = TraceAnchor::junction(uuid);
anchors.append(AnchorData{pos, layer, layer, a});
return a;
}
};
for (const auto& wire : net.wires) {
for (int i = 1; i < wire.path.getVertices().count(); ++i) {
Point p0 = wire.path.getVertices().at(i - 1).getPos();
Point p1 = wire.path.getVertices().at(i).getPos();
const std::optional<OldTrace> oldNl =
findNetLine(net.netSignal, p0, p1, *wire.layer, wire.width);
if (oldNl && (!fuzzyCompare(oldNl->p1, p0))) {
std::swap(p0, p1); // Avoid change in file format.
}
splitter.addTrace(Trace(oldNl ? oldNl->uuid : Uuid::createRandom(),
*wire.layer, PositiveLength(wire.width),
getOrCreateAnchor(p0, wire.layer),
getOrCreateAnchor(p1, wire.layer)));
}
}
// Add netpoints, vias and traces for each segment.
foreach (const BoardNetSegmentSplitter::Segment& segment,
splitter.split()) {
// Find old segment.
QSet<Uuid> nsRefs;
for (const Junction& junction : segment.junctions) {
nsRefs.insert(junction.getUuid());
}
for (const Trace& trace : segment.traces) {
nsRefs.insert(trace.getUuid());
}
for (const Via& via : segment.vias) {
nsRefs.insert(via.getUuid());
}
for (const BoardPadData& pad : segment.pads) {
nsRefs.insert(pad.getUuid());
}
const std::optional<OldSegment> oldNs =
findNetSegment(net.netSignal, nsRefs);
// Add new segment
BI_NetSegment* copy = new BI_NetSegment(
mBoard, oldNs ? oldNs->uuid : Uuid::createRandom(), net.netSignal);
execNewChildCmd(new CmdBoardNetSegmentAdd(*copy));
// Add pads, vias, netpoints and netlines
std::unique_ptr<CmdBoardNetSegmentAddElements> cmdAddElements(
new CmdBoardNetSegmentAddElements(*copy));
QHash<Uuid, BI_Pad*> padMap;
for (const BoardPadData& p : segment.pads) {
BI_Pad* pad = cmdAddElements->addPad(BoardPadData(p));
padMap.insert(p.getUuid(), pad);
}
QHash<Uuid, BI_Via*> viaMap;
for (const Via& v : segment.vias) {
BI_Via* via = cmdAddElements->addVia(Via(
v.getUuid(), v.getStartLayer(), v.getEndLayer(), v.getPosition(),
v.getDrillDiameter(), v.getSize(), v.getExposureConfig()));
viaMap.insert(v.getUuid(), via);
}
QHash<Uuid, BI_NetPoint*> netPointMap;
for (const Junction& junction : segment.junctions) {
BI_NetPoint* netpoint =
new BI_NetPoint(*copy, junction.getUuid(), junction.getPosition());
cmdAddElements->addNetPoint(*netpoint);
netPointMap.insert(junction.getUuid(), netpoint);
}
for (const Trace& trace : segment.traces) {
BI_NetLineAnchor* p1 = nullptr;
if (std::optional<Uuid> anchor = trace.getP1().tryGetJunction()) {
p1 = netPointMap[*anchor];
} else if (std::optional<Uuid> anchor = trace.getP1().tryGetVia()) {
p1 = viaMap[*anchor];
} else if (std::optional<Uuid> anchor = trace.getP1().tryGetPad()) {
p1 = padMap[*anchor];
} else if (std::optional<TraceAnchor::PadAnchor> anchor =
trace.getP1().tryGetFootprintPad()) {
BI_Device* device =
mBoard.getDeviceInstanceByComponentUuid(anchor->device);
p1 = device ? device->getPad(anchor->pad) : nullptr;
}
BI_NetLineAnchor* p2 = nullptr;
if (std::optional<Uuid> anchor = trace.getP2().tryGetJunction()) {
p2 = netPointMap[*anchor];
} else if (std::optional<Uuid> anchor = trace.getP2().tryGetVia()) {
p2 = viaMap[*anchor];
} else if (std::optional<Uuid> anchor = trace.getP2().tryGetPad()) {
p2 = padMap[*anchor];
} else if (std::optional<TraceAnchor::PadAnchor> anchor =
trace.getP2().tryGetFootprintPad()) {
BI_Device* device =
mBoard.getDeviceInstanceByComponentUuid(anchor->device);
p2 = device ? device->getPad(anchor->pad) : nullptr;
}
if ((!p1) || (!p2)) {
throw LogicError(__FILE__, __LINE__);
}
BI_NetLine* netline =
new BI_NetLine(*copy, trace.getUuid(), *p1, *p2, trace.getLayer(),
trace.getWidth());
cmdAddElements->addNetLine(*netline);
}
execNewChildCmd(cmdAddElements.release());
}
}
// Print some statistics.
mLogger->info(
tr("Updated %1 components (%2 unmodified components skipped).")
.arg(updatedComponents.count())
.arg(importedComponents.count() - updatedComponents.count()));
mLogger->info(tr("Updated %1 net objects (%2 unmodified objects skipped).")
.arg(newUuids)
.arg(reusedUuids.count()));
undoScopeGuard.dismiss(); // no undo required
return getChildCount() > 0;
}
static std::optional<std::pair<bool, PositiveLength>> extractViaDimension(
const QString& padStackId, int index) noexcept {
// Note: Keep in sync with BoardSpecctraExport::getWiringPadStackId().
const QStringList tokens = padStackId.split("-");
if ((tokens.count() >= 4) && (tokens[0] == "via")) {
const bool autoDrill = tokens[index].contains("auto");
try {
return std::make_pair(
autoDrill,
PositiveLength(Length::fromMm(tokens[index].split(":").first())));
} catch (const Exception&) {
// Not critical.
}
}
return std::nullopt;
}
std::optional<std::pair<bool, PositiveLength>>
CmdBoardSpecctraImport::extractViaDrillDiameter(
const QString& padStackId) noexcept {
return extractViaDimension(padStackId, 1);
}
std::optional<std::pair<bool, PositiveLength>>
CmdBoardSpecctraImport::extractViaSize(const QString& padStackId) noexcept {
return extractViaDimension(padStackId, 2);
}
std::optional<MaskConfig> CmdBoardSpecctraImport::extractViaExposureConfig(
const QString& padStackId) noexcept {
// Note: Keep in sync with BoardSpecctraExport::getWiringPadStackId().
const QStringList tokens = padStackId.split("-");
if ((tokens.count() >= 4) && (tokens[0] == "via")) {
const QString exposure = tokens.last();
if (exposure == "exposed") {
return MaskConfig::automatic();
} else if (exposure.startsWith("exposed:")) {
try {
const QString offset = QString(exposure).remove("exposed:");
return MaskConfig::manual(Length::fromMm(offset));
} catch (const Exception&) {
// Not critical.
}
} else {
return MaskConfig::off();
}
}
return std::nullopt;
}
/*******************************************************************************
* End of File
******************************************************************************/
} // namespace editor
} // namespace librepcb