Power System Platform  2026w11a-beta
Loading...
Searching...
No Matches
Line.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 "Line.h"
19#include "../../utils/Path.h"
20
21Line::Line() : Branch()
22{
23 m_elementType = TYPE_LINE;
24 for (int i = 0; i < 2; i++) {
25 for (int j = 0; j < 3; j++) { m_electricalData.faultCurrent[i][j] = std::complex<double>(0.0, 0.0); }
26 }
27}
28
29Line::Line(wxString name) : Branch()
30{
31 m_elementType = TYPE_LINE;
32 for (int i = 0; i < 2; i++) {
33 for (int j = 0; j < 3; j++) { m_electricalData.faultCurrent[i][j] = std::complex<double>(0.0, 0.0); }
34 }
35 m_electricalData.name = name;
36}
37Line::~Line() {}
38bool Line::Contains(wxPoint2DDouble position) const
39{
40 if (PointToLineDistance(position) < 5.0) { return true; }
41 return false;
42}
43
44//void Line::Draw(wxPoint2DDouble translation, double scale) const
45//{
46// OpenGLColour elementColour;
47// if (m_online) {
48// if (m_dynEvent)
49// elementColour = m_dynamicEventColour;
50// else
51// elementColour = m_onlineElementColour;
52//
53// }
54// else
55// elementColour = m_offlineElementColour;
56//
57// std::vector<wxPoint2DDouble> pointList = m_pointList;
58// if (!m_inserted && pointList.size() > 0) {
59// wxPoint2DDouble secondPoint = m_position;
60// if (pointList.size() > 2) { secondPoint = pointList[2]; }
61// pointList[1] = GetSwitchPoint(m_parentList[0], pointList[0], secondPoint);
62// pointList.push_back(m_position);
63// }
64//
65// // Line selected (Layer 1).
66// if (m_selected) {
67// glLineWidth(1.5 + m_borderSize * 2.0);
68// glColor4dv(m_selectionColour.GetRGBA());
69// DrawLine(pointList);
70//
71// // Draw nodes selection.
72// if (pointList.size() > 0) {
73// DrawCircle(pointList[0], 5.0 + m_borderSize / scale, 10, GL_POLYGON);
74// if (m_inserted) { DrawCircle(pointList[pointList.size() - 1], 5.0 + m_borderSize / scale, 10, GL_POLYGON); }
75// }
76// }
77//
78// // Draw line (Layer 2)
79// glLineWidth(1.5);
80// glColor4dv(elementColour.GetRGBA());
81// DrawLine(pointList);
82//
83// if (m_inserted) {
84// DrawSwitches();
85// DrawPowerFlowPts();
86// }
87//
88// // Draw nodes.
89// if (pointList.size() > 0) {
90// glColor4dv(elementColour.GetRGBA());
91// DrawCircle(pointList[0], 5.0, 10, GL_POLYGON);
92// if (m_inserted) { DrawCircle(pointList[pointList.size() - 1], 5.0, 10, GL_POLYGON); }
93// }
94//
95// // Draw pickboxes (Layer 3).
96// if (m_showPickbox) {
97// glPushMatrix();
98// glLoadIdentity();
99//
100// for (int i = 2; i < (int)m_pointList.size() - 2; i++) {
101// DrawPickbox(WorldToScreen(m_pointList[i], translation, scale));
102// }
103//
104// glPopMatrix();
105// }
106//}
107
108void Line::DrawDC(wxPoint2DDouble translation, double scale, wxGraphicsContext* gc) const
109{
110 //gc->SetBrush(*wxTRANSPARENT_BRUSH);
111
112 wxGraphicsMatrix identityMatrix = gc->GetTransform();
113 identityMatrix.Set(); // Set to identity
114
115 wxColour elementColour;
116 if (m_online) {
117 if (m_dynEvent)
118 elementColour = m_dynamicEventColour;
119 else
120 elementColour = m_onlineElementColour;
121
122 }
123 else
124 elementColour = m_offlineElementColour;
125
126 std::vector<wxPoint2DDouble> pointList = m_pointList;
127 if (!m_inserted && pointList.size() > 0) {
128 wxPoint2DDouble secondPoint = m_position;
129 if (pointList.size() > 2) { secondPoint = pointList[2]; }
130 pointList[1] = GetSwitchPoint(m_parentList[0], pointList[0], secondPoint);
131 pointList.push_back(m_position);
132 }
133
134 // Line selected (Layer 1).
135 if (m_selected) {
136 gc->SetPen(wxPen(m_selectionColour, 2 + m_borderSize * 2.0));
137 gc->SetBrush(*wxTRANSPARENT_BRUSH);
138 if (pointList.size() > 0)
139 gc->StrokeLines(pointList.size(), &pointList[0]);
140
141 // Draw nodes selection.
142 gc->SetPen(*wxTRANSPARENT_PEN);
143 gc->SetBrush(wxBrush(m_selectionColour));
144 if (pointList.size() > 0) {
145 DrawDCCircle(pointList[0], 5.0 + m_borderSize / scale, 10, gc);
146 if (m_inserted) { DrawDCCircle(pointList[pointList.size() - 1], 5.0 + m_borderSize / scale, 10, gc); }
147 }
148 }
149
150 // Draw line (Layer 2)
151 gc->SetPen(wxPen(elementColour, 2));
152 gc->SetBrush(*wxTRANSPARENT_BRUSH);
153 if (pointList.size() > 0)
154 gc->StrokeLines(pointList.size(), &pointList[0]);
155
156 if (m_inserted) {
157 DrawDCSwitches(gc);
159 }
160
161 // Draw nodes.
162 gc->SetPen(*wxTRANSPARENT_PEN);
163 gc->SetBrush(wxBrush(elementColour));
164 if (pointList.size() > 0) {
165 DrawDCCircle(pointList[0], 5.0, 10, gc);
166 if (m_inserted) { DrawDCCircle(pointList[pointList.size() - 1], 5.0, 10, gc); }
167 }
168
169 // Draw pickboxes (Layer 3).
170 if (m_showPickbox) {
171 gc->PushState();
172 gc->SetTransform(identityMatrix);
173
174 for (int i = 2; i < (int)m_pointList.size() - 2; i++) {
175 DrawDCPickbox(WorldToScreen(m_pointList[i], translation - wxPoint2DDouble(4 / scale, 4 / scale), scale), gc);
176 }
177
178 gc->PopState();
179 }
180}
181
182void Line::DrawDC(wxPoint2DDouble translation, double scale, wxDC& dc) const
183{
184
185 wxColour elementColour;
186 if (m_online) {
187 if (m_dynEvent)
188 elementColour = m_dynamicEventColour;
189 else
190 elementColour = m_onlineElementColour;
191
192 }
193 else
194 elementColour = m_offlineElementColour;
195
196 std::vector<wxPoint> pointList;
197 for (auto pt : m_pointList) {
198 pointList.emplace_back(pt.m_x, pt.m_y);
199 }
200 //if (!m_inserted && pointList.size() > 0) {
201 // wxPoint secondPoint = wxPoint(m_position.m_x, m_position.m_y);
202 // if (pointList.size() > 2) { secondPoint = pointList[2]; }
203 // wxPoint2DDouble swPoint = GetSwitchPoint(m_parentList[0], pointList[0], secondPoint)
204 // pointList[1] = wxPoint(m_position.m_x, m_position.m_y);
205 // pointList.push_back(m_position);
206 //}
207
208 // Line selected (Layer 1).
209 if (m_selected) {
210 dc.SetPen(wxPen(m_selectionColour, 2 + m_borderSize * 2.0));
211 dc.SetBrush(*wxTRANSPARENT_BRUSH);
212 if (pointList.size() > 0)
213 dc.DrawLines(pointList.size(), &pointList[0]);
214
215 // Draw nodes selection.
216 dc.SetPen(*wxTRANSPARENT_PEN);
217 dc.SetBrush(wxBrush(m_selectionColour));
218 if (pointList.size() > 0) {
219 DrawDCCircle(pointList[0], 5.0 + m_borderSize / scale, dc);
220 if (m_inserted) { DrawDCCircle(pointList[pointList.size() - 1], 5.0 + m_borderSize / scale, dc); }
221 }
222 }
223
224 // Draw line (Layer 2)
225 dc.SetPen(wxPen(elementColour, 2));
226 dc.SetBrush(*wxTRANSPARENT_BRUSH);
227 if (pointList.size() > 0)
228 dc.DrawLines(pointList.size(), &pointList[0]);
229
230 if (m_inserted) {
231 DrawDCSwitches(dc);
233 }
234
235 // Draw nodes.
236 dc.SetPen(*wxTRANSPARENT_PEN);
237 dc.SetBrush(wxBrush(elementColour));
238 if (pointList.size() > 0) {
239 DrawDCCircle(pointList[0], 5.0, dc);
240 if (m_inserted) { DrawDCCircle(pointList[pointList.size() - 1], 5.0, dc); }
241 }
242
243 // Draw pickboxes (Layer 3).
244 //if (m_showPickbox) {
245 // gc->PushState();
246 // gc->SetTransform(identityMatrix);
247 //
248 // for (int i = 2; i < (int)m_pointList.size() - 2; i++) {
249 // DrawDCPickbox(WorldToScreen(m_pointList[i], translation - wxPoint2DDouble(4 / scale, 4 / scale), scale), gc);
250 // }
251 //
252 // gc->PopState();
253 //}
254}
255
256void Line::Move(wxPoint2DDouble position)
257{
258 if (!m_parentList[0]) {
259 m_pointList[0] = m_movePts[0] + position - m_moveStartPt;
260 UpdateSwitchesPosition();
261 UpdatePowerFlowArrowsPosition();
262 }
263 if (!m_parentList[1]) {
264 m_pointList[m_pointList.size() - 1] = m_movePts[m_pointList.size() - 1] + position - m_moveStartPt;
265 UpdateSwitchesPosition();
266 UpdatePowerFlowArrowsPosition();
267 }
268
269 if (!m_parentList[0] && !m_parentList[1]) {
270 for (int i = 2; i < (int)m_pointList.size() - 2; i++) {
271 m_pointList[i] = m_movePts[i] + position - m_moveStartPt;
272 }
273 }
274}
275
276bool Line::AddParent(Element* parent, wxPoint2DDouble position)
277{
278 if (parent) {
279 // First bus.
280 if (m_parentList.size() == 0) {
281 m_position = position;
282 m_parentList.push_back(parent);
283 parent->AddChild(this);
284 wxPoint2DDouble parentPt =
285 parent->RotateAtPosition(position, -parent->GetAngle()); // Rotate click to horizontal position.
286 parentPt.m_y = parent->GetPosition().m_y; // Centralize on bus.
287 parentPt = parent->RotateAtPosition(parentPt, parent->GetAngle()); // Rotate back.
288 m_pointList.push_back(parentPt); // First point
289 m_pointList.push_back(GetSwitchPoint(parent, parentPt, m_position));
290
291 wxRect2DDouble genRect(0, 0, 0, 0);
292 m_switchRect.push_back(genRect);
294
295 Bus* parentBus = static_cast<Bus*>(parent);
296 m_electricalData.nominalVoltage = parentBus->GetElectricalData().nominalVoltage;
297 m_electricalData.nominalVoltageUnit = parentBus->GetElectricalData().nominalVoltageUnit;
298
299 return false;
300 }
301 // Second bus.
302 else if (parent != m_parentList[0]) {
303 Bus* parentBus = static_cast<Bus*>(parent);
304 if (m_electricalData.nominalVoltage != parentBus->GetElectricalData().nominalVoltage ||
305 m_electricalData.nominalVoltageUnit != parentBus->GetElectricalData().nominalVoltageUnit) {
306 wxMessageDialog msgDialog(nullptr,
307 _("Unable to connect two buses with different nominal voltages.\n"
308 "Use a transformer or edit the bus properties."),
309 _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
310 msgDialog.ShowModal();
311 return false;
312 }
313
314 m_parentList.push_back(parent);
315 parent->AddChild(this);
316 wxPoint2DDouble parentPt =
317 parent->RotateAtPosition(position, -parent->GetAngle()); // Rotate click to horizontal position.
318 parentPt.m_y = parent->GetPosition().m_y; // Centralize on bus.
319 parentPt = parent->RotateAtPosition(parentPt, parent->GetAngle()); // Rotate back.
320
321 // Set first switch point.
322 wxPoint2DDouble secondPoint = parentPt;
323 if (m_pointList.size() > 2) { secondPoint = m_pointList[2]; }
324 m_pointList[1] = GetSwitchPoint(m_parentList[0], m_pointList[0], secondPoint);
325
326 // Set the second switch point.
327 m_pointList.push_back(GetSwitchPoint(parent, parentPt, m_pointList[m_pointList.size() - 1]));
328
329 m_pointList.push_back(parentPt); // Last point.
330
331 wxRect2DDouble genRect(0, 0, 0, 0);
332 m_switchRect.push_back(genRect);
334
335 SetInserted();
336 UpdatePowerFlowArrowsPosition();
337 return true;
338 }
339 }
340 return false;
341}
342bool Line::Intersects(wxRect2DDouble rect) const
343{
344 for (auto it = m_pointList.begin(); it != m_pointList.end(); ++it) {
345 if (rect.Contains(*it)) return true;
346 }
347 return false;
348}
349void Line::MovePickbox(wxPoint2DDouble position)
350{
351 if (m_activePickboxID == ID_PB_NONE) return;
352
353 for (int i = 2; i < (int)m_pointList.size() - 2; i++) {
354 if (m_activePickboxID == i) {
355 m_pointList[i] = m_movePts[i] + position - m_moveStartPt;
356 UpdateSwitchesPosition();
357 UpdatePowerFlowArrowsPosition();
358 }
359 }
360}
361bool Line::PickboxContains(wxPoint2DDouble position)
362{
363 for (int i = 2; i < (int)m_pointList.size() - 2; i++) {
364 wxRect2DDouble rect(m_pointList[i].m_x - 5.0, m_pointList[i].m_y - 5.0, 10.0, 10.0);
365 if (rect.Contains(position)) {
366 m_activePickboxID = i;
367 return true;
368 }
369 }
370 return false;
371}
372
373void Line::AddPoint(wxPoint2DDouble point)
374{
375 if (m_parentList.size() != 0) { m_pointList.push_back(point); }
376}
377
378void Line::StartMove(wxPoint2DDouble position)
379{
380 m_moveStartPt = position;
381 m_movePts = m_pointList;
382}
383
384void Line::MoveNode(Element* parent, wxPoint2DDouble position)
385{
386 if (parent) {
387 // First bus.
388 if (parent == m_parentList[0]) {
389 m_pointList[0] = m_movePts[0] + position - m_moveStartPt;
390 }
391 // Second bus.
392 else if (parent == m_parentList[1]) {
393 m_pointList[m_pointList.size() - 1] = m_movePts[m_pointList.size() - 1] + position - m_moveStartPt;
394 }
395
396 // If the line is selected, move all the points, except the switches and buses points.
397 if (m_selected) {
398 for (int i = 2; i < (int)m_pointList.size() - 1; i++) {
399 m_pointList[i] = m_movePts[i] + position - m_moveStartPt;
400 }
401 }
402 }
403 else {
404 // If parent is setted to nullptr for the firts time, remove the parent child
405 if (m_activeNodeID == 1) {
406 m_pointList[0] = m_movePts[0] + position - m_moveStartPt;
407 if (m_parentList[0]) {
408 m_parentList[0]->RemoveChild(this);
409 m_parentList[0] = nullptr;
410 m_online = false;
411 }
412 }
413 else if (m_activeNodeID == 2) {
414 m_pointList[m_pointList.size() - 1] = m_movePts[m_pointList.size() - 1] + position - m_moveStartPt;
415 if (m_parentList[1]) {
416 m_parentList[1]->RemoveChild(this);
417 m_parentList[1] = nullptr;
418 m_online = false;
419 }
420 }
421 }
422
423 // Recalculate switches positions
424 UpdateSwitchesPosition();
425 UpdatePowerFlowArrowsPosition();
426}
427
428bool Line::GetContextMenu(wxMenu& menu)
429{
430 //wxFileName exeFileName(wxStandardPaths::Get().GetExecutablePath());
431 //wxString exePath = exeFileName.GetPath();
432
433 menu.Append(ID_EDIT_ELEMENT, _("Edit line"));
434
435 wxString busName[2] = { "?", "?" };
436 if (m_parentList.size() == 2) {
437 int i = 0;
438 for (Element* element : m_parentList) {
439 if (element) {
440 Bus* bus = static_cast<Bus*>(element);
441 busName[i] = bus->GetElectricalData().name;
442 }
443 i++;
444 }
445 }
446
447 wxMenu* textMenu = new wxMenu();
448
449 textMenu->Append(ID_TXT_NAME, _("Name"));
450 textMenu->Append(ID_TXT_BRANCH_ACTIVE_POWER_1_2, _("Active power (") + busName[0] + _(" to ") + busName[1] + wxT(")"));
451 textMenu->Append(ID_TXT_BRANCH_ACTIVE_POWER_2_1, _("Active power (") + busName[1] + _(" to ") + busName[0] + wxT(")"));
452 textMenu->Append(ID_TXT_BRANCH_REACTIVE_POWER_1_2, _("Reactive power (") + busName[0] + _(" to ") + busName[1] + wxT(")"));
453 textMenu->Append(ID_TXT_BRANCH_REACTIVE_POWER_2_1, _("Reactive power (") + busName[1] + _(" to ") + busName[0] + wxT(")"));
454 textMenu->Append(ID_TXT_BRANCH_LOSSES, _("Losses"));
455 textMenu->Append(ID_TXT_BRANCH_CURRENT_1_2, _("Current (") + busName[0] + _(" to ") + busName[1] + wxT(")"));
456 textMenu->Append(ID_TXT_BRANCH_CURRENT_2_1, _("Current (") + busName[1] + _(" to ") + busName[0] + wxT(")"));
457 textMenu->Append(ID_TXT_BRANCH_FAULT_CURRENT_1_2, _("Fault current (") + busName[0] + _(" to ") + busName[1] + wxT(")"));
458 textMenu->Append(ID_TXT_BRANCH_FAULT_CURRENT_2_1, _("Fault current (") + busName[1] + _(" to ") + busName[0] + wxT(")"));
459 textMenu->SetClientData(menu.GetClientData());
460 menu.AppendSubMenu(textMenu, _("Add text"));
461
462 if (m_activePickboxID == ID_PB_NONE) {
463 wxMenuItem* addNodeItem = new wxMenuItem(&menu, ID_LINE_ADD_NODE, _("Insert node"));
464 addNodeItem->SetBitmap(wxImage(Paths::GetDataPath() + "/images/menu/addNode16.png"));
465 menu.Append(addNodeItem);
466 }
467 else {
468 wxMenuItem* addNodeItem = new wxMenuItem(&menu, ID_LINE_REMOVE_NODE, _("Remove node"));
469 addNodeItem->SetBitmap(wxImage(Paths::GetDataPath() + "/images/menu/removeNode16.png"));
470 menu.Append(addNodeItem);
471 }
472 wxMenuItem* deleteItem = new wxMenuItem(&menu, ID_DELETE, _("Delete"));
473 deleteItem->SetBitmap(wxImage(Paths::GetDataPath() + "/images/menu/delete16.png"));
474 menu.Append(deleteItem);
475 return true;
476}
477
478void Line::RemoveNode(wxPoint2DDouble point)
479{
480 if (PickboxContains(point)) {
481 for (int i = 2; i < (int)m_pointList.size() - 2; i++) {
482 if (m_activePickboxID == i) {
483 m_pointList.erase(m_pointList.begin() + i);
484 break;
485 }
486 }
487 }
488 UpdateSwitchesPosition();
489 UpdatePowerFlowArrowsPosition();
490}
491
492void Line::AddNode(wxPoint2DDouble point)
493{
494 int segmentNumber = 0;
495 PointToLineDistance(point, &segmentNumber);
496 if (segmentNumber > 0 && segmentNumber < (int)m_pointList.size() - 2) {
497 m_pointList.insert(m_pointList.begin() + segmentNumber + 1, point);
498 }
499 UpdateSwitchesPosition();
500 UpdatePowerFlowArrowsPosition();
501}
502
503void Line::CalculateBoundaries(wxPoint2DDouble& leftUp, wxPoint2DDouble& rightBottom) const
504{
505 if (m_pointList.size() > 0) {
506 // Check points list boundaries.
507 leftUp = m_pointList[0];
508 rightBottom = m_pointList[0];
509 for (int i = 1; i < (int)m_pointList.size(); i++) {
510 if (m_pointList[i].m_x < leftUp.m_x) leftUp.m_x = m_pointList[i].m_x;
511 if (m_pointList[i].m_y < leftUp.m_y) leftUp.m_y = m_pointList[i].m_y;
512 if (m_pointList[i].m_x > rightBottom.m_x) rightBottom.m_x = m_pointList[i].m_x;
513 if (m_pointList[i].m_y > rightBottom.m_y) rightBottom.m_y = m_pointList[i].m_y;
514 }
515 }
516}
517
518bool Line::ShowForm(wxWindow* parent, Element* element)
519{
520 LineForm lineForm(parent, this);
521 lineForm.CenterOnParent();
522 if (lineForm.ShowModal() == wxID_OK) {
523 return true;
524 }
525 return false;
526}
527
528void Line::SetNominalVoltage(std::vector<double> nominalVoltage, std::vector<ElectricalUnit> nominalVoltageUnit)
529{
530 if (nominalVoltage.size() > 0) {
531 m_electricalData.nominalVoltage = nominalVoltage[0];
532 m_electricalData.nominalVoltageUnit = nominalVoltageUnit[0];
533 }
534}
535
537{
538 if (m_activeNodeID == 1 && parent == m_parentList[0]) return false;
539 if (m_activeNodeID == 2 && parent == m_parentList[1]) return false;
540
541 if (parent && m_activeNodeID != 0) {
542 wxRect2DDouble nodeRect(0, 0, 0, 0);
543 if (m_activeNodeID == 1) {
544 nodeRect = wxRect2DDouble(m_pointList[0].m_x - 5.0 - m_borderSize, m_pointList[0].m_y - 5.0 - m_borderSize,
545 10 + 2.0 * m_borderSize, 10 + 2.0 * m_borderSize);
546 }
547 if (m_activeNodeID == 2) {
548 nodeRect = wxRect2DDouble(m_pointList[m_pointList.size() - 1].m_x - 5.0 - m_borderSize,
549 m_pointList[m_pointList.size() - 1].m_y - 5.0 - m_borderSize,
550 10 + 2.0 * m_borderSize, 10 + 2.0 * m_borderSize);
551 }
552
553 if (parent->Intersects(nodeRect)) {
554 // If the line has no parents set the new rated voltage, otherwise check if it's not connecting
555 // two different voltages buses
556 Bus* parentBus = static_cast<Bus*>(parent);
557 if (!m_parentList[0] && !m_parentList[1]) {
558 m_electricalData.nominalVoltage = parentBus->GetElectricalData().nominalVoltage;
559 m_electricalData.nominalVoltageUnit = parentBus->GetElectricalData().nominalVoltageUnit;
560 }
561 else if (m_electricalData.nominalVoltage != parentBus->GetElectricalData().nominalVoltage ||
562 m_electricalData.nominalVoltageUnit != parentBus->GetElectricalData().nominalVoltageUnit) {
563 wxMessageDialog msgDialog(nullptr,
564 _("Unable to connect two buses with different nominal voltages.\n"
565 "Use a transformer or edit the bus properties."),
566 _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
567 msgDialog.ShowModal();
568 m_activeNodeID = 0;
569 return false;
570 }
571
572 if (m_activeNodeID == 1) {
573 // Check if the user is trying to connect the same bus.
574 if (m_parentList[1] == parent) {
575 m_activeNodeID = 0;
576 return false;
577 }
578
579 m_parentList[0] = parent;
580
581 // Centralize the node on bus.
582 wxPoint2DDouble parentPt = parent->RotateAtPosition(
583 m_pointList[0], -parent->GetAngle()); // Rotate click to horizontal position.
584 parentPt.m_y = parent->GetPosition().m_y; // Centralize on bus.
585 parentPt = parent->RotateAtPosition(parentPt, parent->GetAngle());
586 m_pointList[0] = parentPt;
587
588 UpdateSwitchesPosition();
589 UpdatePowerFlowArrowsPosition();
590 return true;
591 }
592 if (m_activeNodeID == 2) {
593 if (m_parentList[0] == parent) {
594 m_activeNodeID = 0;
595 return false;
596 }
597
598 m_parentList[1] = parent;
599
600 wxPoint2DDouble parentPt =
601 parent->RotateAtPosition(m_pointList[m_pointList.size() - 1], -parent->GetAngle());
602 parentPt.m_y = parent->GetPosition().m_y;
603 parentPt = parent->RotateAtPosition(parentPt, parent->GetAngle());
604 m_pointList[m_pointList.size() - 1] = parentPt;
605
606 UpdateSwitchesPosition();
607 UpdatePowerFlowArrowsPosition();
608 return true;
609 }
610 }
611 else {
612 if (m_activeNodeID == 1) m_parentList[0] = nullptr;
613 if (m_activeNodeID == 2) m_parentList[1] = nullptr;
614 }
615 }
616 return false;
617}
618
620{
621 m_pfDirection = pfDirection;
622 UpdatePowerFlowArrowsPosition();
623}
624
625void Line::UpdatePowerFlowArrowsPosition()
626{
627 std::vector<wxPoint2DDouble> edges;
628 switch (m_pfDirection) {
630 m_powerFlowArrow.clear();
631 } break;
633 for (int i = 1; i < (int)m_pointList.size() - 1; i++) { edges.push_back(m_pointList[i]); }
634 } break;
636 for (int i = (int)m_pointList.size() - 2; i > 0; i--) { edges.push_back(m_pointList[i]); }
637 } break;
638 default:
639 break;
640 }
642}
643
644void Line::RotateNode(Element* parent, bool clockwise)
645{
646 double rotAngle = m_rotationAngle;
647 if (!clockwise) rotAngle = -m_rotationAngle;
648
649 if (parent == m_parentList[0]) {
650 m_pointList[0] = parent->RotateAtPosition(m_pointList[0], rotAngle);
651 }
652 else if (parent == m_parentList[1]) {
653 m_pointList[m_pointList.size() - 1] = parent->RotateAtPosition(m_pointList[m_pointList.size() - 1], rotAngle);
654 }
655 UpdateSwitchesPosition();
656 UpdatePowerFlowArrowsPosition();
657}
658
659void Line::SetPointList(std::vector<wxPoint2DDouble> pointList)
660{
661 m_pointList = pointList;
662 UpdateSwitchesPosition();
663 UpdatePowerFlowArrowsPosition();
664}
665
667{
668 Line* copy = new Line();
669 *copy = *this;
670 return copy;
671}
672
673wxString Line::GetTipText() const
674{
675 wxString tipText = m_electricalData.name;
676
677 if (m_online) {
678 tipText += "\n";
679 int busNumber[2];
680 busNumber[0] = static_cast<Bus*>(m_parentList[0])->GetElectricalData().number + 1;
681 busNumber[1] = static_cast<Bus*>(m_parentList[1])->GetElectricalData().number + 1;
682
683 tipText += _("\nP") + wxString::Format("(%d-%d) = ", busNumber[0], busNumber[1]) +
684 wxString::FromDouble(m_electricalData.powerFlow[0].real(), 5) + _(" p.u.");
685 tipText += _("\nQ") + wxString::Format("(%d-%d) = ", busNumber[0], busNumber[1]) +
686 wxString::FromDouble(m_electricalData.powerFlow[0].imag(), 5) + _(" p.u.");
687 tipText += _("\nP") + wxString::Format("(%d-%d) = ", busNumber[1], busNumber[0]) +
688 wxString::FromDouble(m_electricalData.powerFlow[1].real(), 5) + _(" p.u.");
689 tipText += _("\nQ") + wxString::Format("(%d-%d) = ", busNumber[1], busNumber[0]) +
690 wxString::FromDouble(m_electricalData.powerFlow[1].imag(), 5) + _(" p.u.");
691
692 if (!m_electricalData.harmonicOrder.empty()) {
693 tipText += _("\n\nHarmonic currents:");
694 int i = 0;
695 for (auto& hCurrent1 : m_electricalData.harmonicCurrent[0]) {
696 auto& hCurrent2 = m_electricalData.harmonicCurrent[1][i];
697 wxString i1, i2;
698 i1.Printf(_("\nIh(%d)(%d-%d) = %.5e%s%.2f%s p.u."), m_electricalData.harmonicOrder[i], busNumber[0], busNumber[1], std::abs(hCurrent1), wxString(L'\u2220'), wxRadToDeg(std::arg(hCurrent1)), wxString(L'\u00B0'));
699 i2.Printf(_("\nIh(%d)(%d-%d) = %.5e%s%.2f%s p.u."), m_electricalData.harmonicOrder[i], busNumber[1], busNumber[0], std::abs(hCurrent2), wxString(L'\u2220'), wxRadToDeg(std::arg(hCurrent2)), wxString(L'\u00B0'));
700
701 tipText += i1 + i2;
702 i++;
703 }
704 }
705 }
706
707 return tipText;
708}
709
710LineElectricalData Line::GetPUElectricalData(double systemBasePower)
711{
712 LineElectricalData data = m_electricalData;
713 double lineBasePower = GetValueFromUnit(data.nominalPower, data.nominalPowerUnit);
714 double baseVoltage = GetValueFromUnit(data.nominalVoltage, data.nominalVoltageUnit);
715 double systemBaseImpedance = (baseVoltage * baseVoltage) / systemBasePower;
716 double lineBaseImpedance = (baseVoltage * baseVoltage) / lineBasePower;
717
718 // Resistance
719 double r = data.resistance;
720 if (data.resistanceUnit == ElectricalUnit::UNIT_OHM_km) data.resistance = r * data.lineSize / systemBaseImpedance;
721 else if (data.resistanceUnit == ElectricalUnit::UNIT_PU) {
722 if (data.useLinePower) data.resistance = (r * lineBaseImpedance) / systemBaseImpedance;
723 }
724 else { // Ohm
725 data.resistance = r / systemBaseImpedance;
726 }
727 data.resistanceUnit = ElectricalUnit::UNIT_PU;
728
729 // Inductive reactance
730 double x = data.indReactance;
731 if (data.indReactanceUnit == ElectricalUnit::UNIT_OHM_km) data.indReactance = x * data.lineSize / systemBaseImpedance;
732 else if (data.indReactanceUnit == ElectricalUnit::UNIT_PU) {
733 if (data.useLinePower) data.indReactance = (x * lineBaseImpedance) / systemBaseImpedance;
734 }
735 else { // Ohm
736 data.indReactance = x / systemBaseImpedance;
737 }
738 data.indReactanceUnit = ElectricalUnit::UNIT_PU;
739
740 // Capacitive susceptance
741 double b = data.capSusceptance;
742 if (data.capSusceptanceUnit == ElectricalUnit::UNIT_S_km) data.capSusceptance = b * data.lineSize * systemBaseImpedance;
743 else if (data.capSusceptanceUnit == ElectricalUnit::UNIT_PU) {
744 if (data.useLinePower) data.capSusceptance = (b / lineBaseImpedance) * systemBaseImpedance;
745 }
746 else { // S
747 data.capSusceptance = b * systemBaseImpedance;
748 }
749 data.capSusceptanceUnit = ElectricalUnit::UNIT_PU;
750
751 // Fault
752
753 // Zero seq. resistance
754 double r0 = data.zeroResistance;
755 if (data.useLinePower) data.zeroResistance = (r0 * lineBaseImpedance) / systemBaseImpedance;
756
757 // Zero seq. ind. reactance
758 double x0 = data.zeroIndReactance;
759 if (data.useLinePower) data.zeroIndReactance = (x0 * lineBaseImpedance) / systemBaseImpedance;
760
761 // Zero seq. cap. susceptance
762 double b0 = data.zeroCapSusceptance;
763 if (data.useLinePower) data.zeroCapSusceptance = (b0 / lineBaseImpedance) * systemBaseImpedance;
764
765 if (!m_online) {
766 data.powerFlow[0] = std::complex<double>(0, 0);
767 data.powerFlow[1] = std::complex<double>(0, 0);
768 data.faultCurrent[0][0] = std::complex<double>(0, 0);
769 data.faultCurrent[0][1] = std::complex<double>(0, 0);
770 data.faultCurrent[0][2] = std::complex<double>(0, 0);
771 data.faultCurrent[1][0] = std::complex<double>(0, 0);
772 data.faultCurrent[1][1] = std::complex<double>(0, 0);
773 data.faultCurrent[1][2] = std::complex<double>(0, 0);
774 }
775
776 return data;
777}
778
779rapidxml::xml_node<>* Line::SaveElement(rapidxml::xml_document<>& doc, rapidxml::xml_node<>* elementListNode)
780{
781 auto elementNode = XMLParser::AppendNode(doc, elementListNode, "Line");
782 XMLParser::SetNodeAttribute(doc, elementNode, "ID", m_elementID);
783 auto cadProp = XMLParser::AppendNode(doc, elementNode, "CADProperties");
784 auto nodeList = XMLParser::AppendNode(doc, cadProp, "NodeList");
785 int nodeID = 0;
786 // Parse all the points.
787 for (unsigned int i = 0; i < m_pointList.size(); i++) {
788 // Don't save switch points, the method UpdateSwitchesPosition() calculate these points properly after open the
789 // element
790 if ((i != 1) && (i != m_pointList.size() - 2)) {
791 auto nodePos = XMLParser::AppendNode(doc, nodeList, "Node");
792 XMLParser::SetNodeAttribute(doc, nodePos, "ID", nodeID);
793 auto nodePosX = XMLParser::AppendNode(doc, nodePos, "X");
794 XMLParser::SetNodeValue(doc, nodePosX, m_pointList[i].m_x);
795 auto nodePosY = XMLParser::AppendNode(doc, nodePos, "Y");
796 XMLParser::SetNodeValue(doc, nodePosY, m_pointList[i].m_y);
797 nodeID++;
798 }
799 }
800
801 auto parentIDList = XMLParser::AppendNode(doc, cadProp, "ParentIDList");
802 for (unsigned int i = 0; i < m_parentList.size(); i++) {
803 if (m_parentList[i]) {
804 auto parentID = XMLParser::AppendNode(doc, parentIDList, "ParentID");
805 XMLParser::SetNodeAttribute(doc, parentID, "ID", static_cast<int>(i));
806 XMLParser::SetNodeValue(doc, parentID, m_parentList[i]->GetID());
807 }
808 }
809
810 auto electricalProp = XMLParser::AppendNode(doc, elementNode, "ElectricalProperties");
811 auto isOnline = XMLParser::AppendNode(doc, electricalProp, "IsOnline");
812 XMLParser::SetNodeValue(doc, isOnline, m_online);
813 auto name = XMLParser::AppendNode(doc, electricalProp, "Name");
814 XMLParser::SetNodeValue(doc, name, m_electricalData.name);
815 auto nominalVoltage = XMLParser::AppendNode(doc, electricalProp, "NominalVoltage");
816 XMLParser::SetNodeValue(doc, nominalVoltage, m_electricalData.nominalVoltage);
817 XMLParser::SetNodeAttribute(doc, nominalVoltage, "UnitID", static_cast<int>(m_electricalData.nominalVoltageUnit));
818 auto nominalPower = XMLParser::AppendNode(doc, electricalProp, "NominalPower");
819 XMLParser::SetNodeValue(doc, nominalPower, m_electricalData.nominalPower);
820 XMLParser::SetNodeAttribute(doc, nominalPower, "UnitID", static_cast<int>(m_electricalData.nominalPowerUnit));
821 auto resistance = XMLParser::AppendNode(doc, electricalProp, "Resistance");
822 XMLParser::SetNodeValue(doc, resistance, m_electricalData.resistance);
823 XMLParser::SetNodeAttribute(doc, resistance, "UnitID", static_cast<int>(m_electricalData.resistanceUnit));
824 auto indReactance = XMLParser::AppendNode(doc, electricalProp, "IndReactance");
825 XMLParser::SetNodeValue(doc, indReactance, m_electricalData.indReactance);
826 XMLParser::SetNodeAttribute(doc, indReactance, "UnitID", static_cast<int>(m_electricalData.indReactanceUnit));
827 auto capSusceptance = XMLParser::AppendNode(doc, electricalProp, "CapSusceptance");
828 XMLParser::SetNodeValue(doc, capSusceptance, m_electricalData.capSusceptance);
829 XMLParser::SetNodeAttribute(doc, capSusceptance, "UnitID", static_cast<int>(m_electricalData.capSusceptanceUnit));
830 auto lineSize = XMLParser::AppendNode(doc, electricalProp, "LineSize");
831 XMLParser::SetNodeValue(doc, lineSize, m_electricalData.lineSize);
832 auto useLinePower = XMLParser::AppendNode(doc, electricalProp, "UseLinePower");
833 XMLParser::SetNodeValue(doc, useLinePower, m_electricalData.useLinePower);
834
835 auto fault = XMLParser::AppendNode(doc, electricalProp, "Fault");
836 auto zeroResistance = XMLParser::AppendNode(doc, fault, "ZeroResistance");
837 XMLParser::SetNodeValue(doc, zeroResistance, m_electricalData.zeroResistance);
838 auto zeroIndReactance = XMLParser::AppendNode(doc, fault, "ZeroIndReactance");
839 XMLParser::SetNodeValue(doc, zeroIndReactance, m_electricalData.zeroIndReactance);
840 auto zeroCapSusceptance = XMLParser::AppendNode(doc, fault, "ZeroCapSusceptance");
841 XMLParser::SetNodeValue(doc, zeroCapSusceptance, m_electricalData.zeroCapSusceptance);
842
843 SaveSwitchingData(doc, electricalProp);
844
845 return elementNode;
846}
847
848bool Line::OpenElement(rapidxml::xml_node<>* elementNode, std::vector<Element*> parentList)
849{
850 auto cadPropNode = elementNode->first_node("CADProperties");
851 if (!cadPropNode) return false;
852
853 // Get nodes points
854 std::vector<wxPoint2DDouble> ptsList;
855 auto nodePosList = cadPropNode->first_node("NodeList");
856 if (!nodePosList) return false;
857 auto nodePos = nodePosList->first_node("Node");
858 while (nodePos) {
859 double nodePosX = XMLParser::GetNodeValueDouble(nodePos, "X");
860 double nodePosY = XMLParser::GetNodeValueDouble(nodePos, "Y");
861 ptsList.push_back(wxPoint2DDouble(nodePosX, nodePosY));
862 nodePos = nodePos->next_sibling("Node");
863 }
864
865 // Get parents IDs
866 auto parentIDList = cadPropNode->first_node("ParentIDList");
867 if (!parentIDList) return false;
868 auto parentNode = parentIDList->first_node("ParentID");
869 long parentID[2] = { -1, -1 };
870 while (parentNode) {
871 long index = 0;
872 wxString(parentNode->first_attribute("ID")->value()).ToCLong(&index);
873 wxString(parentNode->value()).ToCLong(&parentID[index]);
874 parentNode = parentNode->next_sibling("ParentID");
875 }
876
877 std::vector<wxPoint2DDouble> nodePtsList; // List of node points
878 nodePtsList.push_back(ptsList[0]); // First point on the list
879 nodePtsList.push_back(ptsList[ptsList.size() - 1]); // Last point on the list
880
881 // List of dummy buses to set not connected nodes properly
882 std::vector<Bus*> dummyBusList;
883 for (unsigned int i = 0; i < nodePtsList.size(); ++i) {
884 if (parentID[i] == -1) // No parent connected
885 {
886 Bus* dummyBus = new Bus(nodePtsList[i]);
887 dummyBusList.push_back(dummyBus);
888 AddParent(dummyBus, nodePtsList[i]);
889 }
890 else { // Parent connected (necessarily a bus, get from bus list)
891 AddParent(parentList[parentID[i]], nodePtsList[i]);
892 }
893 }
894
895 // Add the others nodes (if exists)
896 std::vector<wxPoint2DDouble> midPts;
897 for (unsigned int i = 1; i < ptsList.size() - 1; i++) midPts.push_back(ptsList[i]);
898 m_pointList.insert(m_pointList.begin() + 2, midPts.begin(), midPts.end());
899 SetPointList(m_pointList);
900
901 // Remove dummy buses
902 for (auto it = dummyBusList.begin(), itEnd = dummyBusList.end(); it != itEnd; ++it) {
903 RemoveParent(*it);
904 delete* it;
905 }
906 dummyBusList.clear();
907
908 auto electricalProp = elementNode->first_node("ElectricalProperties");
909 if (!electricalProp) return false;
910
911 SetOnline(XMLParser::GetNodeValueInt(electricalProp, "IsOnline"));
912 m_electricalData.name = electricalProp->first_node("Name")->value();
913 m_electricalData.nominalVoltage = XMLParser::GetNodeValueDouble(electricalProp, "NominalVoltage");
914 m_electricalData.nominalVoltageUnit =
915 static_cast<ElectricalUnit>(XMLParser::GetAttributeValueInt(electricalProp, "NominalVoltage", "UnitID"));
916 m_electricalData.nominalPower = XMLParser::GetNodeValueDouble(electricalProp, "NominalPower");
917 m_electricalData.nominalPowerUnit =
918 static_cast<ElectricalUnit>(XMLParser::GetAttributeValueInt(electricalProp, "NominalPower", "UnitID"));
919 m_electricalData.resistance = XMLParser::GetNodeValueDouble(electricalProp, "Resistance");
920 m_electricalData.resistanceUnit =
921 static_cast<ElectricalUnit>(XMLParser::GetAttributeValueInt(electricalProp, "Resistance", "UnitID"));
922 m_electricalData.indReactance = XMLParser::GetNodeValueDouble(electricalProp, "IndReactance");
923 m_electricalData.indReactanceUnit =
924 static_cast<ElectricalUnit>(XMLParser::GetAttributeValueInt(electricalProp, "IndReactance", "UnitID"));
925 m_electricalData.capSusceptance = XMLParser::GetNodeValueDouble(electricalProp, "CapSusceptance");
926 m_electricalData.capSusceptanceUnit =
927 static_cast<ElectricalUnit>(XMLParser::GetAttributeValueInt(electricalProp, "CapSusceptance", "UnitID"));
928 m_electricalData.lineSize = XMLParser::GetNodeValueDouble(electricalProp, "LineSize");
929 m_electricalData.useLinePower = XMLParser::GetNodeValueInt(electricalProp, "UseLinePower");
930
931 auto fault = electricalProp->first_node("Fault");
932 m_electricalData.zeroResistance = XMLParser::GetNodeValueDouble(fault, "ZeroResistance");
933 m_electricalData.zeroIndReactance = XMLParser::GetNodeValueDouble(fault, "ZeroIndReactance");
934 m_electricalData.zeroCapSusceptance = XMLParser::GetNodeValueDouble(fault, "ZeroCapSusceptance");
935
936 if (!OpenSwitchingData(electricalProp)) return false;
937 if (m_swData.swTime.size() != 0) SetDynamicEvent(true);
938 return true;
939}
@ ID_LINE_REMOVE_NODE
Definition Element.h:77
@ ID_DELETE
Definition Element.h:80
@ ID_EDIT_ELEMENT
Definition Element.h:75
@ ID_LINE_ADD_NODE
Definition Element.h:76
@ ID_PB_NONE
Definition Element.h:61
ElectricalUnit
Electrical units.
PowerFlowDirection
Direction of power flow arrows.
Abstract class for branch power elements.
Definition Branch.h:32
virtual void UpdateSwitches()
Update the switch position.
Definition Branch.cpp:179
virtual void RemoveParent(Element *parent)
Remove a parent.
Definition Branch.cpp:105
Node for power elements. All others power elements are connected through this.
Definition Bus.h:86
Base class of all elements of the program. This class is responsible for manage graphical and his dat...
Definition Element.h:112
virtual bool Intersects(wxRect2DDouble rect) const =0
Check if the element's rect intersects other rect.
virtual int GetID() const
Get the element ID.
Definition Element.h:271
virtual double PointToLineDistance(wxPoint2DDouble point, int *segmentNumber=nullptr) const
Calculate the distance between a line (formed by point list) and a point.
Definition Element.cpp:529
wxPoint2DDouble GetPosition() const
Get the element position.
Definition Element.h:186
double GetAngle() const
Get the element angle.
Definition Element.h:211
virtual wxPoint2DDouble RotateAtPosition(wxPoint2DDouble pointToRotate, double angle, bool degrees=true) const
Rotate a point as element position being the origin.
Definition Element.cpp:223
virtual void DrawDCPickbox(wxPoint2DDouble position, wxGraphicsContext *gc) const
Draw a point.
Definition Element.cpp:216
virtual wxPoint2DDouble WorldToScreen(wxPoint2DDouble translation, double scale, double offsetX=0.0, double offsetY=0.0) const
Convert the element position to screen position.
Definition Element.cpp:267
virtual void AddChild(Element *child)
Add a child to the child list.
Definition Element.cpp:494
bool SetOnline(bool online=true)
Set if the element is online or offline.
Definition Element.cpp:378
virtual void DrawDCCircle(wxPoint2DDouble position, double radius, int numSegments, wxGraphicsContext *gc) const
Draw a circle using device context.
Definition Element.cpp:168
void SetInserted(bool inserted=true)
Set if the element is properly inserted in the workspace.
Definition Element.h:631
Form to edit the line power data.
Definition LineForm.h:33
Power line element.
Definition Line.h:64
virtual void AddPoint(wxPoint2DDouble point)
Add point to the list of points that connect the element to the bus.
Definition Line.cpp:373
virtual bool Contains(wxPoint2DDouble position) const
Checks if the element contains a position.
Definition Line.cpp:38
virtual bool Intersects(wxRect2DDouble rect) const
Check if the element's rect intersects other rect.
Definition Line.cpp:342
virtual void SetNominalVoltage(std::vector< double > nominalVoltage, std::vector< ElectricalUnit > nominalVoltageUnit)
Set nominal voltage of the element.
Definition Line.cpp:528
virtual bool ShowForm(wxWindow *parent, Element *element)
Show element data form.
Definition Line.cpp:518
virtual bool AddParent(Element *parent, wxPoint2DDouble position)
Add a parent to the element. This method must be used on power elements that connect to a bus,...
Definition Line.cpp:276
virtual void DrawDC(wxPoint2DDouble translation, double scale, wxGraphicsContext *gc) const
Draw the element using GDI+.
Definition Line.cpp:108
virtual void Move(wxPoint2DDouble position)
Move the element other position.
Definition Line.cpp:256
virtual void SetPowerFlowDirection(PowerFlowDirection pfDirection)
Set the direction of the power flow.
Definition Line.cpp:619
virtual wxString GetTipText() const
Get the tip text.
Definition Line.cpp:673
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 Line.cpp:536
virtual bool GetContextMenu(wxMenu &menu)
Get the element contex menu.
Definition Line.cpp:428
virtual void CalculateBoundaries(wxPoint2DDouble &leftUp, wxPoint2DDouble &rightBottom) const
Calculate the element boundaries.
Definition Line.cpp:503
virtual void RotateNode(Element *parent, bool clockwise=true)
Rotate a node.
Definition Line.cpp:644
virtual void StartMove(wxPoint2DDouble position)
Update the element attributes related to the movement.
Definition Line.cpp:378
virtual void MoveNode(Element *parent, wxPoint2DDouble position)
Move a node. StartMove(wxPoint2DDouble position) before start moving.
Definition Line.cpp:384
virtual bool PickboxContains(wxPoint2DDouble position)
Check if a pickbox contains a point. If contains the attributes related to pickbox movement will be c...
Definition Line.cpp:361
virtual void SetPointList(std::vector< wxPoint2DDouble > pointList)
Set the list of points that connect the element to the bus.
Definition Line.cpp:659
virtual void MovePickbox(wxPoint2DDouble position)
Move the pickbox.
Definition Line.cpp:349
virtual Element * GetCopy()
Get a the element copy.
Definition Line.cpp:666
virtual void SetDynamicEvent(bool dynEvent=true)
Set if the power element have dynamic event.
virtual void CalculatePowerFlowPts(std::vector< wxPoint2DDouble > edges)
Calculate the points of the power flow arrows.
virtual void DrawDCSwitches(wxGraphicsContext *gc) const
Draw switch.
virtual void DrawDCPowerFlowPts(wxGraphicsContext *gc) const
Draw power flow arrows.
virtual wxPoint2DDouble GetSwitchPoint(Element *parent, wxPoint2DDouble parentPoint, wxPoint2DDouble secondPoint) const
Get the correct switch position.
std::vector< double > swTime