pyrbd 0.2.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyrbd/__init__.py +11 -0
- pyrbd/block.py +456 -0
- pyrbd/diagram.py +186 -0
- pyrbd-0.2.5.dist-info/METADATA +45 -0
- pyrbd-0.2.5.dist-info/RECORD +7 -0
- pyrbd-0.2.5.dist-info/WHEEL +4 -0
- pyrbd-0.2.5.dist-info/licenses/LICENSE +21 -0
pyrbd/__init__.py
ADDED
pyrbd/block.py
ADDED
@@ -0,0 +1,456 @@
|
|
1
|
+
"""Module containing Block, Series and Group class definitions."""
|
2
|
+
|
3
|
+
from typing import Optional
|
4
|
+
from itertools import combinations
|
5
|
+
from copy import deepcopy
|
6
|
+
|
7
|
+
|
8
|
+
class Block:
|
9
|
+
"""Block entering a reliability block diagram.
|
10
|
+
|
11
|
+
Parameters
|
12
|
+
----------
|
13
|
+
text : str
|
14
|
+
block text string
|
15
|
+
color : str
|
16
|
+
block color
|
17
|
+
parent : Optional[Block]
|
18
|
+
parent `Block` instance
|
19
|
+
shift : tuple[float, float], optional
|
20
|
+
additional position shift `(x, y)` relative to `parent` `Block` instance
|
21
|
+
|
22
|
+
Attributes
|
23
|
+
----------
|
24
|
+
tikz_options : str
|
25
|
+
TikZ node formatting options
|
26
|
+
|
27
|
+
|
28
|
+
Examples
|
29
|
+
--------
|
30
|
+
>>> block_1 = Block("Start", "green")
|
31
|
+
>>> block_1.id
|
32
|
+
'1'
|
33
|
+
>>> block_2 = Block("End", "red", parent=block_1)
|
34
|
+
>>> block_2.id
|
35
|
+
'2'
|
36
|
+
"""
|
37
|
+
|
38
|
+
tikz_options: str = ", ".join(
|
39
|
+
[
|
40
|
+
"anchor=west",
|
41
|
+
"align=center",
|
42
|
+
"fill={fill_color}",
|
43
|
+
"draw=black",
|
44
|
+
"minimum height=1cm",
|
45
|
+
"rounded corners=0.3mm",
|
46
|
+
"inner sep=4pt",
|
47
|
+
"outer sep=0pt",
|
48
|
+
]
|
49
|
+
)
|
50
|
+
|
51
|
+
def __init__(
|
52
|
+
self,
|
53
|
+
text: str,
|
54
|
+
color: str,
|
55
|
+
parent: Optional["Block"] = None,
|
56
|
+
shift: tuple[float, float] = (0.0, 0.0),
|
57
|
+
) -> None:
|
58
|
+
self.text = text
|
59
|
+
self.color = color
|
60
|
+
self.parent = parent
|
61
|
+
self.shift = shift
|
62
|
+
self.id: str = str(int(self.parent.id) + 1) if self.parent is not None else "1"
|
63
|
+
|
64
|
+
@property
|
65
|
+
def position(self) -> str:
|
66
|
+
"""Block position TikZ string."""
|
67
|
+
|
68
|
+
if self.parent is None:
|
69
|
+
return ""
|
70
|
+
|
71
|
+
return f"[right={0.5 + self.shift[0]}cm of {self.parent.id}, yshift={self.shift[1]}cm]"
|
72
|
+
|
73
|
+
def arrow(self, connector_position: float) -> str:
|
74
|
+
"""Get TikZ arrow string.
|
75
|
+
|
76
|
+
|
77
|
+
Parameters
|
78
|
+
----------
|
79
|
+
connector_position : float
|
80
|
+
distance in cm to right angle bend in connector
|
81
|
+
|
82
|
+
Returns
|
83
|
+
-------
|
84
|
+
str
|
85
|
+
TikZ string for arrow from `parent` to `self` or empty string if `parent` is `None`
|
86
|
+
"""
|
87
|
+
|
88
|
+
if self.parent is None:
|
89
|
+
return ""
|
90
|
+
|
91
|
+
return "".join(
|
92
|
+
[
|
93
|
+
f"\\draw[thick, rectangle connector={connector_position}cm]",
|
94
|
+
f"({self.parent.id}.east) to ({self.id}.west);\n\n",
|
95
|
+
]
|
96
|
+
)
|
97
|
+
|
98
|
+
def get_node(self, connector_position: float = 0.25) -> str:
|
99
|
+
"""Get TikZ node string.
|
100
|
+
|
101
|
+
Parameters
|
102
|
+
----------
|
103
|
+
connector_position : float, default: 0.25
|
104
|
+
distance in cm to right angle bend in connector
|
105
|
+
|
106
|
+
Returns
|
107
|
+
-------
|
108
|
+
str
|
109
|
+
TikZ string for rendering block
|
110
|
+
"""
|
111
|
+
|
112
|
+
node = "".join(
|
113
|
+
[
|
114
|
+
"% Block\n",
|
115
|
+
f"\\node[{self.tikz_options.format(fill_color=self.color)}] ",
|
116
|
+
f"({self.id}) ",
|
117
|
+
self.position,
|
118
|
+
f"{{{self.text}}};\n",
|
119
|
+
self.arrow(connector_position),
|
120
|
+
]
|
121
|
+
)
|
122
|
+
return node
|
123
|
+
|
124
|
+
def __add__(self, block: "Block") -> "Series":
|
125
|
+
"""Add two `Block` instances to make a `Series` instance.
|
126
|
+
|
127
|
+
Parameters
|
128
|
+
----------
|
129
|
+
block : Block
|
130
|
+
another `Block` instance
|
131
|
+
|
132
|
+
Returns
|
133
|
+
-------
|
134
|
+
Series
|
135
|
+
`Series` instance with `blocks = [self, block]`
|
136
|
+
|
137
|
+
Raises
|
138
|
+
------
|
139
|
+
TypeError
|
140
|
+
If `block` is not an instance of `Block`
|
141
|
+
"""
|
142
|
+
|
143
|
+
if not isinstance(block, Block):
|
144
|
+
raise TypeError(
|
145
|
+
f"cannot add object of type {type(block)=} to Block instance."
|
146
|
+
)
|
147
|
+
|
148
|
+
return Series([self, block], parent=self.parent)
|
149
|
+
|
150
|
+
def __mul__(self, value: int) -> "Group":
|
151
|
+
"""Multiply `Block` instance by `value` to make `Group` with repeated blocks.
|
152
|
+
|
153
|
+
Parameters
|
154
|
+
----------
|
155
|
+
value : int
|
156
|
+
multiplicative factor
|
157
|
+
|
158
|
+
Returns
|
159
|
+
-------
|
160
|
+
Group
|
161
|
+
`Group` instance with `value` copies of block
|
162
|
+
|
163
|
+
Raises
|
164
|
+
------
|
165
|
+
ValueError
|
166
|
+
If `value` is not a positive integer
|
167
|
+
"""
|
168
|
+
|
169
|
+
if not isinstance(value, int) or value <= 0:
|
170
|
+
raise ValueError("Multiplicative factor `value` must be a positive integer")
|
171
|
+
|
172
|
+
blocks: list[Block] = [deepcopy(self) for _ in range(value)]
|
173
|
+
|
174
|
+
return Group(blocks, parent=self.parent)
|
175
|
+
|
176
|
+
__rmul__ = __mul__
|
177
|
+
|
178
|
+
|
179
|
+
class Series(Block):
|
180
|
+
"""Series configuration of `Block` instances for horisontal grouping.
|
181
|
+
|
182
|
+
Parameters
|
183
|
+
----------
|
184
|
+
blocks : list[Block]
|
185
|
+
list of `Block` instances
|
186
|
+
text: str, optional
|
187
|
+
series label text
|
188
|
+
color: str, optional
|
189
|
+
series color
|
190
|
+
parent : Optional[Block]
|
191
|
+
parent `Block` instance
|
192
|
+
|
193
|
+
Attributes
|
194
|
+
----------
|
195
|
+
tikz_options : str
|
196
|
+
TikZ node options
|
197
|
+
|
198
|
+
"""
|
199
|
+
|
200
|
+
tikz_options: str = ", ".join(
|
201
|
+
[
|
202
|
+
"anchor=west",
|
203
|
+
"align=center",
|
204
|
+
"inner sep=0pt",
|
205
|
+
"outer sep=0pt",
|
206
|
+
]
|
207
|
+
)
|
208
|
+
|
209
|
+
def __init__(
|
210
|
+
self,
|
211
|
+
blocks: list[Block],
|
212
|
+
text: str = "",
|
213
|
+
color: str = "",
|
214
|
+
parent: Optional[Block] = None,
|
215
|
+
) -> None:
|
216
|
+
Block.__init__(self, text, color, parent)
|
217
|
+
|
218
|
+
self.blocks = blocks
|
219
|
+
self.blocks[0].id = f"{self.id}+0"
|
220
|
+
for i, (block, new_parent) in enumerate(
|
221
|
+
zip(self.blocks[1::], self.blocks[0:-1]), start=1
|
222
|
+
):
|
223
|
+
block.parent = new_parent
|
224
|
+
block.id = f"{self.id}+{i}"
|
225
|
+
|
226
|
+
@property
|
227
|
+
def background(self) -> str:
|
228
|
+
"""Background rectangle TikZ string."""
|
229
|
+
|
230
|
+
if self.color in ("white", ""):
|
231
|
+
return ""
|
232
|
+
|
233
|
+
return "".join(
|
234
|
+
[
|
235
|
+
"\\begin{pgfonlayer}{background}\n",
|
236
|
+
f"\\coordinate (sw) at ($({self.id}.south west)+(-1mm, -1mm)$);\n",
|
237
|
+
f"\\coordinate (ne) at ($({self.id}.north east)+(1mm, 1mm)$);\n",
|
238
|
+
f"\\draw[{self.color}, thick] (sw) rectangle (ne);\n",
|
239
|
+
"\\end{pgfonlayer}\n",
|
240
|
+
]
|
241
|
+
)
|
242
|
+
|
243
|
+
@property
|
244
|
+
def label(self) -> str:
|
245
|
+
"""Series label string."""
|
246
|
+
|
247
|
+
if len(self.text) == 0:
|
248
|
+
return ""
|
249
|
+
|
250
|
+
return "".join(
|
251
|
+
[
|
252
|
+
f"\\coordinate (nw) at ($({self.id}.north west)+(-1mm, 1mm)$);\n",
|
253
|
+
f"\\coordinate (ne) at ($({self.id}.north east)+(1mm, 1mm)$);\n",
|
254
|
+
f"\\coordinate (n) at ($({self.id}.north)+(0mm, 1mm)$);\n",
|
255
|
+
f"\\draw[{self.color}, fill={self.color}!50, thick] (nw) ",
|
256
|
+
"rectangle ($(ne)+(0, 0.5cm)$);\n",
|
257
|
+
f"\\node[anchor=south] at (n) {{{self.text}}};\n",
|
258
|
+
]
|
259
|
+
)
|
260
|
+
|
261
|
+
def get_node(self, connector_position: float = 0.25) -> str:
|
262
|
+
"""Get TikZ node string.
|
263
|
+
|
264
|
+
Parameters
|
265
|
+
----------
|
266
|
+
connector_position : float, default: 0.25
|
267
|
+
distance in cm to right angle bend in connector
|
268
|
+
|
269
|
+
Returns
|
270
|
+
-------
|
271
|
+
str
|
272
|
+
TikZ string for rendering series
|
273
|
+
|
274
|
+
"""
|
275
|
+
|
276
|
+
block_nodes = "\n".join(
|
277
|
+
block.get_node(connector_position) for block in self.blocks
|
278
|
+
)
|
279
|
+
series_node = "".join(
|
280
|
+
[
|
281
|
+
f"\\node[{self.tikz_options}]",
|
282
|
+
f"({self.id})",
|
283
|
+
self.position,
|
284
|
+
"{\\begin{tikzpicture}\n",
|
285
|
+
block_nodes,
|
286
|
+
"\\end{tikzpicture}};\n\n",
|
287
|
+
self.arrow(connector_position),
|
288
|
+
self.background,
|
289
|
+
self.label,
|
290
|
+
]
|
291
|
+
)
|
292
|
+
return series_node
|
293
|
+
|
294
|
+
|
295
|
+
class Group(Block):
|
296
|
+
"""Group of `Block` instances for vertical stacking.
|
297
|
+
|
298
|
+
Parameters
|
299
|
+
----------
|
300
|
+
blocks : list[Block]
|
301
|
+
list of `Block` instances
|
302
|
+
text : str, optional
|
303
|
+
group label text
|
304
|
+
color : str, optional
|
305
|
+
group color
|
306
|
+
parent : Optional[Block]
|
307
|
+
parent `Block` instance
|
308
|
+
|
309
|
+
Attributes
|
310
|
+
----------
|
311
|
+
shift_scale : float
|
312
|
+
scaling factor for vertical shifts of blocks
|
313
|
+
tikz_options : str
|
314
|
+
TikZ node options
|
315
|
+
"""
|
316
|
+
|
317
|
+
shift_scale: float = 1.2
|
318
|
+
tikz_options: str = ", ".join(
|
319
|
+
[
|
320
|
+
"anchor=west",
|
321
|
+
]
|
322
|
+
)
|
323
|
+
|
324
|
+
def __init__(
|
325
|
+
self,
|
326
|
+
blocks: list[Block],
|
327
|
+
text: str = "",
|
328
|
+
color: str = "",
|
329
|
+
parent: Optional[Block] = None,
|
330
|
+
) -> None:
|
331
|
+
Block.__init__(self, text, color, parent)
|
332
|
+
|
333
|
+
self.blocks = blocks
|
334
|
+
for i, (block, shift) in enumerate(zip(self.blocks, self.shifts)):
|
335
|
+
block.shift = (0, shift)
|
336
|
+
block.parent = self
|
337
|
+
block.id = f"{self.id}-{i}"
|
338
|
+
|
339
|
+
@property
|
340
|
+
def shifts(self) -> list[float]:
|
341
|
+
"""List of vertical position shifts for each `Block` instance in group.
|
342
|
+
|
343
|
+
Returns
|
344
|
+
-------
|
345
|
+
list[float]
|
346
|
+
list of vertical position shifts for each `Block` instance in group
|
347
|
+
"""
|
348
|
+
|
349
|
+
n_blocks = len(self.blocks)
|
350
|
+
|
351
|
+
return list(-self.shift_scale * n for n in range(n_blocks))
|
352
|
+
|
353
|
+
@property
|
354
|
+
def background(self) -> str:
|
355
|
+
"""Background rectangle TikZ string."""
|
356
|
+
|
357
|
+
if self.color in ("white", ""):
|
358
|
+
return ""
|
359
|
+
|
360
|
+
return "".join(
|
361
|
+
[
|
362
|
+
"\\begin{pgfonlayer}{background}\n",
|
363
|
+
f"\\coordinate (sw) at ($({self.id}.south west)+(-1mm, -1mm)$);\n",
|
364
|
+
f"\\coordinate (ne) at ($({self.id}.north east)+(1mm, 1mm)$);\n",
|
365
|
+
f"\\draw[{self.color}, thick] (sw) rectangle (ne);\n",
|
366
|
+
"\\end{pgfonlayer}\n",
|
367
|
+
]
|
368
|
+
)
|
369
|
+
|
370
|
+
@property
|
371
|
+
def label(self) -> str:
|
372
|
+
"""Series label string."""
|
373
|
+
|
374
|
+
if len(self.text) == 0:
|
375
|
+
return ""
|
376
|
+
|
377
|
+
return "".join(
|
378
|
+
[
|
379
|
+
f"\\coordinate (nw) at ($({self.id}.north west)+(-1mm, 1mm)$);\n",
|
380
|
+
f"\\coordinate (ne) at ($({self.id}.north east)+(1mm, 1mm)$);\n",
|
381
|
+
f"\\coordinate (n) at ($({self.id}.north)+(0mm, 1mm)$);\n",
|
382
|
+
f"\\draw[{self.color}, fill={self.color}!50, thick] (nw) ",
|
383
|
+
"rectangle ($(ne)+(0, 0.5cm)$);\n",
|
384
|
+
f"\\node[anchor=south] at (n) {{{self.text}}};\n",
|
385
|
+
]
|
386
|
+
)
|
387
|
+
|
388
|
+
def arrow(self, connector_position: float) -> str:
|
389
|
+
"""Get TikZ arrow string.
|
390
|
+
|
391
|
+
Parameters
|
392
|
+
----------
|
393
|
+
connector_position : float
|
394
|
+
distance in cm to right angle bend in connector (not used in `Group` class)
|
395
|
+
|
396
|
+
Returns
|
397
|
+
-------
|
398
|
+
str
|
399
|
+
TikZ string for arrow from `parent` to `self` or empty string if `parent` is `None`
|
400
|
+
"""
|
401
|
+
|
402
|
+
if self.parent is None:
|
403
|
+
return ""
|
404
|
+
|
405
|
+
return f"\\draw[thick] ({self.parent.id}.east) to ({self.id}.west);\n"
|
406
|
+
|
407
|
+
@property
|
408
|
+
def arrows(self) -> str:
|
409
|
+
"""Get TikZ string for arrow connecting stacked blocks."""
|
410
|
+
|
411
|
+
return "\n".join(
|
412
|
+
[
|
413
|
+
" ".join(
|
414
|
+
[
|
415
|
+
"\\draw[thick, rectangle line]",
|
416
|
+
f"({block1.id}.east) to ({block2.id}.east);\n",
|
417
|
+
]
|
418
|
+
)
|
419
|
+
for (block1, block2) in combinations(self.blocks, 2)
|
420
|
+
]
|
421
|
+
)
|
422
|
+
|
423
|
+
def get_node(self, connector_position: float = 0.0) -> str:
|
424
|
+
"""Get TikZ node string.
|
425
|
+
|
426
|
+
Parameters
|
427
|
+
----------
|
428
|
+
connector_position : float, default: 0.0
|
429
|
+
distance in cm to right angle bend in connector
|
430
|
+
|
431
|
+
Returns
|
432
|
+
-------
|
433
|
+
str
|
434
|
+
TikZ string for rendering group
|
435
|
+
"""
|
436
|
+
|
437
|
+
block_nodes = "\n".join(
|
438
|
+
block.get_node(connector_position) for block in self.blocks
|
439
|
+
)
|
440
|
+
|
441
|
+
group_node = "".join(
|
442
|
+
[
|
443
|
+
f"\\node[anchor=west, outer sep=0pt, inner sep=0pt, align=center] ({self.id}) ",
|
444
|
+
self.position,
|
445
|
+
"{\\begin{tikzpicture}\n",
|
446
|
+
f"\\coordinate ({self.id}) at (0, 0);\n",
|
447
|
+
block_nodes,
|
448
|
+
self.arrows,
|
449
|
+
"\\end{tikzpicture}};\n\n",
|
450
|
+
self.arrow(connector_position),
|
451
|
+
self.background,
|
452
|
+
self.label,
|
453
|
+
]
|
454
|
+
)
|
455
|
+
|
456
|
+
return group_node
|
pyrbd/diagram.py
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
"""Module containing Diagram class definition."""
|
2
|
+
|
3
|
+
import subprocess
|
4
|
+
|
5
|
+
import pymupdf
|
6
|
+
|
7
|
+
from .block import Block
|
8
|
+
|
9
|
+
|
10
|
+
class Diagram:
|
11
|
+
"""Reliability block diagram class definition.
|
12
|
+
|
13
|
+
Parameters
|
14
|
+
----------
|
15
|
+
name : str
|
16
|
+
name of diagram
|
17
|
+
blocks : list[Block]
|
18
|
+
list of `Block` instances
|
19
|
+
hazard : str, optional
|
20
|
+
string defining the `hazard` block text
|
21
|
+
"""
|
22
|
+
|
23
|
+
def __init__(self, name: str, blocks: list[Block], hazard: str = "") -> None:
|
24
|
+
self.filename = name
|
25
|
+
if hazard:
|
26
|
+
self.head = Block(hazard, "red!60")
|
27
|
+
else:
|
28
|
+
self.head = blocks.pop(0)
|
29
|
+
|
30
|
+
self.head.id = "0"
|
31
|
+
self.blocks = blocks
|
32
|
+
self.blocks[0].parent = self.head
|
33
|
+
|
34
|
+
def write(self) -> None:
|
35
|
+
"""Write diagram to .tex file."""
|
36
|
+
|
37
|
+
with open(f"{self.filename}.tex", mode="w", encoding="utf-8") as file:
|
38
|
+
file.write(TEX_PREAMBLE)
|
39
|
+
for block in [self.head, *self.blocks]:
|
40
|
+
file.write(block.get_node())
|
41
|
+
file.write(TEX_END)
|
42
|
+
|
43
|
+
def _to_svg(self) -> str:
|
44
|
+
"""Convert diagram file from pdf to svg.
|
45
|
+
|
46
|
+
Returns
|
47
|
+
-------
|
48
|
+
str
|
49
|
+
filename of .svg file
|
50
|
+
"""
|
51
|
+
|
52
|
+
pdf_document = pymupdf.open(f"{self.filename}.pdf")
|
53
|
+
page = pdf_document[0]
|
54
|
+
|
55
|
+
# Get and convert page to svg image
|
56
|
+
svg_content = page.get_svg_image()
|
57
|
+
|
58
|
+
# Save to file
|
59
|
+
with open(output_file := f"{self.filename}.svg", "w", encoding="utf-8") as file:
|
60
|
+
file.write(svg_content)
|
61
|
+
|
62
|
+
pdf_document.close()
|
63
|
+
|
64
|
+
return output_file
|
65
|
+
|
66
|
+
def _to_png(self) -> str:
|
67
|
+
"""Convert diagram file from pdf to png.
|
68
|
+
|
69
|
+
Returns
|
70
|
+
-------
|
71
|
+
str
|
72
|
+
filename of .png file
|
73
|
+
"""
|
74
|
+
|
75
|
+
pdf_document = pymupdf.open(f"{self.filename}.pdf")
|
76
|
+
page = pdf_document[0]
|
77
|
+
|
78
|
+
# Get image
|
79
|
+
image = page.get_pixmap(dpi=300) # type: ignore
|
80
|
+
|
81
|
+
# Save to file
|
82
|
+
image.save(output_file := f"{self.filename}.png")
|
83
|
+
|
84
|
+
pdf_document.close()
|
85
|
+
|
86
|
+
return output_file
|
87
|
+
|
88
|
+
def compile(
|
89
|
+
self, output: str | list[str] = "pdf", clear_source: bool = True
|
90
|
+
) -> list[str]:
|
91
|
+
"""Compile diagram .tex file.
|
92
|
+
|
93
|
+
Parameters
|
94
|
+
----------
|
95
|
+
output : str | list[str], default: 'pdf'
|
96
|
+
output format string or list of output formats for diagram. Valid output formats are
|
97
|
+
|
98
|
+
- `'pdf'` (default)
|
99
|
+
- `'svg'`
|
100
|
+
- `'png'`
|
101
|
+
|
102
|
+
clear_source : bool, default: True
|
103
|
+
.tex source file is deleted after compilation if `True`
|
104
|
+
|
105
|
+
Returns
|
106
|
+
-------
|
107
|
+
list[str]
|
108
|
+
list of output filenames
|
109
|
+
|
110
|
+
Raises
|
111
|
+
------
|
112
|
+
FileNotFoundError
|
113
|
+
If .tex file is not found, e.g. because `Diagram.write()` has not been called
|
114
|
+
before `Diagram.compile()`.
|
115
|
+
"""
|
116
|
+
|
117
|
+
try:
|
118
|
+
subprocess.check_call(["latexmk", f"{self.filename}.tex", "--silent"])
|
119
|
+
subprocess.check_call(["latexmk", "-c", f"{self.filename}.tex"])
|
120
|
+
if clear_source:
|
121
|
+
subprocess.check_call(["rm", f"{self.filename}.tex"])
|
122
|
+
except subprocess.CalledProcessError as err:
|
123
|
+
if err.returncode == 11:
|
124
|
+
raise FileNotFoundError(
|
125
|
+
(
|
126
|
+
f"File {self.filename} not found. "
|
127
|
+
+ "Check if call to Class method write() is missing."
|
128
|
+
)
|
129
|
+
) from err
|
130
|
+
|
131
|
+
output_files: list[str] = []
|
132
|
+
|
133
|
+
if not isinstance(output, list):
|
134
|
+
output = [output]
|
135
|
+
|
136
|
+
if "svg" in output:
|
137
|
+
output_files.append(self._to_svg())
|
138
|
+
if "png" in output:
|
139
|
+
output_files.append(self._to_png())
|
140
|
+
if "pdf" not in output:
|
141
|
+
subprocess.check_call(["rm", f"{self.filename}.pdf"])
|
142
|
+
else:
|
143
|
+
output_files.append(f"{self.filename}.pdf")
|
144
|
+
|
145
|
+
return output_files
|
146
|
+
|
147
|
+
|
148
|
+
TEX_PREAMBLE = "\n".join(
|
149
|
+
[
|
150
|
+
r"\documentclass{standalone}",
|
151
|
+
r"\usepackage{tikz}",
|
152
|
+
r"\usetikzlibrary{shapes,arrows,positioning,calc}",
|
153
|
+
r"\pgfdeclarelayer{background}",
|
154
|
+
r"\pgfsetlayers{background, main}",
|
155
|
+
r"\tikzset{",
|
156
|
+
r"connector/.style={",
|
157
|
+
r"-latex,",
|
158
|
+
r"font=\scriptsize},",
|
159
|
+
r"line/.style={",
|
160
|
+
r"font=\scriptsize},",
|
161
|
+
r"rectangle connector/.style={",
|
162
|
+
r"connector,"
|
163
|
+
r"to path={(\tikztostart) -- ++(#1,0pt) \tikztonodes |- (\tikztotarget) },",
|
164
|
+
r"pos=0.5},"
|
165
|
+
r"rectangle connector/.default=0.5cm,",
|
166
|
+
r"rectangle line/.style={",
|
167
|
+
r"line,"
|
168
|
+
r"to path={(\tikztostart) -- ++(#1,0pt) \tikztonodes |- (\tikztotarget) },",
|
169
|
+
r"pos=0.5},"
|
170
|
+
r"rectangle line/.default=0.5cm,",
|
171
|
+
r"straight connector/.style={",
|
172
|
+
r"connector,",
|
173
|
+
r"to path=--(\tikztotarget) \tikztonodes}",
|
174
|
+
r"}",
|
175
|
+
r"\begin{document}",
|
176
|
+
r"\begin{tikzpicture}",
|
177
|
+
"",
|
178
|
+
]
|
179
|
+
)
|
180
|
+
|
181
|
+
TEX_END = "\n".join(
|
182
|
+
[
|
183
|
+
r"\end{tikzpicture}",
|
184
|
+
r"\end{document}",
|
185
|
+
]
|
186
|
+
)
|
@@ -0,0 +1,45 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: pyrbd
|
3
|
+
Version: 0.2.5
|
4
|
+
Summary: Package for creating simple reliability block diagrams (RBDs) using LaTeX and TikZ.
|
5
|
+
Project-URL: Repository, https://github.com/hghugdal/pyrbd
|
6
|
+
Project-URL: Issues, https://github.com/hghugdal/pyrbd/issues
|
7
|
+
Project-URL: Documentation, https://hghugdal.github.io/pyrbd
|
8
|
+
Author-email: hghugdal <hghugdal@gmail.com>
|
9
|
+
License-Expression: MIT
|
10
|
+
License-File: LICENSE
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
13
|
+
Requires-Python: >=3.10
|
14
|
+
Requires-Dist: pymupdf>=1.26.3
|
15
|
+
Description-Content-Type: text/markdown
|
16
|
+
|
17
|
+
# <img alt="pyRBDlogo" src=docs/images/logo.svg width=40 align=top> pyRBD
|
18
|
+
|
19
|
+
<img alt="Python" src="https://img.shields.io/badge/Python->= 3.10-blue?logo=python&link=None">  <img alt="Tests" src="https://img.shields.io/badge/Tests-Passing-darkgreen?logo=pytest&link=None"> <img alt="Coverage" src="https://img.shields.io/badge/Coverage-100%25-darkgreen?link=None"> <img alt="Pylint" src="https://img.shields.io/badge/Pylint-10%2F10-darkgreen?link=None">
|
20
|
+
|
21
|
+
A Python package for creating simple reliability block diagrams (RBDs) using `LaTeX` and [`TikZ`](https://en.wikipedia.org/wiki/PGF/TikZ).
|
22
|
+
|
23
|
+
## Dependencies
|
24
|
+
`pyRBD` requires a working installation of `LaTeX` including [`TikZ`](https://en.wikipedia.org/wiki/PGF/TikZ) and [`latexmk`](https://ctan.org/pkg/latexmk/).
|
25
|
+
|
26
|
+
## Simple example diagram
|
27
|
+
The blocks of the RBD are defined using `Block`, `Series` and `Group`, and the diagram itself is handled by the `Diagram` class. A simple example is given by the code
|
28
|
+
```python linenums="1"
|
29
|
+
from pyrbd import Block, Diagram
|
30
|
+
|
31
|
+
start_block = Block("Start", "blue!30", parent=None)
|
32
|
+
parallel = 2 * Block("Parallel blocks", "gray", parent=start_block)
|
33
|
+
end_block = Block("End", "green!50", parent=parallel)
|
34
|
+
|
35
|
+
diagram = Diagram(
|
36
|
+
"simple_RBD",
|
37
|
+
blocks=[start_block, parallel, end_block],
|
38
|
+
)
|
39
|
+
diagram.write()
|
40
|
+
diagram.compile()
|
41
|
+
```
|
42
|
+
producing the following diagram
|
43
|
+
<div><img src="docs/examples/simple_RBD.svg" width=500/></div>
|
44
|
+
|
45
|
+
For more examples, visit the [documentation](https://hghugdal.github.io/pyrbd/).
|
@@ -0,0 +1,7 @@
|
|
1
|
+
pyrbd/__init__.py,sha256=mpsb022BX9eDGSBhuYIr2gOr4sg_M9sYbPGHLPIdvl0,219
|
2
|
+
pyrbd/block.py,sha256=ETRZJeEUU4OPk0qGo6zZIsaR1oLkyDPf1kelfuKWNsc,12219
|
3
|
+
pyrbd/diagram.py,sha256=NVAsdfmqAu6-6J2mT5Gp2W_8a9WsI2iMbKjcEI6QFlE,5092
|
4
|
+
pyrbd-0.2.5.dist-info/METADATA,sha256=WN1raYOu5KJtH5wj-7b1P8wH8-1GGHmnnqvwrt5hmZs,2196
|
5
|
+
pyrbd-0.2.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
6
|
+
pyrbd-0.2.5.dist-info/licenses/LICENSE,sha256=mxXMgWpFgk71JpEEElFrb9FJxeoaDgvrU8gjUUU9LzA,1069
|
7
|
+
pyrbd-0.2.5.dist-info/RECORD,,
|
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 H. G. Hugdal
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|