diff --git a/sources/cli_export.cpp b/sources/cli_export.cpp index 439b328f6..034005d03 100644 --- a/sources/cli_export.cpp +++ b/sources/cli_export.cpp @@ -70,6 +70,7 @@ const QHash &exportFlags() {"--export-cables", "cables"}, {"--export-wires", "wires"}, {"--export-bom", "bom"}, + {"--export-wiring", "wiring"}, {"--export-nets", "nets"}, {"--export-links", "links"}, {"--info", "info"}, @@ -543,6 +544,54 @@ QHash folioIndex(QETProject &project) return folio; } +/// From-to wiring list: one row per conductor, each endpoint resolved to its +/// element label and terminal name. +/// +/// Reads wiring_list_view out of the project database. --export-cables produces +/// the same logical list from the document XML instead, and the two are meant +/// to agree: running both and diffing them is a direct check that the database +/// still describes the project, which is otherwise only observable through the +/// GUI. +int exportWiring(QETProject &project, const QString &output) +{ + // The project database is built lazily; force a (re)build before querying. + project.dataBase()->updateDB(); + + static const QStringList columns { + "wire_number", "from_element_label", "from_terminal", + "to_element_label", "to_terminal", "diagram_position", "conductor_uuid" + }; + + QSqlQuery query = project.dataBase()->newQuery( + "SELECT " % columns.join(", ") % + " FROM wiring_list_view ORDER BY diagram_position, wire_number"); + if (!query.exec()) { + err << "Wiring list query failed: " << query.lastError().text() << "\n"; + return 1; + } + + QString csv = columns.join(";") % "\n"; + int rows = 0; + while (query.next()) { + QStringList values; + for (int i = 0; i < columns.size(); ++i) + values << csvField(query.value(i).toString()); + csv += values.join(";") % "\n"; + ++rows; + } + + QFile file(output); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + err << "Cannot open '" << output << "' for writing.\n"; + return 1; + } + QTextStream fout(&file); + fout << csv; + file.close(); + out << "Exported " << rows << " conductor(s) -> " << output << "\n"; + return 0; +} + /// Electrical nets: groups of terminals joined into one potential. /// Walks QET's own potential graph, so each net is a connected component /// of terminals across all folios. The ground truth for connectivity. @@ -847,6 +896,8 @@ int run(const QStringList &args) return exportCsv(project, format, output); if (format == "bom") return exportBom(project, output); + if (format == "wiring") + return exportWiring(project, output); if (format == "nets") return exportNets(project, output); if (format == "links") diff --git a/sources/cli_export.h b/sources/cli_export.h index fefb5b97a..2c2bc7d81 100644 --- a/sources/cli_export.h +++ b/sources/cli_export.h @@ -48,6 +48,7 @@ namespace CLIExport { qelectrotech --export-cables qelectrotech --export-wires qelectrotech --export-bom + qelectrotech --export-wiring qelectrotech --export-nets qelectrotech --export-links qelectrotech --info [output.json] @@ -64,6 +65,11 @@ namespace CLIExport { cables: wiring list (one row per conductor) as CSV. wires: list of distinct wire numbers as CSV. bom: bill of materials (one row per element) as CSV. + wiring: from-to wiring list (one row per conductor) as CSV, read from + the project database. Same logical list as `cables`, which + reads the document XML instead; the two are meant to agree, + so diffing them checks that the database still describes the + project. nets: electrical nets (connected-terminal groups) as JSON. links: element cross-references (coil/contact) as CSV, with unresolved links flagged. diff --git a/sources/dataBase/projectdatabase.cpp b/sources/dataBase/projectdatabase.cpp index 124176f90..333afe22f 100644 --- a/sources/dataBase/projectdatabase.cpp +++ b/sources/dataBase/projectdatabase.cpp @@ -550,6 +550,7 @@ bool projectDataBase::createDataBase() createElementNomenclatureView(); createSummaryView(); + createWiringListView(); prepareQuery(); updateDB(); return true; @@ -667,6 +668,64 @@ void projectDataBase::createSummaryView() } } +/** + @brief projectDataBase::createWiringListView + A from-to wiring list: one row per conductor, each endpoint resolved to + its element label and terminal name. + + Two deliberate differences from an ordinary inner-join view like + element_nomenclature_view: + + - No join to the element table. A terminal row already carries its + element_uuid, so joining element back just to read the same uuid adds + nothing -- and would actively drop rows, because populateElementTable() + only inserts elements matching Simple|Terminal|Master|Thumbnail. Slave + elements (relay contacts and the like, extremely common at the end of a + wire) and report elements are absent from that table after a project + load, so an inner join through it silently loses their conductors. + - element_info is LEFT joined for the same reason. A wire whose endpoint + element carries no info row still belongs in a wiring list; it comes + back with an empty label rather than vanishing. Losing a wire from a + wiring list is a worse failure than showing one with a blank end. + + - diagram is LEFT joined for the same reason. It should + always match, since QETProject::diagramAdded is wired to addDiagram() + and a conductor cannot exist before its folio -- but an inner join here + would make that an assumption the view silently enforces, and a wire + missing from a wiring list is the one failure this view must not have. + + The result is that this view returns exactly as many rows as the + conductor table holds -- what is already excluded upstream (conductors + on legacy terminals without uuids) stays excluded, and nothing new is + dropped here. Only the terminal joins are inner, and both are guaranteed + by insertTerminal() running for each endpoint before the conductor row + is written. +*/ +void projectDataBase::createWiringListView() +{ + QString create_view ("CREATE VIEW wiring_list_view AS SELECT " + "c.uuid AS conductor_uuid," + "c.text AS wire_number," + "t1.element_uuid AS from_element_uuid," + "ei1.label AS from_element_label," + "t1.name AS from_terminal," + "t2.element_uuid AS to_element_uuid," + "ei2.label AS to_element_label," + "t2.name AS to_terminal," + "d.pos AS diagram_position" + " FROM conductor c" + " JOIN terminal t1 ON c.terminal1_uuid = t1.uuid AND c.terminal1_element_uuid = t1.element_uuid" + " JOIN terminal t2 ON c.terminal2_uuid = t2.uuid AND c.terminal2_element_uuid = t2.element_uuid" + " LEFT JOIN element_info ei1 ON t1.element_uuid = ei1.element_uuid" + " LEFT JOIN element_info ei2 ON t2.element_uuid = ei2.element_uuid" + " LEFT JOIN diagram d ON c.diagram_uuid = d.uuid"); + + QSqlQuery query(m_data_base); + if (!query.exec(create_view)) { + qDebug() << query.lastError(); + } +} + void projectDataBase::populateDiagramTable() { QSqlQuery query_(m_data_base); diff --git a/sources/dataBase/projectdatabase.h b/sources/dataBase/projectdatabase.h index 9b5505b4c..091daf36f 100644 --- a/sources/dataBase/projectdatabase.h +++ b/sources/dataBase/projectdatabase.h @@ -77,6 +77,7 @@ class projectDataBase : public QObject bool createDataBase(); void createElementNomenclatureView(); void createSummaryView(); + void createWiringListView(); void populateDiagramTable(); void populateElementTable(); void populateElementInfoTable();