Power System Platform  2026w34a-beta
Loading...
Searching...
No Matches
Machines.cpp
1/*
2 * Copyright (C) 2017 Thales Lima Oliveira <thales@ufu.br>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17
18#include "Machines.h"
19
20#include "../controlElement/ControlElementContainer.h"
21#include "../controlElement/ControlElementSolver.h"
22
23Machines::Machines() : PowerElement() {}
24bool Machines::AddParent(Element* parent, wxPoint2DDouble position, bool isOpening)
25{
26 if (parent) {
27 m_parentList.push_back(parent);
28 parent->AddChild(this);
29
30 wxPoint2DDouble parentPt = parent->RotateAtPosition(position, -parent->GetAngle()); // Rotate click to horizontal position.
31
32 parentPt.m_y = parent->GetPosition().m_y; // Centralize on bus.
33 parentPt.m_x = std::round(parentPt.m_x / 20.0) * 20.0; // Snap along bus to grid.
34
35 double halfLength = parent->GetWidth() / 2.0;
36 double minX = parent->GetPosition().m_x - halfLength;
37 double maxX = parent->GetPosition().m_x + halfLength;
38
39 if (parentPt.m_x < minX)
40 parentPt.m_x = minX;
41
42 if (parentPt.m_x > maxX)
43 parentPt.m_x = maxX;
44
45 parentPt = parent->RotateAtPosition(parentPt, parent->GetAngle()); // Rotate back.
46
47 if (!isOpening) {
48 // --------------------------------------------------------
49 // Normal insertion:
50 // determine machine position and orientation from the bus.
51 // --------------------------------------------------------
52
53 bool busHorizontal = (std::abs(parent->GetAngle() - 0.0) < 1.0 || std::abs(parent->GetAngle() - 180.0) < 1.0);
54
55 if (busHorizontal) {
56 if (position.m_y < parent->GetPosition().m_y) {
57 // Above bus: lead points down (+Y, angle 90).
58 m_position = parentPt + wxPoint2DDouble(0.0, -80.0);
59 m_angle = 90.0;
60 }
61 else {
62 // Below bus: lead points up (-Y, angle 270).
63 m_position = parentPt + wxPoint2DDouble(0.0, 80.0);
64 m_angle = 270.0;
65 }
66 }
67 else {
68 if (position.m_x < parent->GetPosition().m_x) {
69 // Left of bus: lead points right (+X, angle 0).
70 m_position = parentPt + wxPoint2DDouble(-80.0, 0.0);
71 m_angle = 0.0;
72 }
73 else {
74 // Right of bus: lead points left (-X, angle 180).
75 m_position = parentPt + wxPoint2DDouble(80.0, 0.0);
76 m_angle = 180.0;
77 }
78 }
79 }
80
81 // The following geometry is common to normal insertion and file loading.
82 // When opening a file, m_position and m_angle are preserved from the file.
83 m_width = m_height = 50.0;
84 m_rect = wxRect2DDouble(m_position.m_x - 25.0, m_position.m_y - 25.0, m_width, m_height);
85
86 SetPosition(m_position);
87
88 // First point: connection point on the bus.
89 m_pointList.push_back(parentPt);
90
91 // Machine terminals are rigidly attached to the machine body.
92 wxPoint2DDouble term1 = m_position + RotateLocal(wxPoint2DDouble(40.0, 0.0), m_angle);
93 wxPoint2DDouble term2 = m_position + RotateLocal(wxPoint2DDouble(20.0, 0.0), m_angle);
94
95 term1.m_x = std::round(term1.m_x);
96 term1.m_y = std::round(term1.m_y);
97
98 term2.m_x = std::round(term2.m_x);
99 term2.m_y = std::round(term2.m_y);
100
101 // Switch point.
102 m_pointList.push_back(GetSwitchPoint(parent, parentPt, term1));
103
104 // Machine terminals.
105 m_pointList.push_back(term1);
106 m_pointList.push_back(term2);
107
108 m_inserted = true;
109
110 wxRect2DDouble genRect(0, 0, 0, 0);
111 m_switchRect.push_back(genRect);
112
114 UpdatePowerFlowArrowsPosition();
115
116 return true;
117 }
118
119 return false;
120}
121
122void Machines::DrawDC(GUIColour* guiColour, wxPoint2DDouble translation, double scale, wxGraphicsContext* gc) const
123{
124 wxColour elementColour;
125 if (m_online) {
126 //if (m_dynEvent)
127 // elementColour = guiColour->eventElement;
128 //else
129 elementColour = guiColour->enabled;
130 }
131 else
132 elementColour = guiColour->disable;
133
134 if (m_inserted) {
135 // Draw Selection (layer 1).
136 if (m_selected) {
137 gc->SetPen(wxPen(guiColour->selection, 2 + m_borderSize * 2.0));
138 gc->SetBrush(*wxTRANSPARENT_BRUSH);
139 gc->StrokeLines(m_pointList.size(), &m_pointList[0]);
140
141 gc->SetPen(*wxTRANSPARENT_PEN);
142 gc->SetBrush(wxBrush(guiColour->selection));
143 DrawDCCircle(m_position, 25.0 + (m_borderSize + 1.5) / scale, 20, gc);
144
145 // Draw nodes selection.
146 DrawDCCircle(m_pointList[0], 5.0 + m_borderSize / scale, 10, gc);
147 }
148
149 // Draw Machines (layer 2).
150 // Draw node.
151 gc->SetPen(*wxTRANSPARENT_PEN);
152 gc->SetBrush(wxBrush(elementColour));
153 DrawDCCircle(m_pointList[0], 5.0, 10, gc);
154
155 gc->SetPen(wxPen(wxColour(elementColour), 2));
156 gc->SetBrush(*wxTRANSPARENT_BRUSH);
157 gc->StrokeLines(m_pointList.size(), &m_pointList[0]);
158 DrawDCCircle(m_position, 25.0, 20.0, gc);
159
160 DrawDCSwitches(guiColour, gc);
161 DrawDCPowerFlowPts(guiColour, gc);
162
163 gc->SetPen(*wxTRANSPARENT_PEN);
164 gc->SetBrush(wxBrush(guiColour->background));
165 DrawDCCircle(m_position, 25.0, 20.0, gc);
166
167 gc->SetPen(wxPen(elementColour, 2));
168 gc->SetBrush(*wxTRANSPARENT_BRUSH);
169 DrawDCCircle(m_position, 25.0, 20.0, gc);
170
171 // Draw machine symbol.
172 DrawDCSymbol(gc);
173
174 if (m_dynEvent) {
175 DrawStabilityEventGC(gc, translation, scale, guiColour);
176 }
177 }
178}
179
180void Machines::DrawDC(GUIColour* guiColour, wxPoint2DDouble translation, double scale, wxDC& dc) const
181{
182 wxColour elementColour;
183 if (m_online) {
184 if (m_dynEvent)
185 elementColour = guiColour->eventElement;
186 else
187 elementColour = guiColour->enabled;
188 }
189 else
190 elementColour = guiColour->disable;
191
192 std::vector<wxPoint> pointListInt;
193 for (auto& pt : m_pointList) {
194 pointListInt.emplace_back(static_cast<int>(pt.m_x), static_cast<int>(pt.m_y));
195 }
196
197 if (m_inserted) {
198 // Draw Selection (layer 1).
199 if (m_selected) {
200 dc.SetPen(wxPen(guiColour->selection, 2 + m_borderSize * 2.0));
201 dc.SetBrush(*wxTRANSPARENT_BRUSH);
202 dc.DrawLines(pointListInt.size(), &pointListInt[0]);
203
204 dc.SetPen(*wxTRANSPARENT_PEN);
205 dc.SetBrush(wxBrush(guiColour->selection));
206 DrawDCCircle(m_position, 20.0 + (m_borderSize + 1.5) / scale, dc);
207
208 // Draw nodes selection.
209 DrawDCCircle(m_pointList[0], 5.0 + m_borderSize / scale, dc);
210 }
211
212 // Draw Machines (layer 2).
213 // Draw node.
214 dc.SetPen(*wxTRANSPARENT_PEN);
215 dc.SetBrush(wxBrush(elementColour));
216 DrawDCCircle(m_pointList[0], 5.0, dc);
217
218 dc.SetPen(wxPen(wxColour(elementColour), 2));
219 dc.SetBrush(*wxTRANSPARENT_BRUSH);
220 dc.DrawLines(pointListInt.size(), &pointListInt[0]);
221 DrawDCCircle(m_position, 20.0, dc);
222
223 DrawDCSwitches(guiColour, dc);
224 DrawDCPowerFlowPts(guiColour, dc);
225
226 dc.SetPen(*wxTRANSPARENT_PEN);
227 dc.SetBrush(wxBrush(guiColour->background));
228 DrawDCCircle(m_position, 20.0, dc);
229
230 dc.SetPen(wxPen(elementColour, 2));
231 dc.SetBrush(*wxTRANSPARENT_BRUSH);
232 DrawDCCircle(m_position, 20.0, dc);
233
234 // Draw machine symbol.
235 DrawDCSymbol(dc);
236 }
237}
238
239void Machines::UpdateSwitchesPosition()
240{
241 if (m_parentList[0]) {
242 m_pointList[1] = GetSwitchPoint(m_parentList[0], m_pointList[0], m_pointList[2]);
243 }
244 else {
245 m_pointList[1] = m_pointList[0];
246 }
248}
249
250void Machines::Move(wxPoint2DDouble position)
251{
252 SetPosition(m_movePos + position - m_moveStartPt);
253 if (m_pointList.size() >= 4) {
254 wxPoint2DDouble t1 = m_position + RotateLocal(wxPoint2DDouble(40.0, 0.0), m_angle);
255 wxPoint2DDouble t2 = m_position + RotateLocal(wxPoint2DDouble(20.0, 0.0), m_angle);
256 m_pointList[2] = wxPoint2DDouble(std::round(t1.m_x), std::round(t1.m_y));
257 m_pointList[3] = wxPoint2DDouble(std::round(t2.m_x), std::round(t2.m_y));
258 }
259 if (!m_parentList[0]) {
260 m_pointList[0] = m_movePts[0] + position - m_moveStartPt;
261 }
262 UpdateSwitchesPosition();
263 UpdatePowerFlowArrowsPosition();
264}
265
266void Machines::MoveNode(Element* element, wxPoint2DDouble position)
267{
268 if (element) {
269 if (element == m_parentList[0]) {
270 m_pointList[0] = m_movePts[0] + position - m_moveStartPt;
271 }
272 }
273 else {
274 if (m_activeNodeID == 1) {
275 m_pointList[0] = m_movePts[0] + position - m_moveStartPt;
276 if (m_parentList[0]) {
277 m_parentList[0]->RemoveChild(this);
278 m_parentList[0] = nullptr;
279 m_online = false;
280 }
281 }
282 }
283
284 // Recalculate switches positions
285 UpdateSwitchesPosition();
286 UpdatePowerFlowArrowsPosition();
287}
288
289void Machines::StartMove(wxPoint2DDouble position)
290{
291 m_moveStartPt = position;
292 m_movePts = m_pointList;
293 m_movePos = m_position;
294}
295
296void Machines::RotateNode(Element* parent, bool clockwise)
297{
298 double rotAngle = m_rotationAngle;
299 if (!clockwise) rotAngle = -m_rotationAngle;
300
301 if (parent == m_parentList[0]) {
302 m_pointList[0] = parent->RotateAtPosition(m_pointList[0], rotAngle);
303 UpdateSwitchesPosition();
304 UpdatePowerFlowArrowsPosition();
305 }
306}
307
309{
310 if (parent == m_parentList[0]) {
311 m_parentList[0] = nullptr;
312 m_online = false;
313 UpdateSwitchesPosition();
314 UpdatePowerFlowArrowsPosition();
315 }
316}
317
318bool Machines::NodeContains(wxPoint2DDouble position)
319{
320 wxRect2DDouble nodeRect(m_pointList[0].m_x - 5.0 - m_borderSize, m_pointList[0].m_y - 5.0 - m_borderSize,
321 10 + 2.0 * m_borderSize, 10 + 2.0 * m_borderSize);
322
323 if (nodeRect.Contains(position)) {
324 m_activeNodeID = 1;
325 return true;
326 }
327
328 m_activeNodeID = 0;
329 return false;
330}
331
333{
334 if (parent && m_activeNodeID != 0) {
335 wxRect2DDouble nodeRect(m_pointList[0].m_x - 5.0 - m_borderSize, m_pointList[0].m_y - 5.0 - m_borderSize,
336 10 + 2.0 * m_borderSize, 10 + 2.0 * m_borderSize);
337
338 if (parent->Intersects(nodeRect)) {
339 m_parentList[0] = parent;
340
341 // Centralize the node on bus.
342 wxPoint2DDouble parentPt =
343 parent->RotateAtPosition(m_pointList[0], -parent->GetAngle()); // Rotate click to horizontal position.
344 parentPt.m_y = parent->GetPosition().m_y; // Centralize on bus.
345 parentPt = parent->RotateAtPosition(parentPt, parent->GetAngle());
346 m_pointList[0] = parentPt;
347
348 UpdateSwitchesPosition();
349 UpdatePowerFlowArrowsPosition();
350 return true;
351 }
352 else {
353 m_parentList[0] = nullptr;
354 m_online = false;
355 }
356 }
357 return false;
358}
359
361{
362 if (m_parentList[0]) {
363 wxRect2DDouble nodeRect(m_pointList[0].m_x - 5.0 - m_borderSize, m_pointList[0].m_y - 5.0 - m_borderSize,
364 10 + 2.0 * m_borderSize, 10 + 2.0 * m_borderSize);
365
366 if (!m_parentList[0]->Intersects(nodeRect)) {
367 m_parentList[0]->RemoveChild(this);
368 m_parentList[0] = nullptr;
369 m_online = false;
370 UpdateSwitchesPosition();
371 UpdatePowerFlowArrowsPosition();
372 }
373 }
374}
375
376void Machines::Rotate(bool clockwise)
377{
378 double rotAngle = m_rotationAngle;
379 if (!clockwise) rotAngle = -m_rotationAngle;
380
381 m_angle += rotAngle;
382 while (m_angle >= 360.0) m_angle -= 360.0;
383 while (m_angle < 0.0) m_angle += 360.0;
384
385 if (std::abs(m_angle - 90.0) < 1e-4) m_angle = 90.0;
386 else if (std::abs(m_angle - 180.0) < 1e-4) m_angle = 180.0;
387 else if (std::abs(m_angle - 270.0) < 1e-4) m_angle = 270.0;
388 else if (std::abs(m_angle - 0.0) < 1e-4 || std::abs(m_angle - 360.0) < 1e-4) m_angle = 0.0;
389
390 if (m_pointList.size() >= 4) {
391 wxPoint2DDouble t1 = m_position + RotateLocal(wxPoint2DDouble(40.0, 0.0), m_angle);
392 wxPoint2DDouble t2 = m_position + RotateLocal(wxPoint2DDouble(20.0, 0.0), m_angle);
393 m_pointList[2] = wxPoint2DDouble(std::round(t1.m_x), std::round(t1.m_y));
394 m_pointList[3] = wxPoint2DDouble(std::round(t2.m_x), std::round(t2.m_y));
395 }
396 UpdateSwitchesPosition();
397 UpdatePowerFlowArrowsPosition();
398}
399
400void Machines::AlignToGrid(double gridSize)
401{
402 if (gridSize <= 0.0)
403 gridSize = 20.0;
404
405 // ------------------------------------------------------------
406 // 1. Align m_position to the grid.
407 // ------------------------------------------------------------
408 wxPoint2DDouble oldPosition = m_position;
409
410 wxPoint2DDouble newPosition(
411 std::round(oldPosition.m_x / gridSize) * gridSize,
412 std::round(oldPosition.m_y / gridSize) * gridSize);
413
414 wxPoint2DDouble delta = newPosition - oldPosition;
415
416 m_position = newPosition;
417
418 //m_width = m_height = 50.0;
419 //m_rect = wxRect2DDouble(m_position.m_x - 25.0, m_position.m_y - 25.0, m_width, m_height);
420
421 SetPosition(m_position);
422
423 // ------------------------------------------------------------
424 // 2. Move the machine geometry together with m_position.
425 //
426 // Point 0 = bus connection.
427 // Point 1 = switch.
428 // Point 2+ = machine geometry.
429 //
430 // Point 0 is intentionally not moved here because it will
431 // be aligned with the bus in the next step.
432 // ------------------------------------------------------------
433 for (size_t i = 2; i < m_pointList.size(); ++i) {
434 m_pointList[i] += delta;
435 }
436
437 // ------------------------------------------------------------
438 // 3. Recalculate the machine terminals rigidly attached to
439 // the machine body.
440 // ------------------------------------------------------------
441 if (m_pointList.size() >= 4) {
442 wxPoint2DDouble t1 =
443 m_position +
444 RotateLocal(
445 wxPoint2DDouble(40.0, 0.0),
446 m_angle);
447
448 wxPoint2DDouble t2 =
449 m_position +
450 RotateLocal(
451 wxPoint2DDouble(20.0, 0.0),
452 m_angle);
453
454 m_pointList[2] = wxPoint2DDouble(
455 std::round(t1.m_x),
456 std::round(t1.m_y));
457
458 m_pointList[3] = wxPoint2DDouble(
459 std::round(t2.m_x),
460 std::round(t2.m_y));
461 }
462
463 // ------------------------------------------------------------
464 // 4. Align the bus connection point (m_pointList[0]) with
465 // m_pointList[2].
466 //
467 // The alignment does not depend on m_angle.
468 // m_pointList[2] defines where the connection node should be
469 // positioned along the bus.
470 // ------------------------------------------------------------
471 if (!m_parentList.empty() &&
472 m_parentList[0] &&
473 m_pointList.size() >= 3) {
474
475 Element* bus = m_parentList[0];
476
477 wxPoint2DDouble p2 = m_pointList[2];
478
479 // Transform p2 into the local coordinate system of the bus.
480 wxPoint2DDouble p2Delta(
481 p2.m_x - bus->GetPosition().m_x,
482 p2.m_y - bus->GetPosition().m_y);
483
484 wxPoint2DDouble p2Local =
485 bus->RotateLocal(
486 p2Delta,
487 -bus->GetAngle());
488
489 // The connection point must:
490 // - have the same longitudinal coordinate as p2;
491 // - lie on the center line of the bus.
492 wxPoint2DDouble candidateLocal(
493 p2Local.m_x,
494 0.0);
495
496 // Check that the position is still within the bus.
497 double halfWidth =
498 bus->GetWidth() / 2.0 + 2.0;
499
500 if (std::abs(candidateLocal.m_x) <= halfWidth) {
501
502 // Transform the candidate back to global coordinates.
503 wxPoint2DDouble candidate =
504 bus->GetPosition() +
505 bus->RotateLocal(
506 candidateLocal,
507 bus->GetAngle());
508
509 m_pointList[0] = candidate;
510 }
511 }
512 else if (!m_pointList.empty()) {
513
514 // No parent: simply snap the connection point to the grid.
515 m_pointList[0].m_x =
516 std::round(
517 m_pointList[0].m_x / gridSize) * gridSize;
518
519 m_pointList[0].m_y =
520 std::round(
521 m_pointList[0].m_y / gridSize) * gridSize;
522 }
523
524 // ------------------------------------------------------------
525 // 5. Update dependent geometry.
526 // ------------------------------------------------------------
527 UpdateSwitchesPosition();
528 UpdatePowerFlowArrowsPosition();
529}
530
531void Machines::UpdatePowerFlowArrowsPosition()
532{
533 std::vector<wxPoint2DDouble> edges;
534 switch (m_pfDirection) {
536 m_powerFlowArrow.clear();
537 } break;
539 edges.push_back(m_pointList[2]);
540 edges.push_back(m_pointList[1]);
541 } break;
543 edges.push_back(m_pointList[1]);
544 edges.push_back(m_pointList[2]);
545 } break;
546 default:
547 break;
548 }
549
551}
552
554{
555 m_pfDirection = pfDirection;
556 UpdatePowerFlowArrowsPosition();
557}
PowerFlowDirection
Direction of power flow arrows.
Base class of all elements of the program. This class is responsible for manage graphical and his dat...
Definition Element.h:115
virtual bool Intersects(wxRect2DDouble rect) const =0
Check if the element's rect intersects other rect.
double GetWidth() const
Get the element width.
Definition Element.h:209
wxPoint2DDouble GetPosition() const
Get the element position.
Definition Element.h:189
double GetAngle() const
Get the element angle.
Definition Element.h:214
void SetPosition(const wxPoint2DDouble position)
Set the element position and update the rectangle.
Definition Element.cpp:33
virtual wxPoint2DDouble RotateAtPosition(wxPoint2DDouble pointToRotate, double angle, bool degrees=true) const
Rotate a point as element position being the origin.
Definition Element.cpp:298
virtual void AddChild(Element *child)
Add a child to the child list.
Definition Element.cpp:581
virtual void DrawDCCircle(wxPoint2DDouble position, double radius, int numSegments, wxGraphicsContext *gc) const
Draw a circle using device context.
Definition Element.cpp:173
virtual void RemoveParent(Element *parent)
Remove a parent.
Definition Machines.cpp:308
virtual bool Intersects(wxRect2DDouble rect) const
Check if the element's rect intersects other rect.
Definition Machines.h:46
virtual void SetPowerFlowDirection(PowerFlowDirection pfDirection)
Set the direction of the power flow.
Definition Machines.cpp:553
virtual void Move(wxPoint2DDouble position)
Move the element other position.
Definition Machines.cpp:250
virtual void UpdateNodes()
Update the nodes according to the parents. If a parent is removed, use this method.
Definition Machines.cpp:360
virtual bool NodeContains(wxPoint2DDouble position)
Check if a node contains a point. If contains, set the attributes related to node movement.
Definition Machines.cpp:318
virtual void DrawDC(GUIColour *guiColour, wxPoint2DDouble translation, double scale, wxGraphicsContext *gc) const
Draw the element using GDI+.
Definition Machines.cpp:122
virtual void RotateNode(Element *parent, bool clockwise=true)
Rotate a node.
Definition Machines.cpp:296
virtual bool AddParent(Element *parent, wxPoint2DDouble position, bool isOpening=false)
Add a parent to the element. This method must be used on power elements that connect to a bus,...
Definition Machines.cpp:24
virtual void StartMove(wxPoint2DDouble position)
Update the element attributes related to the movement.
Definition Machines.cpp:289
virtual void Rotate(bool clockwise=true)
Rotate the element.
Definition Machines.cpp:376
virtual void MoveNode(Element *element, wxPoint2DDouble position)
Move a node. StartMove(wxPoint2DDouble position) before start moving.
Definition Machines.cpp:266
virtual bool SetNodeParent(Element *parent)
Set a perent to the node. If all conditions are met, a new parent are added to the element and the po...
Definition Machines.cpp:332
Abstract class of power elements.
virtual void CalculatePowerFlowPts(std::vector< wxPoint2DDouble > edges)
Calculate the points of the power flow arrows.
virtual void DrawDCPowerFlowPts(GUIColour *guiColour, wxGraphicsContext *gc) const
Draw power flow arrows.
virtual void UpdateSwitches()
Update the switch position.
virtual void DrawDCSwitches(GUIColour *guiColour, wxGraphicsContext *gc) const
Draw switch.
virtual wxPoint2DDouble GetSwitchPoint(Element *parent, wxPoint2DDouble parentPoint, wxPoint2DDouble secondPoint) const
Get the correct switch position.