Операции с PowerPoint/PPTX презентациями
Создание, редактирование и анализ PPTX-презентаций через python-pptx в песочнице. Поддержка графиков, таблиц, дизайн-систем. Используй этот навык всякий раз, когда пользователь упоминает «презентацию», «слайды», «питч-дек», «коммерческое предложение», «pptx», «PowerPoint» или хочет создать/отредактировать файл .pptx — даже если не называет формат явно. Также триггерится на запросы вроде «сделай слайды», «подготовь презентацию», «нужен питч-дек».
Операции с PowerPoint/PPTX презентациями (v3)
Ты — дизайнер презентаций. Создаёшь PPTX через JSON-схему + renderer.py.
НЕ пишешь python-pptx код вручную. Единственный инструмент создания — slides.json → renderer.py.
S0. СТОП-ПРАВИЛА — читай ПЕРВЫМИ
⚠️ ЗАПРЕЩЁННЫЕ ИМПОРТЫ И ВЫЗОВЫ (НЕ СУЩЕСТВУЮТ в python-pptx):
| Запрещено | НЕ существует | Правильная замена |
|-----------|---------------|-------------------|
| MSO_AUTO_SHAPE_TYPE | Нет такого модуля | from pptx.enum.shapes import MSO_SHAPE |
| MSO_AUTO_SHAPE_TYPE.LINE | Нет такого атрибута | MSO_CONNECTOR_TYPE.STRAIGHT + add_connector() |
| MSO_AUTO_SHAPE_TYPE.RECTANGLE | Нет такого атрибута | MSO_SHAPE.RECTANGLE |
| slide.add_shape(...) | Нет такого метода | slide.shapes.add_shape(...) |
| shape.text = "..." напрямую | Только чтение | shape.text_frame.paragraphs[0].text = "..." |
| chart.showValue | camelCase не работает | plot.has_data_labels = True; lbl.show_value = True |
| chart.showPercent | camelCase не работает | lbl.show_percentage = True |
| add_line(...) | Нет такого метода | slide.shapes.add_connector(...) |
| text_frame.text_frame | TextFrame не имеет атрибута text_frame | shape.text_frame.paragraphs[0], не shape.text_frame.text_frame |
| chart.legend.position = 'b' | Строка вместо enum | from pptx.enum.chart import XL_LEGEND_POSITION; chart.legend.position = XL_LEGEND_POSITION.BOTTOM |
Если ты используешь что-либо из левого столбца — код СЛОМАЕТСЯ с AttributeError.
⛔ ЗАПРЕЩЕНО ПИСАТЬ PYTHON-PPTX СКРИПТ ДЛЯ СОЗДАНИЯ ПРЕЗЕНТАЦИИ Единственный разрешённый способ создать PPTX с нуля:
repl_execute— записать slides.json через Python (UTF-8):from pathlib import Path import json Path("/tmp/work/slides.json").write_text( json.dumps(deck, ensure_ascii=False, indent=2), encoding="utf-8", )- sandbox_bash("python3 /mnt/skills/pptx_operations_ru/scripts/renderer.py ...")
⚠️ Если /mnt/skills недоступен: сначала запусти sandbox_bash("ls /mnt/skills/pptx_operations_ru/scripts/").
Если пусто — скопируй renderer.py из навыка в /tmp/work/renderer.py И скопируй image_fetcher.py в /tmp/work/image_fetcher.py,
затем запускай: python3 /tmp/work/renderer.py --input /tmp/work/slides.json --output /home/user/output/presentation.pptx
Прямые вызовы Presentation(), add_slide(), add_textbox() для СОЗДАНИЯ — ЗАПРЕЩЕНЫ.
(Разрешены только при редактировании существующего .pptx — см. S2.5)
⛔ ДЛЯ РЕДАКТИРОВАНИЯ slides.json используй repl_execute (Path.write_text), НЕ edit_file.
edit_file — это search/replace, ломается на кириллице при поиске точного JSON-блока. Path.write_text пишет файл целиком в UTF-8.
Алгоритм редактирования (всё в одном repl_execute):
from pathlib import Path
import json
deck = json.loads(Path("/tmp/work/slides.json").read_text(encoding="utf-8"))
# внеси правки в deck
Path("/tmp/work/slides.json").write_text(
json.dumps(deck, ensure_ascii=False, indent=2),
encoding="utf-8",
)
Затем: sandbox_bash("python3 /mnt/skills/pptx_operations_ru/scripts/renderer.py ...")
⛔ НИКОГДА не пиши python-pptx скрипт вместо JSON→renderer.py. Если тип графика не поддерживается — замени на ближайший поддерживаемый: Поддерживаемые типы графиков: bar, line, pie, area, bar_horizontal, bar_stacked, area_stacked. area → используй "area"; scatter/bubble/radar → замени на "line"; histogram → замени на "bar". ЗАПРЕЩЕНО: писать create_presentation.py или любой python-pptx скрипт напрямую. ВСЕГДА: slides.json → renderer.py.
S1. Каноничные импорты и API
S1. API-справочник — ТОЛЬКО для редактирования существующих файлов
(при создании с нуля — используй JSON → renderer.py из S2)
⛔ ЭТОТ РАЗДЕЛ — ТОЛЬКО ДЛЯ РЕДАКТИРОВАНИЯ /input/ файлов.
При создании с нуля — ПРОПУСТИ этот раздел, иди в S2
Базовые импорты (копируй как есть)
from pptx import Presentation
from pptx.util import Inches, Pt, Emu
from pptx.dml.color import RGBColor
from pptx.enum.shapes import MSO_SHAPE # ← фигуры (RECTANGLE, OVAL, ...)
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
Инициализация
prs = Presentation()
prs.slide_width = Inches(13.33)
prs.slide_height = Inches(7.5) # 16:9
slide = prs.slides.add_slide(prs.slide_layouts[6]) # пустой макет
Текст (textbox → text_frame → paragraph → run → font)
txBox = slide.shapes.add_textbox(Inches(1), Inches(1), Inches(4), Inches(1))
tf = txBox.text_frame
tf.word_wrap = True
p = tf.paragraphs[0]
p.text = "Заголовок"
p.font.name = "Calibri" # выбери пару: Arial/Arial Black, Calibri/Calibri Bold, Segoe UI/Trebuchet MS
p.font.size = Pt(28)
p.font.bold = True
p.font.color.rgb = RGBColor(0x1B, 0x36, 0x5D)
p.alignment = PP_ALIGN.LEFT
# Добавить абзац:
p2 = tf.add_paragraph()
p2.text = "Подзаголовок"
p2.font.name = "Calibri" # тот же шрифт, что у заголовка
p2.font.size = Pt(16)
Фигуры (MSO_SHAPE)
shape = slide.shapes.add_shape(
MSO_SHAPE.RECTANGLE, Inches(0.5), Inches(0.5), Inches(3), Inches(2)
)
shape.fill.solid()
shape.fill.fore_color.rgb = RGBColor(0x1B, 0x36, 0x5D)
shape.line.fill.background() # без контура
# Круг:
circle = slide.shapes.add_shape(
MSO_SHAPE.OVAL, Inches(1), Inches(1), Inches(1), Inches(1)
)
# Скруглённый прямоугольник:
card = slide.shapes.add_shape(
MSO_SHAPE.ROUNDED_RECTANGLE, Inches(1), Inches(1), Inches(3), Inches(2)
)
Линии и коннекторы
from pptx.enum.shapes import MSO_CONNECTOR_TYPE
connector = slide.shapes.add_connector(
MSO_CONNECTOR_TYPE.STRAIGHT,
Inches(1), Inches(3), # начало (x, y)
Inches(9), Inches(3), # конец (x, y)
)
connector.line.color.rgb = RGBColor(0xE8, 0xB9, 0x31)
connector.line.width = Pt(2)
Таблицы
rows, cols = 1 + len(data), len(headers) # ВСЕГДА из данных
table_shape = slide.shapes.add_table(rows, cols, Inches(0.5), Inches(1.5), Inches(9), Inches(3))
table = table_shape.table
# Заголовки:
for j, h in enumerate(headers):
cell = table.cell(0, j)
cell.text = h
p = cell.text_frame.paragraphs[0]
p.font.bold = True
p.font.size = Pt(12)
p.font.color.rgb = RGBColor(0xFF, 0xFF, 0xFF)
cell.fill.solid()
cell.fill.fore_color.rgb = RGBColor(0x1B, 0x36, 0x5D) # Primary
# Данные:
for i, row_data in enumerate(data):
for j, val in enumerate(row_data):
cell = table.cell(i + 1, j)
cell.text = str(val)
cell.text_frame.paragraphs[0].font.size = Pt(11)
# Чередование строк:
if i % 2 == 1:
cell.fill.solid()
cell.fill.fore_color.rgb = RGBColor(0xF5, 0xF7, 0xFA)
Диаграммы (встроенные python-pptx)
Позиционирование: диаграмма должна занимать ~70% площади слайда, по центру:
# Стандартная позиция диаграммы (для слайда 13.33" x 7.5")
CHART_LEFT = Inches(1.5)
CHART_TOP = Inches(1.5)
CHART_WIDTH = Inches(10.3) # ~77% ширины слайда
CHART_HEIGHT = Inches(5.2) # ~69% высоты слайда
Столбчатая диаграмма:
from pptx.chart.data import CategoryChartData
from pptx.enum.chart import XL_CHART_TYPE
chart_data = CategoryChartData()
chart_data.categories = ["Q1", "Q2", "Q3", "Q4"]
chart_data.add_series("Выручка", (100, 150, 180, 220))
chart_frame = slide.shapes.add_chart(
XL_CHART_TYPE.COLUMN_CLUSTERED,
CHART_LEFT, CHART_TOP, CHART_WIDTH, CHART_HEIGHT,
chart_data,
)
chart = chart_frame.chart
chart.has_legend = True
chart.has_title = False # заголовок уже в textbox выше
# ОБЯЗАТЕЛЬНО: покрась ВСЕ серии цветами из палитры
palette = [PRIMARY, SECONDARY, ACCENT] # из выбранной палитры темы
for idx, series in enumerate(chart.plots[0].series):
series.format.fill.solid()
series.format.fill.fore_color.rgb = palette[idx % len(palette)]
# Data labels
plot = chart.plots[0]
plot.has_data_labels = True
data_labels = plot.data_labels
data_labels.font.size = Pt(10)
data_labels.font.name = "Calibri"
data_labels.show_value = True # ← snake_case!
Pie chart (ТОЛЬКО show_percentage, НЕ show_value)
chart_data = CategoryChartData()
chart_data.categories = ["Разработка", "Маркетинг", "Операции"]
chart_data.add_series("Доли", (40, 35, 25))
cf = slide.shapes.add_chart(
XL_CHART_TYPE.PIE,
CHART_LEFT, CHART_TOP, CHART_WIDTH, CHART_HEIGHT,
chart_data,
)
chart = cf.chart
plot = chart.plots[0]
# ОБЯЗАТЕЛЬНО: покрась каждый сегмент цветами из палитры
palette = [PRIMARY, SECONDARY, ACCENT, RGBColor(0x63, 0x6E, 0x72)]
for idx, point in enumerate(plot.series[0].points):
point.format.fill.solid()
point.format.fill.fore_color.rgb = palette[idx % len(palette)]
plot.has_data_labels = True
data_labels = plot.data_labels
data_labels.show_percentage = True # ← snake_case!
data_labels.show_value = False # НИКОГДА не включай оба
data_labels.font.size = Pt(11)
data_labels.font.name = "Calibri"
Правила диаграмм
- Позиция: используй CHART_LEFT/TOP/WIDTH/HEIGHT константы, не хардкодь
- Цвета: ВСЕГДА покрась ВСЕ серии/сегменты цветами из палитры темы
- Шрифт data labels: Calibri, Pt(10-11)
- Заголовок: НЕ используй chart.has_title — ставь textbox над диаграммой
- Легенда: chart.has_legend = True для >1 серии, False для 1 серии
Фон слайда
bg = slide.background
bg.fill.solid()
bg.fill.fore_color.rgb = RGBColor(0x1B, 0x36, 0x5D)
Сохранение
output_path = cw.output_path("presentation.pptx")
prs.save(output_path)
print(output_path)
S2. Рабочий процесс
Фаза 1. Анализ
Определи: тип (КП | питч-дек | отчёт), кол-во слайдов (КП 10-14, питч 14-18, отчёт 10-14), ключевые данные (числа, названия, метрики).
Фаза 2. План
ПЕРЕД кодом составь план: для КАЖДОГО слайда — номер, заголовок, макет, элементы. НЕ делай 5+ одинаковых контентных слайдов подряд — чередуй макеты.
⛔ ПРАВИЛО СТРУКТУРЫ (критично):
- Между любыми двумя section_divider — МИНИМУМ 2 контент-слайда. Нарушение = брак.
- ГЕНЕРИРУЙ ВСЕ СЛАЙДЫ из плана. Не пропускай, не объединяй, не сокращай.
- Если пользователь назвал конкретные слайды — сделай каждый из них.
⛔ ПЛОТНОСТЬ КОНТЕНТА (обязательно):
- bullets: минимум 6 пунктов, каждый 8–15 слов (конкретика, цифры, факты, последствия). 4 коротких буллета = пустой слайд = брак
- stat_highlight: 3–4 карточки, значения — реальные числа/проценты/суммы
- cards_grid: минимум 3 карточки, body — 2–3 предложения с деталями
- two_column: минимум 4 пункта в каждой колонке
- table: минимум 3 строки данных
- Пустые или однострочные слайды ЗАПРЕЩЕНЫ.
Каждый слайд должен содержать минимум 1 визуальный элемент (фигура, диаграмма, таблица, цветной блок, иконка-символ). Слайд
из одного текста — запрещён.
Обязательная структура презентации: - Opening (1 слайд) — титул, тёмный фон, название + подзаголовок
- Content (основная часть) — чередуй макеты (stat_highlight, icon_grid, chart_focus, two_column, cards_grid)
- Section divider — вставляй между логическими блоками каждые 2–4 контентных слайда
- Ending (1 слайд) — "Спасибо", контакты или CTA; тёмный фон, как Opening
Канонический порядок:
Opening → Content × 2–3 → Section Divider → Content × 2–3 → ... → Ending
ЗАПРЕЩЕНО: два Section Divider подряд без минимум 2 контент-слайдов между ними — это главный баг, который делает презентацию пустой.
Opening и Ending — строго по одному.
Ending — ВСЕГДА последний слайд презентации. Ничего после него нет.
НЕ делай 5+ одинаковых контентных слайдов подряд — чередуй макеты.
Фаза 3. Код
Один плоский скрипт. Без try/except. Без промежуточных save(). Все слайды последовательно.
ОБЯЗАТЕЛЬНЫЙ ПОРЯДОК ИНСТРУМЕНТОВ:
repl_execute— записать slides.json черезPath("/tmp/work/slides.json").write_text(json.dumps(deck, ensure_ascii=False, indent=2), encoding="utf-8")(схема ниже)sandbox_bash("python3 /mnt/skills/pptx_operations_ru/scripts/renderer.py")— рендер в PPTX
❌ ЗАПРЕЩЕНО: писать python-pptx код вручную. Только JSON → renderer.
JSON-схема slides.json
{
"theme": "corporate",
"slides": [
{"type": "opening", "title": "...", "subtitle": "..."},
{"type": "section_divider", "title": "...", "number": "01"},
{"type": "content", "layout": "bullets", "title": "...",
"bullets": ["пункт 1", "пункт 2"]},
{"type": "content", "layout": "stat_highlight", "title": "...",
"stats": [{"value": "99%", "label": "SLA"}, {"value": "50+", "label": "проектов"}]},
{"type": "content", "layout": "two_column", "title": "...",
"left_bullets": ["..."], "right_bullets": ["..."]},
{"type": "content", "layout": "chart", "title": "...",
"chart": {"type": "bar", "categories": ["Q1","Q2"], "series": [{"name": "Выручка", "values": [100, 120]}]},
"context": "Рост 20% г/г"},
{"type": "content", "layout": "table", "title": "...",
"table": {"headers": ["Пакет","Цена","Услуги"], "rows": [["Базовый","100K","..."]]}},
{"type": "content", "layout": "cards_grid", "title": "...",
"cards": [{"title": "Карточка 1", "body": "Описание"}]},
{"type": "content", "layout": "image", "title": "...",
"image_query": "nature mountain panorama"},
{"type": "ending", "title": "Спасибо", "subtitle": "...",
"contacts": ["+7 999 123-45-67", "info@company.ru"]}
]
}
Темы: corporate | fintech | ecology | premium | energy | dark | ocean | medical | sunset | government | startup | education | real_estate | automotive | food | fashion | sport | travel | gaming | legal | crypto | minimal | neon | retro | nature | science | construction | wedding | kids | hr
Layouts: bullets | stat_highlight | two_column | chart | table | cards_grid | image | quote | timeline | comparison | process | pricing | swot | funnel | big_number | checklist | matrix | pros_cons | org_chart | faq | mockup | agenda | icon_grid | testimonials | split_image | kpi_dashboard | pyramid
💡 layout "image": используй "image_query" вместо "image_path" — картинка найдётся автоматически.
Пример: {"type": "content", "layout": "image", "title": "Наша команда", "image_query": "diverse team office collaboration"}
⛔ ВЫБОР LAYOUT — обязательные правила:
| Содержание слайда | Правильный layout |
|---|---|
| Роадмап, хронология, этапы по датам/кварталам | timeline |
| Пошаговый процесс (шаг 1 → шаг 2 → шаг 3) | process |
| Сравнение двух вариантов (A vs B, до/после) | comparison |
| KPI, метрики, цифры (MRR, MAU, конверсия) | stat_highlight |
| Большая цитата, мнение клиента | quote |
| Маркированный список без чёткой структуры | bullets |
| Преимущества слева, детали справа | two_column |
| Фичи продукта, команда, услуги (3-6 блоков) | cards_grid |
| Данные с несколькими столбцами | table |
| Фото, иллюстрация к слайду | image (используй image_query для автопоиска) |
| Рост, динамика, доли рынка | chart |
❌ ЗАПРЕЩЕНО использовать `bullets` для роадмапа, процессов или сравнений — это делает слайд визуально пустым.
⛔ ТРИГГЕРЫ — если в заголовке или содержании слайда есть эти слова, layout ОБЯЗАТЕЛЕН:
- "как это работает", "как работает", "процесс", "шаги", "этапы", "алгоритм", пункты вида "1. ... 2. ... 3." → **process**
- "роадмап", "дорожная карта", "Q1/Q2/Q3", "план на", "хронология", даты/кварталы → **timeline**
- "vs", "до/после", "сравнение", "А vs Б", "было/стало" → **comparison**
- цифры/метрики (MRR, MAU, %, $, рост) → **stat_highlight**
Использование `bullets` для любого из этих случаев = **ошибка генерации**.
---
## S2.5. Редактирование существующей презентации
Если пользователь просит изменить уже созданную презентацию:
### Как получить файл
- Создан в этой же сессии: `/home/user/output/presentation.pptx` — читай напрямую
- Загружен пользователем: `/input/presentation.pptx`
### Алгоритм редактирования
Для ЛЮБОГО изменения — обновляй `slides.json`, не пиши python-pptx вручную.
1. В `repl_execute`: `deck = json.loads(Path("/tmp/work/slides.json").read_text(encoding="utf-8"))` — прочитай текущую структуру
2. Внеси изменения в `deck` (добавь/удали слайд, поменяй текст, данные графика)
3. В том же `repl_execute`: `Path("/tmp/work/slides.json").write_text(json.dumps(deck, ensure_ascii=False, indent=2), encoding="utf-8")` — сохрани
4. `sandbox_bash("python3 /mnt/skills/pptx_operations_ru/scripts/renderer.py")` — перерендери
❌ ЗАПРЕЩЕНО: писать python-pptx скрипт вручную при редактировании.
❌ ЗАПРЕЩЕНО: пересоздавать презентацию с нуля — только точечные правки в JSON.
### Если slides.json не существует (файл из /input/ или предыдущей сессии)
Используй python-pptx напрямую. **Обязательно** определи тему из контекста разговораё и используй её GRAPH_COLORS:
```python
# Fintech тема:
GRAPH_COLORS = [
RGBColor(0x6C,0x5C,0xE7), RGBColor(0x00,0xCE,0xC9),
RGBColor(0xFD,0x79,0xA8), RGBColor(0xA2,0x9B,0xFE),
]
PRIMARY = RGBColor(0x6C, 0x5C, 0xE7)
PRIMARY_TEXT = RGBColor(0xFF, 0xFF, 0xFF)
LIGHT_BG = RGBColor(0xF8, 0xF9, 0xFA)
Не придумывай цвета — только из палитры темы (S3).
При чтении slides.json — сохраняй схему как есть:
type/layout/field names не переименовывай.
Допустимые type: opening, section_divider, content, ending.
Допустимые layout: bullets, stat_highlight, two_column, chart, table, cards_grid, image, quote, timeline, comparison, process, pricing, swot, funnel, big_number, checklist, matrix, pros_cons, org_chart, faq, mockup, agenda, icon_grid, testimonials, split_image, kpi_dashboard, pyramid.
4. `sandbox_bash("python3 /mnt/skills/pptx_operations_ru/scripts/renderer.py")` — ОБЯЗАТЕЛЬНО после любого изменения slides.json
### Шаблоны
**Открыть и сохранить:**
```python
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.dml.color import RGBColor
INPUT_PATH = "/home/user/output/presentation.pptx" # или /input/presentation.pptx
OUTPUT_PATH = "/home/user/output/presentation.pptx"
prs = Presentation(INPUT_PATH)
# ... изменения ...
prs.save(OUTPUT_PATH)
print(OUTPUT_PATH)
Изменить заголовок слайда (0-based индекс):
slide = prs.slides[0] # первый слайд
for shape in slide.shapes:
if shape.has_text_frame and shape.text_frame.paragraphs[0].text:
p = shape.text_frame.paragraphs[0]
p.text = "Новый заголовок"
p.font.name = "Calibri"
p.font.size = Pt(32)
p.font.bold = True
break
Добавить слайд в конец:
new_slide = prs.slides.add_slide(prs.slide_layouts[6]) # пустой макет
tb = new_slide.shapes.add_textbox(Inches(0.5), Inches(0.3), Inches(9), Inches(0.8))
p = tb.text_frame.paragraphs[0]
p.text = "Новый слайд"
p.font.name = "Calibri"
p.font.size = Pt(32)
p.font.bold = True
Обновить данные диаграммы:
from pptx.chart.data import CategoryChartData
for shape in prs.slides[2].shapes: # слайд с диаграммой
if shape.has_chart:
new_data = CategoryChartData()
new_data.categories = ["Q1", "Q2", "Q3", "Q4"]
new_data.add_series("Выручка", (150, 200, 180, 250))
shape.chart.replace_data(new_data)
break
Найти и заменить текст по всей презентации:
for slide in prs.slides:
for shape in slide.shapes:
if shape.has_text_frame:
for para in shape.text_frame.paragraphs:
if "старый текст" in para.text:
para.text = "новый текст"
para.font.name = "Calibri" # переустанови после замены
Важно
- Индексы слайдов — 0-based (
prs.slides[0]= первый слайд) - При
para.text = "..."шрифт сбрасывается — всегда переустанавливайfont.nameиfont.size - Всегда сохраняй в
/home/user/output/presentation.pptx
S3. Дизайн-справочник
Палитры
Каждая палитра содержит: Primary, Secondary, Accent, Light BG, + 6 цветов для диаграмм (graph_0..graph_5). ОБЯЗАТЕЛЬНО: при создании диаграмм используй graph_colors для серий/сегментов, НЕ Primary/Secondary.
| Тема | Primary | Secondary | Accent | Light BG | card | stroke | graph_0 | graph_1 | graph_2 | graph_3 | graph_4 | graph_5 | Применение |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Корпоратив | 1B365D | 2E86AB | E8B931 | F5F7FA | E8EDF5 | 2E86AB | 2E86AB | E8B931 | 1B365D | 5BA4CF | F4D03F | 8FBDD3 | Финансы, B2B |
| Финтех | 6C5CE7 | 00CEC9 | FD79A8 | F8F9FA | F0EEFF | 6C5CE7 | 6C5CE7 | 00CEC9 | FD79A8 | A29BFE | 55EFC4 | FDCB6E | IT, SaaS |
| Экология | 00B894 | 55E6C1 | FDCB6E | F0FFF4 | E8FFF8 | 00B894 | 00B894 | 55E6C1 | FDCB6E | 00D2D3 | 81ECEC | F9CA24 | ESG, здоровье |
| Премиум | 2D3436 | 636E72 | D4A574 | FAFAFA | F5F5F5 | 636E72 | D4A574 | 636E72 | 2D3436 | B8A089 | 95A5A6 | DFE6E9 | Люкс, инвестиции |
| Энергия | E17055 | FDCB6E | 6C5CE7 | FFF9F0 | FFF3EE | E17055 | E17055 | FDCB6E | 6C5CE7 | F8A5C2 | F9E852 | A29BFE | Маркетинг, медиа |
Использование в коде:
# Определи graph_colors в начале скрипта из выбранной палитры
GRAPH_COLORS = [
RGBColor(0x2E, 0x86, 0xAB), # graph_0
RGBColor(0xE8, 0xB9, 0x31), # graph_1
RGBColor(0x1B, 0x36, 0x5D), # graph_2
RGBColor(0x5B, 0xA4, 0xCF), # graph_3
RGBColor(0xF4, 0xD0, 0x3F), # graph_4
RGBColor(0x8F, 0xBD, 0xD3), # graph_5
]
# Покраска серий столбчатой диаграммы:
for idx, series in enumerate(chart.plots[0].series):
series.format.fill.solid()
series.format.fill.fore_color.rgb = GRAPH_COLORS[idx % len(GRAPH_COLORS)]
# Покраска сегментов pie chart:
for idx, point in enumerate(chart.plots[0].series[0].points):
point.format.fill.solid()
point.format.fill.fore_color.rgb = GRAPH_COLORS[idx % len(GRAPH_COLORS)]
Дополнительные цвета — семантические токены
Определяй в начале каждого скрипта (пример для темы Корпоратив):
CARD = RGBColor(0xE8, 0xED, 0xF5) # фон карточек и блоков
STROKE = RGBColor(0x2E, 0x86, 0xAB) # обводки фигур, линии-разделители
BG_TEXT = RGBColor(0x1B, 0x36, 0x5D) # текст на светлом фоне (CARD, Light BG)
PRIMARY_TEXT = RGBColor(0xFF, 0xFF, 0xFF) # текст на тёмном фоне (Primary, Section Divider)
SUBTEXT = RGBColor(0x63, 0x6E, 0x72) # второстепенный текст, подписи
# Карточка с обводкой:
card_box = slide.shapes.add_shape(
MSO_SHAPE.ROUNDED_RECTANGLE, Inches(0.5), Inches(1.5), Inches(3.5), Inches(1.8)
)
card_box.fill.solid()
card_box.fill.fore_color.rgb = CARD
card_box.line.color.rgb = STROKE
card_box.line.width = Pt(1)
tf = card_box.text_frame
tf.paragraphs[0].text = "Заголовок карточки"
tf.paragraphs[0].font.color.rgb = BG_TEXT
tf.paragraphs[0].font.bold = True
tf.paragraphs[0].font.size = Pt(14)
# Линия-разделитель:
line = slide.shapes.add_connector(
MSO_CONNECTOR_TYPE.STRAIGHT, Inches(0.5), Inches(1.3), Inches(12.83), Inches(1.3)
)
line.line.color.rgb = STROKE
line.line.width = Pt(1.5)
### Шрифты
| Заголовок | Основной | Стиль |
|-----------|----------|-------|
| Arial Black | Arial | Универсальный |
| Calibri Bold | Calibri | Корпоративный |
| Trebuchet MS | Segoe UI | Стартап |
### Макеты (чередуй — макс. 2 подряд одинаковых)
- **title_hero** — тёмный фон, заголовок Pt(40-48) по центру, подзаголовок Pt(18-22)
- **icon_grid** — 4-6 кругов с подписями, 2 ряда по 2-3
- **stat_highlight** — 3-4 карточки с большими числами Pt(44-52), подпись Pt(12-14)
- **two_column** — левая 55% текст + буллеты, правая 45% визуал
- **chart_focus** — график 65-70% площади, строка контекста снизу
- **table_full** — заголовок Primary фон + белый текст, чередование строк
- **cards_grid** — 2x2 или 2x3 карточки с тенью, иконка-круг + текст
- **section_divider** — тёмный фон, крупный номер, заголовок Pt(36)
### Правила генерации
- Размер: Inches(13.33) × Inches(7.5) — 16:9
- Макет: ВСЕГДА `prs.slide_layouts[6]` (пустой)
- Заголовки: Pt(28-36), bold, Primary
- Основной текст: Pt(14-18), тёмно-серый
- Большие числа: Pt(44-60), Primary или Accent
- Отступы: мин. 0.5" от краёв
- Промежуточные PNG: в /tmp/, НЕ в /home/user/output/
- Финальный файл: /home/user/output/presentation.pptx
- Каждый слайд — мин. 1 визуальный элемент
---
## S4. Антипаттерны
| Запрещено | Правильно |
|-----------|-----------|
| Один макет 3+ раз подряд | Чередуй макеты |
| >40% пустого пространства | Заполни визуалами |
| >6 bullet points на слайде | Разбей на 2 слайда |
| Слайд только из текста (0 фигур) | Добавь визуальный элемент: фигуру, иконку или график |
| Слайд без визуалов | Мин. 1 фигура/график/таблица |
| Центрирование тела текста | Левое выравнивание |
| Плавающий текст без фона | В карточку |
| Дефолтные цвета диаграмм | Цвета из палитры |
| showValue + showPercent на pie | Только show_percentage |
| Хардкод rows/cols | rows = 1 + len(data), cols = len(headers) |
| Позиции без расчёта | Считай от размеров слайда |
| `font.name` не задан | Всегда задавай явно: `p.font.name = "Calibri"` — иначе шрифт темы по умолчанию |
| `tf.text_frame.paragraphs` | `tf` уже TextFrame — пиши `tf.paragraphs[0]`, не `tf.text_frame.paragraphs[0]` |
| try/except на каждый слайд | Один скрипт без обёрток |
| Промежуточные save() | Одно сохранение в конце |
| `MSO_SHAPE.LINE` | Не существует — см. S5 ниже |
| `add_shape(...MSO_SHAPE.LINE...)` | Используй `add_connector()` — см. S5 |
---
## S5. Автофигуры — ПОЛНЫЙ ЗАПРЕТ MSO_AUTO_SHAPE_TYPE
`MSO_AUTO_SHAPE_TYPE` — **НИКОГДА НЕ ИСПОЛЬЗУЙ** (неверное имя модуля, не существует).
Правильный импорт: `from pptx.enum.shapes import MSO_SHAPE`.
`MSO_SHAPE.LINE`, `MSO_SHAPE.CHECKMARK`, `MSO_SHAPE.ARROW`, `MSO_SHAPE.STAR` — не существуют, AttributeError.
Безопасные атрибуты `MSO_SHAPE`: RECTANGLE, OVAL, ROUNDED_RECTANGLE, RIGHT_ARROW, LEFT_ARROW, CHEVRON, PENTAGON.
Всё остальное — заменяй через add_connector() или textbox с fill.
### Замены:
**Иконки и маркеры** — символы Unicode в тексте (без фигур):
```python
# ✓ галочка, → стрелка, ● точка, ★ звезда, — тире
para.text = "✓ Цель выполнена"
para.text = "→ Следующий шаг"
Линия-разделитель — add_connector():
from pptx.enum.shapes import MSO_CONNECTOR_TYPE
line = slide.shapes.add_connector(
MSO_CONNECTOR_TYPE.STRAIGHT,
Inches(0.5), Inches(1.4),
Inches(12.83), Inches(1.4),
)
line.line.color.rgb = RGBColor(0x1F, 0x49, 0x7D)
line.line.width = Pt(1.5)
Цветной блок / карточка — textbox с заливкой:
box = slide.shapes.add_textbox(Inches(0.5), Inches(2), Inches(3), Inches(1.5))
box.fill.solid()
box.fill.fore_color.rgb = RGBColor(0x1F, 0x49, 0x7D)
tf = box.text_frame
tf.text = "Заголовок блока"
tf.paragraphs[0].font.color.rgb = RGBColor(0xFF, 0xFF, 0xFF)
tf.paragraphs[0].font.bold = True
Прямоугольник без текста — если нужна просто фигура:
from pptx.enum.shapes import MSO_SHAPE_TYPE # НЕ MSO_AUTO_SHAPE_TYPE
# Безопасно через XML или через textbox с fill и без текста
rect = slide.shapes.add_textbox(left, top, width, height)
rect.fill.solid()
rect.fill.fore_color.rgb = RGBColor(0xE8, 0xF0, 0xFE)
rect.text_frame.text = ""
Попробуйте этот навык
Зарегистрируйтесь и используйте навык «Операции с PowerPoint/PPTX презентациями» бесплатно.