mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-11 22:54:12 +02:00
bf9b34398d
Cables are drawn line objects (toolbar button next to the auto break/reconnect button, click-move-click, right click cancels), not components. The type comes from a CSV catalog following the material list pattern (designation, cores, core colours, editable through the entry dialog with "Nouvelle entrée" / "Modifier l'entrée"). Each core is bound to the wire its colour label stands on, and the cable reference is generated per core and written into the Conductor entry via Conductor::setCableReference(label, cable uuid, slot). The conductor entry is where it has to live because the terminal strip plan (Klemmenplan) consumes it in the next step. Numbering: "Cables" rule under Programmeinstellungen/Neues Projekt/Nummerierung auto (mirrored in project properties), literal "W" fallback without a rule, French gate dialog when no rule exists, Nummerierung menu with the renumber dialog (preferred axis, per-folio counter, rule and counter state written back, hand-typed names ask, whole-project scope). Also included: label block and under-line texts with per-text font and alignment plus the material-list cell margins, free-core placement and right-click removal, claim questions for wires of other cables, multi-selection drag as one undo step, type change with confirmation, cross-references for type "Cable" (%f-%l%c, own font, clickable links in exported PDFs), report propagation of the cable definition, Listes menu (table of contents, material list, terminal strip manager, terminal generator plugin, cable list), cable list with freely selectable columns (installation/localisation of cable, start and end, Blatt start/end, length, used cores...), CSV export dialog with column pick and preview in the Projekt menu, script API (addTable kind "cable_list", exportCableList) and a unit test for the type catalog.
283 lines
8.7 KiB
C++
283 lines
8.7 KiB
C++
/*
|
|
Copyright 2006-2026 The QElectroTech Team
|
|
This file is part of QElectroTech.
|
|
|
|
QElectroTech 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 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
QElectroTech 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 QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include "cablerenumber.h"
|
|
|
|
#include "cable.h"
|
|
#include "../autoNum/assignvariables.h"
|
|
#include "../autoNum/numerotationcontextcommands.h"
|
|
#include "../diagram.h"
|
|
#include "../qetproject.h"
|
|
|
|
#include <QCoreApplication>
|
|
#include <QSet>
|
|
|
|
#include <algorithm>
|
|
#include <limits>
|
|
|
|
namespace {
|
|
|
|
/**
|
|
@brief One cable of the project, and where it stands so that the
|
|
cables can be laid out in an order everybody can see again.
|
|
*/
|
|
struct OrderedCable
|
|
{
|
|
Cable *cable = nullptr;
|
|
///The lowest folio the cable is drawn on
|
|
int folio = 0;
|
|
///How far it reaches to the left and how far to the top on
|
|
///that folio
|
|
qreal left = 0.0;
|
|
qreal top = 0.0;
|
|
///The folio the rule is read on for this cable
|
|
Diagram *diagram = nullptr;
|
|
};
|
|
|
|
/**
|
|
@brief Read a rule once, the way the numbering of a cable reads it.
|
|
|
|
The rule is read on a copy, since reading it fills in what its
|
|
folio-bound parts are worth on that one folio, and that must not
|
|
creep into the rule itself.
|
|
@param context the rule as it stands at this point of the numbering
|
|
@param diagram the folio the number is worked out on
|
|
@param key the name the rule is kept under in the project
|
|
@return the number, empty when the rule says nothing here
|
|
*/
|
|
QString readRule(NumerotationContext context, Diagram *diagram, const QString &key)
|
|
{
|
|
const QString formula = autonum::numerotationContextToFormula(context);
|
|
autonum::sequentialNumbers seq;
|
|
autonum::setSequential(formula, seq, context, diagram, key);
|
|
return autonum::AssignVariables::formulaToLabel(formula, seq, diagram);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
/**
|
|
@brief CableRenumber::byHandCount
|
|
@param project
|
|
@return how many cables of that project carry a name typed in by hand
|
|
*/
|
|
int CableRenumber::byHandCount(QETProject *project)
|
|
{
|
|
if (!project) return 0;
|
|
|
|
int count = 0;
|
|
for (Cable *cable : project->cables())
|
|
{
|
|
if (cable && cable->designationByHand()) {
|
|
++count;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
@brief CableRenumber::plan
|
|
Work the number of every cable of the project out, without writing
|
|
anywhere. The cables are put in the order they are numbered in --
|
|
folio by folio, and on each folio along whichever axis leads -- and
|
|
the rule is then read from its beginning once for each of them, so
|
|
that the numbering runs on without a hole.
|
|
@param project the project whose cables are numbered
|
|
@param rule the numbering rule to read
|
|
@param x_axis_first true when the leftmost cable of a folio comes first
|
|
@param include_by_hand false to leave the names typed in by hand alone
|
|
@return the numbers, or what stopped the numbering
|
|
*/
|
|
CableRenumberPlan CableRenumber::plan(QETProject *project,
|
|
const NumerotationContext &rule,
|
|
bool x_axis_first,
|
|
bool include_by_hand)
|
|
{
|
|
CableRenumberPlan result;
|
|
if (!project)
|
|
{
|
|
result.error = QCoreApplication::translate(
|
|
"CableRenumber", "Aucun projet n'est ouvert.");
|
|
return result;
|
|
}
|
|
if (project->isReadOnly())
|
|
{
|
|
result.error = QCoreApplication::translate(
|
|
"CableRenumber",
|
|
"Ce projet est en lecture seule : ses câbles ne peuvent pas "
|
|
"être renumérotés.");
|
|
return result;
|
|
}
|
|
const QString key = project->cableCurrentAutoNum();
|
|
if (key.isEmpty() || rule.isEmpty())
|
|
{
|
|
result.error = QCoreApplication::translate(
|
|
"CableRenumber",
|
|
"Aucune règle de numérotation des câbles n'est définie pour ce "
|
|
"projet. Définissez-en une dans cette fenêtre avant de "
|
|
"renuméroter ses câbles.");
|
|
return result;
|
|
}
|
|
|
|
//Where every cable stands, and which ones are left alone: their
|
|
//names stay as they are, so the numbering must not hand the
|
|
//same number out again
|
|
QList<OrderedCable> ordered;
|
|
QSet<QString> taken;
|
|
for (Cable *cable : project->cables())
|
|
{
|
|
if (!cable) continue;
|
|
if (!include_by_hand && cable->designationByHand())
|
|
{
|
|
if (!cable->designation().isEmpty()) {
|
|
taken.insert(cable->designation());
|
|
}
|
|
continue;
|
|
}
|
|
|
|
OrderedCable entry;
|
|
entry.cable = cable;
|
|
|
|
for (const CablePartData &part : cable->parts())
|
|
{
|
|
Diagram *diagram = project->diagramByUuid(part.diagram);
|
|
if (!diagram) continue;
|
|
|
|
const int index = project->folioIndex(diagram);
|
|
const qreal left = qMin(part.p1.x(), part.p2.x());
|
|
const qreal top = qMin(part.p1.y(), part.p2.y());
|
|
|
|
if (!entry.diagram || index < entry.folio)
|
|
{
|
|
entry.folio = index;
|
|
entry.diagram = diagram;
|
|
entry.left = left;
|
|
entry.top = top;
|
|
} else if (index == entry.folio) {
|
|
entry.left = qMin(entry.left, left);
|
|
entry.top = qMin(entry.top, top);
|
|
}
|
|
}
|
|
//A cable which stands on no folio the project still knows
|
|
//is numbered last rather than first by accident
|
|
if (!entry.diagram) {
|
|
entry.folio = std::numeric_limits<int>::max();
|
|
}
|
|
ordered.append(entry);
|
|
}
|
|
|
|
//Folio by folio, and on each folio along the axis which leads:
|
|
//the other axis is only asked when the leading one says two
|
|
//cables are level, so that the numbering never depends on the
|
|
//order the cables happen to be stored in
|
|
std::sort(ordered.begin(), ordered.end(),
|
|
[x_axis_first](const OrderedCable &a, const OrderedCable &b)
|
|
{
|
|
if (a.folio != b.folio) return a.folio < b.folio;
|
|
|
|
const qreal a_first = x_axis_first ? a.left : a.top;
|
|
const qreal b_first = x_axis_first ? b.left : b.top;
|
|
if (a_first != b_first) return a_first < b_first;
|
|
|
|
const qreal a_second = x_axis_first ? a.top : a.left;
|
|
const qreal b_second = x_axis_first ? b.top : b.left;
|
|
if (a_second != b_second) return a_second < b_second;
|
|
|
|
return a.cable->uuid().toString() < b.cable->uuid().toString();
|
|
});
|
|
|
|
//Read the rule from the beginning, once for each cable: its
|
|
//counters are put back first, since numbering a project over
|
|
//starts at the beginning whatever the last number was
|
|
NumerotationContext context = autonum::resetContextCounters(rule);
|
|
int current_folio = -1;
|
|
|
|
for (const OrderedCable &entry : std::as_const(ordered))
|
|
{
|
|
if (!entry.diagram)
|
|
{
|
|
result.error = QCoreApplication::translate(
|
|
"CableRenumber",
|
|
"Un câble n'est sur aucun folio du projet : rien n'a été "
|
|
"changé.");
|
|
return result;
|
|
}
|
|
|
|
//A counter bound to the folio counts from the beginning of
|
|
//that folio: the first cable of a folio gets the first
|
|
//number of it, and the folios after it start again rather
|
|
//than carrying the number of the folio before on
|
|
if (entry.folio != current_folio)
|
|
{
|
|
context = autonum::resetFolioCounters(context);
|
|
current_folio = entry.folio;
|
|
}
|
|
|
|
const QString candidate = readRule(context, entry.diagram, key);
|
|
if (candidate.isEmpty())
|
|
{
|
|
result.error = QCoreApplication::translate(
|
|
"CableRenumber",
|
|
"La règle en cours ne donne aucun numéro ici : rien n'a été "
|
|
"changé.");
|
|
return result;
|
|
}
|
|
//A rule which does not count hands out the same number
|
|
//twice, and two cables of one project may not be called
|
|
//the same thing
|
|
if (result.designations.contains(candidate))
|
|
{
|
|
result.error = QCoreApplication::translate(
|
|
"CableRenumber",
|
|
"La règle en cours donne deux fois le même numéro (%1) : "
|
|
"elle doit compter pour renuméroter un projet entier. "
|
|
"Rien n'a été changé.")
|
|
.arg(candidate);
|
|
return result;
|
|
}
|
|
//A name which stays because it was typed in keeps its
|
|
//number, and the numbering may not hand that number out
|
|
if (taken.contains(candidate))
|
|
{
|
|
result.error = QCoreApplication::translate(
|
|
"CableRenumber",
|
|
"Le numéro %1 est déjà porté par un câble dont le nom est "
|
|
"laissé tel quel : rien n'a été changé.")
|
|
.arg(candidate);
|
|
return result;
|
|
}
|
|
|
|
taken.insert(candidate);
|
|
result.cables.append(entry.cable);
|
|
result.designations.append(candidate);
|
|
|
|
//Step the rule on for the next cable; a rule which cannot
|
|
//step stays where it is and is caught by the check above
|
|
NumerotationContextCommands ncc(context, entry.diagram);
|
|
const NumerotationContext following = ncc.next();
|
|
if (following.size() == context.size()) {
|
|
context = following;
|
|
}
|
|
}
|
|
|
|
//Where the rule stands after the last cable has been numbered:
|
|
//the caller writes that into the project, so numbering goes on
|
|
//from there instead of from wherever the rule happened to stand
|
|
//before this numbering ran
|
|
result.after = context;
|
|
return result;
|
|
}
|