mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-27 12:34:14 +02:00
c740cdf1ac
The scripting API (bugtracker #162) could place an element and move it, and could count conductors but not make one. So a script could put a coil and a motor on a folio and had no way to connect them, which is most of what drawing is. This adds the missing verbs: addConductor() wire terminal i of one element to terminal j of another rotateElement() setElementInfo() any information key setElementLabel() the label key, by name, since it is the one people want addFolio() setFolioTitle() elementUuids() what is on this folio elementName() elementTerminals() which terminal index is which, before wiring it Each goes through the command the GUI already uses, so a script's edits undo like manual ones and reach the project database the same way: ConductorCreator (the drag-a-rectangle-over-terminals path, which is what makes a new conductor inherit an existing potential's properties and join auto-numbering), ChangeElementInformationCommand, QETProject::addNewDiagram(), ChangeTitleBlockCommand. rotateElement() pushes the same QPropertyUndoCommand on "rotation" that RotateSelectionCommand pushes for an Element, rather than RotateSelectionCommand itself, which works on the diagram's selection and would mean rewriting the user's selection to rotate one element. Terminals are addressed by index, not uuid. Terminal::uuid() is a property of the catalog .elmt definition: empty for most of the installed base, and where present, identical across every instance of that element -- two coils of the same type placed side by side have byte-identical terminal uuids, so a uuid cannot say which coil's A1 is meant. elementTerminals() exists so a script can see the indexing instead of guessing it. The one real hazard is that ConductorCreator asks the user which potential to inherit from when the two terminals sit on two different existing ones, and it asks with a plain modal QDialog that QET::QetMessageBox's non-interactive mode does not cover -- so under headless --run there is nobody to answer and the call never returns. Measured: with the check removed, that one call hangs until killed; with it, it declines in 0.4 s. addConductor() therefore refuses that case, the same way and for the same reason addElement() already refuses the import-conflict dialog. To make that check without duplicating the condition, existingPotential() becomes static over an explicit terminal list and ConductorCreator gains a public needsPotentialChoice() predicate. Behaviour of the GUI path is unchanged; setUpPropertieToUse() passes m_terminals_list to the same code it called before. Verified headlessly against a copy of examples/ArduinoLCD.qet: new folio titled, two coils placed, wired, labelled, an info key set and the element rotated; saved, reloaded, and the conductor, label, title and rotation (persisted as orientation="1") all read back. Re-saving the result is byte-identical. qet-lint clean on the generated project; qet-coherence-check clean on it and on the 24-project example corpus, and shown to report 9 findings on a deliberately broken copy of the same file, so the clean result discriminates. Qt 6.10.2, ctest identical to master (the 61 failures are the vendored KDE ECM suite, present on both). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
233 lines
6.6 KiB
C++
233 lines
6.6 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 "conductorcreator.h"
|
|
|
|
#include "../conductorautonumerotation.h"
|
|
#include "../diagram.h"
|
|
#include "../undocommand/addgraphicsobjectcommand.h"
|
|
#include "../qetgraphicsitem/conductor.h"
|
|
#include "../qetgraphicsitem/element.h"
|
|
#include "../qetgraphicsitem/terminal.h"
|
|
#include "../ui/potentialselectordialog.h"
|
|
#include "qgraphicsitem.h"
|
|
|
|
#include <QPolygonF>
|
|
|
|
/**
|
|
@brief ConductorCreator::ConductorCreator
|
|
Create an electrical potential between all terminals of terminals_list.
|
|
the terminals of the list must be in the same diagram.
|
|
@param d Diagram
|
|
@param terminals_list QList<Terminal *>
|
|
*/
|
|
ConductorCreator::ConductorCreator(Diagram *d, QList<Terminal *> terminals_list) :
|
|
m_terminals_list(terminals_list)
|
|
{
|
|
if (m_terminals_list.size() <= 1) {
|
|
return;
|
|
}
|
|
m_properties = m_terminals_list.first()->diagram()->defaultConductorProperties;
|
|
|
|
if (!setUpPropertieToUse()) {
|
|
return;
|
|
}
|
|
Terminal *hub_terminal = hubTerminal();
|
|
|
|
d->undoStack().beginMacro(QObject::tr("Création de conducteurs"));
|
|
|
|
QList<Conductor *> c_list;
|
|
for (Terminal *t : m_terminals_list)
|
|
{
|
|
if (t == hub_terminal) {
|
|
continue;
|
|
}
|
|
|
|
Conductor *cond = new Conductor(hub_terminal, t);
|
|
cond->setProperties(m_properties);
|
|
cond->setSequenceNum(m_sequential_number);
|
|
d->undoStack().push(new AddGraphicsObjectCommand(cond, d));
|
|
|
|
c_list.append(cond);
|
|
}
|
|
d->undoStack().endMacro();
|
|
|
|
for(Conductor *c : c_list) {
|
|
c->refreshText();
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief ConductorCreator::create
|
|
Create an electrical potential between the terminals of the diagram d, contained in the polygon
|
|
@param d Diagram
|
|
@param polygon : polygon in diagram coordinate
|
|
*/
|
|
void ConductorCreator::create(Diagram *d, const QPolygonF &polygon)
|
|
{
|
|
QList<Terminal *> t_list;
|
|
|
|
for (QGraphicsItem *item : d->items(polygon))
|
|
{
|
|
if (item->type() == Terminal::Type) {
|
|
t_list.append(qgraphicsitem_cast<Terminal *>(item));
|
|
}
|
|
}
|
|
|
|
if (t_list.size() <= 1) {
|
|
return;
|
|
} else {
|
|
ConductorCreator cc(d, t_list);
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief ConductorCreator::needsPotentialChoice
|
|
Whether creating a potential between these terminals would ask the user
|
|
to choose which of several existing potentials to inherit from -- that
|
|
is, whether the constructor would reach PotentialSelectorDialog.
|
|
|
|
This exists for callers with nobody there to answer: the dialog is a
|
|
plain QDialog::exec(), not routed through QET::QetMessageBox, so its
|
|
non-interactive mode does not cover it and a headless caller would hang
|
|
on it indefinitely. Such a caller can check this first and decline.
|
|
Exposed here, rather than reimplemented by the caller, so the condition
|
|
cannot drift away from the one setUpPropertieToUse() actually applies.
|
|
@param terminals_list the terminals a potential would be created between
|
|
@return true if the constructor would open the dialog
|
|
*/
|
|
bool ConductorCreator::needsPotentialChoice(const QList<Terminal *> &terminals_list)
|
|
{
|
|
if (terminals_list.size() <= 1) {
|
|
return false;
|
|
}
|
|
return existingPotential(terminals_list).size() >= 2;
|
|
}
|
|
|
|
/**
|
|
@brief ConductorCreator::propertieToUse
|
|
@return true if the caller should proceed with conductor creation,
|
|
false if the user cancelled the potential-selection dialog (in which
|
|
case no properties were chosen and creation must be aborted rather
|
|
than proceeding with blank/default properties).
|
|
*/
|
|
bool ConductorCreator::setUpPropertieToUse()
|
|
{
|
|
QList<Conductor *> potentials = existingPotential(m_terminals_list);
|
|
|
|
//There is an existing potential
|
|
//we get one of them
|
|
if (!potentials.isEmpty())
|
|
{
|
|
if (potentials.size() >= 2)
|
|
{
|
|
QList <ConductorProperties> cp_list;
|
|
for(Conductor *c : potentials) {
|
|
cp_list.append(c->properties());
|
|
}
|
|
|
|
bool cancelled = false;
|
|
m_properties = PotentialSelectorDialog::chosenProperties(cp_list, nullptr, &cancelled);
|
|
if (cancelled) {
|
|
return false;
|
|
}
|
|
for (Conductor *c : potentials) {
|
|
if (c->properties() == m_properties) {
|
|
m_sequential_number = c->sequenceNum();
|
|
}
|
|
}
|
|
}
|
|
else if (potentials.size() == 1)
|
|
{
|
|
m_properties = potentials.first()->properties();
|
|
m_sequential_number = potentials.first()->sequenceNum();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//get a new properties
|
|
ConductorAutoNumerotation::newProperties(m_terminals_list.first()->diagram(), m_properties, m_sequential_number);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
@brief ConductorCreator::existingPotential
|
|
Return the list of existing potential of
|
|
the terminal list
|
|
@param terminals_list the terminals to inspect
|
|
@return c_list QList<Conductor *>
|
|
*/
|
|
QList<Conductor *> ConductorCreator::existingPotential(const QList<Terminal *> &terminals_list)
|
|
{
|
|
QList<Conductor *> c_list;
|
|
QList<Terminal *> t_exclude;
|
|
|
|
for (Terminal *t : terminals_list)
|
|
{
|
|
if (t_exclude.contains(t)) {
|
|
continue;
|
|
}
|
|
|
|
if (!t->conductors().isEmpty())
|
|
{
|
|
c_list.append(t->conductors().first());
|
|
|
|
//We must check if m_terminals_list contains a terminal
|
|
//in the same potential of c, and if true, exclude this terminal from the search.
|
|
for (Conductor *c : t->conductors().first()->relatedPotentialConductors(false))
|
|
{
|
|
if (terminals_list.contains(c->terminal1)) {
|
|
t_exclude.append(c->terminal1);
|
|
} else if (terminals_list.contains(c->terminal2)) {
|
|
t_exclude.append(c->terminal2);
|
|
}
|
|
}
|
|
}
|
|
else if (t->parentElement()->linkType() & Element::AllReport && !t->parentElement()->isFree())
|
|
{
|
|
Element *linked_report = t->parentElement()->linkedElements().first();
|
|
if (!linked_report->conductors().isEmpty()) {
|
|
c_list.append(linked_report->conductors().first());
|
|
}
|
|
}
|
|
}
|
|
|
|
return c_list;
|
|
}
|
|
|
|
/**
|
|
@brief ConductorCreator::hubTerminal
|
|
@return hub_terminal
|
|
*/
|
|
Terminal *ConductorCreator::hubTerminal()
|
|
{
|
|
Terminal *hub_terminal = m_terminals_list.first();
|
|
|
|
for (Terminal *tt : m_terminals_list)
|
|
{
|
|
if (tt->scenePos().x() < hub_terminal->scenePos().x()) {
|
|
hub_terminal = tt;
|
|
} else if (tt->scenePos().x() == hub_terminal->scenePos().x()) {
|
|
if (tt->scenePos().y() < hub_terminal->scenePos().y()) {
|
|
hub_terminal = tt;
|
|
}
|
|
}
|
|
}
|
|
|
|
return hub_terminal;
|
|
}
|