Files
fmd26/build_pptx.py
T

485 lines
21 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.enum.shapes import MSO_SHAPE, MSO_CONNECTOR
OUT = 'Метод_Дегтярева_презентация.pptx'
BG = '121417'
CARD = '1C2024'
CARD2 = '23282D'
GREEN = '3FA37A'
GREEN_LIGHT = '7FD1AE'
ROSE = 'D94A6A'
WHITE = 'F5F5F5'
GRAY = '9AA0A6'
DARK_GRAY = '6B7075'
GOLD = 'C9A227'
def rgb(hexstr):
return RGBColor.from_string(hexstr)
def add_bg(slide, color=BG):
bg = slide.background
fill = bg.fill
fill.solid()
fill.fore_color.rgb = rgb(color)
def add_text(slide, x, y, w, h, text, font='Arial', size=12, color=WHITE,
bold=False, italic=False, align=PP_ALIGN.LEFT,
anchor=MSO_ANCHOR.TOP, line_spacing=1.0, space_after=0,
margins=(0, 0, 0, 0)):
tb = slide.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h))
tf = tb.text_frame
tf.word_wrap = True
tf.margin_left = Inches(margins[0])
tf.margin_right = Inches(margins[1])
tf.margin_top = Inches(margins[2])
tf.margin_bottom = Inches(margins[3])
tf.vertical_anchor = anchor
lines = text.split('\n')
for i, line in enumerate(lines):
p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
p.alignment = align
p.line_spacing = line_spacing
p.space_after = Pt(space_after)
r = p.add_run()
r.text = line
r.font.name = font
r.font.size = Pt(size)
r.font.bold = bold
r.font.italic = italic
r.font.color.rgb = rgb(color)
return tb
def add_rect(slide, x, y, w, h, fill=None, line=None, line_w=1.0,
shape=MSO_SHAPE.ROUNDED_RECTANGLE, radius=0.06):
shp = slide.shapes.add_shape(shape, Inches(x), Inches(y), Inches(w), Inches(h))
if fill:
shp.fill.solid()
shp.fill.fore_color.rgb = rgb(fill)
else:
shp.fill.background()
if line:
shp.line.color.rgb = rgb(line)
shp.line.width = Pt(line_w)
else:
shp.line.fill.background()
try:
shp.shadow.inherit = False
except Exception:
pass
if shape == MSO_SHAPE.ROUNDED_RECTANGLE:
try:
shp.adjustments[0] = radius
except Exception:
pass
return shp
def add_line(slide, x1, y1, x2, y2, color=DARK_GRAY, width=1.0):
conn = slide.shapes.add_connector(MSO_CONNECTOR.STRAIGHT,
Inches(x1), Inches(y1), Inches(x2), Inches(y2))
conn.line.color.rgb = rgb(color)
conn.line.width = Pt(width)
return conn
def add_notes(slide, text):
slide.notes_slide.notes_text_frame.text = text
def slide_title(prs):
s = prs.slides.add_slide(prs.slide_layouts[6])
add_bg(s)
add_rect(s, 0.75, 1.15, 0.055, 4.95, fill=GREEN, shape=MSO_SHAPE.RECTANGLE)
add_text(s, 1.05, 0.72, 8.2, 0.3,
'ФОРУМ МОЛОДЁЖИ ДОНБАССА • СМЕНА 1 «ПСБ ПАТРИОТ»',
size=10, color=GRAY, bold=True)
add_text(s, 1.0, 1.32, 8.3, 2.35, 'МЕТОД\nДЕГТЯРЕВА',
font='Georgia', size=54, color=WHITE, bold=True, line_spacing=0.92)
add_text(s, 1.05, 3.78, 8.2, 0.55, 'Цифра, у которой есть хозяин',
font='Georgia', size=24, color=GREEN, italic=True)
add_text(s, 1.05, 4.48, 8.2, 0.4, 'От забоя до миллиона роз',
size=14, color=GRAY)
add_text(s, 1.05, 5.05, 8.2, 0.35, 'Бойко К.С. • эксперт и наставник трека',
size=11, color=GREEN_LIGHT, bold=True)
add_line(s, 1.05, 6.18, 8.4, 6.18, color='2A2F34', width=1.0)
add_text(s, 1.05, 6.38, 8.2, 0.35, '3 дня • 5 шагов • 1 защита',
size=11, color=DARK_GRAY)
# Big 5 circle
add_rect(s, 9.55, 1.55, 3.1, 3.1, fill=None, line=GREEN, line_w=1.5,
shape=MSO_SHAPE.OVAL)
add_text(s, 9.55, 1.72, 3.1, 1.9, '5', font='Georgia', size=96,
color=GREEN, bold=True, align=PP_ALIGN.CENTER,
anchor=MSO_ANCHOR.MIDDLE)
add_text(s, 9.55, 3.62, 3.1, 0.35, 'ШАГОВ', size=12, color=GRAY,
bold=True, align=PP_ALIGN.CENTER)
add_rect(s, 12.28, 4.28, 0.34, 0.34, fill=ROSE, shape=MSO_SHAPE.OVAL)
add_notes(s, 'Хук: В 1970-х ЮНЕСКО назвала Донецк самым зелёным промышленным городом планеты. '
'Город, который мы привыкли называть всесоюзной кочегаркой. Это сделал человек, '
'у которого не было ни интернета, ни смартфона, ни искусственного интеллекта. '
'Его звали Владимир Дегтярев. И у него был метод. За три дня мы применим этот метод к цифре.')
return s
def slide_paradox(prs):
s = prs.slides.add_slide(prs.slide_layouts[6])
add_bg(s)
add_text(s, 0.75, 0.55, 11.8, 0.7, 'Парадокс, который стоит изучать',
font='Georgia', size=30, color=WHITE, bold=True)
add_text(s, 0.75, 1.22, 11.8, 0.4,
'Донецк: из «всесоюзной кочегарки» — в самый зелёный промышленный город планеты',
size=13, color=GRAY)
# Left card
add_rect(s, 0.75, 2.0, 4.55, 2.15, fill=CARD)
add_text(s, 1.0, 2.2, 4.05, 0.3, 'ДО 1963', size=10, color=DARK_GRAY, bold=True)
add_text(s, 1.0, 2.55, 4.05, 0.95, 'ВСЕСОЮЗНАЯ\nКОЧЕГАРКА',
font='Georgia', size=22, color=GRAY, bold=True, line_spacing=0.95)
add_text(s, 1.0, 3.55, 4.05, 0.4, 'уголь, бараки, дефицит воды', size=11, color=DARK_GRAY)
# Arrow
add_rect(s, 5.55, 2.82, 0.65, 0.5, fill=GREEN, shape=MSO_SHAPE.RIGHT_ARROW)
# Right card
add_rect(s, 6.45, 2.0, 6.1, 2.15, fill=CARD)
add_text(s, 6.7, 2.2, 5.6, 0.3, '1970-Е', size=10, color=GREEN, bold=True)
add_text(s, 6.7, 2.55, 5.6, 1.0, 'САМЫЙ ЗЕЛЁНЫЙ\nПРОМЫШЛЕННЫЙ ГОРОД\nПЛАНЕТЫ',
font='Georgia', size=19, color=GREEN, bold=True, line_spacing=0.95)
add_text(s, 6.7, 3.62, 5.6, 0.35, 'ЮНЕСКО. Миллион роз. Ботанический сад.',
size=11, color=GRAY)
# Stats
stats = [
('3,1×', 'рост жилищного фонда', GREEN),
('5', 'академических институтов', GREEN),
('1 000 000', 'кустов роз', ROSE),
]
x = 0.75
for num, label, color in stats:
add_rect(s, x, 4.65, 3.8, 1.45, fill=CARD2)
add_text(s, x + 0.25, 4.82, 3.3, 0.65, num, font='Georgia', size=32,
color=color, bold=True)
add_text(s, x + 0.25, 5.5, 3.3, 0.35, label, size=10.5, color=GRAY)
x += 4.0
add_text(s, 0.75, 6.55, 11.8, 0.3,
'По материалам Донецкого агентства новостей, 20.02.2026',
size=9, color=DARK_GRAY)
add_notes(s, 'Парадокс: город угля и бараков стал самым зелёным промышленным городом планеты. '
'Жильё выросло в 3,1 раза, появился научный центр из пяти институтов, миллион роз. '
'Вопрос: как? Ответ — метод.')
return s
def slide_man(prs):
s = prs.slides.add_slide(prs.slide_layouts[6])
add_bg(s)
add_text(s, 0.75, 0.55, 11.8, 0.7, 'Человек, который не был айтишником',
font='Georgia', size=30, color=WHITE, bold=True)
add_text(s, 0.75, 1.22, 11.8, 0.4,
'Владимир Иванович Дегтярев (1920–1993) — «хозяин Донбасса»',
size=13, color=GRAY)
facts = [
('1920', 'Родился под Ставрополем в семье железнодорожного рабочего'),
('1948', 'Назначен на шахту «Центральная» в Чистяково (Торез)'),
('1953', 'Возглавил трест «Чистяковантрацит». Ему неполных 33 года'),
('1963–1976', 'Первый секретарь Донецкого обкома. 13 лет преобразований'),
]
x = 0.75
for year, text in facts:
add_rect(s, x, 2.0, 2.85, 2.55, fill=CARD)
add_text(s, x + 0.22, 2.2, 2.4, 0.55, year, font='Georgia', size=24,
color=GREEN, bold=True)
add_text(s, x + 0.22, 2.85, 2.4, 1.5, text, size=10.5, color=WHITE,
line_spacing=1.1)
x += 3.05
# Quote
add_rect(s, 0.75, 4.95, 11.8, 1.25, fill=CARD2)
add_rect(s, 0.75, 4.95, 0.06, 1.25, fill=ROSE, shape=MSO_SHAPE.RECTANGLE)
add_text(s, 1.05, 5.12, 11.2, 0.6,
'«Опыта в работе мало, да и годы невелики: было всего неполных 33 года».',
font='Georgia', size=16, color=WHITE, italic=True)
add_text(s, 1.05, 5.78, 11.2, 0.3, '— В. И. Дегтярев', size=10, color=GRAY)
add_text(s, 0.75, 6.5, 11.8, 0.35,
'Его называли «хозяином» не за должность, а за метод.',
size=12, color=GREEN_LIGHT, bold=True)
add_notes(s, 'Биография: горный инженер, начальник участка шахты, в 32 года — управляющий трестом, '
'в 43 — первый секретарь обкома. За 13 лет: жильё, наука, культура, зелёный город. '
'Он не был айтишником. Он был хозяином.')
return s
def slide_method(prs):
s = prs.slides.add_slide(prs.slide_layouts[6])
add_bg(s)
add_text(s, 0.75, 0.55, 11.8, 0.7, 'Пять шагов хозяина',
font='Georgia', size=30, color=WHITE, bold=True)
add_text(s, 0.75, 1.22, 11.8, 0.4,
'Каждый шаг — вопрос и артефакт на канвасе',
size=13, color=GRAY)
steps = [
('01', 'ЗАБОЙ', 'Что болит\nна самом деле?', 'Карта проблемы', GREEN),
('02', 'СМЕТА', 'Кому и насколько\nстанет лучше?', 'Портрет + метрики', GREEN),
('03', 'ЗАВОД', 'Что нужно, чтобы\nрешалось системно?', 'Архитектура + ресурсы', GREEN),
('04', 'РОЗА', 'Как люди это\nпочувствуют?', 'Ценность + MVP', ROSE),
('05', 'ХОЗЯИН', 'Кто останется\nпосле нас?', 'Устойчивость + план', ROSE),
]
x = 0.75
for num, name, question, artifact, color in steps:
add_rect(s, x, 2.0, 2.3, 3.85, fill=CARD)
add_text(s, x + 0.2, 2.18, 1.9, 0.3, num, size=12, color=color, bold=True)
add_text(s, x + 0.2, 2.55, 1.9, 0.45, name, font='Georgia', size=19,
color=WHITE, bold=True)
add_text(s, x + 0.2, 3.15, 1.9, 0.95, question, size=11, color=GRAY,
line_spacing=1.1)
add_line(s, x + 0.2, 4.35, x + 2.1, 4.35, color='2E3439', width=1.0)
add_text(s, x + 0.2, 4.5, 1.9, 0.7, artifact, size=9.5, color=color,
bold=True, line_spacing=1.05)
x += 2.45
add_text(s, 0.75, 6.25, 11.8, 0.4,
'Метод работает и для проекта, и для карьеры, и для региона.',
size=12, color=GREEN_LIGHT, bold=True)
add_notes(s, 'Метод: Забой — спустись туда, где реально болит. Смета — посчитай в людях, а не в отчётах. '
'Завод — построй систему, а не разовую акцию. Роза — сделай результат видимым и желанным. '
'Хозяин — останься после запуска.')
return s
def slide_map(prs):
s = prs.slides.add_slide(prs.slide_layouts[6])
add_bg(s)
add_text(s, 0.75, 0.55, 11.8, 0.7, 'Три дня — пять шагов',
font='Georgia', size=30, color=WHITE, bold=True)
add_text(s, 0.75, 1.22, 11.8, 0.4,
'Лекции дают контекст. Лаборатории дают артефакты. Защита даёт обратную связь.',
size=13, color=GRAY)
add_line(s, 0.9, 3.05, 12.45, 3.05, color='2E3439', width=2.0)
nodes = [
('10.11 • 19:30', 'Установка', 'Зачем и как', 1.9),
('11.11 • 10:45', 'Забой + Смета', 'Проблема и пользователь', 4.9),
('11.11 • 18:00', 'Завод + Роза', 'Решение и ценность', 7.9),
('12.11 • 10:30', 'Хозяин', 'Устойчивость и метрики', 10.9),
]
for date, title, desc, cx in nodes:
add_rect(s, cx - 0.11, 2.94, 0.22, 0.22, fill=GREEN, shape=MSO_SHAPE.OVAL)
add_text(s, cx - 1.25, 2.35, 2.5, 0.3, date, size=10.5, color=GREEN,
bold=True, align=PP_ALIGN.CENTER)
add_text(s, cx - 1.4, 3.35, 2.8, 0.4, title, font='Georgia', size=15,
color=WHITE, bold=True, align=PP_ALIGN.CENTER)
add_text(s, cx - 1.4, 3.78, 2.8, 0.6, desc, size=10, color=GRAY,
align=PP_ALIGN.CENTER, line_spacing=1.05)
chips = [
'Цифровая трансформация',
'Ограничения как топливо',
'Технология человечная',
'Регион как лаборатория',
'ИИ в действии',
]
x = 0.75
for chip in chips:
add_rect(s, x, 5.0, 2.25, 0.55, fill=CARD, line='2E3439', line_w=0.75)
add_text(s, x + 0.1, 5.08, 2.05, 0.4, chip, size=9, color=GRAY,
align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
x += 2.4
add_text(s, 0.75, 6.15, 11.8, 0.4,
'14:45 — защита проектов. 3 минуты питч + 2 минуты вопросы.',
size=12, color=GREEN_LIGHT, bold=True)
add_notes(s, 'Карта трека: 10 ноября — установка. 11 ноября — проблема, пользователь, решение, ценность. '
'12 ноября — устойчивость, консультации, сборка и защита. Лекции работают на метод.')
return s
def slide_team(prs):
s = prs.slides.add_slide(prs.slide_layouts[6])
add_bg(s)
add_text(s, 0.75, 0.55, 11.8, 0.7, 'Команда = маленький обком',
font='Georgia', size=30, color=WHITE, bold=True)
add_text(s, 0.75, 1.22, 11.8, 0.4,
'5–7 человек. Разные специальности. Один канвас.',
size=13, color=GRAY)
roles = [
('ЗАБОЙЩИК', 'исследователь: поле, интервью, данные', GREEN),
('ИНЖЕНЕР', 'технарь: решение, архитектура, MVP', GREEN),
('ХОЗЯИН', 'экономист: ресурсы, метрики, устойчивость', ROSE),
('ГОЛОС', 'коммуникатор: питч, визуал, история', ROSE),
]
positions = [(0.75, 2.0), (3.9, 2.0), (0.75, 3.95), (3.9, 3.95)]
for (name, desc, color), (x, y) in zip(roles, positions):
add_rect(s, x, y, 2.95, 1.7, fill=CARD)
add_text(s, x + 0.22, y + 0.18, 2.5, 0.35, name, font='Georgia', size=15,
color=color, bold=True)
add_text(s, x + 0.22, y + 0.65, 2.5, 0.85, desc, size=10, color=GRAY,
line_spacing=1.1)
# Canvas preview
add_rect(s, 7.15, 2.0, 5.45, 4.25, fill=CARD2)
add_text(s, 7.4, 2.18, 4.9, 0.3, 'КАНВАС «МЕТОД ДЕГТЯРЕВА»', size=10,
color=GREEN, bold=True)
rows = [
('01', 'Забой', 'проблема и доказательства'),
('02', 'Смета', 'пользователь и метрики'),
('03', 'Завод', 'решение и ресурсы'),
('04', 'Роза', 'ценность и MVP'),
('05', 'Хозяин', 'устойчивость и план'),
]
y = 2.65
for num, name, desc in rows:
add_text(s, 7.4, y, 0.45, 0.3, num, size=9, color=GREEN, bold=True)
add_text(s, 7.85, y - 0.02, 1.3, 0.3, name, size=11, color=WHITE, bold=True)
add_text(s, 9.15, y, 3.2, 0.3, desc, size=9.5, color=GRAY)
add_line(s, 7.4, y + 0.42, 12.3, y + 0.42, color='2E3439', width=0.75)
y += 0.68
add_text(s, 0.75, 6.5, 11.8, 0.35,
'Канвас печатается на А4. Один на команду.',
size=11, color=DARK_GRAY)
add_notes(s, 'Роли: Забойщик, Инженер, Хозяин, Голос. Команда смешанная. Канвас — один на команду, '
'заполняется по шагам. Это не отчёт, а рабочий инструмент.')
return s
def slide_result(prs):
s = prs.slides.add_slide(prs.slide_layouts[6])
add_bg(s)
add_text(s, 0.75, 0.55, 11.8, 0.7, 'Что вы унесёте',
font='Georgia', size=30, color=WHITE, bold=True)
add_text(s, 0.75, 1.22, 11.8, 0.4,
'Проект не заканчивается защитой.',
size=13, color=GRAY)
# Left column
add_text(s, 0.75, 1.95, 5.5, 0.35, 'УЧАСТНИК', size=11, color=GREEN, bold=True)
left = [
('1', 'паспорт проекта'),
('3', 'минуты питча'),
('30/90', 'дней дорожная карта'),
('5', 'шагов методичка'),
]
y = 2.45
for num, label in left:
add_text(s, 0.75, y, 1.35, 0.55, num, font='Georgia', size=24,
color=GREEN, bold=True)
add_text(s, 2.15, y + 0.08, 4.0, 0.4, label, size=12, color=WHITE)
add_line(s, 0.75, y + 0.72, 6.1, y + 0.72, color='2E3439', width=0.75)
y += 0.9
# Right column
add_text(s, 6.95, 1.95, 5.5, 0.35, 'РЕГИОН', size=11, color=ROSE, bold=True)
right = [
('5–7', 'концепций цифровых сервисов'),
('1', 'банк проблем'),
('2–3', 'проекта к пилоту или гранту'),
('∞', 'тиражируемая методика'),
]
y = 2.45
for num, label in right:
add_text(s, 6.95, y, 1.35, 0.55, num, font='Georgia', size=24,
color=ROSE, bold=True)
add_text(s, 8.35, y + 0.08, 4.2, 0.4, label, size=12, color=WHITE)
add_line(s, 6.95, y + 0.72, 12.3, y + 0.72, color='2E3439', width=0.75)
y += 0.9
add_text(s, 0.75, 6.35, 11.8, 0.4,
'Лучшие проекты получают дорожную карту и поддержку партнёров.',
size=12, color=GREEN_LIGHT, bold=True)
add_notes(s, 'Результат: у участника — паспорт, питч, дорожная карта, методичка. '
'У региона — банк проблем, 5–7 концепций, 2–3 проекта к пилоту или гранту, '
'тиражируемая методика.')
return s
def slide_cta(prs):
s = prs.slides.add_slide(prs.slide_layouts[6])
add_bg(s)
add_rect(s, 0.75, 1.15, 0.055, 4.95, fill=ROSE, shape=MSO_SHAPE.RECTANGLE)
add_text(s, 1.05, 1.35, 8.2, 0.4, 'ЗАВТРА В 10:45', size=16, color=GREEN, bold=True)
add_text(s, 1.0, 1.9, 8.6, 1.2, 'Спуск в забой.',
font='Georgia', size=48, color=WHITE, bold=True)
add_text(s, 1.05, 3.35, 8.4, 1.2, 'Приходите с проблемой,\nа не с идеей.',
font='Georgia', size=26, color=ROSE, italic=True, line_spacing=1.05)
add_text(s, 1.05, 4.95, 8.2, 0.4, 'Зал «Жуков 2»', size=14, color=GRAY)
add_line(s, 1.05, 6.15, 8.4, 6.15, color='2A2F34', width=1.0)
add_text(s, 1.05, 6.35, 8.2, 0.35,
'Метод Дегтярева • Цифра, у которой есть хозяин',
size=11, color=DARK_GRAY)
# Decorative circle
add_rect(s, 9.55, 1.55, 3.1, 3.1, fill=None, line=ROSE, line_w=1.5,
shape=MSO_SHAPE.OVAL)
add_text(s, 9.55, 2.55, 3.1, 1.1, '→', font='Georgia', size=64,
color=ROSE, bold=True, align=PP_ALIGN.CENTER,
anchor=MSO_ANCHOR.MIDDLE)
add_notes(s, 'Вызов: завтра в 10:45 — спуск в забой. Зал «Жуков 2». '
'Приходите с проблемой, а не с идеей. Дальше — метод Дегтярева.')
return s
def build():
prs = Presentation()
prs.slide_width = Inches(13.333)
prs.slide_height = Inches(7.5)
slide_title(prs)
slide_paradox(prs)
slide_man(prs)
slide_method(prs)
slide_map(prs)
slide_team(prs)
slide_result(prs)
slide_cta(prs)
prs.save(OUT)
print('saved', OUT)
if __name__ == '__main__':
build()