Coverage for src / sdynpy / doc / sdynpy_ppt.py: 6%
360 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-11 16:22 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-11 16:22 +0000
1# -*- coding: utf-8 -*-
2"""
3Functions for creating PowerPoint presentations from SDynPy objects.
5This modules includes functions for assembling a PowerPoint presentation from
6SDynPy objects, saving users the tediousness of putting a large number of images
7and tables into the presentation.
8"""
9"""
10Copyright 2022 National Technology & Engineering Solutions of Sandia,
11LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S.
12Government retains certain rights in this software.
14This program is free software: you can redistribute it and/or modify
15it under the terms of the GNU General Public License as published by
16the Free Software Foundation, either version 3 of the License, or
17(at your option) any later version.
19This program is distributed in the hope that it will be useful,
20but WITHOUT ANY WARRANTY; without even the implied warranty of
21MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22GNU General Public License for more details.
24You should have received a copy of the GNU General Public License
25along with this program. If not, see <https://www.gnu.org/licenses/>.
26"""
28import pptx
29from pptx.util import Inches
30from pptx.enum.text import MSO_AUTO_SIZE
31import tempfile
32import io
33from PIL import Image
34import matplotlib.pyplot as plt
35import numpy as np
36import os
37import time
39from ..core.sdynpy_shape import mac, matrix_plot
42def position_placeholder(presentation, placeholder, left=None, top=None, right=None, bottom=None):
43 sw = presentation.slide_width
44 sh = presentation.slide_height
45 if left is None:
46 left = placeholder.left
47 if left < 0:
48 left = sw - abs(left)
49 if right is None:
50 right = placeholder.left + placeholder.width
51 if right < 0:
52 right = sw - abs(right)
53 if top is None:
54 top = placeholder.top
55 if top < 0:
56 top = sh - abs(top)
57 if bottom is None:
58 bottom = placeholder.top + placeholder.height
59 if bottom < 0:
60 bottom = sh - abs(bottom)
61 width = right - left
62 height = bottom - top
63 placeholder.left = left
64 placeholder.top = top
65 placeholder.width = width
66 placeholder.height = height
69def add_title_slide(presentation, title, subtitle='', title_slide_layout_index=0):
70 title_slide_layout = presentation.slide_layouts[title_slide_layout_index]
71 slide = presentation.slides.add_slide(title_slide_layout)
72 slide.shapes.title.text = title
73 position_placeholder(presentation, slide.shapes.title, right=-slide.shapes.title.left)
74 slide.placeholders[1].text = subtitle
77def add_section_header_slide(presentation, title, subtitle='', section_header_slide_layout_index=2):
78 section_header_slide_layout = presentation.slide_layouts[section_header_slide_layout_index]
79 slide = presentation.slides.add_slide(section_header_slide_layout)
80 slide.shapes.title.text = title
81 position_placeholder(presentation, slide.shapes.title, right=-slide.shapes.title.left)
82 slide.placeholders[1].text = subtitle
85def add_geometry_overview_slide(presentation, geometry, title='Geometry',
86 geometry_plot_kwargs={},
87 animate_geometry=True,
88 save_animation_kwargs={'frames': 200, 'frame_rate': 20},
89 content_slide_layout_index=1):
90 bullet_slide = presentation.slide_layouts[content_slide_layout_index]
91 geometry_plot = geometry.plot(**geometry_plot_kwargs)[0]
92 slide = presentation.slides.add_slide(bullet_slide)
93 text_placeholder = slide.placeholders[1]
94 if animate_geometry:
95 output_save_file = os.path.join(tempfile.gettempdir(),
96 'Geometry.gif')
97 geometry_plot.save_rotation_animation(output_save_file, **save_animation_kwargs)
98 geometry_plot.close()
99 pic = slide.shapes.add_picture(output_save_file, Inches(1), Inches(1))
100 else:
101 with io.BytesIO() as output:
102 time.sleep(0.5)
103 img = Image.fromarray(geometry_plot.screenshot())
104 img.save(output, format='PNG')
105 pic = slide.shapes.add_picture(output, Inches(1), Inches(1))
106 ar = pic.width / pic.height
107 slide.shapes.title.text = title
108 # Move the bullet points to the left
109 position_placeholder(presentation, slide.placeholders[1], right=(
110 presentation.slide_width - slide.placeholders[1].left) // 2)
111 # Pic to the right
112 position_placeholder(presentation, pic, left=(presentation.slide_width + slide.placeholders[1].left) // 2,
113 right=-slide.placeholders[1].left,
114 top=slide.placeholders[1].top)
115 pic.height = int(pic.width / ar)
116 geometry_plot.close()
118 text_placeholder.text_frame.paragraphs[0].text = 'Geometry Information:'
119 for label, array in zip(['Nodes', 'Coordinate Systems', 'Tracelines', 'Elements'],
120 [geometry.node, geometry.coordinate_system, geometry.traceline, geometry.element]):
121 p = text_placeholder.text_frame.add_paragraph()
122 p.text = '{:}: {:}'.format(label, array.size)
123 p.level = 1
126def add_shape_overview_slide(presentation, shapes, title='Modal Parameters',
127 exp_data=None, fit_data=None,
128 subplots_kwargs={}, plot_kwargs={}, axes_modifiers={},
129 mac_subplots_kwargs={}, matrix_plot_kwargs={},
130 frequency_format='{:0.2f}', damping_format='{:.03f}',
131 empty_slide_layout_index=5):
132 empty_slide = presentation.slide_layouts[empty_slide_layout_index]
133 slide = presentation.slides.add_slide(empty_slide)
134 slide.shapes.title.text = title
135 # Add a table for the mode shapes
136 table_shape = slide.shapes.add_table(shapes.size + 1, 4, slide.shapes.title.left,
137 slide.shapes.title.top +
138 slide.shapes.title.height + Inches(0.5),
139 presentation.slide_width // 2 - int(1.5 * slide.shapes.title.left), Inches(1))
140 table = table_shape.table
141 table.cell(0, 0).text = 'Mode'
142 table.cell(0, 1).text = 'Freq (Hz)'
143 table.cell(0, 2).text = 'Damp (%)'
144 table.cell(0, 3).text = 'Description'
146 table.columns[0].width = Inches(1)
147 table.columns[1].width = Inches(1)
148 table.columns[2].width = Inches(1)
149 table.columns[3].width = presentation.slide_width // 2 - \
150 int(1.5 * slide.shapes.title.left) - sum([table.columns[i].width for i in range(3)])
152 for i, shape in enumerate(shapes.flatten()):
153 table.cell(i + 1, 0).text = '{:}'.format(i + 1)
154 table.cell(i + 1, 1).text = frequency_format.format(shape.frequency)
155 table.cell(i + 1, 2).text = damping_format.format(shape.damping * 100)
156 table.cell(i + 1, 3).text = shape.comment1
158 mac_matrix = mac(shapes)
159 fig, ax = plt.subplots(1, 1, **mac_subplots_kwargs)
160 matrix_plot(mac_matrix, ax, **matrix_plot_kwargs)
162 with io.BytesIO() as output:
163 fig.savefig(output)
164 pic = slide.shapes.add_picture(output, Inches(1), Inches(1))
165 ar = pic.width / pic.height
166 position_placeholder(presentation, pic, left=(presentation.slide_width + table_shape.left) // 2,
167 right=-table_shape.left,
168 top=table_shape.top,
169 bottom=table_shape.top + (presentation.slide_height - table_shape.top) // 2)
170 pic.width = int(pic.height * ar)
171 plt.close(fig)
173 if exp_data is not None:
174 fig, ax = plt.subplots(1, 1, **subplots_kwargs)
175 h1 = ax.plot(exp_data.abscissa.T, exp_data.ordinate.T, 'r', **plot_kwargs)
176 if fit_data is not None:
177 h2 = ax.plot(fit_data.abscissa.T, fit_data.ordinate.T, 'b', **plot_kwargs)
178 ax.legend([np.atleast_1d(h1)[0], np.atleast_1d(h2)[0]],
179 ['Experiment', 'Analytic Fit'])
180 for key, val in axes_modifiers.items():
181 getattr(ax, key)(val)
182 if ('set_yscale', 'log'.lower()) in [(key, val) for key, val in axes_modifiers.items()]:
183 ax.set_ylim(exp_data.ordinate.min() / 1.5, exp_data.ordinate.max() * 1.5)
184 else:
185 rng = exp_data.ordinate.max() - exp_data.ordinate.min()
186 ax.set_ylim(exp_data.ordinate.min() - rng / 20, exp_data.ordinate.max() + rng / 20)
187 with io.BytesIO() as output:
188 fig.savefig(output)
189 pic = slide.shapes.add_picture(output, Inches(1), Inches(1))
190 ar = pic.width / pic.height
191 plt.close(fig)
192 position_placeholder(presentation, pic, left=(presentation.slide_width + table_shape.left) // 2,
193 right=-table_shape.left,
194 top=table_shape.top +
195 (presentation.slide_height - table_shape.top) // 2,
196 bottom=-slide.shapes.title.top)
197 pic.width = int(pic.height * ar)
200def add_shape_animation_slides(presentation, geometry, shapes, title_format='Mode {number:}',
201 text_format='Mode {number:}\nFrequency: {frequency:0.2f}\nDamping: {damping:0.3f}%',
202 save_animation_kwargs={'frames': 20, 'frame_rate': 20},
203 geometry_plot_kwargs={},
204 plot_shape_kwargs={},
205 content_slide_layout_index=1,
206 left_right=True):
207 bullet_slide = presentation.slide_layouts[content_slide_layout_index]
208 output_save_file = os.path.join(tempfile.gettempdir(),
209 'Shape_{:}.gif')
210 shapes_plot = geometry.plot_shape(
211 shapes.flatten(), plot_kwargs=geometry_plot_kwargs, **plot_shape_kwargs)
212 shapes_plot.save_animation_all_shapes(output_save_file, **save_animation_kwargs)
213 shapes_plot.close()
214 for i, shape in enumerate(shapes.flatten()):
215 slide = presentation.slides.add_slide(bullet_slide)
216 text_placeholder = slide.placeholders[1]
217 pic = slide.shapes.add_picture(output_save_file.format(i + 1), Inches(1), Inches(1))
218 ar = pic.width / pic.height
219 slide.shapes.title.text = title_format.format(index=i, number=i + 1, frequency=shape.frequency,
220 damping=shape.damping * 100, modal_mass=shape.modal_mass,
221 comment1=shape.comment1, comment2=shape.comment2,
222 comment3=shape.comment3, comment4=shape.comment4,
223 comment5=shape.comment5)
224 if left_right:
225 # Move the bullet points to the left
226 position_placeholder(presentation, text_placeholder,
227 right=presentation.slide_width // 2 - Inches(0.25))
228 # Pic to the right
229 position_placeholder(presentation, pic, left=presentation.slide_width // 2 + Inches(0.25),
230 right=-text_placeholder.left,
231 top=text_placeholder.top)
232 else:
233 # Move the bullet points to the left
234 position_placeholder(presentation, text_placeholder, right=-text_placeholder.left,
235 bottom=text_placeholder.top + Inches(1))
236 # Pic to the right
237 position_placeholder(presentation, pic, left=text_placeholder.left,
238 right=-text_placeholder.left,
239 top=text_placeholder.top + text_placeholder.height + Inches(0.25))
240 pic.height = int(pic.width / ar)
241 # Check to see if it is off the screen
242 distance_over = (pic.height + pic.top - presentation.slide_height +
243 Inches(0.25)) / pic.height
244 if distance_over > 0:
245 pic.height = int(pic.height * (1 - distance_over))
246 pic.width = int(pic.width * (1 - distance_over))
248 os.remove(output_save_file.format(i + 1))
249 # Now add text
250 for j, text_line in enumerate(text_format.split('\n')):
251 text = text_line.replace('\t', '').format(index=i, number=i + 1, frequency=shape.frequency,
252 damping=shape.damping * 100, modal_mass=shape.modal_mass,
253 comment1=shape.comment1, comment2=shape.comment2,
254 comment3=shape.comment3, comment4=shape.comment4,
255 comment5=shape.comment5)
256 if j == 0:
257 paragraph = text_placeholder.text_frame.paragraphs[0]
258 else:
259 paragraph = text_placeholder.text_frame.add_paragraph()
260 paragraph.text = text
261 paragraph.level = text_line.count('\t')
262 text_placeholder.text_frame.fit_text()
263 text_placeholder.text_frame.auto_size = MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE
266def add_shape_comparison_overview_slide(presentation, shapes_1, shapes_2,
267 title='Modal Parameter Comparison',
268 shapes_1_label=None, shapes_2_label=None,
269 subplots_kwargs={}, plot_kwargs={}, axes_modifiers={},
270 frequency_format='{:0.2f}', damping_format='{:.03f}',
271 mac_format='{:0.2f}', mac_subplots_kwargs={},
272 matrix_plot_kwargs={},
273 table_threshold=0.75,
274 empty_slide_layout_index=5):
275 empty_slide = presentation.slide_layouts[empty_slide_layout_index]
276 slide = presentation.slides.add_slide(empty_slide)
277 slide.shapes.title.text = title
278 mac_matrix = mac(shapes_1, shapes_2)
279 shape_indices = np.where(mac_matrix > table_threshold)
280 # Plot the MAC matrix
281 fig, ax = plt.subplots(1, 1, **mac_subplots_kwargs)
282 matrix_plot(mac_matrix, ax, **matrix_plot_kwargs)
283 if shapes_1_label is not None:
284 ax.set_ylabel(shapes_1_label)
285 if shapes_2_label is not None:
286 ax.set_xlabel(shapes_2_label)
287 fig.tight_layout()
288 # Add a table for the mode shapes
289 table_shape = slide.shapes.add_table(shape_indices[0].size + 1, 7, slide.shapes.title.left,
290 slide.shapes.title.top +
291 slide.shapes.title.height + Inches(0.5),
292 presentation.slide_width // 2 - int(1.5 * slide.shapes.title.left), Inches(1))
293 table = table_shape.table
294 table.cell(0, 0).text = '{:} Mode'.format(
295 'Shape 1' if shapes_1_label is None else shapes_1_label)
296 table.cell(0, 1).text = 'Freq (Hz)'
297 table.cell(0, 2).text = 'Damp (%)'
298 table.cell(0, 3).text = '{:} Mode'.format(
299 'Shape 2' if shapes_2_label is None else shapes_2_label)
300 table.cell(0, 4).text = 'Freq (Hz)'
301 table.cell(0, 5).text = 'Damp (%)'
302 table.cell(0, 6).text = 'MAC'
304 for i, indices in enumerate(zip(*shape_indices)):
305 index_1, index_2 = indices
306 shape_1 = shapes_1[index_1]
307 shape_2 = shapes_2[index_2]
308 table.cell(i + 1, 0).text = '{:}'.format(index_1 + 1)
309 table.cell(i + 1, 3).text = '{:}'.format(index_2 + 1)
310 table.cell(i + 1, 1).text = frequency_format.format(shape_1.frequency)
311 table.cell(i + 1, 4).text = frequency_format.format(shape_2.frequency)
312 table.cell(i + 1, 2).text = damping_format.format(shape_1.damping * 100)
313 table.cell(i + 1, 5).text = damping_format.format(shape_2.damping * 100)
314 table.cell(i + 1, 6).text = mac_format.format(mac_matrix[index_1, index_2])
316 with io.BytesIO() as output:
317 fig.savefig(output)
318 pic = slide.shapes.add_picture(output, Inches(1), Inches(1))
319 ar = pic.width / pic.height
320 position_placeholder(presentation, pic, left=(presentation.slide_width + table_shape.left) // 2,
321 right=-table_shape.left,
322 top=table_shape.top,
323 bottom=-slide.shapes.title.top)
324 pic.height = int(pic.width / ar)
325 plt.close(fig)
328def add_shape_comparison_animation_slides(presentation, geometry_1, shapes_1,
329 geometry_2, shapes_2,
330 title_format='Comparison for Mode {number:}',
331 text_format='Mode {number:}\nFrequency: {frequency:0.2f}\nDamping: {damping:0.3f}%',
332 save_animation_kwargs={'frames': 20, 'frame_rate': 20},
333 geometry_plot_kwargs_1={},
334 plot_shape_kwargs_1={},
335 geometry_plot_kwargs_2={},
336 plot_shape_kwargs_2={},
337 two_content_slide_layout_index=3):
338 two_content_slide = presentation.slide_layouts[two_content_slide_layout_index]
339 output_save_file = os.path.join(tempfile.gettempdir(),
340 'Shape_{:}.gif')
341 slides = []
342 for j, (shapes, geometry, geometry_plot_kwargs, plot_shape_kwargs) in enumerate(
343 [(shapes_1, geometry_1, geometry_plot_kwargs_1, plot_shape_kwargs_1),
344 (shapes_2, geometry_2, geometry_plot_kwargs_2, plot_shape_kwargs_2)]):
345 shapes_plot = geometry.plot_shape(
346 shapes.flatten(), plot_kwargs=geometry_plot_kwargs, **plot_shape_kwargs)
347 shapes_plot.save_animation_all_shapes(output_save_file, **save_animation_kwargs)
348 shapes_plot.close()
349 for i, shape in enumerate(shapes.flatten()):
350 if j == 0:
351 slide = presentation.slides.add_slide(two_content_slide)
352 slides.append(slide)
353 else:
354 slide = slides[i]
355 text_placeholder = slide.shapes[1 + j]
356 pic = slide.shapes.add_picture(output_save_file.format(i + 1), Inches(1), Inches(1))
357 ar = pic.width / pic.height
358 if j == 0:
359 slide.shapes.title.text = title_format.format(index=i, number=i + 1, frequency=shape.frequency,
360 damping=shape.damping * 100, modal_mass=shape.modal_mass,
361 comment1=shape.comment1, comment2=shape.comment2,
362 comment3=shape.comment3, comment4=shape.comment4,
363 comment5=shape.comment5)
364 position_placeholder(presentation, pic, left=text_placeholder.left,
365 right=text_placeholder.left + text_placeholder.width,
366 top=text_placeholder.top + Inches(1.5))
367 pic.height = int(pic.width / ar)
368 # Check to see if it is off the screen
369 distance_over = (pic.height + pic.top - presentation.slide_height +
370 Inches(0.25)) / pic.height
371 if distance_over > 0:
372 pic.height = int(pic.height * (1 - distance_over))
373 pic.width = int(pic.width * (1 - distance_over))
375 os.remove(output_save_file.format(i + 1))
376 # Now add text
377 for k, text_line in enumerate(text_format.split('\n')):
378 text = text_line.replace('\t', '').format(index=i, number=i + 1, frequency=shape.frequency,
379 damping=shape.damping * 100, modal_mass=shape.modal_mass,
380 comment1=shape.comment1, comment2=shape.comment2,
381 comment3=shape.comment3, comment4=shape.comment4,
382 comment5=shape.comment5)
383 if k == 0:
384 paragraph = text_placeholder.text_frame.paragraphs[0]
385 else:
386 paragraph = text_placeholder.text_frame.add_paragraph()
387 paragraph.text = text
388 paragraph.level = text_line.count('\t')
389 text_placeholder.text_frame.fit_text()
390 text_placeholder.text_frame.auto_size = MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE
393def create_summary_pptx(presentation, title=None, subtitle='',
394 geometry=None, shapes=None, frfs=None,
395 slide_width=None, slide_height=None,
396 max_shapes=None, max_frequency=None,
397 frequency_format='{:0.1f}', damping_format='{:.02f}',
398 cmif_kwargs={'part': 'imag', 'tracking': None},
399 subplots_kwargs={}, plot_kwargs={},
400 save_animation_kwargs={'frames': 20, 'frame_rate': 20},
401 geometry_plot_kwargs={},
402 title_slide_layout_index=0,
403 content_slide_layout_index=1,
404 empty_slide_layout_index=5
405 ):
406 experimental_cmif = None if frfs is None else frfs.compute_cmif(**cmif_kwargs)
407 frequencies = None if experimental_cmif is None else experimental_cmif[0].abscissa
409 analytic_frfs = None if (shapes is None or frfs is None) else shapes.compute_frf(frequencies, np.unique(frfs.coordinate[..., 0]),
410 np.unique(frfs.coordinate[..., 1]))
411 analytic_cmif = analytic_frfs.compute_cmif(**cmif_kwargs)
413 if isinstance(presentation, pptx.presentation.Presentation):
414 prs = presentation
415 else:
416 prs = pptx.Presentation()
417 if slide_width is not None:
418 prs.slide_width = Inches(slide_width)
419 if slide_height is not None:
420 prs.slide_height = Inches(slide_height)
422 def position_placeholder(placeholder, left=None, top=None, right=None, bottom=None):
423 sw = prs.slide_width
424 sh = prs.slide_height
425 if left is None:
426 left = placeholder.left
427 if left < 0:
428 left = sw - abs(left)
429 if right is None:
430 right = placeholder.left + placeholder.width
431 if right < 0:
432 right = sw - abs(right)
433 if top is None:
434 top = placeholder.top
435 if top < 0:
436 top = sh - abs(top)
437 if bottom is None:
438 bottom = placeholder.top + placeholder.height
439 if bottom < 0:
440 bottom = sh - abs(bottom)
441 width = right - left
442 height = bottom - top
443 placeholder.left = left
444 placeholder.top = top
445 placeholder.width = width
446 placeholder.height = height
448 # Add Title Slide
449 if title is not None:
450 title_slide_layout = prs.slide_layouts[title_slide_layout_index]
451 slide = prs.slides.add_slide(title_slide_layout)
452 slide.shapes.title.text = title
453 position_placeholder(slide.shapes.title, right=-slide.shapes.title.left)
454 slide.placeholders[1].text = subtitle
455 position_placeholder(slide.placeholders[1], right=-slide.placeholders[1].left)
457 bullet_slide = prs.slide_layouts[content_slide_layout_index]
458 empty_slide = prs.slide_layouts[empty_slide_layout_index]
460 # Plot the test geometry
461 if geometry is not None:
462 geometry_plot = geometry.plot(**geometry_plot_kwargs)[0]
463 slide = prs.slides.add_slide(bullet_slide)
464 text_placeholder = slide.placeholders[1]
465 with io.BytesIO() as output:
466 time.sleep(0.5)
467 img = Image.fromarray(geometry_plot.screenshot())
468 img.save(output, format='PNG')
469 pic = slide.shapes.add_picture(output, Inches(1), Inches(1))
470 slide.shapes.title.text = 'Test Geometry'
471 # Move the bullet points to the left
472 position_placeholder(slide.placeholders[1], right=(
473 prs.slide_width - slide.placeholders[1].left) // 2)
474 # Pic to the right
475 position_placeholder(pic, left=(prs.slide_width + slide.placeholders[1].left) // 2,
476 right=-slide.placeholders[1].left,
477 top=slide.placeholders[1].top)
478 pic.height = int(pic.width * img.size[1] / img.size[0])
479 geometry_plot.close()
481 text_placeholder.text_frame.paragraphs[0].text = 'Geometry Information:'
482 for label, array in zip(['Nodes', 'Coordinate Systems', 'Tracelines', 'Elements'],
483 [geometry.node, geometry.coordinate_system, geometry.traceline, geometry.element]):
484 p = text_placeholder.text_frame.add_paragraph()
485 p.text = '{:}: {:}'.format(label, array.size)
486 p.level = 1
488 # Plot the list of shapes
489 if shapes is not None:
490 if max_shapes is not None:
491 shapes = shapes.flatten()[:max_shapes]
492 if max_frequency is not None:
493 shapes = shapes.flatten()[shapes.flatten().frequency < max_frequency]
494 slide = prs.slides.add_slide(empty_slide)
496 slide.shapes.title.text = 'Modal Parameters'
497 # Add a table for the mode shapes
498 table_shape = slide.shapes.add_table(shapes.size + 1, 4, slide.shapes.title.left,
499 slide.shapes.title.top +
500 slide.shapes.title.height + Inches(0.5),
501 prs.slide_width // 2 - int(1.5 * slide.shapes.title.left), Inches(1))
502 table = table_shape.table
503 table.cell(0, 0).text = 'Mode'
504 table.cell(0, 1).text = 'Freq (Hz)'
505 table.cell(0, 2).text = 'Damp (%)'
506 table.cell(0, 3).text = 'Description'
508 table.columns[0].width = Inches(1)
509 table.columns[1].width = Inches(1)
510 table.columns[2].width = Inches(1)
511 table.columns[3].width = prs.slide_width // 2 - \
512 int(1.5 * slide.shapes.title.left) - sum([table.columns[i].width for i in range(3)])
514 for i, shape in enumerate(shapes.flatten()):
515 table.cell(i + 1, 0).text = '{:}'.format(i + 1)
516 table.cell(i + 1, 1).text = frequency_format.format(shape.frequency)
517 table.cell(i + 1, 2).text = damping_format.format(shape.damping * 100)
518 table.cell(i + 1, 3).text = shape.comment1
520 if analytic_cmif is not None:
521 fig, ax = plt.subplots(1, 1, **subplots_kwargs)
522 experimental_cmif.plot(ax, **plot_kwargs)
523 analytic_cmif.plot(ax, **plot_kwargs)
524 ax.legend(['Experiment', 'Analytic Fit'])
525 ax.set_yscale('log')
526 ax.set_ylim(experimental_cmif.ordinate.min() / 1.5,
527 experimental_cmif.ordinate.max() * 1.5)
528 with io.BytesIO() as output:
529 fig.savefig(output)
530 pic = slide.shapes.add_picture(output, Inches(1), Inches(1))
531 ar = pic.width / pic.height
532 plt.close(fig)
533 position_placeholder(pic, left=(prs.slide_width + table_shape.left) // 2,
534 right=-table_shape.left,
535 top=table_shape.top)
536 pic.height = int(pic.width / ar)
538 # Now we need to plot each shape if possible
539 if geometry is not None:
540 output_save_file = os.path.join(tempfile.gettempdir(),
541 'Shape_{:}.gif')
542 shapes_plot = geometry.plot_shape(shapes.flatten(), plot_kwargs=geometry_plot_kwargs)
543 shapes_plot.save_animation_all_shapes(output_save_file, **save_animation_kwargs)
544 shapes_plot.close()
545 for i, shape in enumerate(shapes.flatten()):
546 slide = prs.slides.add_slide(bullet_slide)
547 text_placeholder = slide.placeholders[1]
548 pic = slide.shapes.add_picture(output_save_file.format(i + 1), Inches(1), Inches(1))
549 ar = pic.width / pic.height
550 slide.shapes.title.text = 'Mode {:}'.format(i + 1)
551 # Move the bullet points to the left
552 position_placeholder(text_placeholder, right=-text_placeholder.left,
553 bottom=text_placeholder.top + Inches(1))
554 # Pic to the right
555 position_placeholder(pic, left=text_placeholder.left,
556 right=-text_placeholder.left,
557 top=text_placeholder.top + text_placeholder.height + Inches(0.25))
558 pic.height = int(pic.width / ar)
559 # Check to see if it is off the screen
560 distance_over = (pic.height + pic.top - prs.slide_height +
561 Inches(0.25)) / pic.height
562 if distance_over > 0:
563 pic.height = int(pic.height * (1 - distance_over))
564 pic.width = int(pic.width * (1 - distance_over))
566 os.remove(output_save_file.format(i + 1))
567 text_placeholder.text_frame.paragraphs[0].text = 'Mode {:}'.format(i + 1)
568 for label, value in zip(['Frequency: ' + frequency_format + ' Hz', 'Damping: ' + damping_format + ' %'],
569 [shape.frequency, shape.damping * 100]):
570 p = text_placeholder.text_frame.add_paragraph()
571 p.text = label.format(value)
572 p.level = 1
573 text_placeholder.text_frame.fit_text()
574 text_placeholder.text_frame.auto_size = MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE
576 if isinstance(presentation, pptx.presentation.Presentation):
577 return prs
578 else:
579 prs.save(presentation)
580 return prs