mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-06-15 01:13:14 +02:00
Merge pull request #489 from ispyisail/cli-tools
CLI: add verification & data-export tools (info, BOM, nets, links, check-elements, resave)
This commit is contained in:
+439
-10
@@ -19,15 +19,29 @@
|
|||||||
|
|
||||||
#include "bordertitleblock.h"
|
#include "bordertitleblock.h"
|
||||||
#include "conductornumexport.h"
|
#include "conductornumexport.h"
|
||||||
|
#include "conductorproperties.h"
|
||||||
|
#include "dataBase/projectdatabase.h"
|
||||||
#include "diagram.h"
|
#include "diagram.h"
|
||||||
|
#include "diagramcontext.h"
|
||||||
|
#include "qetgraphicsitem/conductor.h"
|
||||||
|
#include "qetgraphicsitem/element.h"
|
||||||
|
#include "qetgraphicsitem/terminal.h"
|
||||||
#include "qetproject.h"
|
#include "qetproject.h"
|
||||||
#include "wiringlistexport.h"
|
#include "wiringlistexport.h"
|
||||||
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
|
#include <QDirIterator>
|
||||||
|
#include <QDomDocument>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QPdfWriter>
|
#include <QPdfWriter>
|
||||||
|
#include <QSet>
|
||||||
|
#include <QSqlError>
|
||||||
|
#include <QSqlQuery>
|
||||||
#include <QSvgGenerator>
|
#include <QSvgGenerator>
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
|
|
||||||
@@ -36,7 +50,7 @@ namespace {
|
|||||||
QTextStream out(stdout);
|
QTextStream out(stdout);
|
||||||
QTextStream err(stderr);
|
QTextStream err(stderr);
|
||||||
|
|
||||||
/// All export option flags, mapped to a short format name.
|
/// All CLI option flags, mapped to a short format name.
|
||||||
const QHash<QString, QString> &exportFlags()
|
const QHash<QString, QString> &exportFlags()
|
||||||
{
|
{
|
||||||
static const QHash<QString, QString> flags {
|
static const QHash<QString, QString> flags {
|
||||||
@@ -45,10 +59,23 @@ const QHash<QString, QString> &exportFlags()
|
|||||||
{"--export-svg", "svg"},
|
{"--export-svg", "svg"},
|
||||||
{"--export-cables", "cables"},
|
{"--export-cables", "cables"},
|
||||||
{"--export-wires", "wires"},
|
{"--export-wires", "wires"},
|
||||||
|
{"--export-bom", "bom"},
|
||||||
|
{"--export-nets", "nets"},
|
||||||
|
{"--export-links", "links"},
|
||||||
|
{"--info", "info"},
|
||||||
|
{"--check-elements", "check"},
|
||||||
|
{"--resave", "resave"},
|
||||||
};
|
};
|
||||||
return flags;
|
return flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Device tag of an element ("K1", "Q55"), falling back to its name.
|
||||||
|
QString elementLabel(Element *element)
|
||||||
|
{
|
||||||
|
const QString label = element->elementInformations()["label"].toString();
|
||||||
|
return label.isEmpty() ? element->name() : label;
|
||||||
|
}
|
||||||
|
|
||||||
/// Pixel rect of a diagram's border + title block (the printable page area).
|
/// Pixel rect of a diagram's border + title block (the printable page area).
|
||||||
QRect diagramRect(Diagram *diagram)
|
QRect diagramRect(Diagram *diagram)
|
||||||
{
|
{
|
||||||
@@ -195,6 +222,381 @@ int exportCsv(QETProject &project, const QString &format, const QString &output)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Quote a field for CSV output (RFC-4180 style, ';' delimiter).
|
||||||
|
QString csvField(const QString &value)
|
||||||
|
{
|
||||||
|
if (value.contains(';') || value.contains('"')
|
||||||
|
|| value.contains('\n') || value.contains('\r')) {
|
||||||
|
QString v = value;
|
||||||
|
v.replace('"', "\"\"");
|
||||||
|
return '"' % v % '"';
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bill of materials: one row per element, key component-data fields.
|
||||||
|
/// Pulls from QET's own project database (the same source as the GUI BOM
|
||||||
|
/// export), so the output matches what the editor produces.
|
||||||
|
int exportBom(QETProject &project, const QString &output)
|
||||||
|
{
|
||||||
|
// The project database is built lazily; force a (re)build before querying.
|
||||||
|
project.dataBase()->updateDB();
|
||||||
|
|
||||||
|
static const QStringList columns {
|
||||||
|
"label", "designation", "manufacturer", "manufacturer_reference",
|
||||||
|
"quantity", "location", "function", "title", "folio"
|
||||||
|
};
|
||||||
|
|
||||||
|
QSqlQuery query = project.dataBase()->newQuery(
|
||||||
|
"SELECT " % columns.join(", ") %
|
||||||
|
" FROM element_nomenclature_view ORDER BY label");
|
||||||
|
if (!query.exec()) {
|
||||||
|
err << "BOM 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 << " component(s) -> " << output << "\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Count terminals on @p element that no conductor connects to.
|
||||||
|
int freeTerminals(Element *element)
|
||||||
|
{
|
||||||
|
int free = 0;
|
||||||
|
const QList<Terminal *> terminals = element->terminals();
|
||||||
|
for (Terminal *t : terminals)
|
||||||
|
if (t->conductorsCount() == 0)
|
||||||
|
++free;
|
||||||
|
return free;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Structural ground-truth dump of a project, as JSON, to stdout (or a file).
|
||||||
|
/// Uses QET's own loaded model, so it reports what the editor actually sees:
|
||||||
|
/// per-page element / conductor counts and unconnected terminals.
|
||||||
|
int exportInfo(QETProject &project, const QString &output)
|
||||||
|
{
|
||||||
|
const QList<Diagram *> diagrams = project.diagrams();
|
||||||
|
|
||||||
|
int total_elements = 0, total_conductors = 0, total_free = 0;
|
||||||
|
QJsonArray pages;
|
||||||
|
int index = 0;
|
||||||
|
for (Diagram *diagram : diagrams) {
|
||||||
|
++index;
|
||||||
|
const QList<Element *> elements = diagram->elements();
|
||||||
|
const int conductors = diagram->conductors().size();
|
||||||
|
int page_free = 0;
|
||||||
|
for (Element *e : elements)
|
||||||
|
page_free += freeTerminals(e);
|
||||||
|
|
||||||
|
const QRect r = diagramRect(diagram);
|
||||||
|
QJsonObject page;
|
||||||
|
page["index"] = index;
|
||||||
|
page["title"] = diagram->title();
|
||||||
|
page["folio"] = QStringLiteral("%1 of %2")
|
||||||
|
.arg(index).arg(diagrams.size());
|
||||||
|
page["width_px"] = r.width();
|
||||||
|
page["height_px"] = r.height();
|
||||||
|
page["elements"] = elements.size();
|
||||||
|
page["conductors"] = conductors;
|
||||||
|
page["free_terminals"] = page_free;
|
||||||
|
pages.append(page);
|
||||||
|
|
||||||
|
total_elements += elements.size();
|
||||||
|
total_conductors += conductors;
|
||||||
|
total_free += page_free;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject root;
|
||||||
|
root["project"] = project.title();
|
||||||
|
root["diagrams"] = diagrams.size();
|
||||||
|
root["elements"] = total_elements;
|
||||||
|
root["conductors"] = total_conductors;
|
||||||
|
root["free_terminals"] = total_free;
|
||||||
|
root["pages"] = pages;
|
||||||
|
|
||||||
|
const QByteArray json =
|
||||||
|
QJsonDocument(root).toJson(QJsonDocument::Indented);
|
||||||
|
|
||||||
|
if (output.isEmpty()) {
|
||||||
|
out << QString::fromUtf8(json);
|
||||||
|
} else {
|
||||||
|
QFile file(output);
|
||||||
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||||
|
err << "Cannot open '" << output << "' for writing.\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
file.write(json);
|
||||||
|
file.close();
|
||||||
|
out << "Wrote project info -> " << output << "\n";
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validate one .elmt file against QET's element schema.
|
||||||
|
/// @return 0 = OK, 1 = warning (loads but suspicious), 2 = failure.
|
||||||
|
int checkOneElement(const QString &path)
|
||||||
|
{
|
||||||
|
QFile file(path);
|
||||||
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
|
out << "FAIL " << path << " (cannot open)\n";
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
QDomDocument doc;
|
||||||
|
QString error;
|
||||||
|
int line = 0;
|
||||||
|
if (!doc.setContent(&file, &error, &line)) {
|
||||||
|
file.close();
|
||||||
|
out << "FAIL " << path << " (XML error line "
|
||||||
|
<< line << ": " << error << ")\n";
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
const QDomElement root = doc.documentElement();
|
||||||
|
if (root.tagName() != "definition" || root.attribute("type") != "element") {
|
||||||
|
out << "FAIL " << path << " (root is not <definition type=\"element\">)\n";
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool w_ok = false, h_ok = false;
|
||||||
|
const double w = root.attribute("width").toDouble(&w_ok);
|
||||||
|
const double h = root.attribute("height").toDouble(&h_ok);
|
||||||
|
if (!w_ok || !h_ok || w == 0 || h == 0) {
|
||||||
|
out << "FAIL " << path << " (missing/zero bounding box "
|
||||||
|
<< root.attribute("width") << "x"
|
||||||
|
<< root.attribute("height") << ")\n";
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int terminals = root.elementsByTagName("terminal").count();
|
||||||
|
|
||||||
|
// Negative dimensions are malformed but QET still loads them; surface as a
|
||||||
|
// warning rather than a failure so this agrees with QET's own loader.
|
||||||
|
if (w < 0 || h < 0) {
|
||||||
|
out << "WARN " << path << " (negative bounding box "
|
||||||
|
<< w << "x" << h << ", " << terminals << " terminals)\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (terminals == 0) {
|
||||||
|
out << "WARN " << path << " (loads, but 0 terminals)\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
out << "OK " << path << " (" << terminals << " terminals)\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validate a single .elmt file or every .elmt under a directory.
|
||||||
|
int checkElements(const QString &path)
|
||||||
|
{
|
||||||
|
QStringList files;
|
||||||
|
const QFileInfo info(path);
|
||||||
|
if (info.isDir()) {
|
||||||
|
QDirIterator it(path, {"*.elmt"}, QDir::Files,
|
||||||
|
QDirIterator::Subdirectories);
|
||||||
|
while (it.hasNext())
|
||||||
|
files << it.next();
|
||||||
|
files.sort();
|
||||||
|
} else if (info.isFile()) {
|
||||||
|
files << path;
|
||||||
|
} else {
|
||||||
|
err << "Not found: " << path << "\n";
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (files.isEmpty()) {
|
||||||
|
err << "No .elmt files found under: " << path << "\n";
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
int warnings = 0, failures = 0;
|
||||||
|
for (const QString &f : files) {
|
||||||
|
const int r = checkOneElement(f);
|
||||||
|
if (r == 1) ++warnings;
|
||||||
|
else if (r == 2) ++failures;
|
||||||
|
}
|
||||||
|
out << files.size() << " file(s), " << warnings
|
||||||
|
<< " warning(s), " << failures << " failure(s)\n";
|
||||||
|
return failures > 0 ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Map every element in the project to its 1-based folio (page) position.
|
||||||
|
QHash<Element *, int> folioIndex(QETProject &project)
|
||||||
|
{
|
||||||
|
QHash<Element *, int> folio;
|
||||||
|
int index = 0;
|
||||||
|
const QList<Diagram *> diagrams = project.diagrams();
|
||||||
|
for (Diagram *diagram : diagrams) {
|
||||||
|
++index;
|
||||||
|
const QList<Element *> elements = diagram->elements();
|
||||||
|
for (Element *e : elements)
|
||||||
|
folio.insert(e, index);
|
||||||
|
}
|
||||||
|
return folio;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
int exportNets(QETProject &project, const QString &output)
|
||||||
|
{
|
||||||
|
const QHash<Element *, int> folio = folioIndex(project);
|
||||||
|
|
||||||
|
QList<Conductor *> all_conductors;
|
||||||
|
const QList<Diagram *> diagrams = project.diagrams();
|
||||||
|
for (Diagram *diagram : diagrams)
|
||||||
|
all_conductors << diagram->conductors();
|
||||||
|
|
||||||
|
QSet<Conductor *> visited;
|
||||||
|
QJsonArray nets;
|
||||||
|
int net_no = 0;
|
||||||
|
for (Conductor *c : all_conductors) {
|
||||||
|
if (visited.contains(c))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// The whole potential this conductor belongs to. relatedPotential-
|
||||||
|
// Conductors() also fills t_list with every terminal in the net
|
||||||
|
// (following folio reports and terminal blocks too).
|
||||||
|
QList<Terminal *> t_list;
|
||||||
|
QSet<Conductor *> group = c->relatedPotentialConductors(true, &t_list);
|
||||||
|
group.insert(c);
|
||||||
|
for (Conductor *g : group)
|
||||||
|
visited.insert(g);
|
||||||
|
if (c->terminal1) t_list << c->terminal1;
|
||||||
|
if (c->terminal2) t_list << c->terminal2;
|
||||||
|
|
||||||
|
// Wire number: smallest non-empty conductor text (deterministic).
|
||||||
|
QStringList wire_nos;
|
||||||
|
for (Conductor *g : group)
|
||||||
|
if (!g->properties().text.isEmpty())
|
||||||
|
wire_nos << g->properties().text;
|
||||||
|
wire_nos.sort();
|
||||||
|
|
||||||
|
++net_no;
|
||||||
|
QJsonArray terminals;
|
||||||
|
QSet<Terminal *> seen;
|
||||||
|
for (Terminal *t : t_list) {
|
||||||
|
if (!t || seen.contains(t))
|
||||||
|
continue;
|
||||||
|
seen.insert(t);
|
||||||
|
Element *pe = t->parentElement();
|
||||||
|
QJsonObject to;
|
||||||
|
to["element"] = pe ? elementLabel(pe) : QString();
|
||||||
|
to["terminal"] = t->name();
|
||||||
|
to["folio"] = pe ? folio.value(pe, 0) : 0;
|
||||||
|
terminals.append(to);
|
||||||
|
}
|
||||||
|
QJsonObject net;
|
||||||
|
net["net"] = net_no;
|
||||||
|
net["wire_no"] = wire_nos.value(0);
|
||||||
|
net["terminals"] = terminals;
|
||||||
|
nets.append(net);
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject root;
|
||||||
|
root["project"] = project.title();
|
||||||
|
root["nets"] = nets.size();
|
||||||
|
root["list"] = nets;
|
||||||
|
|
||||||
|
QFile file(output);
|
||||||
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||||
|
err << "Cannot open '" << output << "' for writing.\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
file.write(QJsonDocument(root).toJson(QJsonDocument::Indented));
|
||||||
|
file.close();
|
||||||
|
out << "Exported " << nets.size() << " net(s) -> " << output << "\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cross-references: each linkable element (coil / contact / report) and the
|
||||||
|
/// elements it links to, flagging masters/slaves with no link as unresolved.
|
||||||
|
int exportLinks(QETProject &project, const QString &output)
|
||||||
|
{
|
||||||
|
const QHash<Element *, int> folio = folioIndex(project);
|
||||||
|
|
||||||
|
QString csv("element;link_type;linked_to;folio;status\n");
|
||||||
|
int linkable = 0, unresolved = 0;
|
||||||
|
|
||||||
|
const QList<Diagram *> diagrams = project.diagrams();
|
||||||
|
for (Diagram *diagram : diagrams) {
|
||||||
|
const QList<Element *> elements = diagram->elements();
|
||||||
|
for (Element *e : elements) {
|
||||||
|
if (e->linkType() == Element::Simple)
|
||||||
|
continue;
|
||||||
|
++linkable;
|
||||||
|
|
||||||
|
const QList<Element *> linked = e->linkedElements();
|
||||||
|
QStringList names;
|
||||||
|
for (Element *le : linked)
|
||||||
|
names << elementLabel(le) % "(f"
|
||||||
|
% QString::number(folio.value(le, 0)) % ")";
|
||||||
|
|
||||||
|
QString status = "linked";
|
||||||
|
if ((e->linkType() == Element::Master
|
||||||
|
|| e->linkType() == Element::Slave)
|
||||||
|
&& linked.isEmpty()) {
|
||||||
|
status = "UNRESOLVED";
|
||||||
|
++unresolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
csv += csvField(elementLabel(e)) % ";"
|
||||||
|
% e->linkTypeToString() % ";"
|
||||||
|
% csvField(names.join(", ")) % ";"
|
||||||
|
% QString::number(folio.value(e, 0)) % ";"
|
||||||
|
% status % "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 " << linkable << " linkable element(s), "
|
||||||
|
<< unresolved << " unresolved -> " << output << "\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Round-trip: load the project and write its XML back out, so an external
|
||||||
|
/// diff can reveal markup QET silently normalises (tolerated-but-invalid XML).
|
||||||
|
int resaveProject(QETProject &project, const QString &output)
|
||||||
|
{
|
||||||
|
const QDomDocument doc = project.toXml();
|
||||||
|
QFile file(output);
|
||||||
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||||
|
err << "Cannot open '" << output << "' for writing.\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
QTextStream fout(&file);
|
||||||
|
fout << doc.toString(4);
|
||||||
|
file.close();
|
||||||
|
out << "Re-saved project -> " << output << "\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
|
|
||||||
namespace CLIExport {
|
namespace CLIExport {
|
||||||
@@ -209,21 +611,31 @@ bool isExportRequest(const QStringList &args)
|
|||||||
|
|
||||||
int run(const QStringList &args)
|
int run(const QStringList &args)
|
||||||
{
|
{
|
||||||
QString flag, input, output;
|
QString flag;
|
||||||
|
QStringList rest;
|
||||||
for (int i = 0; i < args.size(); ++i) {
|
for (int i = 0; i < args.size(); ++i) {
|
||||||
if (exportFlags().contains(args.at(i))) {
|
if (exportFlags().contains(args.at(i))) {
|
||||||
flag = args.at(i);
|
flag = args.at(i);
|
||||||
if (i + 2 < args.size()) {
|
for (int j = i + 1; j < args.size(); ++j)
|
||||||
input = args.at(i + 1);
|
rest << args.at(j);
|
||||||
output = args.at(i + 2);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const QString format = exportFlags().value(flag);
|
||||||
|
|
||||||
if (input.isEmpty() || output.isEmpty()) {
|
// --check-elements operates on an element file/directory, not a project.
|
||||||
err << "Usage: qelectrotech " << flag
|
if (format == "check") {
|
||||||
<< " <project.qet> <output>\n";
|
if (rest.isEmpty()) {
|
||||||
|
err << "Usage: qelectrotech --check-elements "
|
||||||
|
"<element.elmt | directory>\n";
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
return checkElements(rest.at(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString input = rest.value(0);
|
||||||
|
if (input.isEmpty()) {
|
||||||
|
err << "Usage: qelectrotech " << flag << " <project.qet> <output>\n";
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
if (!QFileInfo::exists(input)) {
|
if (!QFileInfo::exists(input)) {
|
||||||
@@ -238,11 +650,28 @@ int run(const QStringList &args)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString format = exportFlags().value(flag);
|
// --info writes JSON to stdout, or to an optional output file.
|
||||||
|
if (format == "info")
|
||||||
|
return exportInfo(project, rest.value(1));
|
||||||
|
|
||||||
|
const QString output = rest.value(1);
|
||||||
|
if (output.isEmpty()) {
|
||||||
|
err << "Usage: qelectrotech " << flag
|
||||||
|
<< " <project.qet> <output>\n";
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
if (format == "pdf")
|
if (format == "pdf")
|
||||||
return exportPdf(project, output);
|
return exportPdf(project, output);
|
||||||
if (format == "cables" || format == "wires")
|
if (format == "cables" || format == "wires")
|
||||||
return exportCsv(project, format, output);
|
return exportCsv(project, format, output);
|
||||||
|
if (format == "bom")
|
||||||
|
return exportBom(project, output);
|
||||||
|
if (format == "nets")
|
||||||
|
return exportNets(project, output);
|
||||||
|
if (format == "links")
|
||||||
|
return exportLinks(project, output);
|
||||||
|
if (format == "resave")
|
||||||
|
return resaveProject(project, output);
|
||||||
return exportImages(project, format, output);
|
return exportImages(project, format, output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+19
-5
@@ -42,16 +42,30 @@ namespace CLIExport {
|
|||||||
@return process exit code (0 on success).
|
@return process exit code (0 on success).
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
qelectrotech --export-pdf <project.qet> <output.pdf>
|
qelectrotech --export-pdf <project.qet> <output.pdf>
|
||||||
qelectrotech --export-png <project.qet> <output_dir>
|
qelectrotech --export-png <project.qet> <output_dir>
|
||||||
qelectrotech --export-svg <project.qet> <output_dir>
|
qelectrotech --export-svg <project.qet> <output_dir>
|
||||||
qelectrotech --export-cables <project.qet> <output.csv>
|
qelectrotech --export-cables <project.qet> <output.csv>
|
||||||
qelectrotech --export-wires <project.qet> <output.csv>
|
qelectrotech --export-wires <project.qet> <output.csv>
|
||||||
|
qelectrotech --export-bom <project.qet> <output.csv>
|
||||||
|
qelectrotech --export-nets <project.qet> <output.json>
|
||||||
|
qelectrotech --export-links <project.qet> <output.csv>
|
||||||
|
qelectrotech --info <project.qet> [output.json]
|
||||||
|
qelectrotech --check-elements <element.elmt | directory>
|
||||||
|
qelectrotech --resave <project.qet> <output.qet>
|
||||||
|
|
||||||
PDF: one multi-page document (one diagram per page).
|
PDF: one multi-page document (one diagram per page).
|
||||||
PNG/SVG: one file per diagram, named <output_dir>/<NN>_<title>.<ext>.
|
PNG/SVG: one file per diagram, named <output_dir>/<NN>_<title>.<ext>.
|
||||||
cables: wiring list (one row per conductor) as CSV.
|
cables: wiring list (one row per conductor) as CSV.
|
||||||
wires: list of distinct wire numbers as CSV.
|
wires: list of distinct wire numbers as CSV.
|
||||||
|
bom: bill of materials (one row per element) as CSV.
|
||||||
|
nets: electrical nets (connected-terminal groups) as JSON.
|
||||||
|
links: element cross-references (coil/contact) as CSV, with
|
||||||
|
unresolved links flagged.
|
||||||
|
info: structural project summary as JSON (stdout, or a file) —
|
||||||
|
per-page element / conductor counts and unconnected terminals.
|
||||||
|
check-elements: validate .elmt file(s) against the element schema.
|
||||||
|
resave: load and rewrite the project XML (round-trip integrity).
|
||||||
*/
|
*/
|
||||||
int run(const QStringList &args);
|
int run(const QStringList &args);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user