Loading files
08cd531bbThis 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 percentual 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(), 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*> children = cmpNode->getChildren("place");
if (children.count() != 1) {
throw RuntimeError(__FILE__, __LINE__,
"Unexpected component placement count.");
}
const SExpression* node = children.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*> children = pathNode->getChildren(
SExpression::Type::Token | SExpression::Type::String);
if ((children.count() < 2) || ((children.count() % 2) != 0)) {
throw RuntimeError(__FILE__, __LINE__,
QString("Unexpected number of vertices (%1) in "
"wire path of net '%2'.")
.arg(children.count())
.arg(net.netName));
}
WireOut wire{deserialize<const Layer*>(*children.at(0)),
parseLength(*children.at(1), resolution),
{}};
if (wire.width <= 0) {
mLogger->warning("Skippted wire with zero width.");
continue;
}
for (int i = 3; i < children.count(); i += 2) {
Length x = parseLength(*children.at(i - 1), resolution);
Length y = parseLength(*children.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 : std::as_const(*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 : std::as_const(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