Show used against declared capacity where a master declares contact groups

Second half of #819: where a coil declares what contacts it provides, the
General tab now reports each type as used against declared rather than as
a bare count.

    NO : 3/4, NC : 1/2, inverseurs : 0/1, autres : 0/0

MasterElement::contactCapacity() sums contactCount over the element's
SlaveContactGroup list, per type, reusing the same ContactUsage tally the
used count is built on. The mapping from ElementData::SlaveState onto the
tally's own type is factored into one helper so the used count and the
declared capacity cannot classify a contact differently.

Falls back to the plain count from the previous commit when an element
declares no groups, which is every element in the standard collection
today -- nothing in the corpus declares slaveContactGroups, so this
changes no existing display.

A type used beyond what is declared reads as e.g. "1/0". That is
deliberate: it says this contact does not fit the part.

Display only. Whether a declared capacity should also feed
MasterElement::isFull() is the open question in #819 and is not touched
here.

Verified end to end against a purpose-built fixture, since no existing
element exercises this path: a coil declaring two NO groups of two, one
NC group of two and one changeover group of one parses and reports
NO=4 NC=2 SW=1 other=0 total=7, matching the declaration exactly.
tst_contactusage gains a case covering capacity summed across groups
(10 cases, all passing).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
ispyisail
2026-09-09 10:49:16 +12:00
parent c39c7414cf
commit 6f2c66afef
4 changed files with 77 additions and 14 deletions
+43 -11
View File
@@ -235,6 +235,28 @@ void MasterElement::aboutDeleteXref()
* so they cannot disagree with each other.
* @return the per type usage
*/
namespace {
/**
Map the element data's contact type onto the tally's own, so that
the used count and the declared capacity cannot classify the same
contact type differently.
*/
ContactUsage::Type contactType(ElementData::SlaveState state)
{
switch (state)
{
case ElementData::NO: return ContactUsage::NO;
case ElementData::NC: return ContactUsage::NC;
case ElementData::SW: return ContactUsage::SW;
case ElementData::Other: break;
}
return ContactUsage::Other;
}
}
ContactUsage MasterElement::contactUsage() const
{
ContactUsage usage;
@@ -246,22 +268,32 @@ ContactUsage MasterElement::contactUsage() const
}
const ElementData &data = elmt->elementData();
ContactUsage::Type type = ContactUsage::Other;
switch (data.m_slave_state)
{
case ElementData::NO: type = ContactUsage::NO; break;
case ElementData::NC: type = ContactUsage::NC; break;
case ElementData::SW: type = ContactUsage::SW; break;
case ElementData::Other: type = ContactUsage::Other; break;
}
usage.addSlave(type, data.m_contact_count);
usage.addSlave(contactType(data.m_slave_state), data.m_contact_count);
}
return usage;
}
/**
* @brief MasterElement::contactCapacity
* The contacts this master declares it provides, by type, summed over its
* contact groups. A group stands for contactCount contacts of its type.
* Returns an empty tally when the element declares no groups, which is the
* case for every element in the standard collection today -- callers use
* that to decide whether a capacity is worth showing at all.
* @return the per type capacity
*/
ContactUsage MasterElement::contactCapacity() const
{
ContactUsage capacity;
for (const auto &group : m_data.m_slave_contact_groups) {
capacity.addSlave(contactType(group.type), group.contactCount);
}
return capacity;
}
/**
* @brief MasterElement::isFull
* @return true if the master has reached its maximum number of slaves
+1
View File
@@ -49,6 +49,7 @@ class MasterElement : public Element
QRectF XrefBoundingRect() const;
ContactUsage contactUsage() const;
ContactUsage contactCapacity() const;
bool isFull() const; // Check Slave-Limit
protected:
+19 -3
View File
@@ -393,16 +393,32 @@ QWidget *ElementPropertiesWidget::generalWidget()
//Counted in contacts rather than in linked elements, so that a
//slave standing for several contacts is reported as the number
//of contacts it actually uses.
const ContactUsage usage =
static_cast<MasterElement *>(m_element.data())->contactUsage();
const MasterElement *master =
static_cast<const MasterElement *>(m_element.data());
const ContactUsage usage = master->contactUsage();
description_string += QString(tr("Nombre de contacts esclaves utilisés : %1\n")).arg(usage.total());
//The breakdown is what tells you which auxiliary block would
//satisfy this coil, so it is only worth printing once there is
//something to break down.
if (usage.total() > 0)
const ContactUsage capacity = master->contactCapacity();
if (capacity.total() > 0)
{
//The element declares contact groups, so it can say not
//only what has been used but what it has to offer. A type
//used beyond what is declared shows as e.g. "1/0", which
//is the point: it says this contact does not fit the part.
description_string += QString(tr(" NO : %1/%2, NC : %3/%4, inverseurs : %5/%6, autres : %7/%8\n"))
.arg(usage.no).arg(capacity.no)
.arg(usage.nc).arg(capacity.nc)
.arg(usage.sw).arg(capacity.sw)
.arg(usage.other).arg(capacity.other);
}
else if (usage.total() > 0)
{
//No declared groups, so a plain count of what is in use.
description_string += QString(tr(" NO : %1, NC : %2, inverseurs : %3, autres : %4\n"))
.arg(usage.no)
.arg(usage.nc)