• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

sandialabs / sdynpy / 13036987848

29 Jan 2025 05:23PM UTC coverage: 15.675%. Remained the same
13036987848

push

github

dprohe
Bugfix due to breaking change in qtpy/PyQt5

1 of 2 new or added lines in 2 files covered. (50.0%)

1 existing line in 1 file now uncovered.

2606 of 16625 relevant lines covered (15.68%)

0.47 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

5.57
/src/sdynpy/doc/sdynpy_ppt.py
1
# -*- coding: utf-8 -*-
2
"""
3
Functions for creating PowerPoint presentations from SDynPy objects.
4

5
This modules includes functions for assembling a PowerPoint presentation from
6
SDynPy objects, saving users the tediousness of putting a large number of images
7
and tables into the presentation.
8

9
Copyright 2022 National Technology & Engineering Solutions of Sandia,
10
LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S.
11
Government retains certain rights in this software.
12

13
This program is free software: you can redistribute it and/or modify
14
it under the terms of the GNU General Public License as published by
15
the Free Software Foundation, either version 3 of the License, or
16
(at your option) any later version.
17

18
This program is distributed in the hope that it will be useful,
19
but WITHOUT ANY WARRANTY; without even the implied warranty of
20
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21
GNU General Public License for more details.
22

23
You should have received a copy of the GNU General Public License
24
along with this program.  If not, see <https://www.gnu.org/licenses/>.
25
"""
26

27
import pptx
3✔
28
from pptx.util import Inches
3✔
29
from pptx.enum.text import MSO_AUTO_SIZE
3✔
30
import tempfile
3✔
31
import io
3✔
32
from PIL import Image
3✔
33
import matplotlib.pyplot as plt
3✔
34
import numpy as np
3✔
35
import os
3✔
36
import time
3✔
37

38
from ..core.sdynpy_shape import mac, matrix_plot
3✔
39

40

41
def position_placeholder(presentation, placeholder, left=None, top=None, right=None, bottom=None):
3✔
42
    sw = presentation.slide_width
×
43
    sh = presentation.slide_height
×
44
    if left is None:
×
45
        left = placeholder.left
×
46
    if left < 0:
×
47
        left = sw - abs(left)
×
48
    if right is None:
×
49
        right = placeholder.left + placeholder.width
×
50
    if right < 0:
×
51
        right = sw - abs(right)
×
52
    if top is None:
×
53
        top = placeholder.top
×
54
    if top < 0:
×
55
        top = sh - abs(top)
×
56
    if bottom is None:
×
57
        bottom = placeholder.top + placeholder.height
×
58
    if bottom < 0:
×
59
        bottom = sh - abs(bottom)
×
60
    width = right - left
×
61
    height = bottom - top
×
62
    placeholder.left = left
×
63
    placeholder.top = top
×
64
    placeholder.width = width
×
65
    placeholder.height = height
×
66

67

68
def add_title_slide(presentation, title, subtitle='', title_slide_layout_index=0):
3✔
69
    title_slide_layout = presentation.slide_layouts[title_slide_layout_index]
×
70
    slide = presentation.slides.add_slide(title_slide_layout)
×
71
    slide.shapes.title.text = title
×
72
    position_placeholder(presentation, slide.shapes.title, right=-slide.shapes.title.left)
×
73
    slide.placeholders[1].text = subtitle
×
74

75

76
def add_section_header_slide(presentation, title, subtitle='', section_header_slide_layout_index=2):
3✔
77
    section_header_slide_layout = presentation.slide_layouts[section_header_slide_layout_index]
×
78
    slide = presentation.slides.add_slide(section_header_slide_layout)
×
79
    slide.shapes.title.text = title
×
80
    position_placeholder(presentation, slide.shapes.title, right=-slide.shapes.title.left)
×
81
    slide.placeholders[1].text = subtitle
×
82

83

84
def add_geometry_overview_slide(presentation, geometry, title='Geometry',
3✔
85
                                geometry_plot_kwargs={},
86
                                animate_geometry=True,
87
                                save_animation_kwargs={'frames': 200, 'frame_rate': 20},
88
                                content_slide_layout_index=1):
89
    bullet_slide = presentation.slide_layouts[content_slide_layout_index]
×
90
    geometry_plot = geometry.plot(**geometry_plot_kwargs)[0]
×
91
    slide = presentation.slides.add_slide(bullet_slide)
×
92
    text_placeholder = slide.placeholders[1]
×
93
    if animate_geometry:
×
94
        output_save_file = os.path.join(tempfile.gettempdir(),
×
95
                                        'Geometry.gif')
96
        geometry_plot.save_rotation_animation(output_save_file, **save_animation_kwargs)
×
97
        geometry_plot.close()
×
98
        pic = slide.shapes.add_picture(output_save_file, Inches(1), Inches(1))
×
99
    else:
100
        with io.BytesIO() as output:
×
101
            time.sleep(0.5)
×
102
            img = Image.fromarray(geometry_plot.screenshot())
×
103
            img.save(output, format='PNG')
×
104
            pic = slide.shapes.add_picture(output, Inches(1), Inches(1))
×
105
    ar = pic.width / pic.height
×
106
    slide.shapes.title.text = title
×
107
    # Move the bullet points to the left
108
    position_placeholder(presentation, slide.placeholders[1], right=(
×
109
        presentation.slide_width - slide.placeholders[1].left) // 2)
110
    # Pic to the right
111
    position_placeholder(presentation, pic, left=(presentation.slide_width + slide.placeholders[1].left) // 2,
×
112
                         right=-slide.placeholders[1].left,
113
                         top=slide.placeholders[1].top)
114
    pic.height = int(pic.width / ar)
×
115
    geometry_plot.close()
×
116

117
    text_placeholder.text_frame.paragraphs[0].text = 'Geometry Information:'
×
118
    for label, array in zip(['Nodes', 'Coordinate Systems', 'Tracelines', 'Elements'],
×
119
                            [geometry.node, geometry.coordinate_system, geometry.traceline, geometry.element]):
120
        p = text_placeholder.text_frame.add_paragraph()
×
121
        p.text = '{:}: {:}'.format(label, array.size)
×
122
        p.level = 1
×
123

124

125
def add_shape_overview_slide(presentation, shapes, title='Modal Parameters',
3✔
126
                             exp_data=None, fit_data=None,
127
                             subplots_kwargs={}, plot_kwargs={}, axes_modifiers={},
128
                             mac_subplots_kwargs={}, matrix_plot_kwargs={},
129
                             frequency_format='{:0.2f}', damping_format='{:.03f}',
130
                             empty_slide_layout_index=5):
131
    empty_slide = presentation.slide_layouts[empty_slide_layout_index]
×
132
    slide = presentation.slides.add_slide(empty_slide)
×
133
    slide.shapes.title.text = title
×
134
    # Add a table for the mode shapes
135
    table_shape = slide.shapes.add_table(shapes.size + 1, 4, slide.shapes.title.left,
×
136
                                         slide.shapes.title.top +
137
                                         slide.shapes.title.height + Inches(0.5),
138
                                         presentation.slide_width // 2 - int(1.5 * slide.shapes.title.left), Inches(1))
139
    table = table_shape.table
×
140
    table.cell(0, 0).text = 'Mode'
×
141
    table.cell(0, 1).text = 'Freq (Hz)'
×
142
    table.cell(0, 2).text = 'Damp (%)'
×
143
    table.cell(0, 3).text = 'Description'
×
144

145
    table.columns[0].width = Inches(1)
×
146
    table.columns[1].width = Inches(1)
×
147
    table.columns[2].width = Inches(1)
×
148
    table.columns[3].width = presentation.slide_width // 2 - \
×
149
        int(1.5 * slide.shapes.title.left) - sum([table.columns[i].width for i in range(3)])
150

151
    for i, shape in enumerate(shapes.flatten()):
×
152
        table.cell(i + 1, 0).text = '{:}'.format(i + 1)
×
153
        table.cell(i + 1, 1).text = frequency_format.format(shape.frequency)
×
154
        table.cell(i + 1, 2).text = damping_format.format(shape.damping * 100)
×
155
        table.cell(i + 1, 3).text = shape.comment1
×
156

157
    mac_matrix = mac(shapes)
×
158
    fig, ax = plt.subplots(1, 1, **mac_subplots_kwargs)
×
159
    matrix_plot(mac_matrix, ax, **matrix_plot_kwargs)
×
160

161
    with io.BytesIO() as output:
×
162
        fig.savefig(output)
×
163
        pic = slide.shapes.add_picture(output, Inches(1), Inches(1))
×
164
        ar = pic.width / pic.height
×
165
        position_placeholder(presentation, pic, left=(presentation.slide_width + table_shape.left) // 2,
×
166
                             right=-table_shape.left,
167
                             top=table_shape.top,
168
                             bottom=table_shape.top + (presentation.slide_height - table_shape.top) // 2)
169
        pic.width = int(pic.height * ar)
×
170
        plt.close(fig)
×
171

172
    if exp_data is not None:
×
173
        fig, ax = plt.subplots(1, 1, **subplots_kwargs)
×
174
        h1 = ax.plot(exp_data.abscissa.T, exp_data.ordinate.T, 'r', **plot_kwargs)
×
175
        if fit_data is not None:
×
176
            h2 = ax.plot(fit_data.abscissa.T, fit_data.ordinate.T, 'b', **plot_kwargs)
×
177
            ax.legend([np.atleast_1d(h1)[0], np.atleast_1d(h2)[0]],
×
178
                      ['Experiment', 'Analytic Fit'])
179
        for key, val in axes_modifiers.items():
×
180
            getattr(ax, key)(val)
×
181
        if ('set_yscale', 'log'.lower()) in [(key, val) for key, val in axes_modifiers.items()]:
×
182
            ax.set_ylim(exp_data.ordinate.min() / 1.5, exp_data.ordinate.max() * 1.5)
×
183
        else:
184
            rng = exp_data.ordinate.max() - exp_data.ordinate.min()
×
185
            ax.set_ylim(exp_data.ordinate.min() - rng / 20, exp_data.ordinate.max() + rng / 20)
×
186
        with io.BytesIO() as output:
×
187
            fig.savefig(output)
×
188
            pic = slide.shapes.add_picture(output, Inches(1), Inches(1))
×
189
            ar = pic.width / pic.height
×
190
            plt.close(fig)
×
191
        position_placeholder(presentation, pic, left=(presentation.slide_width + table_shape.left) // 2,
×
192
                             right=-table_shape.left,
193
                             top=table_shape.top +
194
                             (presentation.slide_height - table_shape.top) // 2,
195
                             bottom=-slide.shapes.title.top)
196
        pic.width = int(pic.height * ar)
×
197

198

199
def add_shape_animation_slides(presentation, geometry, shapes, title_format='Mode {number:}',
3✔
200
                               text_format='Mode {number:}\nFrequency: {frequency:0.2f}\nDamping: {damping:0.3f}%',
201
                               save_animation_kwargs={'frames': 20, 'frame_rate': 20},
202
                               geometry_plot_kwargs={},
203
                               plot_shape_kwargs={},
204
                               content_slide_layout_index=1,
205
                               left_right=True):
206
    bullet_slide = presentation.slide_layouts[content_slide_layout_index]
×
207
    output_save_file = os.path.join(tempfile.gettempdir(),
×
208
                                    'Shape_{:}.gif')
209
    shapes_plot = geometry.plot_shape(
×
210
        shapes.flatten(), plot_kwargs=geometry_plot_kwargs, **plot_shape_kwargs)
211
    shapes_plot.save_animation_all_shapes(output_save_file, **save_animation_kwargs)
×
212
    shapes_plot.close()
×
213
    for i, shape in enumerate(shapes.flatten()):
×
214
        slide = presentation.slides.add_slide(bullet_slide)
×
215
        text_placeholder = slide.placeholders[1]
×
216
        pic = slide.shapes.add_picture(output_save_file.format(i + 1), Inches(1), Inches(1))
×
217
        ar = pic.width / pic.height
×
218
        slide.shapes.title.text = title_format.format(index=i, number=i + 1, frequency=shape.frequency,
×
219
                                                      damping=shape.damping * 100, modal_mass=shape.modal_mass,
220
                                                      comment1=shape.comment1, comment2=shape.comment2,
221
                                                      comment3=shape.comment3, comment4=shape.comment4,
222
                                                      comment5=shape.comment5)
223
        if left_right:
×
224
            # Move the bullet points to the left
225
            position_placeholder(presentation, text_placeholder,
×
226
                                 right=presentation.slide_width // 2 - Inches(0.25))
227
            # Pic to the right
228
            position_placeholder(presentation, pic, left=presentation.slide_width // 2 + Inches(0.25),
×
229
                                 right=-text_placeholder.left,
230
                                 top=text_placeholder.top)
231
        else:
232
            # Move the bullet points to the left
233
            position_placeholder(presentation, text_placeholder, right=-text_placeholder.left,
×
234
                                 bottom=text_placeholder.top + Inches(1))
235
            # Pic to the right
236
            position_placeholder(presentation, pic, left=text_placeholder.left,
×
237
                                 right=-text_placeholder.left,
238
                                 top=text_placeholder.top + text_placeholder.height + Inches(0.25))
239
        pic.height = int(pic.width / ar)
×
240
        # Check to see if it is off the screen
241
        distance_over = (pic.height + pic.top - presentation.slide_height +
×
242
                         Inches(0.25)) / pic.height
243
        if distance_over > 0:
×
244
            pic.height = int(pic.height * (1 - distance_over))
×
245
            pic.width = int(pic.width * (1 - distance_over))
×
246

247
        os.remove(output_save_file.format(i + 1))
×
248
        # Now add text
249
        for j, text_line in enumerate(text_format.split('\n')):
×
250
            text = text_line.replace('\t', '').format(index=i, number=i + 1, frequency=shape.frequency,
×
251
                                                      damping=shape.damping * 100, modal_mass=shape.modal_mass,
252
                                                      comment1=shape.comment1, comment2=shape.comment2,
253
                                                      comment3=shape.comment3, comment4=shape.comment4,
254
                                                      comment5=shape.comment5)
255
            if j == 0:
×
256
                paragraph = text_placeholder.text_frame.paragraphs[0]
×
257
            else:
258
                paragraph = text_placeholder.text_frame.add_paragraph()
×
259
            paragraph.text = text
×
260
            paragraph.level = text_line.count('\t')
×
261
        text_placeholder.text_frame.fit_text()
×
262
        text_placeholder.text_frame.auto_size = MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE
×
263

264

265
def add_shape_comparison_overview_slide(presentation, shapes_1, shapes_2,
3✔
266
                                        title='Modal Parameter Comparison',
267
                                        shapes_1_label=None, shapes_2_label=None,
268
                                        subplots_kwargs={}, plot_kwargs={}, axes_modifiers={},
269
                                        frequency_format='{:0.2f}', damping_format='{:.03f}',
270
                                        mac_format='{:0.2f}', mac_subplots_kwargs={},
271
                                        matrix_plot_kwargs={},
272
                                        table_threshold=0.75,
273
                                        empty_slide_layout_index=5):
274
    empty_slide = presentation.slide_layouts[empty_slide_layout_index]
×
275
    slide = presentation.slides.add_slide(empty_slide)
×
276
    slide.shapes.title.text = title
×
277
    mac_matrix = mac(shapes_1, shapes_2)
×
278
    shape_indices = np.where(mac_matrix > table_threshold)
×
279
    # Plot the MAC matrix
280
    fig, ax = plt.subplots(1, 1, **mac_subplots_kwargs)
×
281
    matrix_plot(mac_matrix, ax, **matrix_plot_kwargs)
×
282
    if shapes_1_label is not None:
×
283
        ax.set_ylabel(shapes_1_label)
×
284
    if shapes_2_label is not None:
×
285
        ax.set_xlabel(shapes_2_label)
×
286
    fig.tight_layout()
×
287
    # Add a table for the mode shapes
288
    table_shape = slide.shapes.add_table(shape_indices[0].size + 1, 7, slide.shapes.title.left,
×
289
                                         slide.shapes.title.top +
290
                                         slide.shapes.title.height + Inches(0.5),
291
                                         presentation.slide_width // 2 - int(1.5 * slide.shapes.title.left), Inches(1))
292
    table = table_shape.table
×
293
    table.cell(0, 0).text = '{:} Mode'.format(
×
294
        'Shape 1' if shapes_1_label is None else shapes_1_label)
295
    table.cell(0, 1).text = 'Freq (Hz)'
×
296
    table.cell(0, 2).text = 'Damp (%)'
×
297
    table.cell(0, 3).text = '{:} Mode'.format(
×
298
        'Shape 2' if shapes_2_label is None else shapes_2_label)
299
    table.cell(0, 4).text = 'Freq (Hz)'
×
300
    table.cell(0, 5).text = 'Damp (%)'
×
301
    table.cell(0, 6).text = 'MAC'
×
302

303
    for i, indices in enumerate(zip(*shape_indices)):
×
304
        index_1, index_2 = indices
×
305
        shape_1 = shapes_1[index_1]
×
306
        shape_2 = shapes_2[index_2]
×
307
        table.cell(i + 1, 0).text = '{:}'.format(index_1 + 1)
×
308
        table.cell(i + 1, 3).text = '{:}'.format(index_2 + 1)
×
309
        table.cell(i + 1, 1).text = frequency_format.format(shape_1.frequency)
×
310
        table.cell(i + 1, 4).text = frequency_format.format(shape_2.frequency)
×
311
        table.cell(i + 1, 2).text = damping_format.format(shape_1.damping * 100)
×
312
        table.cell(i + 1, 5).text = damping_format.format(shape_2.damping * 100)
×
313
        table.cell(i + 1, 6).text = mac_format.format(mac_matrix[index_1, index_2])
×
314

315
    with io.BytesIO() as output:
×
316
        fig.savefig(output)
×
317
        pic = slide.shapes.add_picture(output, Inches(1), Inches(1))
×
318
        ar = pic.width / pic.height
×
319
        position_placeholder(presentation, pic, left=(presentation.slide_width + table_shape.left) // 2,
×
320
                             right=-table_shape.left,
321
                             top=table_shape.top,
322
                             bottom=-slide.shapes.title.top)
323
        pic.height = int(pic.width / ar)
×
324
        plt.close(fig)
×
325

326

327
def add_shape_comparison_animation_slides(presentation, geometry_1, shapes_1,
3✔
328
                                          geometry_2, shapes_2,
329
                                          title_format='Comparison for Mode {number:}',
330
                                          text_format='Mode {number:}\nFrequency: {frequency:0.2f}\nDamping: {damping:0.3f}%',
331
                                          save_animation_kwargs={'frames': 20, 'frame_rate': 20},
332
                                          geometry_plot_kwargs_1={},
333
                                          plot_shape_kwargs_1={},
334
                                          geometry_plot_kwargs_2={},
335
                                          plot_shape_kwargs_2={},
336
                                          two_content_slide_layout_index=3):
337
    two_content_slide = presentation.slide_layouts[two_content_slide_layout_index]
×
338
    output_save_file = os.path.join(tempfile.gettempdir(),
×
339
                                    'Shape_{:}.gif')
340
    slides = []
×
341
    for j, (shapes, geometry, geometry_plot_kwargs, plot_shape_kwargs) in enumerate(
×
342
            [(shapes_1, geometry_1, geometry_plot_kwargs_1, plot_shape_kwargs_1),
343
             (shapes_2, geometry_2, geometry_plot_kwargs_2, plot_shape_kwargs_2)]):
344
        shapes_plot = geometry.plot_shape(
×
345
            shapes.flatten(), plot_kwargs=geometry_plot_kwargs, **plot_shape_kwargs)
346
        shapes_plot.save_animation_all_shapes(output_save_file, **save_animation_kwargs)
×
347
        shapes_plot.close()
×
348
        for i, shape in enumerate(shapes.flatten()):
×
349
            if j == 0:
×
350
                slide = presentation.slides.add_slide(two_content_slide)
×
351
                slides.append(slide)
×
352
            else:
353
                slide = slides[i]
×
354
            text_placeholder = slide.shapes[1 + j]
×
355
            pic = slide.shapes.add_picture(output_save_file.format(i + 1), Inches(1), Inches(1))
×
356
            ar = pic.width / pic.height
×
357
            if j == 0:
×
358
                slide.shapes.title.text = title_format.format(index=i, number=i + 1, frequency=shape.frequency,
×
359
                                                              damping=shape.damping * 100, modal_mass=shape.modal_mass,
360
                                                              comment1=shape.comment1, comment2=shape.comment2,
361
                                                              comment3=shape.comment3, comment4=shape.comment4,
362
                                                              comment5=shape.comment5)
363
            position_placeholder(presentation, pic, left=text_placeholder.left,
×
364
                                 right=text_placeholder.left + text_placeholder.width,
365
                                 top=text_placeholder.top + Inches(1.5))
366
            pic.height = int(pic.width / ar)
×
367
            # Check to see if it is off the screen
368
            distance_over = (pic.height + pic.top - presentation.slide_height +
×
369
                             Inches(0.25)) / pic.height
370
            if distance_over > 0:
×
371
                pic.height = int(pic.height * (1 - distance_over))
×
372
                pic.width = int(pic.width * (1 - distance_over))
×
373

374
            os.remove(output_save_file.format(i + 1))
×
375
            # Now add text
376
            for k, text_line in enumerate(text_format.split('\n')):
×
377
                text = text_line.replace('\t', '').format(index=i, number=i + 1, frequency=shape.frequency,
×
378
                                                          damping=shape.damping * 100, modal_mass=shape.modal_mass,
379
                                                          comment1=shape.comment1, comment2=shape.comment2,
380
                                                          comment3=shape.comment3, comment4=shape.comment4,
381
                                                          comment5=shape.comment5)
382
                if k == 0:
×
383
                    paragraph = text_placeholder.text_frame.paragraphs[0]
×
384
                else:
385
                    paragraph = text_placeholder.text_frame.add_paragraph()
×
386
                paragraph.text = text
×
387
                paragraph.level = text_line.count('\t')
×
388
            text_placeholder.text_frame.fit_text()
×
389
            text_placeholder.text_frame.auto_size = MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE
×
390

391

392
def create_summary_pptx(presentation, title=None, subtitle='',
3✔
393
                        geometry=None, shapes=None, frfs=None,
394
                        slide_width=None, slide_height=None,
395
                        max_shapes=None, max_frequency=None,
396
                        frequency_format='{:0.1f}', damping_format='{:.02f}',
397
                        cmif_kwargs={'part': 'imag', 'tracking': None},
398
                        subplots_kwargs={}, plot_kwargs={},
399
                        save_animation_kwargs={'frames': 20, 'frame_rate': 20},
400
                        geometry_plot_kwargs={},
401
                        title_slide_layout_index=0,
402
                        content_slide_layout_index=1,
403
                        empty_slide_layout_index=5
404
                        ):
405
    experimental_cmif = None if frfs is None else frfs.compute_cmif(**cmif_kwargs)
×
406
    frequencies = None if experimental_cmif is None else experimental_cmif[0].abscissa
×
407

408
    analytic_frfs = None if (shapes is None or frfs is None) else shapes.compute_frf(frequencies, np.unique(frfs.coordinate[..., 0]),
×
409
                                                                                     np.unique(frfs.coordinate[..., 1]))
410
    analytic_cmif = analytic_frfs.compute_cmif(**cmif_kwargs)
×
411

412
    if isinstance(presentation, pptx.presentation.Presentation):
×
413
        prs = presentation
×
414
    else:
415
        prs = pptx.Presentation()
×
416
        if slide_width is not None:
×
417
            prs.slide_width = Inches(slide_width)
×
418
        if slide_height is not None:
×
419
            prs.slide_height = Inches(slide_height)
×
420

421
    def position_placeholder(placeholder, left=None, top=None, right=None, bottom=None):
×
422
        sw = prs.slide_width
×
423
        sh = prs.slide_height
×
424
        if left is None:
×
425
            left = placeholder.left
×
426
        if left < 0:
×
427
            left = sw - abs(left)
×
428
        if right is None:
×
429
            right = placeholder.left + placeholder.width
×
430
        if right < 0:
×
431
            right = sw - abs(right)
×
432
        if top is None:
×
433
            top = placeholder.top
×
434
        if top < 0:
×
435
            top = sh - abs(top)
×
436
        if bottom is None:
×
437
            bottom = placeholder.top + placeholder.height
×
438
        if bottom < 0:
×
439
            bottom = sh - abs(bottom)
×
440
        width = right - left
×
441
        height = bottom - top
×
442
        placeholder.left = left
×
443
        placeholder.top = top
×
444
        placeholder.width = width
×
445
        placeholder.height = height
×
446

447
    # Add Title Slide
448
    if title is not None:
×
449
        title_slide_layout = prs.slide_layouts[title_slide_layout_index]
×
450
        slide = prs.slides.add_slide(title_slide_layout)
×
451
        slide.shapes.title.text = title
×
452
        position_placeholder(slide.shapes.title, right=-slide.shapes.title.left)
×
453
        slide.placeholders[1].text = subtitle
×
454
        position_placeholder(slide.placeholders[1], right=-slide.placeholders[1].left)
×
455

456
    bullet_slide = prs.slide_layouts[content_slide_layout_index]
×
457
    empty_slide = prs.slide_layouts[empty_slide_layout_index]
×
458

459
    # Plot the test geometry
460
    if geometry is not None:
×
461
        geometry_plot = geometry.plot(**geometry_plot_kwargs)[0]
×
462
        slide = prs.slides.add_slide(bullet_slide)
×
463
        text_placeholder = slide.placeholders[1]
×
464
        with io.BytesIO() as output:
×
465
            time.sleep(0.5)
×
466
            img = Image.fromarray(geometry_plot.screenshot())
×
467
            img.save(output, format='PNG')
×
468
            pic = slide.shapes.add_picture(output, Inches(1), Inches(1))
×
469
        slide.shapes.title.text = 'Test Geometry'
×
470
        # Move the bullet points to the left
471
        position_placeholder(slide.placeholders[1], right=(
×
472
            prs.slide_width - slide.placeholders[1].left) // 2)
473
        # Pic to the right
474
        position_placeholder(pic, left=(prs.slide_width + slide.placeholders[1].left) // 2,
×
475
                             right=-slide.placeholders[1].left,
476
                             top=slide.placeholders[1].top)
477
        pic.height = int(pic.width * img.size[1] / img.size[0])
×
478
        geometry_plot.close()
×
479

480
        text_placeholder.text_frame.paragraphs[0].text = 'Geometry Information:'
×
481
        for label, array in zip(['Nodes', 'Coordinate Systems', 'Tracelines', 'Elements'],
×
482
                                [geometry.node, geometry.coordinate_system, geometry.traceline, geometry.element]):
483
            p = text_placeholder.text_frame.add_paragraph()
×
484
            p.text = '{:}: {:}'.format(label, array.size)
×
485
            p.level = 1
×
486

487
    # Plot the list of shapes
488
    if shapes is not None:
×
489
        if max_shapes is not None:
×
490
            shapes = shapes.flatten()[:max_shapes]
×
491
        if max_frequency is not None:
×
492
            shapes = shapes.flatten()[shapes.flatten().frequency < max_frequency]
×
493
        slide = prs.slides.add_slide(empty_slide)
×
494

495
        slide.shapes.title.text = 'Modal Parameters'
×
496
        # Add a table for the mode shapes
497
        table_shape = slide.shapes.add_table(shapes.size + 1, 4, slide.shapes.title.left,
×
498
                                             slide.shapes.title.top +
499
                                             slide.shapes.title.height + Inches(0.5),
500
                                             prs.slide_width // 2 - int(1.5 * slide.shapes.title.left), Inches(1))
501
        table = table_shape.table
×
502
        table.cell(0, 0).text = 'Mode'
×
503
        table.cell(0, 1).text = 'Freq (Hz)'
×
504
        table.cell(0, 2).text = 'Damp (%)'
×
505
        table.cell(0, 3).text = 'Description'
×
506

507
        table.columns[0].width = Inches(1)
×
508
        table.columns[1].width = Inches(1)
×
509
        table.columns[2].width = Inches(1)
×
510
        table.columns[3].width = prs.slide_width // 2 - \
×
511
            int(1.5 * slide.shapes.title.left) - sum([table.columns[i].width for i in range(3)])
512

513
        for i, shape in enumerate(shapes.flatten()):
×
514
            table.cell(i + 1, 0).text = '{:}'.format(i + 1)
×
515
            table.cell(i + 1, 1).text = frequency_format.format(shape.frequency)
×
516
            table.cell(i + 1, 2).text = damping_format.format(shape.damping * 100)
×
517
            table.cell(i + 1, 3).text = shape.comment1
×
518

519
        if analytic_cmif is not None:
×
520
            fig, ax = plt.subplots(1, 1, **subplots_kwargs)
×
521
            experimental_cmif.plot(ax, **plot_kwargs)
×
522
            analytic_cmif.plot(ax, **plot_kwargs)
×
523
            ax.legend(['Experiment', 'Analytic Fit'])
×
524
            ax.set_yscale('log')
×
525
            ax.set_ylim(experimental_cmif.ordinate.min() / 1.5,
×
526
                        experimental_cmif.ordinate.max() * 1.5)
527
            with io.BytesIO() as output:
×
528
                fig.savefig(output)
×
529
                pic = slide.shapes.add_picture(output, Inches(1), Inches(1))
×
530
                ar = pic.width / pic.height
×
531
                plt.close(fig)
×
532
            position_placeholder(pic, left=(prs.slide_width + table_shape.left) // 2,
×
533
                                 right=-table_shape.left,
534
                                 top=table_shape.top)
535
            pic.height = int(pic.width / ar)
×
536

537
        # Now we need to plot each shape if possible
538
        if geometry is not None:
×
539
            output_save_file = os.path.join(tempfile.gettempdir(),
×
540
                                            'Shape_{:}.gif')
541
            shapes_plot = geometry.plot_shape(shapes.flatten(), plot_kwargs=geometry_plot_kwargs)
×
542
            shapes_plot.save_animation_all_shapes(output_save_file, **save_animation_kwargs)
×
543
            shapes_plot.close()
×
544
            for i, shape in enumerate(shapes.flatten()):
×
545
                slide = prs.slides.add_slide(bullet_slide)
×
546
                text_placeholder = slide.placeholders[1]
×
547
                pic = slide.shapes.add_picture(output_save_file.format(i + 1), Inches(1), Inches(1))
×
548
                ar = pic.width / pic.height
×
549
                slide.shapes.title.text = 'Mode {:}'.format(i + 1)
×
550
                # Move the bullet points to the left
551
                position_placeholder(text_placeholder, right=-text_placeholder.left,
×
552
                                     bottom=text_placeholder.top + Inches(1))
553
                # Pic to the right
554
                position_placeholder(pic, left=text_placeholder.left,
×
555
                                     right=-text_placeholder.left,
556
                                     top=text_placeholder.top + text_placeholder.height + Inches(0.25))
557
                pic.height = int(pic.width / ar)
×
558
                # Check to see if it is off the screen
559
                distance_over = (pic.height + pic.top - prs.slide_height +
×
560
                                 Inches(0.25)) / pic.height
561
                if distance_over > 0:
×
562
                    pic.height = int(pic.height * (1 - distance_over))
×
563
                    pic.width = int(pic.width * (1 - distance_over))
×
564

565
                os.remove(output_save_file.format(i + 1))
×
566
                text_placeholder.text_frame.paragraphs[0].text = 'Mode {:}'.format(i + 1)
×
567
                for label, value in zip(['Frequency: ' + frequency_format + ' Hz', 'Damping: ' + damping_format + ' %'],
×
568
                                        [shape.frequency, shape.damping * 100]):
569
                    p = text_placeholder.text_frame.add_paragraph()
×
570
                    p.text = label.format(value)
×
571
                    p.level = 1
×
572
                text_placeholder.text_frame.fit_text()
×
573
                text_placeholder.text_frame.auto_size = MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE
×
574

575
    if isinstance(presentation, pptx.presentation.Presentation):
×
576
        return prs
×
577
    else:
578
        prs.save(presentation)
×
579
        return prs
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc