Merge master: add cli_export, pdf_links; EDZ: 10-slot grid, group headers, ToS notice

- Resolve cmake/qet_compilation_vars.cmake conflict: keep both upstream's
  cli_export.cpp/h and pdf_links.cpp/h and the EDZ source additions.

- 10-position grid alignment: pin_y values are multiples of 10 so terminals
  snap cleanly to QET's default grid.  group_gap raised to 10 (one full slot).

- Named connector groups get a header label (group name) placed in the gap
  above the first pin, so the electrician sees block names (XDI, XPOW, …)
  without reading individual terminal designations.

- Device-tag dynamic_text now uses 9pt LABEL_FONT and y = min_y - 9 so it
  clears the element body and is legible at normal zoom.

- Add EPLAN Data Portal Terms of Use disclaimer to sources/import/edz/README.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Shane Ringrose
2026-06-21 21:00:49 +12:00
70 changed files with 6651 additions and 4941 deletions
+18
View File
@@ -44,6 +44,24 @@ used:
The non-UI classes are deliberately decoupled from the widget so they can be
tested headless.
## Terms of Use / Legal notice
The `.edz` files themselves are downloaded from the **EPLAN Data Portal**
(data.eplan.com), which is operated by EPLAN Software & Service GmbH & Co. KG.
The Data Portal Terms of Use (§ 5.4 at time of writing) restrict use of
downloaded data to EPLAN products.
**QElectroTech does not endorse or encourage violation of those terms.**
Whether use of an `.edz` outside EPLAN products is permitted under your
specific licence depends on the agreement you have with EPLAN and the
individual manufacturer. You are solely responsible for ensuring that your use
of `.edz` data complies with the applicable Terms of Use and any applicable
law. If in doubt, contact EPLAN or the part manufacturer directly.
QET's `.edz` reader parses only the portable, factual part data
(pin designations, order numbers, manufacturer names). It does not reproduce
any EPLAN-proprietary geometry or symbol data.
## Bundled LZMA SDK
`lzma/` contains the decode-only subset of Igor Pavlov's **LZMA SDK** (public
+25 -4
View File
@@ -34,6 +34,8 @@ const QString FONT =
QStringLiteral("Liberation Sans,5,-1,5,50,0,0,0,0,0,Regular");
const QString TITLE_FONT =
QStringLiteral("Liberation Sans,5,-1,5,75,0,0,0,0,0,Bold");
const QString LABEL_FONT =
QStringLiteral("Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular");
QString uuidStr()
{
@@ -71,19 +73,25 @@ QDomDocument EdzElementBuilder::build(const EdzPart &part)
}
const int n = pins.size();
const int pitch = 10;
const int group_gap = 5; // extra px inserted between designation groups
const int group_gap = 10; // one full slot between named connector groups
// Pins arrive sorted by functional group then by designation within each
// group (see EdzPart::parse). A group break occurs when the
// functiondefinition block changes; fall back to designation comparison for
// parts that carry no functiondefinition information.
// No gap is inserted for power/busbar pins that carry no group label —
// they flow consecutively so a 3-phase AC source stays connected.
auto groupKey = [](const EdzPin &p) {
return p.group.isEmpty() ? p.designation : p.group;
};
QVector<bool> is_group_break(n, false);
for (int i = 1; i < n; ++i)
is_group_break[i] = (groupKey(pins.at(i)) != groupKey(pins.at(i - 1)));
is_group_break[i] = (groupKey(pins.at(i)) != groupKey(pins.at(i - 1)))
&& (!pins.at(i).group.isEmpty());
// Place every terminal on a multiple-of-10 y coordinate so they align
// cleanly with QET's default grid. The gap slot is also 10 px, giving
// one empty grid row between connector groups.
QVector<int> pin_y(n);
int cur_y = 0;
for (int i = 0; i < n; ++i) {
@@ -218,10 +226,23 @@ QDomDocument EdzElementBuilder::build(const EdzPart &part)
desc.appendChild(makeText(doc, 6, pin_y[i] + 2, label, FONT));
}
// Connector group header labels — one per named group, in the gap above
// the group's first pin so the electrician can see the terminal block name
// (e.g. "XDI", "XPOW") without having to read individual terminal names.
for (int i = 0; i < n; ++i) {
if (pins.at(i).group.isEmpty()) continue;
if (i > 0 && groupKey(pins.at(i)) == groupKey(pins.at(i - 1))) continue;
// Place the label centred in the gap slot above the group's first pin.
const int label_y = (i == 0) ? (pin_y[0] - group_gap / 2)
: (pin_y[i] - group_gap / 2);
desc.appendChild(makeText(doc, body_left + 2, label_y,
pins.at(i).group, FONT));
}
// Device-tag label (per-instance, above the body).
QDomElement dyn = doc.createElement(QStringLiteral("dynamic_text"));
dyn.setAttribute(QStringLiteral("x"), min_x + pad);
dyn.setAttribute(QStringLiteral("y"), min_y - 2);
dyn.setAttribute(QStringLiteral("y"), min_y - 9);
dyn.setAttribute(QStringLiteral("z"), QStringLiteral("5"));
dyn.setAttribute(QStringLiteral("text_width"), QStringLiteral("-1"));
dyn.setAttribute(QStringLiteral("Halignment"), QStringLiteral("AlignLeft"));
@@ -231,7 +252,7 @@ QDomDocument EdzElementBuilder::build(const EdzPart &part)
dyn.setAttribute(QStringLiteral("keep_visual_rotation"), QStringLiteral("false"));
dyn.setAttribute(QStringLiteral("text_from"), QStringLiteral("ElementInfo"));
dyn.setAttribute(QStringLiteral("uuid"), uuidStr());
dyn.setAttribute(QStringLiteral("font"), FONT);
dyn.setAttribute(QStringLiteral("font"), LABEL_FONT);
dyn.appendChild(doc.createElement(QStringLiteral("text")));
QDomElement info_name = doc.createElement(QStringLiteral("info_name"));
info_name.appendChild(doc.createTextNode(QStringLiteral("label")));