libelec
A general purpose library of utility functions designed to make it easier to develop addons for the X-Plane flight simulator.
libelec_drawing.c
1 /*
2  * This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5  */
6 /*
7  * Copyright 2023 Saso Kiselkov. All rights reserved.
8  */
9 
10 #include <acfutils/cairo_utils.h>
11 #include <acfutils/perf.h>
12 
13 #include "libelec_drawing.h"
14 #include "libelec_types_impl.h"
15 
16 #define PX(pos) (pos_scale * (pos))
17 #define LINE_HEIGHT 1
18 #define COMP_INFO_BG_RGB 0.8, 0.8, 0.8
19 #define MAX_NAME_LEN 128
20 
21 enum {
22  TEXT_ALIGN_LEFT,
23  TEXT_ALIGN_CENTER,
24  TEXT_ALIGN_RIGHT
25 };
26 
27 static void show_text_aligned(cairo_t *cr, double x, double y, unsigned align,
28  const char *format, ...) PRINTF_ATTR(5);
29 
30 static void
31 make_comp_name(const char *in_name, char out_name[MAX_NAME_LEN])
32 {
33  unsigned n;
34 
35  ASSERT(in_name);
36  ASSERT(out_name);
37 
38  strlcpy(out_name, in_name, MAX_NAME_LEN);
39  for (unsigned i = 0, n = strlen(out_name); i < n; i++) {
40  if (out_name[i] == '_')
41  out_name[i] = ' ';
42  }
43  if (strncmp(out_name, "CB ", 3) == 0)
44  memmove(out_name, &out_name[3], strlen(out_name) - 3 + 1);
45  n = strlen(out_name);
46  if (n > 4 && strcmp(&out_name[n - 4], " O P") == 0)
47  out_name[n - 2] = '/';
48 }
49 
50 static void
51 get_srcs(const elec_comp_t *comp, elec_comp_t *srcs[ELEC_MAX_SRCS])
52 {
53  ASSERT(comp != NULL);
54  ASSERT(srcs != NULL);
55 
56  mutex_enter(&((elec_comp_t *)comp)->rw_ro_lock);
57  memcpy(srcs, comp->srcs_ext, sizeof (*srcs) * ELEC_MAX_SRCS);
58  mutex_exit(&((elec_comp_t *)comp)->rw_ro_lock);
59 }
60 
61 static unsigned
62 count_srcs(elec_comp_t *srcs[ELEC_MAX_SRCS])
63 {
64  ASSERT(srcs != NULL);
65 
66  for (unsigned n_srcs = 0; n_srcs < ELEC_MAX_SRCS; n_srcs++) {
67  if (srcs[n_srcs] == NULL)
68  return (n_srcs);
69  }
70  return (ELEC_MAX_SRCS);
71 }
72 
73 static void
74 show_text_aligned(cairo_t *cr, double x, double y, unsigned align,
75  const char *format, ...)
76 {
77  cairo_text_extents_t te;
78  char *str;
79  va_list ap;
80 
81  ASSERT(cr != NULL);
82  ASSERT(format != NULL);
83  va_start(ap, format);
84  str = vsprintf_alloc(format, ap);
85  va_end(ap);
86 
87  cairo_text_extents(cr, str, &te);
88  switch (align) {
89  case TEXT_ALIGN_LEFT:
90  cairo_move_to(cr, x - te.x_bearing,
91  y - te.height / 2 - te.y_bearing);
92  break;
93  case TEXT_ALIGN_CENTER:
94  cairo_move_to(cr, x - te.width / 2 - te.x_bearing,
95  y - te.height / 2 - te.y_bearing);
96  break;
97  case TEXT_ALIGN_RIGHT:
98  cairo_move_to(cr, x - te.width - te.x_bearing,
99  y - te.height / 2 - te.y_bearing);
100  break;
101  }
102  cairo_show_text(cr, str);
103 
104  free(str);
105 }
106 
107 static vect2_t
108 tie_node_pos(const elec_comp_t *tie, unsigned i)
109 {
110  vect2_t pos;
111 
112  ASSERT(tie != NULL);
113  ASSERT3U(tie->info->type, ==, ELEC_TIE);
114  ASSERT3U(i, <, tie->n_links);
115  ASSERT(tie->n_links != 0);
116 
117  pos = tie->info->gui.pos;
118  if (tie->n_links == 2) {
119  if (i == 0)
120  return (VECT2(pos.x - 1, pos.y));
121  else
122  return (VECT2(pos.x + 1, pos.y));
123  } else if (tie->n_links == 3) {
124  vect2_t off;
125  switch (i) {
126  case 0:
127  off = VECT2(0, 1);
128  break;
129  case 1:
130  off = VECT2(-1, -1);
131  break;
132  case 2:
133  off = VECT2(1, -1);
134  break;
135  }
136  off = vect2_rot(off, tie->info->gui.rot);
137  return (vect2_add(pos, VECT2(off.x, -off.y)));
138  } else {
139  vect2_t off = vect2_rot(VECT2(0, 1), tie->info->gui.rot);
140 
141  off = vect2_rot(off, i * (360.0 / tie->n_links));
142  return (vect2_add(pos, VECT2(off.x, -off.y)));
143  }
144 }
145 
146 static vect2_t
147 pick_closer(vect2_t ref, vect2_t a, vect2_t b)
148 {
149  double la = vect2_abs(vect2_sub(a, ref));
150  double lb = vect2_abs(vect2_sub(b, ref));
151  return (la <= lb ? a : b);
152 }
153 
154 static bool
155 elec_comp_get_nearest_pos(const elec_comp_t *comp, vect2_t *comp_pos,
156  vect2_t *bus_pos, double bus_sz, bool *align_vert)
157 {
158  vect2_t pos;
159 
160  ASSERT(comp != NULL);
161  ASSERT(comp_pos != NULL);
162  ASSERT(bus_pos != NULL);
163  ASSERT(align_vert != NULL);
164 
165  pos = comp->info->gui.pos;
166  if (IS_NULL_VECT(comp->info->gui.pos))
167  return (false);
168 
169  *align_vert = (comp->info->type == ELEC_TRU ||
170  comp->info->type == ELEC_INV || comp->info->type == ELEC_BATT);
171 
172  switch (comp->info->type) {
173  case ELEC_BATT:
174  *comp_pos = VECT2(pos.x, pos.y - 0.2);
175  break;
176  case ELEC_CB:
177  *comp_pos = pick_closer(*bus_pos, vect2_add(pos, VECT2(-1, 0)),
178  vect2_add(pos, VECT2(1, 0)));
179  break;
180  case ELEC_SHUNT:
181  *comp_pos = pick_closer(*bus_pos,
182  vect2_add(pos, VECT2(-2.5, 0)),
183  vect2_add(pos, VECT2(2.5, 0)));
184  break;
185  case ELEC_TIE: {
186  vect2_t p = VECT2(1e9, 1e9);
187 
188  for (unsigned i = 0; i < comp->n_links; i++)
189  p = pick_closer(*bus_pos, p, tie_node_pos(comp, i));
190  *comp_pos = p;
191  break;
192  }
193  default:
194  *comp_pos = comp->info->gui.pos;
195  break;
196  }
197  *bus_pos = VECT2(bus_pos->x, clamp(comp_pos->y,
198  bus_pos->y - bus_sz, bus_pos->y + bus_sz));
199 
200  return (true);
201 }
202 
203 static void
204 draw_src_path(cairo_t *cr, cairo_path_t *path, const elec_comp_t *comp)
205 {
206  cairo_pattern_t *pat;
207  vect3_t color;
208  elec_comp_t *srcs[ELEC_MAX_SRCS];
209  unsigned n_srcs;
210 
211  ASSERT(cr != NULL);
212  ASSERT(path != NULL);
213  ASSERT(comp != NULL);
214 
215  get_srcs(comp, srcs);
216  n_srcs = count_srcs(srcs);
217 
218  switch (n_srcs) {
219  case 0:
220  break;
221  case 1:
222  color = srcs[0]->info->gui.color;
223  cairo_append_path(cr, path);
224  cairo_set_source_rgb(cr, color.x, color.y, color.z);
225  cairo_stroke(cr);
226  cairo_set_source_rgb(cr, 0, 0, 0);
227  break;
228  default:
229  pat = cairo_pattern_create_linear(0, 0, n_srcs * 8, n_srcs * 8);
230  cairo_pattern_set_extend(pat, CAIRO_EXTEND_REPEAT);
231  for (unsigned i = 0; i < n_srcs; i++) {
232  vect3_t color = srcs[i]->info->gui.color;
233  double off1 = i / (double)n_srcs;
234  double off2 = (i + 1) / (double)n_srcs;
235  cairo_pattern_add_color_stop_rgb(pat, off1,
236  color.x, color.y, color.z);
237  cairo_pattern_add_color_stop_rgb(pat, off2,
238  color.x, color.y, color.z);
239  }
240  cairo_append_path(cr, path);
241  cairo_set_source(cr, pat);
242  cairo_stroke(cr);
243  cairo_set_source_rgb(cr, 0, 0, 0);
244  cairo_pattern_destroy(pat);
245  break;
246  }
247  cairo_path_destroy(path);
248 }
249 
250 static void
251 draw_bus_conns(cairo_t *cr, double pos_scale, const elec_comp_t *bus)
252 {
253  ASSERT(cr != NULL);
254  ASSERT(bus != NULL);
255  ASSERT3U(bus->info->type, ==, ELEC_BUS);
256 
257  if (IS_NULL_VECT(bus->info->gui.pos))
258  return;
259 
260  cairo_new_path(cr);
261 
262  for (unsigned i = 0; i < bus->n_links; i++) {
263  vect2_t bus_pos = bus->info->gui.pos;
264  vect2_t comp_pos;
265  const elec_comp_t *comp = bus->links[i].comp;
266  bool align_vert;
267  cairo_path_t *path;
268 
269  if (!elec_comp_get_nearest_pos(comp, &comp_pos, &bus_pos,
270  bus->info->gui.sz, &align_vert)) {
271  continue;
272  }
273  /*
274  * The connection line itself.
275  */
276  if (align_vert) {
277  cairo_move_to(cr, PX(bus_pos.x), PX(bus_pos.y));
278  cairo_line_to(cr, PX(comp_pos.x), PX(bus_pos.y));
279  cairo_line_to(cr, PX(comp_pos.x), PX(comp_pos.y));
280  } else {
281  cairo_move_to(cr, PX(bus_pos.x), PX(bus_pos.y));
282  cairo_line_to(cr, PX(AVG(bus_pos.x, comp_pos.x)),
283  PX(bus_pos.y));
284  cairo_line_to(cr, PX(AVG(bus_pos.x, comp_pos.x)),
285  PX(comp_pos.y));
286  cairo_line_to(cr, PX(comp_pos.x), PX(comp_pos.y));
287  }
288  path = cairo_copy_path(cr);
289  cairo_set_line_width(cr, 3);
290  cairo_stroke(cr);
291 
292  cairo_set_line_width(cr, 2);
293  draw_src_path(cr, path, bus);
294  /*
295  * The black dimple on the bus showing the connection
296  */
297  if (!bus->info->gui.invis) {
298  if (bus->info->gui.sz != 0 && !bus->info->gui.virt) {
299  cairo_arc(cr, PX(bus_pos.x), PX(bus_pos.y),
300  PX(0.4), 0, DEG2RAD(360));
301  } else if (bus->n_links > 2) {
302  cairo_arc(cr, PX(bus_pos.x), PX(bus_pos.y),
303  PX(0.25), 0, DEG2RAD(360));
304  }
305  cairo_fill(cr);
306  }
307  }
308 }
309 
310 static void
311 draw_gen(cairo_t *cr, double pos_scale, const elec_comp_info_t *info)
312 {
313  vect2_t pos;
314  vect3_t color;
315  char name[MAX_NAME_LEN];
316 
317  ASSERT(cr != NULL);
318  ASSERT(info != NULL);
319  pos = info->gui.pos;
320  color = info->gui.color;
321 
322  cairo_new_path(cr);
323 
324  cairo_set_source_rgb(cr, color.x, color.y, color.z);
325  cairo_arc(cr, PX(pos.x), PX(pos.y), PX(1.2), 0, DEG2RAD(360));
326  cairo_fill(cr);
327 
328  cairo_set_line_width(cr, 2);
329  cairo_set_source_rgb(cr, 0, 0, 0);
330  cairo_arc(cr, PX(pos.x), PX(pos.y), PX(1.2), 0, DEG2RAD(360));
331  if (info->gen.freq != 0) {
332  cairo_move_to(cr, PX(pos.x - 0.9), PX(pos.y));
333  cairo_rel_curve_to(cr, PX(0.2), PX(-0.7),
334  PX(0.7), PX(-0.7), PX(0.9), 0);
335  cairo_rel_curve_to(cr, PX(0.2), PX(0.7),
336  PX(0.7), PX(0.7), PX(0.9), 0);
337  } else {
338  cairo_move_to(cr, PX(pos.x - 0.8), PX(pos.y - 0.2));
339  cairo_rel_line_to(cr, PX(1.6), 0);
340  cairo_move_to(cr, PX(pos.x - 0.8), PX(pos.y + 0.2));
341  cairo_rel_line_to(cr, PX(1.6), 0);
342  }
343  cairo_stroke(cr);
344 
345  make_comp_name(info->name, name);
346  show_text_aligned(cr, PX(pos.x), PX(pos.y + 2), TEXT_ALIGN_CENTER,
347  "%s", name);
348 }
349 
350 static void
351 draw_bus(cairo_t *cr, double pos_scale, const elec_comp_t *bus)
352 {
353  const elec_comp_info_t *info;
354  vect2_t pos;
355 
356  ASSERT(cr != NULL);
357  ASSERT(bus != NULL);
358  info = bus->info;
359  pos = info->gui.pos;
360 
361  if (info->gui.invis)
362  return;
363 
364  cairo_new_path(cr);
365 
366  if (info->gui.sz != 0) {
367  cairo_path_t *path;
368 
369  cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
370  if (!info->gui.virt)
371  cairo_set_line_width(cr, 10);
372  else
373  cairo_set_line_width(cr, 3);
374  cairo_move_to(cr, PX(pos.x), PX(pos.y - info->gui.sz));
375  cairo_rel_line_to(cr, 0, PX(2 * info->gui.sz));
376  path = cairo_copy_path(cr);
377  cairo_stroke(cr);
378  cairo_set_line_width(cr, !info->gui.virt ? 4 : 2);
379  draw_src_path(cr, path, bus);
380  cairo_set_line_width(cr, 2);
381  cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT);
382  }
383 
384  if (info->gui.sz != 0 && !info->gui.virt) {
385  char name[MAX_NAME_LEN];
386  make_comp_name(info->name, name);
387  show_text_aligned(cr, PX(pos.x), PX(pos.y - info->gui.sz - 1),
388  TEXT_ALIGN_CENTER, "%s", name);
389  }
390 }
391 
392 static void
393 draw_cb_icon(cairo_t *cr, double pos_scale, double font_sz, vect2_t pos,
394  bool fuse, bool set, bool triphase, const char *comp_name,
395  vect3_t bg_color, const elec_comp_t *comp)
396 {
397  double text_y_off;
398  cairo_path_t *path;
399  char name[MAX_NAME_LEN];
400 
401  ASSERT(cr != NULL);
402  ASSERT(comp_name != NULL);
403  ASSERT(comp != NULL);
404 
405  make_comp_name(comp_name, name);
406  cairo_new_path(cr);
407 
408  if (!fuse) {
409  /* Yoke on the top */
410  cairo_move_to(cr, PX(pos.x), PX(pos.y - (set ? 0.5 : 1)));
411  cairo_set_line_width(cr, 2);
412  cairo_rel_line_to(cr, 0, PX(-0.5));
413  cairo_rel_move_to(cr, PX(-0.4), 0);
414  cairo_rel_line_to(cr, PX(0.8), 0);
415  cairo_stroke(cr);
416  /* The arch of the breaker */
417  cairo_set_line_width(cr, 3);
418  cairo_arc(cr, PX(pos.x), PX(pos.y + (set ? 0.5 : 0)), PX(1.1),
419  DEG2RAD(215), DEG2RAD(-35));
420  } else {
421  cairo_set_line_width(cr, 3);
422  cairo_move_to(cr, PX(pos.x - 1), PX(pos.y));
423  cairo_rel_curve_to(cr, PX(0.2), PX(0.8), PX(0.8), PX(0.8),
424  PX(1), 0);
425  cairo_rel_curve_to(cr, PX(0.2), PX(-0.8), PX(0.8), PX(-0.8),
426  PX(1), 0);
427  }
428  path = cairo_copy_path(cr);
429  cairo_stroke(cr);
430 
431  cairo_set_line_width(cr, 2);
432  draw_src_path(cr, path, comp);
433 
434  if (fuse && !set) {
435  /* If the fuse is broken, remove the middle bit */
436  cairo_set_source_rgb(cr, bg_color.x, bg_color.y, bg_color.z);
437  cairo_arc(cr, PX(pos.x), PX(pos.y), PX(0.3), 0, DEG2RAD(360));
438  cairo_fill(cr);
439  }
440  /* The two dimples on either side */
441  cairo_set_source_rgb(cr, 1, 1, 1);
442  cairo_arc(cr, PX(pos.x - 1), PX(pos.y), PX(0.2), 0, DEG2RAD(360));
443  cairo_new_sub_path(cr);
444  cairo_arc(cr, PX(pos.x + 1), PX(pos.y), PX(0.2), 0, DEG2RAD(360));
445  cairo_fill(cr);
446  cairo_set_source_rgb(cr, 0, 0, 0);
447  cairo_arc(cr, PX(pos.x - 1), PX(pos.y), PX(0.2), 0, DEG2RAD(360));
448  cairo_new_sub_path(cr);
449  cairo_arc(cr, PX(pos.x + 1), PX(pos.y), PX(0.2), 0, DEG2RAD(360));
450  cairo_stroke(cr);
451 
452  if (triphase) {
453  cairo_set_font_size(cr, round(0.75 * font_sz));
454  show_text_aligned(cr, PX(pos.x), PX(pos.y), TEXT_ALIGN_CENTER,
455  "3P");
456  cairo_set_font_size(cr, font_sz);
457  }
458 
459  text_y_off = (fuse ? 1.5 : 0.8);
460  show_text_aligned(cr, PX(pos.x), PX(pos.y + text_y_off),
461  TEXT_ALIGN_CENTER, "%s", name);
462 }
463 
464 static void
465 draw_cb(cairo_t *cr, double pos_scale, const elec_comp_t *cb, double font_sz,
466  vect3_t bg_color)
467 {
468  ASSERT(cr != NULL);
469  ASSERT(cb != NULL);
470  draw_cb_icon(cr, pos_scale, font_sz, cb->info->gui.pos,
471  cb->info->cb.fuse, !cb->ro.failed && cb->scb.cur_set,
472  cb->info->cb.triphase, cb->info->name, bg_color, cb);
473 }
474 
475 static void
476 draw_shunt(cairo_t *cr, double pos_scale, const elec_comp_t *shunt)
477 {
478  vect2_t pos;
479  cairo_path_t *path;
480  const elec_comp_info_t *info;
481  char name[MAX_NAME_LEN];
482 
483  ASSERT(cr != NULL);
484  ASSERT(shunt != NULL);
485  info = shunt->info;
486  pos = info->gui.pos;
487 
488  cairo_new_path(cr);
489  cairo_set_line_width(cr, 3);
490  cairo_move_to(cr, PX(pos.x - 2.5), PX(pos.y));
491  cairo_rel_line_to(cr, PX(1), PX(0));
492  for (int i = 0; i < 3; i++) {
493  cairo_rel_line_to(cr, PX(0.25), PX(-0.7));
494  cairo_rel_line_to(cr, PX(0.5), PX(1.4));
495  cairo_rel_line_to(cr, PX(0.25), PX(-0.7));
496  }
497  cairo_rel_line_to(cr, PX(1), PX(0));
498  path = cairo_copy_path(cr);
499  cairo_stroke(cr);
500 
501  cairo_set_line_width(cr, 2);
502  draw_src_path(cr, path, shunt);
503 
504  make_comp_name(info->name, name);
505  show_text_aligned(cr, PX(pos.x), PX(pos.y + 1.7),
506  TEXT_ALIGN_CENTER, "%s", name);
507 }
508 
509 static void
510 draw_tru_inv(cairo_t *cr, double pos_scale, const elec_comp_info_t *info)
511 {
512  vect2_t pos;
513  vect3_t color;
514  char name[MAX_NAME_LEN];
515 
516  ASSERT(cr != NULL);
517  ASSERT(info != NULL);
518  pos = info->gui.pos;
519  color = info->gui.color;
520 
521  cairo_new_path(cr);
522 
523  /* Background fill */
524  cairo_set_source_rgb(cr, color.x, color.y, color.z);
525  cairo_rectangle(cr, PX(pos.x - 1.5), PX(pos.y - 1.5), PX(3), PX(3));
526  cairo_fill(cr);
527 
528  /* Line art */
529  cairo_set_source_rgb(cr, 0, 0, 0);
530  /* Surrounding box */
531  cairo_rectangle(cr, PX(pos.x - 1.5), PX(pos.y - 1.5), PX(3), PX(3));
532 
533  /* Cross-line through the box */
534  cairo_move_to(cr, PX(pos.x - 1.5), PX(pos.y + 1.5));
535  cairo_rel_line_to(cr, PX(3), PX(-3));
536 
537  /* Wavy line showing AC side */
538  if (info->type == ELEC_TRU)
539  cairo_move_to(cr, PX(pos.x - 0.8), PX(pos.y - 1));
540  else
541  cairo_move_to(cr, PX(pos.x + 0.8), PX(pos.y + 1));
542  cairo_rel_move_to(cr, PX(-0.5), 0);
543  cairo_rel_curve_to(cr, PX(0.1), PX(-0.4),
544  PX(0.4), PX(-0.4), PX(0.5), 0);
545  cairo_rel_curve_to(cr, PX(0.1), PX(0.4),
546  PX(0.4), PX(0.4), PX(0.5), 0);
547 
548  /* Flat line showing DC side */
549  if (info->type == ELEC_TRU)
550  cairo_move_to(cr, PX(pos.x + 0.8), PX(pos.y + 1));
551  else
552  cairo_move_to(cr, PX(pos.x - 0.8), PX(pos.y - 1));
553  cairo_rel_move_to(cr, PX(-0.5), 0);
554  cairo_rel_line_to(cr, PX(1), 0);
555 
556  cairo_stroke(cr);
557 
558  make_comp_name(info->name, name);
559  show_text_aligned(cr, PX(pos.x - 2), PX(pos.y),
560  TEXT_ALIGN_RIGHT, "%s", name);
561 }
562 
563 static void
564 draw_xfrmr(cairo_t *cr, double pos_scale, const elec_comp_info_t *info)
565 {
566  vect2_t pos;
567  vect3_t color;
568  char name[MAX_NAME_LEN];
569 
570  ASSERT(cr != NULL);
571  ASSERT(info != NULL);
572  pos = info->gui.pos;
573  color = info->gui.color;
574 
575  cairo_new_path(cr);
576 
577  /* Background fill */
578  cairo_set_source_rgb(cr, color.x, color.y, color.z);
579  cairo_rectangle(cr, PX(pos.x - 1.5), PX(pos.y - 1.5), PX(3), PX(3));
580  cairo_fill(cr);
581 
582  /* Line art */
583  cairo_set_source_rgb(cr, 0, 0, 0);
584  /* Surrounding box */
585  cairo_rectangle(cr, PX(pos.x - 1.5), PX(pos.y - 1.5), PX(3), PX(3));
586 
587  /* Cross-line through the box */
588  cairo_move_to(cr, PX(pos.x - 1.5), PX(pos.y + 1.5));
589  cairo_rel_line_to(cr, PX(3), PX(-3));
590 
591  /* Wavy line showing input & output sidesside */
592  cairo_move_to(cr, PX(pos.x - 0.8), PX(pos.y - 1));
593  cairo_rel_move_to(cr, PX(-0.5), 0);
594  cairo_rel_curve_to(cr, PX(0.1), PX(-0.4),
595  PX(0.4), PX(-0.4), PX(0.5), 0);
596  cairo_rel_curve_to(cr, PX(0.1), PX(0.4),
597  PX(0.4), PX(0.4), PX(0.5), 0);
598 
599  cairo_move_to(cr, PX(pos.x + 0.8), PX(pos.y + 1));
600  cairo_rel_move_to(cr, PX(-0.5), 0);
601  cairo_rel_curve_to(cr, PX(0.1), PX(-0.4),
602  PX(0.4), PX(-0.4), PX(0.5), 0);
603  cairo_rel_curve_to(cr, PX(0.1), PX(0.4),
604  PX(0.4), PX(0.4), PX(0.5), 0);
605 
606  cairo_stroke(cr);
607 
608  make_comp_name(info->name, name);
609  show_text_aligned(cr, PX(pos.x - 2), PX(pos.y),
610  TEXT_ALIGN_RIGHT, "%s", name);
611 }
612 
613 static void
614 draw_node(cairo_t *cr, double pos_scale, vect2_t pos)
615 {
616  ASSERT(cr != NULL);
617 
618  cairo_set_source_rgb(cr, 1, 1, 1);
619  cairo_arc(cr, PX(pos.x), PX(pos.y), PX(0.2), 0, DEG2RAD(360));
620  cairo_fill(cr);
621 
622  cairo_set_source_rgb(cr, 0, 0, 0);
623  cairo_arc(cr, PX(pos.x), PX(pos.y), PX(0.2), 0, DEG2RAD(360));
624  cairo_stroke(cr);
625 }
626 
627 static void
628 draw_tie(cairo_t *cr, double pos_scale, const elec_comp_t *tie)
629 {
630  vect2_t endpt[2] = { NULL_VECT2, NULL_VECT2 };
631  vect2_t pos;
632  char name[MAX_NAME_LEN];
633 
634  ASSERT(cr != NULL);
635  ASSERT(tie != NULL);
636  pos = tie->info->gui.pos;
637 
638  cairo_new_path(cr);
639 
640  cairo_set_line_width(cr, 4);
641  for (unsigned i = 0; i < tie->n_links; i++) {
642  elec_comp_t *remote_bus = tie->links[i].comp;
643  vect2_t conn = VECT2(1e9, 1e9);
644 
645  if (!tie->tie.cur_state[i])
646  continue;
647 
648  for (unsigned j = 0; j < tie->n_links; j++) {
649  conn = pick_closer(remote_bus->info->gui.pos, conn,
650  tie_node_pos(tie, j));
651  }
652  if (IS_NULL_VECT(endpt[0])) {
653  endpt[0] = conn;
654  } else {
655  endpt[1] = conn;
656  break;
657  }
658  }
659  if (!IS_NULL_VECT(endpt[0]) && !IS_NULL_VECT(endpt[1])) {
660  cairo_path_t *path;
661 
662  cairo_move_to(cr, PX(endpt[0].x), PX(endpt[0].y));
663  cairo_line_to(cr, PX(endpt[1].x), PX(endpt[1].y));
664  path = cairo_copy_path(cr);
665  cairo_stroke(cr);
666  cairo_set_line_width(cr, 2);
667  draw_src_path(cr, path, tie);
668  } else {
669  /* Nothing tied, show the unconnected state */
670  if (tie->n_links == 2) {
671  cairo_move_to(cr, PX(pos.x - 1), PX(pos.y - 1));
672  cairo_rel_line_to(cr, PX(2), 0);
673  } else {
674  vect2_t node_pos = tie_node_pos(tie, 0);
675  vect2_t line = vect2_rot(VECT2(0, -2),
676  tie->info->gui.rot);
677  cairo_move_to(cr, PX(node_pos.x), PX(node_pos.y));
678  cairo_rel_line_to(cr, PX(line.x), PX(-line.y));
679  }
680  cairo_stroke(cr);
681  }
682  cairo_set_line_width(cr, 2);
683  for (unsigned i = 0; i < tie->n_links; i++)
684  draw_node(cr, pos_scale, tie_node_pos(tie, i));
685 
686  make_comp_name(tie->info->name, name);
687  if (tie->n_links == 3) {
688  show_text_aligned(cr, PX(pos.x), PX(pos.y + 1.8),
689  TEXT_ALIGN_CENTER, "%s", name);
690  } else {
691  show_text_aligned(cr, PX(pos.x), PX(pos.y + 1.5),
692  TEXT_ALIGN_CENTER, "%s", name);
693  }
694 }
695 
696 static void
697 draw_diode(cairo_t *cr, double pos_scale, const elec_comp_t *diode,
698  bool draw_line)
699 {
700  vect2_t pos;
701  char name[MAX_NAME_LEN];
702 
703  ASSERT(cr != NULL);
704  ASSERT(diode != NULL);
705  pos = diode->info->gui.pos;
706 
707  cairo_save(cr);
708  cairo_translate(cr, PX(pos.x), PX(pos.y));
709  cairo_rotate(cr, DEG2RAD(diode->info->gui.rot));
710  cairo_move_to(cr, PX(0.4), 0);
711  cairo_rel_line_to(cr, PX(-1.3), PX(-0.8));
712  cairo_rel_line_to(cr, 0, PX(1.6));
713  cairo_fill(cr);
714  cairo_set_line_width(cr, 4);
715  cairo_move_to(cr, PX(0.5), PX(-0.8));
716  cairo_rel_line_to(cr, 0, PX(1.6));
717  if (draw_line) {
718  cairo_move_to(cr, PX(-2), 0);
719  cairo_rel_line_to(cr, PX(4), 0);
720  }
721  cairo_stroke(cr);
722  cairo_restore(cr);
723 
724  make_comp_name(diode->info->name, name);
725  show_text_aligned(cr, PX(pos.x), PX(pos.y + 1.5),
726  TEXT_ALIGN_CENTER, "%s", name);
727 }
728 
729 static void
730 draw_load(cairo_t *cr, double pos_scale, double font_sz,
731  const elec_comp_info_t *info)
732 {
733  vect2_t pos;
734  char name[MAX_NAME_LEN];
735 
736  ASSERT(cr != NULL);
737  ASSERT(info != NULL);
738  pos = info->gui.pos;
739 
740  cairo_new_path(cr);
741  switch (info->gui.load_type) {
742  case GUI_LOAD_GENERIC:
743  cairo_set_source_rgb(cr, 1, 1, 1);
744  cairo_rectangle(cr, PX(pos.x - 1), PX(pos.y - 1), PX(2), PX(2));
745  cairo_fill(cr);
746  cairo_set_source_rgb(cr, 0, 0, 0);
747  cairo_rectangle(cr, PX(pos.x - 1), PX(pos.y - 1), PX(2), PX(2));
748  cairo_stroke(cr);
749  break;
750  case GUI_LOAD_MOTOR:
751  cairo_set_source_rgb(cr, 1, 1, 1);
752  cairo_arc(cr, PX(pos.x), PX(pos.y), PX(1), 0, DEG2RAD(360));
753  cairo_fill(cr);
754  cairo_set_source_rgb(cr, 0, 0, 0);
755  cairo_arc(cr, PX(pos.x), PX(pos.y), PX(1), 0, DEG2RAD(360));
756  cairo_stroke(cr);
757  cairo_set_font_size(cr, 2 * font_sz);
758  show_text_aligned(cr, PX(pos.x), PX(pos.y),
759  TEXT_ALIGN_CENTER, "M");
760  cairo_set_font_size(cr, font_sz);
761  break;
762  }
763 
764  make_comp_name(info->name, name);
765  show_text_aligned(cr, PX(pos.x), PX(pos.y + 1.7),
766  TEXT_ALIGN_CENTER, "%s", name);
767 }
768 
769 static void
770 draw_batt(cairo_t *cr, double pos_scale, const elec_comp_info_t *info,
771  bool draw_ground)
772 {
773  vect2_t pos;
774  vect3_t color;
775  char name[MAX_NAME_LEN];
776 
777  ASSERT(cr != NULL);
778  ASSERT(info != NULL);
779  pos = info->gui.pos;
780  color = info->gui.color;
781 
782  cairo_new_path(cr);
783 
784  cairo_set_source_rgb(cr, color.x, color.y, color.z);
785  cairo_arc(cr, PX(pos.x), PX(pos.y), PX(1.2), 0, DEG2RAD(360));
786  cairo_fill(cr);
787 
788  cairo_set_source_rgb(cr, 0, 0, 0);
789  cairo_arc(cr, PX(pos.x), PX(pos.y), PX(1.2), 0, DEG2RAD(360));
790  cairo_move_to(cr, PX(pos.x), PX(pos.y - 0.2));
791  cairo_rel_line_to(cr, 0, PX(-1.0));
792  cairo_stroke(cr);
793 
794  cairo_move_to(cr, PX(pos.x + 0.4), PX(pos.y - 0.6));
795  cairo_rel_line_to(cr, PX(0.4), 0);
796  cairo_move_to(cr, PX(pos.x + 0.6), PX(pos.y - 0.8));
797  cairo_rel_line_to(cr, 0, PX(0.4));
798 
799  cairo_move_to(cr, PX(pos.x - 1), PX(pos.y - 0.2));
800  cairo_rel_line_to(cr, PX(2), PX(0));
801  cairo_move_to(cr, PX(pos.x - 0.6), PX(pos.y + 0.2));
802  cairo_rel_line_to(cr, PX(1.2), PX(0));
803 
804  if (draw_ground) {
805  cairo_move_to(cr, PX(pos.x), PX(pos.y + 0.2));
806  cairo_rel_line_to(cr, 0, PX(2.3));
807  cairo_move_to(cr, PX(pos.x - 1), PX(pos.y + 2.5));
808  cairo_rel_line_to(cr, PX(2), PX(0));
809  cairo_move_to(cr, PX(pos.x - 0.7), PX(pos.y + 2.9));
810  cairo_rel_line_to(cr, PX(1.4), PX(0));
811  cairo_move_to(cr, PX(pos.x - 0.4), PX(pos.y + 3.3));
812  cairo_rel_line_to(cr, PX(0.8), PX(0));
813  } else {
814  cairo_move_to(cr, PX(pos.x), PX(pos.y + 0.2));
815  cairo_rel_line_to(cr, 0, PX(1));
816  }
817 
818  cairo_stroke(cr);
819 
820  make_comp_name(info->name, name);
821  show_text_aligned(cr, PX(pos.x - 1.4), PX(pos.y),
822  TEXT_ALIGN_RIGHT, "%s", name);
823 }
824 
825 static void
826 draw_label_box(cairo_t *cr, double pos_scale, double font_sz,
827  const elec_comp_info_t *info)
828 {
829  cairo_text_extents_t te;
830  vect3_t color;
831  vect2_t pos, sz;
832  double dash[2] = { PX(1), PX(0.5) };
833  char name[MAX_NAME_LEN];
834 
835  ASSERT(cr != NULL);
836  ASSERT(info != NULL);
837  ASSERT3U(info->type, ==, ELEC_LABEL_BOX);
838 
839  cairo_save(cr);
840 
841  cairo_set_font_size(cr, font_sz * info->label_box.font_scale);
842 
843  make_comp_name(info->name, name);
844  cairo_text_extents(cr, name, &te);
845  pos = info->label_box.pos;
846  sz = info->label_box.sz;
847  color = info->gui.color;
848 
849  cairo_set_dash(cr, dash, 2, 0);
850 
851  cairo_set_source_rgb(cr, color.x, color.y, color.z);
852  cairo_rectangle(cr, PX(pos.x), PX(pos.y), PX(sz.x), PX(sz.y));
853  cairo_stroke(cr);
854 
855  /*
856  * We add half te.height to the width of the surrounding box to
857  * add a bit of a border around the text.
858  */
859  cairo_set_source_rgb(cr, 0, 0, 0);
860  cairo_set_dash(cr, NULL, 0, 0);
861  cairo_rectangle(cr, PX(pos.x + sz.x / 2) - te.width / 2 - te.height / 2,
862  PX(pos.y) - te.height * 0.75, te.width + te.height,
863  te.height * 1.5);
864  cairo_stroke(cr);
865  /*
866  * White background
867  */
868  cairo_set_source_rgb(cr, 1, 1, 1);
869  cairo_rectangle(cr, PX(pos.x + sz.x / 2) - te.width / 2 - te.height / 2,
870  PX(pos.y) - te.height * 0.75, te.width + te.height,
871  te.height * 1.5);
872  cairo_fill(cr);
873 
874  cairo_set_source_rgb(cr, 0, 0, 0);
875  cairo_move_to(cr, PX(pos.x + sz.x / 2) - te.width / 2,
876  PX(pos.y) - te.height / 2 - te.y_bearing);
877  cairo_show_text(cr, name);
878 
879  cairo_restore(cr);
880 }
881 
897 void
898 libelec_draw_layout(const elec_sys_t *sys, cairo_t *cr, double pos_scale,
899  double font_sz)
900 {
901  ASSERT(sys != NULL);
902  ASSERT(cr != NULL);
903 
904  cairo_set_source_rgb(cr, 0, 0, 0);
905  cairo_set_font_size(cr, font_sz);
906  cairo_set_line_width(cr, 2);
907 
908  /* First draw bus connections, so all components sit on top of them */
909  for (elec_comp_t *comp = list_head(&sys->comps); comp != NULL;
910  comp = list_next(&sys->comps, comp)) {
911  const elec_comp_info_t *info = comp->info;
912 
913  ASSERT(info != NULL);
914  if (info->type == ELEC_BUS)
915  draw_bus_conns(cr, pos_scale, comp);
916  }
917 
918  for (elec_comp_t *comp = list_head(&sys->comps); comp != NULL;
919  comp = list_next(&sys->comps, comp)) {
920  const elec_comp_info_t *info = comp->info;
921 
922  ASSERT(info != NULL);
923  if (IS_NULL_VECT(info->gui.pos) || info->gui.invis)
924  continue;
925 
926  switch (info->type) {
927  case ELEC_BUS:
928  draw_bus(cr, pos_scale, comp);
929  break;
930  case ELEC_GEN:
931  draw_gen(cr, pos_scale, info);
932  break;
933  case ELEC_CB:
934  draw_cb(cr, pos_scale, comp, font_sz, VECT3(1, 1, 1));
935  break;
936  case ELEC_SHUNT:
937  draw_shunt(cr, pos_scale, comp);
938  break;
939  case ELEC_TRU:
940  case ELEC_INV:
941  draw_tru_inv(cr, pos_scale, info);
942  break;
943  case ELEC_XFRMR:
944  draw_xfrmr(cr, pos_scale, info);
945  break;
946  case ELEC_TIE:
947  draw_tie(cr, pos_scale, comp);
948  break;
949  case ELEC_DIODE:
950  draw_diode(cr, pos_scale, comp, false);
951  break;
952  case ELEC_LOAD:
953  draw_load(cr, pos_scale, font_sz, info);
954  break;
955  case ELEC_BATT:
956  draw_batt(cr, pos_scale, info, true);
957  break;
958  case ELEC_LABEL_BOX:
959  VERIFY_FAIL();
960  break;
961  }
962  }
963  for (size_t i = 0; i < sys->num_infos; i++) {
964  const elec_comp_info_t *info = &sys->comp_infos[i];
965 
966  if (info->type == ELEC_LABEL_BOX)
967  draw_label_box(cr, pos_scale, font_sz, info);
968  }
969 }
970 
971 static void
972 draw_comp_bg(cairo_t *cr, double pos_scale, vect2_t pos, vect2_t sz)
973 {
974  cairo_path_t *path;
975 
976  ASSERT(cr != NULL);
977 
978  cairo_utils_rounded_rect(cr, PX(pos.x - sz.x / 2),
979  PX(pos.y - sz.y / 2), PX(sz.x), PX(sz.y), PX(0.5));
980  path = cairo_copy_path(cr);
981  cairo_set_source_rgb(cr, COMP_INFO_BG_RGB);
982  cairo_fill(cr);
983 
984  cairo_append_path(cr, path);
985  cairo_set_source_rgb(cr, 0, 0, 0);
986  cairo_stroke(cr);
987 
988  cairo_path_destroy(path);
989 }
990 
991 static void
992 draw_in_out_suffixes(cairo_t *cr, double pos_scale, vect2_t pos,
993  unsigned num_in, unsigned num_out)
994 {
995  ASSERT(cr != NULL);
996 
997  for (unsigned i = 0; i < num_in + num_out; i++) {
998  show_text_aligned(cr, PX(pos.x + 0.5),
999  PX(pos.y + (i + 0.25) * LINE_HEIGHT),
1000  TEXT_ALIGN_LEFT, i < num_in ? "IN" : "OUT");
1001  }
1002 }
1003 
1004 static void
1005 draw_comp_info(const elec_comp_t *comp, cairo_t *cr, double pos_scale,
1006  double font_sz, vect2_t pos)
1007 {
1008  bool ac;
1009  double U_in, I_in, W_in, U_out, I_out, W_out, f;
1010  elec_comp_t *srcs[ELEC_MAX_SRCS];
1011  unsigned n_srcs;
1012 
1013  ASSERT(comp != NULL);
1014  ASSERT(cr != NULL);
1015 
1016  U_in = libelec_comp_get_in_volts(comp);
1017  U_out = libelec_comp_get_out_volts(comp);
1018  ac = libelec_comp_is_AC(comp);
1019  if (libelec_comp2info(comp)->type == ELEC_INV)
1020  f = libelec_comp_get_out_freq(comp);
1021  else
1022  f = (ac ? libelec_comp_get_in_freq(comp) : 0);
1023  I_in = libelec_comp_get_in_amps(comp);
1024  I_out = libelec_comp_get_out_amps(comp);
1025  W_in = libelec_comp_get_in_pwr(comp);
1026  W_out = libelec_comp_get_out_pwr(comp);
1027  get_srcs(comp, srcs);
1028  n_srcs = count_srcs(srcs);
1029 
1030  if (comp->info->type != ELEC_GEN) {
1031  char name[MAX_NAME_LEN];
1032  const char *powered_by;
1033 
1034  switch (n_srcs) {
1035  case 0:
1036  powered_by = "nothing";
1037  break;
1038  case 1:
1039  make_comp_name(srcs[0]->info->name, name);
1040  powered_by = name;
1041  break;
1042  default:
1043  powered_by = "(multiple)";
1044  break;
1045  }
1046  show_text_aligned(cr, PX(pos.x), PX(pos.y),
1047  TEXT_ALIGN_LEFT, "Powered by: %s", powered_by);
1048  pos.y += LINE_HEIGHT;
1049  }
1050 
1051  switch (comp->info->type) {
1052  case ELEC_BATT:
1053  case ELEC_TRU:
1054  case ELEC_INV:
1055  case ELEC_XFRMR:
1056  cairo_set_font_size(cr, 0.75 * font_sz);
1057  if (comp->info->type == ELEC_INV)
1058  draw_in_out_suffixes(cr, pos_scale, pos, 3, 4);
1059  else if (comp->info->type == ELEC_TRU)
1060  draw_in_out_suffixes(cr, pos_scale, pos, 4, 3);
1061  else
1062  draw_in_out_suffixes(cr, pos_scale, pos, 3, 3);
1063 
1064  cairo_set_font_size(cr, font_sz);
1065  show_text_aligned(cr, PX(pos.x), PX(pos.y), TEXT_ALIGN_LEFT,
1066  "U : %.*fV", fixed_decimals(U_in, 4), U_in);
1067  pos.y += LINE_HEIGHT;
1068  if (comp->info->type != ELEC_INV && ac) {
1069  show_text_aligned(cr, PX(pos.x), PX(pos.y),
1070  TEXT_ALIGN_LEFT, "f : %.*fHz",
1071  fixed_decimals(f, 4), f);
1072  pos.y += LINE_HEIGHT;
1073  }
1074  show_text_aligned(cr, PX(pos.x), PX(pos.y), TEXT_ALIGN_LEFT,
1075  "I : %.*fA", fixed_decimals(I_in, 4), I_in);
1076  pos.y += LINE_HEIGHT;
1077  show_text_aligned(cr, PX(pos.x), PX(pos.y), TEXT_ALIGN_LEFT,
1078  "W : %.*fW", fixed_decimals(W_in, 4), W_in);
1079  pos.y += LINE_HEIGHT;
1080  show_text_aligned(cr, PX(pos.x), PX(pos.y), TEXT_ALIGN_LEFT,
1081  "U : %.*fV", fixed_decimals(U_out, 4), U_out);
1082  pos.y += LINE_HEIGHT;
1083  if (comp->info->type == ELEC_INV) {
1084  show_text_aligned(cr, PX(pos.x), PX(pos.y),
1085  TEXT_ALIGN_LEFT, "f : %.*fHz",
1086  fixed_decimals(f, 4), f);
1087  pos.y += LINE_HEIGHT;
1088  }
1089  show_text_aligned(cr, PX(pos.x), PX(pos.y), TEXT_ALIGN_LEFT,
1090  "I : %.*fA", fixed_decimals(I_out, 4), I_out);
1091  pos.y += LINE_HEIGHT;
1092  show_text_aligned(cr, PX(pos.x), PX(pos.y), TEXT_ALIGN_LEFT,
1093  "W : %.*fW", fixed_decimals(W_out, 4), W_out);
1094  pos.y += LINE_HEIGHT;
1095  break;
1096  case ELEC_GEN:
1097  show_text_aligned(cr, PX(pos.x), PX(pos.y), TEXT_ALIGN_LEFT,
1098  "U: %.*fV", fixed_decimals(U_out, 4), U_out);
1099  pos.y += LINE_HEIGHT;
1100  if (ac) {
1101  show_text_aligned(cr, PX(pos.x), PX(pos.y),
1102  TEXT_ALIGN_LEFT, "f: %.*fHz",
1103  fixed_decimals(f, 4), f);
1104  pos.y += LINE_HEIGHT;
1105  }
1106  show_text_aligned(cr, PX(pos.x), PX(pos.y), TEXT_ALIGN_LEFT,
1107  "I: %.*fA", fixed_decimals(I_out, 4), I_out);
1108  pos.y += LINE_HEIGHT;
1109  show_text_aligned(cr, PX(pos.x), PX(pos.y), TEXT_ALIGN_LEFT,
1110  "W: %.*fW", fixed_decimals(W_out, 4), W_out);
1111  break;
1112  case ELEC_BUS:
1113  case ELEC_LOAD:
1114  case ELEC_CB:
1115  case ELEC_SHUNT:
1116  case ELEC_TIE:
1117  case ELEC_DIODE:
1118  show_text_aligned(cr, PX(pos.x), PX(pos.y), TEXT_ALIGN_LEFT,
1119  "U: %.*fV", fixed_decimals(U_in, 4), U_in);
1120  pos.y += LINE_HEIGHT;
1121  if (ac) {
1122  show_text_aligned(cr, PX(pos.x), PX(pos.y),
1123  TEXT_ALIGN_LEFT, "f: %.*fHz",
1124  fixed_decimals(f, 4), f);
1125  pos.y += LINE_HEIGHT;
1126  }
1127  show_text_aligned(cr, PX(pos.x), PX(pos.y), TEXT_ALIGN_LEFT,
1128  "I: %.*fA", fixed_decimals(I_in, 4), I_in);
1129  pos.y += LINE_HEIGHT;
1130  show_text_aligned(cr, PX(pos.x), PX(pos.y), TEXT_ALIGN_LEFT,
1131  "W: %.*fW", fixed_decimals(W_in, 4), W_in);
1132  break;
1133  case ELEC_LABEL_BOX:
1134  break;
1135  }
1136 }
1137 
1138 static void
1139 draw_diode_info(const elec_comp_t *diode, cairo_t *cr, double pos_scale,
1140  double font_sz, vect2_t pos)
1141 {
1142  const double TEXT_OFF_X = -6.5, TEXT_OFF_Y = 2.5;
1143 
1144  ASSERT(diode != NULL);
1145  ASSERT(diode->info != NULL);
1146  ASSERT3U(diode->info->type, ==, ELEC_DIODE);
1147 
1148  draw_comp_bg(cr, pos_scale, VECT2(pos.x, pos.y + 3), VECT2(14, 10));
1149 
1150  draw_diode(cr, pos_scale, diode, true);
1151  show_text_aligned(cr, PX(pos.x + TEXT_OFF_X), PX(pos.y + TEXT_OFF_Y),
1152  TEXT_ALIGN_LEFT, "Type: Diode");
1153  draw_comp_info(diode, cr, pos_scale, font_sz,
1154  VECT2(pos.x + TEXT_OFF_X, pos.y + TEXT_OFF_Y + LINE_HEIGHT));
1155 }
1156 
1157 static void
1158 draw_scb_info(const elec_comp_t *cb, cairo_t *cr, double pos_scale,
1159  double font_sz, vect2_t pos)
1160 {
1161  const double TEXT_OFF_X = -6.5, TEXT_OFF_Y = 2.5;
1162  const char *type;
1163  double y, height, box_y_off;
1164 
1165  ASSERT(cb != NULL);
1166  ASSERT(cb->info != NULL);
1167  ASSERT(cb->info->type == ELEC_CB || cb->info->type == ELEC_SHUNT);
1168 
1169  height = (cb->info->type == ELEC_CB ? 14 : 12);
1170  box_y_off = (cb->info->type == ELEC_CB ? 5 : 4);
1171  draw_comp_bg(cr, pos_scale, VECT2(pos.x, pos.y + box_y_off),
1172  VECT2(14, height));
1173 
1174  if (cb->info->type == ELEC_CB) {
1175  draw_cb(cr, pos_scale, cb, font_sz,
1176  (vect3_t){COMP_INFO_BG_RGB});
1177  } else {
1178  draw_shunt(cr, pos_scale, cb);
1179  }
1180 
1181  y = pos.y + TEXT_OFF_Y;
1182  if (cb->info->type == ELEC_SHUNT)
1183  y += LINE_HEIGHT;
1184  if (cb->info->type == ELEC_CB) {
1185  type = (cb->info->cb.fuse ? "Fuse" : "Circuit Breaker");
1186  } else {
1187  type = "Shunt Resistor";
1188  }
1189  show_text_aligned(cr, PX(pos.x + TEXT_OFF_X), PX(y),
1190  TEXT_ALIGN_LEFT, "Type: %s%s",
1191  cb->info->cb.triphase ? "3-Phase " : "", type);
1192  y += LINE_HEIGHT;
1193  if (cb->info->type == ELEC_CB) {
1194  show_text_aligned(cr, PX(pos.x + TEXT_OFF_X), PX(y),
1195  TEXT_ALIGN_LEFT, "State: %s",
1196  libelec_cb_get(cb) ? "Closed" : "Open");
1197  y += LINE_HEIGHT;
1198  show_text_aligned(cr, PX(pos.x + TEXT_OFF_X), PX(y),
1199  TEXT_ALIGN_LEFT, "Limit: %s%.*fA",
1200  cb->info->cb.triphase ? "3 x " : "",
1201  fixed_decimals(cb->info->cb.max_amps, 2),
1202  cb->info->cb.max_amps);
1203  y += LINE_HEIGHT;
1204  show_text_aligned(cr, PX(pos.x + TEXT_OFF_X), PX(y),
1205  TEXT_ALIGN_LEFT, "Location: %s", cb->info->location);
1206  y += LINE_HEIGHT;
1207  }
1208  draw_comp_info(cb, cr, pos_scale, font_sz,
1209  VECT2(pos.x + TEXT_OFF_X, y));
1210 }
1211 
1212 static void
1213 draw_gen_info(const elec_comp_t *gen, cairo_t *cr, double pos_scale,
1214  double font_sz, vect2_t pos)
1215 {
1216  const double TEXT_OFF_X = -6.5, TEXT_OFF_Y = 3;
1217  double y;
1218 
1219  ASSERT(gen != NULL);
1220  ASSERT(gen->info != NULL);
1221  ASSERT3U(gen->info->type, ==, ELEC_GEN);
1222 
1223  draw_comp_bg(cr, pos_scale, VECT2(pos.x, pos.y + 4), VECT2(14, 12));
1224  draw_gen(cr, pos_scale, gen->info);
1225 
1226  y = pos.y + TEXT_OFF_Y;
1227 
1228  show_text_aligned(cr, PX(pos.x + TEXT_OFF_X), PX(y), TEXT_ALIGN_LEFT,
1229  "Type: %s Generator", gen->info->gen.freq != 0 ? "AC" : "DC");
1230  y += LINE_HEIGHT;
1231 
1232  show_text_aligned(cr, PX(pos.x + TEXT_OFF_X), PX(y), TEXT_ALIGN_LEFT,
1233  "RPM: %.0f", gen->gen.rpm);
1234  y += LINE_HEIGHT;
1235 
1236  show_text_aligned(cr, PX(pos.x + TEXT_OFF_X), PX(y), TEXT_ALIGN_LEFT,
1237  "Efficiency: %.1f%%", gen->gen.eff * 100);
1238  y += LINE_HEIGHT;
1239 
1240  draw_comp_info(gen, cr, pos_scale, font_sz,
1241  VECT2(pos.x + TEXT_OFF_X, y));
1242 }
1243 
1244 static const char *
1245 tru_inv2str(const elec_comp_info_t *info)
1246 {
1247  ASSERT(info != NULL);
1248  if (info->type == ELEC_INV)
1249  return ("Inverter");
1250  if (info->tru.charger)
1251  return ("Battery Charger");
1252  return ("Transformer-Rectifier");
1253 }
1254 
1255 static void
1256 draw_tru_inv_info(const elec_comp_t *tru, cairo_t *cr, double pos_scale,
1257  double font_sz, vect2_t pos)
1258 {
1259  const double TEXT_OFF_X = -8.5, TEXT_OFF_Y = 3;
1260  double y;
1261 
1262  ASSERT(tru != NULL);
1263  ASSERT(tru->info != NULL);
1264  ASSERT(tru->info->type == ELEC_TRU || tru->info->type == ELEC_INV);
1265 
1266  draw_comp_bg(cr, pos_scale, VECT2(pos.x - 2, pos.y + 5.5),
1267  VECT2(14, 15.5));
1268  draw_tru_inv(cr, pos_scale, tru->info);
1269 
1270  y = pos.y + TEXT_OFF_Y;
1271 
1272  show_text_aligned(cr, PX(pos.x + TEXT_OFF_X), PX(y), TEXT_ALIGN_LEFT,
1273  "Type: %s", tru_inv2str(tru->info));
1274  y += LINE_HEIGHT;
1275 
1276  show_text_aligned(cr, PX(pos.x + TEXT_OFF_X), PX(y), TEXT_ALIGN_LEFT,
1277  "Efficiency: %.1f%%", tru->tru.eff * 100);
1278  y += LINE_HEIGHT;
1279 
1280  draw_comp_info(tru, cr, pos_scale, font_sz,
1281  VECT2(pos.x + TEXT_OFF_X, y));
1282 }
1283 
1284 static void
1285 draw_xfrmr_info(const elec_comp_t *xfrmr, cairo_t *cr, double pos_scale,
1286  double font_sz, vect2_t pos)
1287 {
1288  const double TEXT_OFF_X = -8.5, TEXT_OFF_Y = 3;
1289  double y;
1290 
1291  ASSERT(xfrmr != NULL);
1292  ASSERT(xfrmr->info != NULL);
1293  ASSERT(xfrmr->info->type == ELEC_XFRMR);
1294 
1295  draw_comp_bg(cr, pos_scale, VECT2(pos.x - 2, pos.y + 5.5),
1296  VECT2(14, 15.5));
1297  draw_xfrmr(cr, pos_scale, xfrmr->info);
1298 
1299  y = pos.y + TEXT_OFF_Y;
1300 
1301  show_text_aligned(cr, PX(pos.x + TEXT_OFF_X), PX(y), TEXT_ALIGN_LEFT,
1302  "Type: Transformer");
1303  y += LINE_HEIGHT;
1304 
1305  show_text_aligned(cr, PX(pos.x + TEXT_OFF_X), PX(y), TEXT_ALIGN_LEFT,
1306  "Efficiency: %.1f%%", xfrmr->xfrmr.eff * 100);
1307  y += LINE_HEIGHT;
1308 
1309  draw_comp_info(xfrmr, cr, pos_scale, font_sz,
1310  VECT2(pos.x + TEXT_OFF_X, y));
1311 }
1312 
1313 static void
1314 draw_tie_info(const elec_comp_t *tie, cairo_t *cr, double pos_scale,
1315  double font_sz, vect2_t pos)
1316 {
1317  const double TEXT_OFF_X = -6.5, TEXT_OFF_Y = 3;
1318  double y;
1319 
1320  ASSERT(tie != NULL);
1321  ASSERT(tie->info != NULL);
1322  ASSERT3U(tie->info->type, ==, ELEC_TIE);
1323 
1324  draw_comp_bg(cr, pos_scale, VECT2(pos.x, pos.y + 3.5), VECT2(14, 11));
1325  draw_tie(cr, pos_scale, tie);
1326 
1327  y = pos.y + TEXT_OFF_Y;
1328 
1329  show_text_aligned(cr, PX(pos.x + TEXT_OFF_X), PX(y), TEXT_ALIGN_LEFT,
1330  "Type: Tie/Contactor");
1331  y += LINE_HEIGHT;
1332 
1333  draw_comp_info(tie, cr, pos_scale, font_sz,
1334  VECT2(pos.x + TEXT_OFF_X, y));
1335 }
1336 
1337 static void
1338 draw_batt_info(const elec_comp_t *batt, cairo_t *cr, double pos_scale,
1339  double font_sz, vect2_t pos)
1340 {
1341  const double TEXT_OFF_X = -7.5, TEXT_OFF_Y = 3;
1342  double y;
1343 
1344  ASSERT(batt != NULL);
1345  ASSERT(batt->info != NULL);
1346  ASSERT3U(batt->info->type, ==, ELEC_BATT);
1347 
1348  draw_comp_bg(cr, pos_scale, VECT2(pos.x - 1.5, pos.y + 5.5),
1349  VECT2(13, 16));
1350  draw_batt(cr, pos_scale, batt->info, false);
1351 
1352  y = pos.y + TEXT_OFF_Y;
1353 
1354  show_text_aligned(cr, PX(pos.x + TEXT_OFF_X), PX(y), TEXT_ALIGN_LEFT,
1355  "Type: Battery");
1356  y += LINE_HEIGHT;
1357 
1358  show_text_aligned(cr, PX(pos.x + TEXT_OFF_X), PX(y), TEXT_ALIGN_LEFT,
1359  "Charge: %.1f%%", batt->batt.chg_rel * 100);
1360  y += LINE_HEIGHT;
1361 
1362  show_text_aligned(cr, PX(pos.x + TEXT_OFF_X), PX(y), TEXT_ALIGN_LEFT,
1363  "Temp: %.1f C", KELVIN2C(batt->batt.T));
1364  y += LINE_HEIGHT;
1365 
1366  draw_comp_info(batt, cr, pos_scale, font_sz,
1367  VECT2(pos.x + TEXT_OFF_X, y));
1368 }
1369 
1370 static void
1371 draw_load_info(const elec_comp_t *load, cairo_t *cr, double pos_scale,
1372  double font_sz, vect2_t pos)
1373 {
1374  const double TEXT_OFF_X = -6.5, TEXT_OFF_Y = 3;
1375  const char *type;
1376  double y;
1377 
1378  ASSERT(load != NULL);
1379  ASSERT(load->info != NULL);
1380  ASSERT3U(load->info->type, ==, ELEC_LOAD);
1381 
1382  draw_comp_bg(cr, pos_scale, VECT2(pos.x, pos.y + 3), VECT2(14, 10));
1383  draw_load(cr, pos_scale, font_sz, load->info);
1384 
1385  y = pos.y + TEXT_OFF_Y;
1386 
1387  switch (load->info->gui.load_type) {
1388  case GUI_LOAD_MOTOR:
1389  type = "Motor";
1390  break;
1391  default:
1392  type = "Load";
1393  break;
1394  }
1395 
1396  show_text_aligned(cr, PX(pos.x + TEXT_OFF_X), PX(y), TEXT_ALIGN_LEFT,
1397  "Type: %s", type);
1398  y += LINE_HEIGHT;
1399 
1400  draw_comp_info(load, cr, pos_scale, font_sz,
1401  VECT2(pos.x + TEXT_OFF_X, y));
1402 }
1403 
1404 static void
1405 draw_bus_info(const elec_comp_t *bus, cairo_t *cr, double pos_scale,
1406  double font_sz, vect2_t pos)
1407 {
1408  enum { LINE_H = 3 };
1409  unsigned comp_i = 0, num_loads = 0;
1410  double y, height, U;
1411  cairo_path_t *path;
1412  char name[MAX_NAME_LEN];
1413 
1414  ASSERT(bus != NULL);
1415  ASSERT(bus->info != NULL);
1416  ASSERT3U(bus->info->type, ==, ELEC_BUS);
1417 
1418  for (unsigned i = 0; i < bus->n_links; i++) {
1419  ASSERT(bus->links[i].comp != NULL);
1420  ASSERT(bus->links[i].comp->info != NULL);
1421  if (bus->links[i].comp->info->type == ELEC_CB)
1422  num_loads++;
1423  }
1424  height = LINE_H * (1 + ceil(num_loads / 2.0));
1425  draw_comp_bg(cr, pos_scale, pos, VECT2(30, height));
1426 
1427  make_comp_name(bus->info->name, name);
1428  show_text_aligned(cr, PX(pos.x), PX(pos.y - height / 2 + 0.3 * LINE_H),
1429  TEXT_ALIGN_CENTER, "%s", name);
1430  U = libelec_comp_get_in_volts(bus);
1431  show_text_aligned(cr, PX(pos.x), PX(pos.y - height / 2 + 0.7 * LINE_H),
1432  TEXT_ALIGN_CENTER, "U: %.*fV", fixed_decimals(U, 4), U);
1433  y = pos.y - height / 2 + LINE_H * 1.5;
1434 
1435  for (unsigned i = 0; i < bus->n_links; i++) {
1436  const elec_comp_t *comp = bus->links[i].comp;
1437  const elec_comp_info_t *info = comp->info;
1438  vect2_t comp_pos;
1439  double I, W;
1440 
1441  if (info->type != ELEC_CB)
1442  continue;
1443 
1444  cairo_set_line_width(cr, 3);
1445  if (comp_i % 2 == 0) {
1446  comp_pos = VECT2(pos.x - 7.5, y);
1447  cairo_move_to(cr, PX(comp_pos.x + 1), PX(y));
1448  } else {
1449  comp_pos = VECT2(pos.x + 7.5, y);
1450  cairo_move_to(cr, PX(comp_pos.x - 1), PX(y));
1451  }
1452  cairo_line_to(cr, PX(pos.x), PX(y));
1453  path = cairo_copy_path(cr);
1454  cairo_stroke(cr);
1455  cairo_set_line_width(cr, 2);
1456  draw_src_path(cr, path, comp);
1457 
1458  if (comp_i + 2 < num_loads) {
1459  cairo_set_line_width(cr, 1);
1460  cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
1461  if (comp_i % 2 == 0) {
1462  cairo_move_to(cr, PX(pos.x - 14.5),
1463  PX(y + LINE_H / 2.0));
1464  cairo_rel_line_to(cr, PX(13.5), 0);
1465  } else {
1466  cairo_move_to(cr, PX(pos.x + 14.5),
1467  PX(y + LINE_H / 2.0));
1468  cairo_rel_line_to(cr, PX(-13.5), 0);
1469  }
1470  cairo_stroke(cr);
1471  cairo_set_line_width(cr, 2);
1472  cairo_set_source_rgb(cr, 0, 0, 0);
1473  }
1474  draw_cb_icon(cr, pos_scale, font_sz, comp_pos,
1475  info->cb.fuse, comp->scb.cur_set,
1476  info->cb.triphase, info->name,
1477  (vect3_t){COMP_INFO_BG_RGB}, comp);
1478  I = libelec_comp_get_in_amps(comp);
1479  W = libelec_comp_get_in_pwr(comp);
1480  if (comp_i % 2 == 0) {
1481  show_text_aligned(cr, PX(pos.x - 14.5),
1482  PX(y - 0.33 * LINE_H), TEXT_ALIGN_LEFT,
1483  "I: %.*fA", fixed_decimals(I, 3), I);
1484  show_text_aligned(cr, PX(pos.x - 14.5), PX(y),
1485  TEXT_ALIGN_LEFT,
1486  "W: %.*fW", fixed_decimals(W, 3), W);
1487  show_text_aligned(cr, PX(pos.x - 1),
1488  PX(y - 0.33 * LINE_H), TEXT_ALIGN_RIGHT,
1489  "%s", comp->info->location);
1490  } else {
1491  show_text_aligned(cr, PX(pos.x + 10.5),
1492  PX(y - 0.33 * LINE_H), TEXT_ALIGN_LEFT,
1493  "I: %.*fA", fixed_decimals(I, 3), I);
1494  show_text_aligned(cr, PX(pos.x + 10.5), PX(y),
1495  TEXT_ALIGN_LEFT,
1496  "W: %.*fW", fixed_decimals(W, 3), W);
1497  show_text_aligned(cr, PX(pos.x + 1),
1498  PX(y - 0.33 * LINE_H), TEXT_ALIGN_LEFT, "%s", comp->info->location);
1499  }
1500  comp_i++;
1501  if (comp_i % 2 == 0)
1502  y += LINE_H;
1503  }
1504 
1505  cairo_set_line_width(cr, 10);
1506  cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
1507  cairo_move_to(cr, PX(pos.x), PX(pos.y - height / 2 + LINE_H + 0.5));
1508  cairo_line_to(cr, PX(pos.x), PX(pos.y + height / 2 - LINE_H / 2));
1509  path = cairo_copy_path(cr);
1510  cairo_stroke(cr);
1511  cairo_set_line_width(cr, 4);
1512  draw_src_path(cr, path, bus);
1513  cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT);
1514  cairo_set_line_width(cr, 2);
1515 }
1516 
1522 void
1523 libelec_draw_comp_info(const elec_comp_t *comp, cairo_t *cr,
1524  double pos_scale, double font_sz, vect2_t pos)
1525 {
1526  ASSERT(comp != NULL);
1527  ASSERT(cr != NULL);
1528 
1529  cairo_set_source_rgb(cr, 0, 0, 0);
1530  cairo_set_font_size(cr, font_sz);
1531  cairo_set_line_width(cr, 2);
1532 
1533  ASSERT(comp->info != NULL);
1534  switch (comp->info->type) {
1535  case ELEC_BATT:
1536  draw_batt_info(comp, cr, pos_scale, font_sz, pos);
1537  break;
1538  case ELEC_GEN:
1539  draw_gen_info(comp, cr, pos_scale, font_sz, pos);
1540  break;
1541  case ELEC_TRU:
1542  case ELEC_INV:
1543  draw_tru_inv_info(comp, cr, pos_scale, font_sz, pos);
1544  break;
1545  case ELEC_XFRMR:
1546  draw_xfrmr_info(comp, cr, pos_scale, font_sz, pos);
1547  break;
1548  case ELEC_LOAD:
1549  draw_load_info(comp, cr, pos_scale, font_sz, pos);
1550  break;
1551  case ELEC_BUS:
1552  draw_bus_info(comp, cr, pos_scale, font_sz, pos);
1553  break;
1554  case ELEC_CB:
1555  case ELEC_SHUNT:
1556  draw_scb_info(comp, cr, pos_scale, font_sz, pos);
1557  break;
1558  case ELEC_TIE:
1559  draw_tie_info(comp, cr, pos_scale, font_sz, pos);
1560  break;
1561  case ELEC_DIODE:
1562  draw_diode_info(comp, cr, pos_scale, font_sz, pos);
1563  break;
1564  case ELEC_LABEL_BOX:
1565  break;
1566  }
1567 }
bool libelec_comp_is_AC(const elec_comp_t *comp)
Definition: libelec.c:2590
double libelec_comp_get_out_amps(const elec_comp_t *comp)
Definition: libelec.c:2360
double libelec_comp_get_out_freq(const elec_comp_t *comp)
Definition: libelec.c:2546
bool libelec_cb_get(const elec_comp_t *comp)
Definition: libelec.c:4569
double libelec_comp_get_out_pwr(const elec_comp_t *comp)
Definition: libelec.c:2469
const elec_comp_info_t * libelec_comp2info(const elec_comp_t *comp)
Definition: libelec.c:2131
double libelec_comp_get_in_freq(const elec_comp_t *comp)
Definition: libelec.c:2504
double libelec_comp_get_in_volts(const elec_comp_t *comp)
Definition: libelec.c:2232
double libelec_comp_get_in_pwr(const elec_comp_t *comp)
Definition: libelec.c:2410
@ ELEC_SHUNT
Definition: libelec.h:163
@ ELEC_TIE
Definition: libelec.h:194
@ ELEC_LABEL_BOX
Definition: libelec.h:207
@ ELEC_DIODE
Definition: libelec.h:201
@ ELEC_LOAD
Definition: libelec.h:113
@ ELEC_BUS
Definition: libelec.h:121
@ ELEC_GEN
Definition: libelec.h:73
@ ELEC_TRU
Definition: libelec.h:89
@ ELEC_INV
Definition: libelec.h:96
@ ELEC_CB
Definition: libelec.h:147
@ ELEC_BATT
Definition: libelec.h:64
@ ELEC_XFRMR
Definition: libelec.h:103
double libelec_comp_get_in_amps(const elec_comp_t *comp)
Definition: libelec.c:2323
double libelec_comp_get_out_volts(const elec_comp_t *comp)
Definition: libelec.c:2276
void libelec_draw_comp_info(const elec_comp_t *comp, cairo_t *cr, double pos_scale, double font_sz, vect2_t pos)
void libelec_draw_layout(const elec_sys_t *sys, cairo_t *cr, double pos_scale, double font_sz)