Keep the symbols and wires a folio cannot load, and save them back

Opening a project dropped, with only a debug line, a symbol whose
definition could not be found (missing from the project's collection and
from the library) and every wire on it; and a wire whose terminal could
not be matched, after the symbol's definition was redrawn. The next save
wrote the project without them. One save of a forum upload whose
collection was incomplete kept 1 of 219 symbols and none of its 299 wires.

A folio now keeps what it could not load as it was read
(Diagram::m_kept_symbols, m_kept_wires) and writes it back when it is
saved whole (Diagram::writeKept()):
- a symbol, its terminals given ids after those of the folio's symbols;
- a wire, an end found written from its terminal as Conductor::toXml()
  does, an end not found as the symbol and terminal uuids it names; for a
  wire saved by legacy ids, the terminal's uuid derived from where it was
  saved (Element::savedTerminalUuid(), out of matchMovedTerminals()), so
  it joins again once the definition has it there. A wire whose ends
  cannot be read, or an end naming no terminal of the file, is kept so
  that it joins nothing.
A symbol kept counts as waiting (projectDataBase::waitingSymbols()), so a
partner keeps its link to it. The open warning now lists the symbols not
loaded too, and says both are kept.

Once the definition is back, the symbol and its wires are built again:
741.qet with one transistor definition removed (8 symbols, 33 wires on
them) saves all 67 wires, and with the definition put back opens with
all 65 symbols and 67 wires, the same folio as before.

tst_resaveunchanged::unloadableItemsKept: fixtures/coil_contact_wired.qet
(coil, linked contact, two wires; built with the script API) with the
coil's definition removed, or the contact's terminals moved and given new
uuids: both symbols, wires and links saved, a second save the same, and
the coil's definition put back gives the undamaged project's symbols and
wires. Fails on master (1 symbol; 0 wires).

FINDINGS F127, F128.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ispyisail
2026-10-19 16:45:55 +13:00
parent 3abaf21302
commit 0bcd0ca4b6
9 changed files with 823 additions and 47 deletions
+16 -1
View File
@@ -4119,9 +4119,24 @@ bool projectDataBase::savedLinks(const Element *element, QList<std::pair<QUuid,
@brief projectDataBase::waitingSymbols
@return the symbols on folios not built yet (lazy folios) and not
built alone as partners either (Diagram::buildSymbols()): partners a
symbol keeps its rows to until they are built
symbol keeps its rows to until they are built; and the symbols a built
folio could not build and keeps as read (Diagram::keptSymbolUuids())
*/
QSet<QUuid> projectDataBase::waitingSymbols() const
{
QSet<QUuid> kept;
if (m_project)
for (Diagram *diagram : m_project->folios())
if (diagram->isLoaded()) kept.unite(diagram->keptSymbolUuids());
return unbuiltSymbols().unite(kept);
}
/**
@brief projectDataBase::unbuiltSymbols
@return the symbols on folios not built yet and not built alone
(waitingSymbols())
*/
QSet<QUuid> projectDataBase::unbuiltSymbols() const
{
if (!m_project || !m_project->unloadedFolioCount()) {
return QSet<QUuid>();
+1
View File
@@ -165,6 +165,7 @@ class projectDataBase : public QObject
QList<std::pair<QUuid, int>> linkRowsOf(const QUuid &element) const;
QSet<QUuid> waitingPartners(const QUuid &element, const QSet<QUuid> &waiting);
QSet<QUuid> waitingSymbols() const;
QSet<QUuid> unbuiltSymbols() const;
//Fill the three stores from a project's document before its
//folios are built, then, once
+197 -13
View File
@@ -944,6 +944,132 @@ QStringList Diagram::wiresNotReconnected() const
return m_wires_not_reconnected;
}
/**
@brief Diagram::symbolsNotLoaded
@return one line per symbol of the loaded file that could not be built,
its definition missing from the project and the collections: kept as
read and saved back (F127). Empty for a folio that built every symbol.
*/
QStringList Diagram::symbolsNotLoaded() const
{
return m_symbols_not_loaded;
}
/**
@brief Diagram::keptSymbolUuids
@return the uuids of the symbols this folio could not build and keeps
as read: a partner linked to one of them keeps its link
*/
QSet<QUuid> Diagram::keptSymbolUuids() const
{
QSet<QUuid> uuids;
for (const QDomElement &symbol : m_kept_symbols)
uuids.insert(QUuid(symbol.attribute(QStringLiteral("uuid"))));
return uuids;
}
/**
@brief Diagram::keepWire
Keep @p wire, which could not be joined to its terminals, to save it
back (F127, F128). An end found (@p found1, @p found2) is written from
that terminal at save; an end not found keeps the symbol and terminal
it names, as uuids: for a wire saved by legacy terminal ids, the
symbol among @p symbols (uuid and XML of each symbol read) holding that
id and the uuid derived for that terminal where it was saved
(Element::savedTerminalUuid()), so the wire joins it again once the
symbol's definition has it there. An end naming no terminal of the
file at all pointed nowhere when it was saved: written as the null
uuid, so it names no symbol and is read back as a wire whose ends
cannot be read, kept unchanged and joining nothing (never a legacy id,
which the next save gives to another terminal).
*/
void Diagram::keepWire(const QDomElement &wire, Terminal *found1, Terminal *found2,
const QList<QPair<QUuid, QDomElement>> &symbols)
{
KeptWire kept;
Terminal *found[2] = {found1, found2};
for (int i = 0 ; i < 2 ; ++i) {
const QString index = QString::number(i + 1);
if (found[i]) {
kept.end[i] = found[i];
kept.found[i] = true;
continue;
}
if (wire.hasAttribute(QStringLiteral("element") + index)) {
kept.element[i] = QUuid(wire.attribute(QStringLiteral("element") + index));
kept.terminal[i] = QUuid(wire.attribute(QStringLiteral("terminal") + index));
} else {
const QString id = wire.attribute(QStringLiteral("terminal") + index);
for (const auto &symbol : symbols) {
const QList<QDomElement> records = QET::findInDomElement(
symbol.second, QStringLiteral("terminals"), QStringLiteral("terminal"));
for (int r = 0 ; r < records.size() && kept.element[i].isNull() ; ++r)
if (records.at(r).attribute(QStringLiteral("id")) == id) {
kept.element[i] = symbol.first;
kept.terminal[i] = Element::savedTerminalUuid(records, r);
}
if (!kept.element[i].isNull()) break;
}
}
}
kept.xml = m_kept_document.importNode(wire, true).toElement();
if (QUuid(kept.xml.attribute(QStringLiteral("uuid"))).isNull() && m_project) {
QStringList ends;
for (int i = 0 ; i < 2 ; ++i)
ends << (kept.found[i]
? kept.end[i]->parentElement()->uuid().toString() + QLatin1Char('/')
+ kept.end[i]->stableUuid().toString()
: kept.element[i].toString() + QLatin1Char('/') + kept.terminal[i].toString());
ends.sort();
kept.xml.setAttribute(QStringLiteral("uuid"),
m_project->derivedItemUuid(QStringLiteral("conductor"),
ends.join(QLatin1Char('\n'))).toString());
}
m_kept_wires << kept;
}
/**
@brief Diagram::writeKept
Write what this folio could not load, as it was read, into @p elements
and @p conductors of @p document: the symbols, their terminals given ids
after those of @p table_adr_id so none is read twice, and the wires, an
end found written from its terminal as Conductor::toXml() does. A wire
whose found end was deleted since goes with it.
*/
void Diagram::writeKept(QDomDocument &document, QDomElement &elements,
QDomElement &conductors, const QHash<Terminal *, int> &table_adr_id) const
{
int next_id = 0;
for (int id : table_adr_id) next_id = qMax(next_id, id + 1);
for (const QDomElement &symbol : m_kept_symbols) {
QDomElement copy = document.importNode(symbol, true).toElement();
for (QDomElement record : QET::findInDomElement(copy, QStringLiteral("terminals"),
QStringLiteral("terminal")))
record.setAttribute(QStringLiteral("id"), next_id++);
elements.appendChild(copy);
}
for (const KeptWire &kept : m_kept_wires) {
if ((kept.found[0] && !kept.end[0]) || (kept.found[1] && !kept.end[1])) continue;
QDomElement copy = document.importNode(kept.xml, true).toElement();
for (int i = 0 ; i < 2 && !kept.as_read ; ++i) {
const QString index = QString::number(i + 1);
const QString element = QStringLiteral("element") + index;
const QString terminal = QStringLiteral("terminal") + index;
if (!kept.found[i]) {
copy.setAttribute(element, kept.element[i].toString());
copy.setAttribute(terminal, kept.terminal[i].toString());
} else if (kept.end[i]->uuid().isNull()) {
copy.removeAttribute(element);
copy.setAttribute(terminal, table_adr_id.value(kept.end[i]));
} else {
copy.setAttribute(element, kept.end[i]->parentElement()->uuid().toString());
copy.setAttribute(terminal, kept.end[i]->uuid().toString());
}
}
conductors.appendChild(copy);
}
}
/**
@brief Diagram::uuidUsedByOtherDiagram
A hand-edited or merged project file can contain two folios with the same
@@ -1268,7 +1394,10 @@ QDomDocument Diagram::toXml(bool whole_content, bool is_copy_command) {
}
document.appendChild(dom_root);
if (items().isEmpty())
//What the folio could not load is written back with a whole folio
const bool write_kept = whole_content && !is_copy_command
&& (!m_kept_symbols.isEmpty() || !m_kept_wires.isEmpty());
if (items().isEmpty() && !write_kept)
return(document);
QVector<Element *> list_elements;
@@ -1375,8 +1504,8 @@ QDomDocument Diagram::toXml(bool whole_content, bool is_copy_command) {
// table de correspondance entre les adresses des bornes et leurs ids
QHash<Terminal *, int> table_adr_id;
if (!list_elements.isEmpty()) {
auto dom_elements = document.createElement(QStringLiteral("elements"));
auto dom_elements = document.createElement(QStringLiteral("elements"));
if (!list_elements.isEmpty() || write_kept) {
for (auto elmt : list_elements) {
QDomElement dom_element = elmt->toXml(document, table_adr_id);
ItemGroups::write(dom_element, elmt);
@@ -1388,6 +1517,7 @@ QDomDocument Diagram::toXml(bool whole_content, bool is_copy_command) {
dom_root.appendChild(dom_elements);
}
auto dom_conductors = document.createElement(QStringLiteral("conductors"));
if (!list_conductors.isEmpty()) {
//Symbols copied in old versions can share one uuid. A wire
//saved by that uuid would reopen on the first of them (#1408).
@@ -1397,7 +1527,6 @@ QDomDocument Diagram::toXml(bool whole_content, bool is_copy_command) {
shared_uuids.insert(elmt->uuid());
seen_uuids.insert(elmt->uuid());
}
auto dom_conductors = document.createElement(QStringLiteral("conductors"));
for (auto cond : list_conductors) {
dom_conductors.appendChild(cond->toXml(document,
table_adr_id,
@@ -1405,6 +1534,11 @@ QDomDocument Diagram::toXml(bool whole_content, bool is_copy_command) {
}
dom_root.appendChild(dom_conductors);
}
if (write_kept) {
writeKept(document, dom_elements, dom_conductors, table_adr_id);
if (dom_conductors.hasChildNodes() && dom_conductors.parentNode().isNull())
dom_root.insertAfter(dom_conductors, dom_elements);
}
// A rect query, unlike items(), returns true stacking order (z, then
// insertion order) even with NoIndex.
@@ -1603,6 +1737,23 @@ void Diagram::folioSequentialsToXml(QHash<QString,
fromXml() does for each symbol it reads.
@return the symbol, or nullptr if it could not be made (said in the log)
*/
/**
@brief Diagram::derivedSymbolUuid
@return the uuid of the symbol @p element_xml, saved without one:
derived from what it is and where it sits on its folio, never from the
folio's index (see buildElement())
*/
QUuid Diagram::derivedSymbolUuid(const QDomElement &element_xml) const
{
return m_project->derivedItemUuid(
QStringLiteral("element"),
QStringList{element_xml.attribute(QStringLiteral("type")),
element_xml.attribute(QStringLiteral("x")),
element_xml.attribute(QStringLiteral("y")),
element_xml.attribute(QStringLiteral("orientation"))}
.join(QLatin1Char('\n')));
}
Element *Diagram::buildElement(QDomElement element_xml, QHash<int, Terminal *> &table_adr_id,
bool consider_informations)
{
@@ -1650,13 +1801,7 @@ Element *Diagram::buildElement(QDomElement element_xml, QHash<int, Terminal *> &
//uuids anyway.
if (consider_informations && m_project
&& QUuid(element_xml.attribute(QStringLiteral("uuid"))).isNull()) {
nvel_elmt->setUuid(m_project->derivedItemUuid(
QStringLiteral("element"),
QStringList{type_id,
element_xml.attribute(QStringLiteral("x")),
element_xml.attribute(QStringLiteral("y")),
element_xml.attribute(QStringLiteral("orientation"))}
.join(QLatin1Char('\n'))));
nvel_elmt->setUuid(derivedSymbolUuid(element_xml));
}
ItemGroups::setGroup(nvel_elmt, ItemGroups::read(element_xml));
return nvel_elmt;
@@ -2092,6 +2237,14 @@ bool Diagram::fromXml(QDomElement &document,
QHash<int, Terminal *> table_adr_id;
if (m_loading_deferred) {
table_adr_id = m_partial_table; //the terminals of symbols built already
}
//Each symbol read, by uuid: the ends of a wire kept (keepWire())
QList<QPair<QUuid, QDomElement>> read_symbols;
if (consider_informations) {
m_kept_document = QDomDocument();
m_kept_symbols.clear();
m_kept_wires.clear();
m_symbols_not_loaded.clear();
}
for (auto element_xml :
QET::findInDomElement(root, QStringLiteral("elements"), QStringLiteral("element")))
@@ -2102,11 +2255,30 @@ bool Diagram::fromXml(QDomElement &document,
Element *built = m_partial_elements.value(QUuid(element_xml.attribute(QStringLiteral("uuid"))));
if (built) {
added_elements << built;
read_symbols.append({built->uuid(), element_xml});
continue;
}
}
if (Element *nvel_elmt = buildElement(element_xml, table_adr_id, consider_informations))
if (Element *nvel_elmt = buildElement(element_xml, table_adr_id, consider_informations)) {
added_elements << nvel_elmt;
if (consider_informations) read_symbols.append({nvel_elmt->uuid(), element_xml});
} else if (consider_informations) {
//Its definition is missing: kept as read, saved back, so a
//save loses nothing (F127)
QDomElement kept = m_kept_document.importNode(element_xml, true).toElement();
if (QUuid(kept.attribute(QStringLiteral("uuid"))).isNull() && m_project)
kept.setAttribute(QStringLiteral("uuid"), derivedSymbolUuid(element_xml).toString());
m_kept_symbols << kept;
read_symbols.append({QUuid(kept.attribute(QStringLiteral("uuid"))), element_xml});
QString label;
for (const QDomElement &info : QET::findInDomElement(
element_xml, QStringLiteral("elementInformations"), QStringLiteral("elementInformation")))
if (info.attribute(QStringLiteral("name")) == QLatin1String("label")) label = info.text();
const QString type = element_xml.attribute(QStringLiteral("type"));
m_symbols_not_loaded << (label.isEmpty()
? type.section(QLatin1Char('/'), -1)
: QStringLiteral("%1 (%2)").arg(label, type.section(QLatin1Char('/'), -1)));
}
}
//Texts, images and shapes written before they carried a uuid (or
@@ -2262,7 +2434,17 @@ bool Diagram::fromXml(QDomElement &document,
QStringLiteral("conductors"),
QStringLiteral("conductor")))
{
if (!Conductor::valideXml(f)) continue;
if (!Conductor::valideXml(f)) {
//Its ends cannot be read: kept and written back unchanged
//(F128), it can join nothing
if (consider_informations) {
KeptWire kept;
kept.xml = m_kept_document.importNode(f, true).toElement();
kept.as_read = true;
m_kept_wires << kept;
}
continue;
}
//Check if terminal that conductor must be linked is know
@@ -2297,6 +2479,8 @@ bool Diagram::fromXml(QDomElement &document,
if (!num.isEmpty() && num != QLatin1String("_"))
wire += QStringLiteral(" (%1)").arg(num);
m_wires_not_reconnected << wire;
//Kept as read, saved back (F128)
keepWire(f, p1, p2, read_symbols);
}
if (p1 && p2 && p1 != p2)
+25
View File
@@ -39,6 +39,7 @@ class DiagramContent;
class DiagramPosition;
class DiagramTextItem;
class Element;
class Terminal;
class ElementsLocation;
class DiagramImageItem;
class DiagramEventInterface;
@@ -165,6 +166,28 @@ class Diagram : public QGraphicsScene
//Wires of the loaded file whose ends could not be found
QStringList m_wires_not_reconnected;
//Symbols of the loaded file that could not be built
QStringList m_symbols_not_loaded;
//What the folio could not load, written back as it was read
//(F127, F128): symbols whose definition is missing, and wires
//to a terminal not found
struct KeptWire {
QDomElement xml;
QPointer<Terminal> end[2]; ///< an end found, null if not
bool found[2] = {false, false};
QUuid element[2]; ///< an end not found: its symbol
QUuid terminal[2]; ///< and its terminal
bool as_read = false; ///< no end can be read: written unchanged
};
QDomDocument m_kept_document;
QList<QDomElement> m_kept_symbols;
QList<KeptWire> m_kept_wires;
QUuid derivedSymbolUuid(const QDomElement &element_xml) const;
void keepWire(const QDomElement &wire, Terminal *found1, Terminal *found2,
const QList<QPair<QUuid, QDomElement>> &symbols);
void writeKept(QDomDocument &document, QDomElement &elements,
QDomElement &conductors, const QHash<Terminal *, int> &table_adr_id) const;
// METHODS
protected:
@@ -189,6 +212,8 @@ class Diagram : public QGraphicsScene
void restoreText(Element* elmt);
QUuid uuid();
QStringList wiresNotReconnected() const;
QStringList symbolsNotLoaded() const;
QSet<QUuid> keptSymbolUuids() const;
void setEventInterface (DiagramEventInterface *event_interface);
void clearEventInterface();
+30 -7
View File
@@ -2262,15 +2262,38 @@ bool QETDiagramEditor::openAndAddProject(
);
}
//Report wires left out of the load because a terminal they join
//was not found: they would otherwise vanish on the next save
//without the user ever being told.
QStringList lost_wires;
//Report the elements and wires left out of the load: an element
//whose definition is missing, a wire whose terminal was not found.
//They are kept in the file as they were (F127, F128), but are not
//shown.
QStringList lost_elements, lost_wires;
for (Diagram *diagram : project->folios()) {
if (!diagram->isLoaded()) continue;
for (const QString &element : diagram->symbolsNotLoaded())
lost_elements << tr("Sheet %1: %2").arg(diagram->folioIndex() + 1).arg(element);
}
for (Diagram *diagram : project->foliosWithWiresNotReconnected()) {
for (const QString &wire : diagram->wiresNotReconnected()) {
lost_wires << tr("Sheet %1: %2").arg(diagram->folioIndex() + 1).arg(wire);
}
}
if (interactive && !lost_elements.isEmpty())
{
QMessageBox box(QMessageBox::Warning,
tr("Elements not loaded", "message box title"),
tr("%n elements could not be loaded: their definition is "
"missing from the project and from the collections.\n"
"\n"
"They are not shown, but they are kept in the file as they "
"are, with their conductors, and come back once their "
"definition is available again.",
"message box content",
lost_elements.size()),
QMessageBox::Ok,
this);
box.setDetailedText(lost_elements.join(QLatin1Char('\n')));
box.exec();
}
if (interactive && !lost_wires.isEmpty())
{
QMessageBox box(QMessageBox::Warning,
@@ -2280,9 +2303,9 @@ bool QETDiagramEditor::openAndAddProject(
"was probably replaced by another one with different "
"terminals.\n"
"\n"
"If you save the project, these conductors will disappear "
"from the file. Close it without saving to keep the file as "
"it is.",
"They are not shown, but they are kept in the file as they "
"are, and join their terminals again once the definition "
"has them where they were.",
"message box content",
lost_wires.size()),
QMessageBox::Ok,
+37 -26
View File
@@ -975,6 +975,41 @@ bool Element::valideXml(QDomElement &e)
return(true);
}
/**
@brief Element::savedTerminalUuid
@return the uuid TerminalUuids derives for the terminal saved as
@p records [@p index] (a symbol's saved terminals, in file order) at the
place it was saved: its docking point less the stub Terminal::init()
adds, and its rank among the records at that place, as the project
counted it when it gave the terminals their uuids
*/
QUuid Element::savedTerminalUuid(const QList<QDomElement> &records, int index)
{
const auto dock_of = [](const QDomElement &r) {
return QPointF(r.attribute(QStringLiteral("x")).toDouble(),
r.attribute(QStringLiteral("y")).toDouble());
};
const QDomElement &record = records.at(index);
const int orientation = record.attribute(QStringLiteral("orientation")).toInt();
const QPointF dock = dock_of(record);
QPointF old_pos = dock;
switch (orientation) {
case Qet::North: old_pos -= QPointF(0, Terminal::terminalSize); break;
case Qet::East : old_pos -= QPointF(-Terminal::terminalSize, 0); break;
case Qet::West : old_pos -= QPointF(Terminal::terminalSize, 0); break;
case Qet::South: old_pos -= QPointF(0, -Terminal::terminalSize); break;
}
int rank = 0;
for (int j = 0; j < index; ++j) {
const QPointF other = dock_of(records.at(j));
if (records.at(j).attribute(QStringLiteral("orientation")).toInt() == orientation
&& qAbs(other.x() - dock.x()) < 1e-6
&& qAbs(other.y() - dock.y()) < 1e-6)
++rank;
}
return TerminalUuids::derived(old_pos.x(), old_pos.y(), orientation, rank);
}
/**
@brief Element::matchMovedTerminals
Pair the saved terminals that no live terminal matched exactly with the
@@ -1033,32 +1068,8 @@ void Element::matchMovedTerminals(const QList<QDomElement> &records,
//Pair a record with a live terminal: register its legacy id, and the
//uuid derived for the terminal at its old place
const auto pair_up = [&](int index, Terminal *t) {
const QDomElement &record = records.at(index);
const QPointF dock = dock_of(record);
//The old place of the terminal: its docking point less the
//stub that Terminal::init() added
QPointF old_pos = dock;
switch (orientation_of(record)) {
case Qet::North: old_pos -= QPointF(0, Terminal::terminalSize); break;
case Qet::East : old_pos -= QPointF(-Terminal::terminalSize, 0); break;
case Qet::West : old_pos -= QPointF(Terminal::terminalSize, 0); break;
case Qet::South: old_pos -= QPointF(0, -Terminal::terminalSize); break;
}
//The rank among the records at the same place, as the project
//counted it when it gave the terminals their uuids
int rank = 0;
for (int j = 0; j < index; ++j) {
const QPointF other = dock_of(records.at(j));
if (orientation_of(records.at(j)) == orientation_of(record)
&& qAbs(other.x() - dock.x()) < 1e-6
&& qAbs(other.y() - dock.y()) < 1e-6)
++rank;
}
id_adr.insert(record.attribute(QStringLiteral("id")).toInt(), t);
m_old_place_terminals.insert(TerminalUuids::derived(old_pos.x(), old_pos.y(),
orientation_of(record), rank), t);
id_adr.insert(records.at(index).attribute(QStringLiteral("id")).toInt(), t);
m_old_place_terminals.insert(savedTerminalUuid(records, index), t);
m_moved_terminals << t;
};
const auto same_place = [](const QPointF &a, const QPointF &b) {
+1
View File
@@ -183,6 +183,7 @@ class Element : public QetGraphicsItem
QPoint hotspot() const;
void editProperty() override;
static bool valideXml(QDomElement &);
static QUuid savedTerminalUuid(const QList<QDomElement> &records, int index);
virtual bool fromXml(
QDomElement &,
QHash<int,
@@ -0,0 +1,421 @@
<project title="Free text widths" uuid="{5b0f5e1c-8a43-4c1e-9e57-3f0d0f0a7a00}" version="0.200.1">
<properties>
<property name="saveddate" show="1">10/2/26</property>
<property name="saveddate-eu" show="1">02-10-2026</property>
<property name="saveddate-us" show="1">2026-10-02</property>
<property name="savedfilename" show="1">free_text_width</property>
<property name="savedtime" show="1">18:39</property>
</properties>
<usage enabled="true" time_spent="0"/>
<newdiagrams>
<border cols="17" colsize="60" displaycols="true" displayrows="true" rows="8" rowsize="80"/>
<inset author="" auto_page_num="" date="null" displayAt="bottom" filename="" folio="" indexrev="" locmach="" plant="" title="" version=""/>
<conductors bicolor="false" bus="" cable="" color2="#000000" condsize="1" conductor_color="" conductor_section="" dash-size="1" displaytext="1" formula="" function="" horizontal-alignment="AlignBottom" horizrotatetext="0" num="_" numsize="7" onetextperfolio="0" tension_protocol="" text_color="#000000" type="multi" vertical-alignment="AlignRight" vertirotatetext="270"/>
<report label="%f-%l%c"/>
<xrefs>
<xref delayprefix="" displayhas="cross" master_label="%f-%l%c" offset="0" powerprefix="" showallconfiguredslaves="false" showpowerctc="true" showterminalname="true" slave_label="(%f-%l%c)" slave_offset="0" snapto="label" stackoverlapping="true" switchprefix="" type="cable" xrefpos="AlignBottom"/>
<xref delayprefix="" displayhas="cross" master_label="%f-%l%c" offset="0" powerprefix="" showallconfiguredslaves="false" showpowerctc="true" showterminalname="true" slave_label="(%f-%l%c)" slave_offset="0" snapto="label" stackoverlapping="true" switchprefix="" type="coil" xrefpos="AlignBottom"/>
<xref delayprefix="" displayhas="cross" master_label="%f-%l%c" offset="0" powerprefix="" showallconfiguredslaves="false" showpowerctc="true" showterminalname="true" slave_label="(%f-%l%c)" slave_offset="0" snapto="label" stackoverlapping="true" switchprefix="" type="commutator" xrefpos="AlignBottom"/>
<xref delayprefix="" displayhas="cross" master_label="%f-%l%c" offset="0" powerprefix="" showallconfiguredslaves="false" showpowerctc="true" showterminalname="true" slave_label="(%f-%l%c)" slave_offset="0" snapto="label" stackoverlapping="true" switchprefix="" type="plc" xrefpos="AlignBottom"/>
<xref delayprefix="" displayhas="cross" master_label="%f-%l%c" offset="0" powerprefix="" showallconfiguredslaves="false" showpowerctc="true" showterminalname="true" slave_label="(%f-%l%c)" slave_offset="0" snapto="label" stackoverlapping="true" switchprefix="" type="protection" xrefpos="AlignBottom"/>
</xrefs>
<conductors_autonums auto_break_conductors="false" current_autonum="" freeze_new_conductors="false"/>
<folio_autonums/>
<element_autonums current_autonum="" freeze_new_elements="false"/>
<guides/>
</newdiagrams>
<diagram author="" auto_page_num="" cols="17" colsize="60" date="null" displayAt="bottom" displaycols="true" displayrows="true" filename="" folio="" freezeNewConductor="false" freezeNewElement="false" height="660" indexrev="" locmach="" order="1" plant="" rows="8" rowsize="80" title="" uuid="{5b0f5e1c-8a43-4c1e-9e57-3f0d0f0a7a01}" version="0.200.1-dev">
<defaultconductor bicolor="false" bus="" cable="" color2="#000000" condsize="1" conductor_color="" conductor_section="" dash-size="1" displaytext="1" formula="" function="" horizontal-alignment="AlignBottom" horizrotatetext="0" num="_" numsize="7" onetextperfolio="0" tension_protocol="" text_color="#000000" type="multi" vertical-alignment="AlignRight" vertirotatetext="270"/>
<elements>
<element freezeLabel="false" is_movable="1" orientation="0" prefix="K" type="embed://import/10_electric/10_allpole/310_relays_contactors_contacts/01_coils/bobine3.elmt" uuid="{ee860e7c-eaf3-404a-9c71-5ae4b296bdde}" x="200" y="200" z="10">
<terminals>
<terminal id="0" orientation="0" x="0" y="-16"/>
<terminal id="1" orientation="2" x="0" y="16"/>
</terminals>
<inputs/>
<links_uuids>
<link_uuid uuid="{8f8831dc-ebbe-48e0-8916-c014c9dd31eb}"/>
</links_uuids>
<elementInformations>
<elementInformation name="label" show="1">K1</elementInformation>
</elementInformations>
<dynamic_texts>
<dynamic_elmt_text Halignment="AlignLeft" Valignment="AlignTop" font="Liberation Sans,4,-1,5,25,0,0,0,0,0,Regular" frame="false" keep_visual_rotation="false" rotation="0" rotation_point_center="false" text_from="UserText" text_width="-1" uuid="{28faae8b-3f47-4578-9708-eee8500d3507}" x="2" y="11.33">
<text>A2</text>
</dynamic_elmt_text>
<dynamic_elmt_text Halignment="AlignLeft" Valignment="AlignTop" font="Liberation Sans,4,-1,5,25,0,0,0,0,0,Regular" frame="false" keep_visual_rotation="false" rotation="0" rotation_point_center="false" text_from="UserText" text_width="-1" uuid="{bcabc653-1e9a-43d2-addd-722e2b0bff35}" x="2" y="-24.67">
<text>A1</text>
</dynamic_elmt_text>
<dynamic_elmt_text Halignment="AlignLeft" Valignment="AlignTop" font="Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular" frame="false" keep_visual_rotation="false" rotation="0" rotation_point_center="false" text_from="ElementInfo" text_width="-1" uuid="{93524f29-abab-4d7a-950c-fe6bfa385fa9}" x="25" y="-9.17">
<text>K1</text>
<info_name>label</info_name>
</dynamic_elmt_text>
</dynamic_texts>
<texts_groups/>
</element>
<element freezeLabel="false" is_movable="1" orientation="0" prefix="S" type="embed://import/10_electric/10_allpole/310_relays_contactors_contacts/02_contacts_cross_referencing/01_auxiliary_contacts/con_simple.elmt" uuid="{8f8831dc-ebbe-48e0-8916-c014c9dd31eb}" x="400" y="200" z="10">
<terminals>
<terminal id="2" orientation="0" x="0" y="-16"/>
<terminal id="3" orientation="2" x="0" y="16"/>
</terminals>
<inputs/>
<links_uuids>
<link_uuid uuid="{ee860e7c-eaf3-404a-9c71-5ae4b296bdde}"/>
</links_uuids>
<dynamic_texts>
<dynamic_elmt_text Halignment="AlignLeft" Valignment="AlignTop" font="Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular" frame="false" keep_visual_rotation="false" rotation="0" rotation_point_center="false" text_from="ElementInfo" text_width="-1" uuid="{e6230b67-df36-4e2b-96f9-5e1b6ff44cf9}" x="2.75" y="-9.5">
<text>K1</text>
<info_name>label</info_name>
</dynamic_elmt_text>
</dynamic_texts>
<texts_groups/>
</element>
</elements>
<conductors>
<conductor bicolor="false" bus="" cable="" color2="#000000" condsize="1" conductor_color="" conductor_section="" dash-size="1" displaytext="1" element1="{ee860e7c-eaf3-404a-9c71-5ae4b296bdde}" element1_label="K1" element1_linked="" element1_name="Coil" element2="{8f8831dc-ebbe-48e0-8916-c014c9dd31eb}" element2_label="" element2_linked="K1" element2_name="Simple contact" formula="" freezeLabel="false" function="" horizontal-alignment="AlignBottom" horizrotatetext="0" num="_" numsize="7" onetextperfolio="0" tension_protocol="" terminal1="{8d0fa333-2d98-4a75-8a4e-21c81cce7ec3}" terminal2="{b3b3dc63-1c98-409b-ab79-3dbaea3556cb}" terminalname1="A1" terminalname2="" text_color="#000000" type="multi" uuid="{965e3155-9b9d-41f3-af7d-03ecf52ddf9a}" vertical-alignment="AlignRight" vertirotatetext="270" x="0" y="0">
<sequentialNumbers/>
</conductor>
<conductor bicolor="false" bus="" cable="" color2="#000000" condsize="1" conductor_color="" conductor_section="" dash-size="1" displaytext="1" element1="{ee860e7c-eaf3-404a-9c71-5ae4b296bdde}" element1_label="K1" element1_linked="" element1_name="Coil" element2="{8f8831dc-ebbe-48e0-8916-c014c9dd31eb}" element2_label="" element2_linked="K1" element2_name="Simple contact" formula="" freezeLabel="false" function="" horizontal-alignment="AlignBottom" horizrotatetext="0" num="_" numsize="7" onetextperfolio="0" tension_protocol="" terminal1="{c5376fd7-bdf1-4c10-985a-0d7d5f52c8f9}" terminal2="{b9683546-f0a2-4386-b114-ef1d46b2df3d}" terminalname1="A2" terminalname2="" text_color="#000000" type="multi" uuid="{5ccf65d5-ed6a-45f3-b0bc-d041c36cebca}" vertical-alignment="AlignRight" vertirotatetext="270" x="0" y="0">
<sequentialNumbers/>
</conductor>
</conductors>
<inputs>
<input font="Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular" rotation="0" text="&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;>&#xa;&lt;html>&lt;head>&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; />&lt;meta charset=&quot;utf-8&quot; />&lt;style type=&quot;text/css&quot;>&#xa;p, li { white-space: pre-wrap; }&#xa;hr { height: 1px; border-width: 0; }&#xa;li.unchecked::marker { content: &quot;\2610&quot;; }&#xa;li.checked::marker { content: &quot;\2612&quot;; }&#xa;&lt;/style>&lt;/head>&lt;body style=&quot; font-family:'Liberation Sans'; font-size:9pt; font-weight:400; font-style:normal;&quot;>&#xa;&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;>FreeAlpha FreeBeta FreeGamma FreeDelta FreeEpsilon&lt;/p>&lt;/body>&lt;/html>" text_width="70" uuid="{5b0f5e1c-8a43-4c1e-9e57-3f0d0f0a7a03}" x="100" y="100"/>
<input font="Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular" rotation="0" text="&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;>&#xa;&lt;html>&lt;head>&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; />&lt;meta charset=&quot;utf-8&quot; />&lt;style type=&quot;text/css&quot;>&#xa;p, li { white-space: pre-wrap; }&#xa;hr { height: 1px; border-width: 0; }&#xa;li.unchecked::marker { content: &quot;\2610&quot;; }&#xa;li.checked::marker { content: &quot;\2612&quot;; }&#xa;&lt;/style>&lt;/head>&lt;body style=&quot; font-family:'Liberation Sans'; font-size:9pt; font-weight:400; font-style:normal;&quot;>&#xa;&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;>OpenAlpha OpenBeta OpenGamma OpenDelta OpenEpsilon&lt;/p>&lt;/body>&lt;/html>" uuid="{5b0f5e1c-8a43-4c1e-9e57-3f0d0f0a7a04}" x="100" y="400"/>
<input font="Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular" rotation="0" text="&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;>&#xa;&lt;html>&lt;head>&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; />&lt;meta charset=&quot;utf-8&quot; />&lt;style type=&quot;text/css&quot;>&#xa;p, li { white-space: pre-wrap; }&#xa;hr { height: 1px; border-width: 0; }&#xa;li.unchecked::marker { content: &quot;\2610&quot;; }&#xa;li.checked::marker { content: &quot;\2612&quot;; }&#xa;&lt;/style>&lt;/head>&lt;body style=&quot; font-family:'Liberation Sans'; font-size:9pt; font-weight:400; font-style:normal;&quot;>&#xa;&lt;p align=&quot;center&quot; style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;>CentAlpha CentBeta CentGamma&lt;/p>&lt;/body>&lt;/html>" text_width="120" uuid="{5b0f5e1c-8a43-4c1e-9e57-3f0d0f0a7a08}" x="400" y="100"/>
</inputs>
</diagram>
<collection>
<category name="import">
<names>
<name lang="en">Imported elements</name>
</names>
<category name="10_electric">
<names>
<name lang="ar">كهربائي</name>
<name lang="ca">Elèctric</name>
<name lang="cs">Elektrotechnika</name>
<name lang="da">Elektrisk</name>
<name lang="de">Elektrik</name>
<name lang="el">Ηλεκτρικά</name>
<name lang="en">Electric</name>
<name lang="es">Eléctrica</name>
<name lang="fi">Sähkö</name>
<name lang="fr">Electrique</name>
<name lang="hr">Električno</name>
<name lang="hu">Elektromos</name>
<name lang="it">Elettrica</name>
<name lang="ja">電気</name>
<name lang="ko">전기</name>
<name lang="mn">Цахилгаан</name>
<name lang="nb">Elektrisk</name>
<name lang="nl">Elektrotechniek</name>
<name lang="nl_BE">Elektrisch</name>
<name lang="pl">Elektrotechnika</name>
<name lang="pt">Elétrico</name>
<name lang="pt_BR">Elétrica</name>
<name lang="ro">Electric</name>
<name lang="ru">Электротехника</name>
<name lang="sk">Elektrické</name>
<name lang="sl">Elektrika</name>
<name lang="sr">Електрика</name>
<name lang="sv">El</name>
<name lang="tr">Elektrik</name>
<name lang="uk">Електрика</name>
<name lang="zh">10_电气</name>
<name lang="zn">电气工程</name>
</names>
<category name="10_allpole">
<names>
<name lang="ar">متعدد الأقطاب</name>
<name lang="ca">Multifilar</name>
<name lang="cs">Vícežilový</name>
<name lang="da">Flere ledere</name>
<name lang="de">Allpolig</name>
<name lang="el">Πολυγραμμικό</name>
<name lang="en">All-pole</name>
<name lang="es">Multifilar</name>
<name lang="fi">Yleisnapa</name>
<name lang="fr">Multifilaire</name>
<name lang="hr">Univerzalni</name>
<name lang="hu">Többvonalas szimbólumok</name>
<name lang="it">Multifilare</name>
<name lang="ja">複線</name>
<name lang="ko">다-극</name>
<name lang="mn">Бүх туйл</name>
<name lang="nb">Alle poler</name>
<name lang="nl">Veel polig</name>
<name lang="nl_BE">All-pole</name>
<name lang="pl">Schematy wieloliniowe</name>
<name lang="pt">Multipolar</name>
<name lang="pt_BR">Multifilar</name>
<name lang="ro">Bipolar</name>
<name lang="ru">Многополюсные</name>
<name lang="sk">Všepólové</name>
<name lang="sl">Vsi poli</name>
<name lang="sr">Свe-полни</name>
<name lang="sv">Allpolig</name>
<name lang="tr">Çok kutuplu</name>
<name lang="uk">Багатополюсні</name>
<name lang="zh">10_全极</name>
</names>
<category name="310_relays_contactors_contacts">
<names>
<name lang="ar">المرحّلات والموصّلات وجهات الاتصال</name>
<name lang="ca">Relés, contactors i contactes</name>
<name lang="cs">Relé, stykače a kontakty</name>
<name lang="da">Relæer, kontaktorer og kontakter</name>
<name lang="de">Relais, Schütze und Kontakte</name>
<name lang="el">Ηλεκτρονόμοι και επαφές</name>
<name lang="en">Relays, contactors and contacts</name>
<name lang="es">Relevadores, contactores y contactos</name>
<name lang="fi">Releet, kontaktorit ja koskettimet</name>
<name lang="fr">Relais, contacteurs et contacts</name>
<name lang="hr">Releji, kontaktori i kontakti</name>
<name lang="hu">Relék, kontaktorok és érintkezők</name>
<name lang="it">Relè, contattori e contatti</name>
<name lang="ja">継電器、接触器、接点</name>
<name lang="ko">릴레이, 커넥터및 접점</name>
<name lang="mn">Реле, контактор болон контакт</name>
<name lang="nb">Reléer, kontaktorere og kontakter</name>
<name lang="nl">Relais, contactoren en contacten</name>
<name lang="nl_BE">Relais, contacteurs en contacten</name>
<name lang="pl">Przekaźniki i styczniki</name>
<name lang="pt">Relés, contatores e contactos</name>
<name lang="pt_BR">Relés, Contatores e contatos</name>
<name lang="ro">Relee, contactoare și contacte</name>
<name lang="ru">Реле, пускатели и контакты</name>
<name lang="sk">Relé, stykače a kontakty</name>
<name lang="sl">Releji, kontaktorji in kontakti</name>
<name lang="sr">Релеји, контактори и контакти</name>
<name lang="sv">Reläer, kontaktorer och kontakter</name>
<name lang="tr">Röleler, kontaktörler ve kontaklar</name>
<name lang="uk">Реле, контактори та контакти</name>
<name lang="zh">继电器、接触器与触点</name>
</names>
<category name="01_coils">
<names>
<name lang="ar">لفه</name>
<name lang="ca">Bobines</name>
<name lang="cs">cívka</name>
<name lang="da">spole</name>
<name lang="de">Spule</name>
<name lang="el">σπείρα</name>
<name lang="en">coil</name>
<name lang="es">bobina</name>
<name lang="fi">Käämi</name>
<name lang="fr">bobine</name>
<name lang="hr">zavojnica</name>
<name lang="hu">tekercs</name>
<name lang="it">bobina</name>
<name lang="ja">コイル</name>
<name lang="ko">코일</name>
<name lang="mn">ороомог</name>
<name lang="nb">Spole</name>
<name lang="nl">spoel</name>
<name lang="nl_BE">Spoel</name>
<name lang="pl">cewka</name>
<name lang="pt">bobina</name>
<name lang="pt_BR">bobina</name>
<name lang="ro">bobina</name>
<name lang="ru">катушка</name>
<name lang="sk">Cievka</name>
<name lang="sl">tuljava</name>
<name lang="sr">калем</name>
<name lang="sv">spole</name>
<name lang="tr">bobin</name>
<name lang="uk">котушка</name>
<name lang="zh">线圈</name>
</names>
<element name="bobine3.elmt">
<definition height="60" hotspot_x="20" hotspot_y="32" link_type="master" type="element" version="0.100.0" width="40">
<uuid uuid="{793302b1-e96a-f7f8-70bc-dec53eeaab5b}"/>
<names>
<name lang="ar">ملف</name>
<name lang="ca">Bobina</name>
<name lang="cs">Cívka relé</name>
<name lang="da">Relæ</name>
<name lang="de">Spule</name>
<name lang="el">Πηνίο</name>
<name lang="en">Coil</name>
<name lang="es">Bobina</name>
<name lang="fi">Käämi</name>
<name lang="fr">Bobine</name>
<name lang="hr">Zavojnica</name>
<name lang="hu">Tekercs</name>
<name lang="it">Bobina</name>
<name lang="ja">コイル</name>
<name lang="ko">코일</name>
<name lang="mn">Ороомог</name>
<name lang="nb">Spole</name>
<name lang="nl">Spoel</name>
<name lang="nl_BE">Relais spoel (algemeen)</name>
<name lang="pl">Cewka</name>
<name lang="pt">Bobina</name>
<name lang="pt_BR">Bobina</name>
<name lang="ro">Bobina</name>
<name lang="ru">Обмотка</name>
<name lang="sk">Cievka</name>
<name lang="sl">Tuljava</name>
<name lang="sr">Каlem</name>
<name lang="sv">Spole</name>
<name lang="tr">Bobin</name>
<name lang="uk">Котушка</name>
<name lang="zh">三线圈</name>
</names>
<kindInformations>
<kindInformation name="type">coil</kindInformation>
</kindInformations>
<informations>Author: The QElectroTech team
License: see http://qelectrotech.org/wiki/doc/elements_license</informations>
<description>
<rect antialias="false" height="16" rx="0" ry="0" style="line-style:normal;line-weight:normal;filling:none;color:black" width="28" x="-14" y="-8"/>
<line antialias="false" end1="none" end2="none" length1="1.5" length2="1.5" style="line-style:normal;line-weight:normal;filling:none;color:black" x1="0" x2="0" y1="-20" y2="-8"/>
<dynamic_text Halignment="AlignLeft" Valignment="AlignTop" font="Liberation Sans,4,-1,5,25,0,0,0,0,0,Regular" frame="false" keep_visual_rotation="false" rotation="0" text_from="UserText" text_width="-1" uuid="{0a58bdda-9173-48ba-acaa-35f2dc00c4bb}" x="2" y="11.33" z="3">
<text>A2</text>
</dynamic_text>
<dynamic_text Halignment="AlignLeft" Valignment="AlignTop" font="Liberation Sans,4,-1,5,25,0,0,0,0,0,Regular" frame="false" keep_visual_rotation="false" rotation="0" text_from="UserText" text_width="-1" uuid="{7e98ddfe-fde0-4715-a926-9f60cf750078}" x="2" y="-24.67" z="4">
<text>A1</text>
</dynamic_text>
<line antialias="false" end1="none" end2="none" length1="1.5" length2="1.5" style="line-style:normal;line-weight:normal;filling:none;color:black" x1="0" x2="0" y1="8" y2="20"/>
<dynamic_text Halignment="AlignLeft" Valignment="AlignTop" font="Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular" frame="false" keep_visual_rotation="false" rotation="0" text_from="ElementInfo" text_width="-1" uuid="{3ef105d2-add6-4c4b-a4cd-120c40096202}" x="25" y="-9.17" z="6">
<text/>
<info_name>label</info_name>
</dynamic_text>
<terminal name="A1" orientation="n" type="Generic" uuid="{8d0fa333-2d98-4a75-8a4e-21c81cce7ec3}" x="0" y="-20"/>
<terminal name="A2" orientation="s" type="Generic" uuid="{c5376fd7-bdf1-4c10-985a-0d7d5f52c8f9}" x="0" y="20"/>
</description>
</definition>
</element>
</category>
<category name="02_contacts_cross_referencing">
<names>
<name lang="ar">تماسات مع إحالات</name>
<name lang="ca">Contactes amb referències creuades</name>
<name lang="cs">Kontakty s křížovými odkazy</name>
<name lang="da">Kontaker med krydsreference</name>
<name lang="de">Kontakte mit Querverweis</name>
<name lang="el">Επαφές με παραπομπές</name>
<name lang="en">Contacts with cross referencing</name>
<name lang="es">Contactos con referencia cruzada</name>
<name lang="fi">Kosketimet lehtiviittauksella</name>
<name lang="fr">Contacts avec référence croisée</name>
<name lang="hr">Kontakti s unakrsnim referenciranjem</name>
<name lang="hu">Érintkezők kereszthivatkozásokkal</name>
<name lang="it">Contatti con riferimenti incrociati</name>
<name lang="ja">相互参照付き接点</name>
<name lang="ko">교차 참조 접점</name>
<name lang="mn">Харилцан холбоос бүхий контакт</name>
<name lang="nb">Kontakter med kryssreferanse</name>
<name lang="nl">Contacten met kruisreferentie</name>
<name lang="nl_BE">Contacten met kruisverwijzing</name>
<name lang="pl">Zestyki (automatyczne oznaczenia)</name>
<name lang="pt">Contactos com renvoi de folio</name>
<name lang="pt_BR">Contatos com referencia cruzada</name>
<name lang="ro">Contacte cu renvoi de folio</name>
<name lang="ru">Контакты с перекрестными ссылками</name>
<name lang="sk">Kontakty s krížovým odkazom</name>
<name lang="sl">Kontakti s sklici</name>
<name lang="sr">Контакти са унакрсним референцирањем</name>
<name lang="sv">Kontakter med bladhänvisning</name>
<name lang="tr">Çapraz referanslı kontaklar</name>
<name lang="uk">Контакти з перехресними посиланнями</name>
<name lang="zh">触点交叉引用</name>
</names>
<category name="01_auxiliary_contacts">
<names>
<name lang="ar">مساعد</name>
<name lang="ca">Auxiliars</name>
<name lang="cs">Pomocné kontakty</name>
<name lang="da">Hjælpe kontakter</name>
<name lang="de">Hilfsglieder</name>
<name lang="el">Βοηθητικές επαφές</name>
<name lang="en">Auxiliary</name>
<name lang="es">Contactos auxiliares</name>
<name lang="fi">Apupiiri</name>
<name lang="fr">Auxilliaires</name>
<name lang="hr">Pomoćni</name>
<name lang="hu">Segéd</name>
<name lang="it">Contatti ausiliari</name>
<name lang="ja">補助</name>
<name lang="ko">보조 접점</name>
<name lang="mn">Туслах</name>
<name lang="nb">Hjelpe</name>
<name lang="nl">Hulpcontacten</name>
<name lang="nl_BE">Hulpcontacten</name>
<name lang="pl">Zestyki pomocnicze</name>
<name lang="pt">Auxiliar</name>
<name lang="pt_BR">Auxiliares</name>
<name lang="ro">Auxiliare</name>
<name lang="ru">Внешние</name>
<name lang="sk">Pomocné</name>
<name lang="sl">Pomožni</name>
<name lang="sr">Помоћни</name>
<name lang="sv">Hjälp</name>
<name lang="tr">Yardımcı</name>
<name lang="uk">Допоміжні</name>
<name lang="zh">辅助触点</name>
</names>
<element name="con_simple.elmt">
<definition height="60" hotspot_x="10" hotspot_y="30" link_type="slave" type="element" version="0.100.0" width="20">
<uuid uuid="{9d34fad7-8a48-2a3e-df68-74d26a8f1012}"/>
<names>
<name lang="ar">مُلامس بسيط (NO)</name>
<name lang="ca">Contacte senzill</name>
<name lang="cs">Spínací kontakt</name>
<name lang="da">Enkel kontakt</name>
<name lang="de">Schliesser</name>
<name lang="el">Απλή επαφή, ανοικτή</name>
<name lang="en">Simple contact</name>
<name lang="es">Contactor simple</name>
<name lang="fi">Yksinkertainen kosketin</name>
<name lang="fr">Contact simple</name>
<name lang="hr">Jednostavni kontakt</name>
<name lang="hu">Egyszerű érintkező</name>
<name lang="it">Contatto semplice (NO)</name>
<name lang="ja">単純接点</name>
<name lang="ko">단순 접점</name>
<name lang="mn">Энгийн контакт</name>
<name lang="nb">Enkel kontakt</name>
<name lang="nl">contact enkel</name>
<name lang="nl_BE">Eenvoudig contact</name>
<name lang="pl">Zestyk zwierny</name>
<name lang="pt">Contacto simples (NO)</name>
<name lang="pt_BR">Contato simples</name>
<name lang="ro">Contact simplu</name>
<name lang="ru">Проктой контакт (НР)</name>
<name lang="sk">Jednoduchý kontakt</name>
<name lang="sl">Enostavni kontakt</name>
<name lang="sr">Прости контакт</name>
<name lang="sv">Enkelkontakt</name>
<name lang="tr">Basit kontak</name>
<name lang="uk">Простий контакт</name>
<name lang="zh">简单连接器</name>
</names>
<kindInformations>
<kindInformation name="type">simple</kindInformation>
<kindInformation name="state">NO</kindInformation>
<kindInformation name="number">1</kindInformation>
</kindInformations>
<informations>Author: The QElectroTech team
License: see http://qelectrotech.org/wiki/doc/elements_license</informations>
<description>
<polygon antialias="true" closed="false" style="line-style:normal;line-weight:normal;filling:none;color:black" x1="-5" x2="0" x3="0" y1="-10" y2="10" y3="20"/>
<dynamic_text Halignment="AlignLeft" Valignment="AlignTop" font="Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular" frame="false" keep_visual_rotation="false" rotation="0" text_from="ElementInfo" text_width="-1" uuid="{a4a5addc-2580-413c-828c-6e93cfe494ef}" x="2.75" y="-9.5" z="2">
<text/>
<info_name>label</info_name>
</dynamic_text>
<line antialias="false" end1="none" end2="none" length1="1.5" length2="1.5" style="line-style:normal;line-weight:normal;filling:none;color:black" x1="0" x2="0" y1="-20" y2="-10"/>
<terminal name="" orientation="n" type="Generic" uuid="{b3b3dc63-1c98-409b-ab79-3dbaea3556cb}" x="0" y="-20"/>
<terminal name="" orientation="s" type="Generic" uuid="{b9683546-f0a2-4386-b114-ef1d46b2df3d}" x="0" y="20"/>
</description>
</definition>
</element>
</category>
</category>
</category>
</category>
</category>
</category>
</collection>
</project>
+95
View File
@@ -802,6 +802,101 @@ private slots:
QStringLiteral("<property [^>]*name=\"accents\"[^>]*>Armoire façade été</property>"))),
"the accented value changed");
}
// What a folio cannot load is saved back as it was read (F127, F128).
// fixtures/coil_contact_wired.qet: a coil K1 and its contact, linked,
// two wires between them. Each row damages it as an uploaded project
// can be: the coil's definition missing from the file, or the
// contact's definition redrawn (terminals moved, other uuids). A save
// keeps both symbols, both wires and both links, a second save changes
// nothing, and once the coil's definition is back the save is the
// undamaged project's again.
void unloadableItemsKept_data()
{
QTest::addColumn<QString>("damage");
QTest::newRow("definition missing") << QStringLiteral("coil");
QTest::newRow("terminals redrawn") << QStringLiteral("contact");
}
void unloadableItemsKept()
{
QFETCH(QString, damage);
const QString fixture = QFINDTESTDATA("fixtures/coil_contact_wired.qet");
const QString whole = resave(fixture);
QVERIFY2(!whole.isEmpty(), "--resave of the fixture failed");
const QString xml = QString::fromUtf8(read(whole));
static const QRegularExpression coil_definition(QStringLiteral(
"<element name=\"bobine3\\.elmt\">.*?</element>\\s*"),
QRegularExpression::DotMatchesEverythingOption);
const QString coil = coil_definition.match(xml).captured(0);
QVERIFY(!coil.isEmpty());
QString damaged = xml;
if (damage == QLatin1String("coil")) {
damaged.remove(coil);
} else {
//The contact's terminals 40 further right, with new uuids
static const QRegularExpression contact_definition(QStringLiteral(
"<element name=\"con_simple\\.elmt\">.*?</element>"),
QRegularExpression::DotMatchesEverythingOption);
QString contact = contact_definition.match(xml).captured(0);
QVERIFY(!contact.isEmpty());
const QString before = contact;
static const QRegularExpression terminal(QStringLiteral(
"(<terminal [^>]*uuid=\"\\{)[0-9a-f]{8}([^>]*\\bx=\")(-?[0-9.]+)"));
int n = 0;
for (auto it = terminal.globalMatch(before); it.hasNext() ; ++n) {
const auto m = it.next();
contact.replace(m.captured(0), m.captured(1) + QStringLiteral("%1").arg(n, 8, 10, QLatin1Char('0'))
+ m.captured(2) + QString::number(m.captured(3).toDouble() + 40));
}
QCOMPARE(n, 2);
damaged.replace(before, contact);
}
const QString damaged_path = m_dir.filePath(QStringLiteral("damaged%1.qet").arg(m_run));
QFile out(damaged_path);
QVERIFY(out.open(QIODevice::WriteOnly));
out.write(damaged.toUtf8());
out.close();
auto count = [](const QString &text, const char *pattern) {
return int(text.count(QRegularExpression(QString::fromLatin1(pattern))));
};
const QString first = resave(damaged_path);
QVERIFY2(!first.isEmpty(), "--resave of the damaged project failed");
const QString saved = QString::fromUtf8(read(first));
QCOMPARE(count(saved, "<element [^>]*\\btype="), 2);
QCOMPARE(count(saved, "<conductor "), 2);
QCOMPARE(count(saved, "<link_uuid "), 2);
const QString second = resave(first);
QVERIFY2(!second.isEmpty(), "second --resave failed");
QVERIFY2(read(second) == read(first), "the second save changed the file");
if (damage != QLatin1String("coil")) return;
//The definition back: the coil and its wires are built again
static const QRegularExpression collection_category(QStringLiteral(
"(<category name=\"01_coils\">\\s*<names>.*?</names>\\s*)"),
QRegularExpression::DotMatchesEverythingOption);
QString mended = saved;
QVERIFY(collection_category.match(mended).hasMatch());
mended.replace(collection_category, QStringLiteral("\\1") + coil);
const QString mended_path = m_dir.filePath(QStringLiteral("mended%1.qet").arg(m_run));
QFile mend(mended_path);
QVERIFY(mend.open(QIODevice::WriteOnly));
mend.write(mended.toUtf8());
mend.close();
const QString back = resave(mended_path);
QVERIFY2(!back.isEmpty(), "--resave of the mended project failed");
auto items = [](const QString &text, const char *tag) {
QStringList found;
static const QRegularExpression noise(QStringLiteral(" (element[12]_(label|linked|name)|terminalname[12])=\"[^\"]*\""));
auto it = QRegularExpression(QStringLiteral("<%1 [^>]*>").arg(QLatin1String(tag))).globalMatch(text);
while (it.hasNext()) found << it.next().captured(0).remove(noise);
found.sort();
return found;
};
const QString again = QString::fromUtf8(read(back));
QCOMPARE(items(again, "element"), items(xml, "element"));
QCOMPARE(items(again, "conductor"), items(xml, "conductor"));
}
};
QTEST_APPLESS_MAIN(tst_resaveunchanged)