165 lines
5.5 KiB
Python
165 lines
5.5 KiB
Python
# -*- coding: utf-8 -*-
|
||||
|
|
"""在当前 PPT 业绩章节开头插入「科技创新类六大评审维度」总览页"""
|
|||
|
|
import copy
|
|||
|
|
from pptx import Presentation
|
|||
|
|
from pptx.util import Inches, Pt, Emu
|
|||
|
|
from pptx.dml.color import RGBColor
|
|||
|
|
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
|
|||
|
|
from pptx.enum.shapes import MSO_SHAPE
|
|||
|
|
|
|||
|
|
F = r"E:\SynologyDrive1\project\2026-06-09 长沙市紧缺急需人才项目申报\紧缺急需-陈越-2026-9-11.pptx"
|
|||
|
|
|
|||
|
|
MAIN = RGBColor(0x40, 0xE0, 0xD0)
|
|||
|
|
DARK = RGBColor(0x33, 0x33, 0x33)
|
|||
|
|
GREY = RGBColor(0x55, 0x55, 0x55)
|
|||
|
|
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
|
|||
|
|
TEAL = RGBColor(0x00, 0x8B, 0x8B)
|
|||
|
|
FONT = "Noto Sans SC"
|
|||
|
|
|
|||
|
|
|
|||
|
|
def set_text(shape, text, size, color, bold=False, align=PP_ALIGN.LEFT):
|
|||
|
|
tf = shape.text_frame
|
|||
|
|
tf.word_wrap = True
|
|||
|
|
p = tf.paragraphs[0]
|
|||
|
|
runs = p.runs
|
|||
|
|
if runs:
|
|||
|
|
runs[0].text = text
|
|||
|
|
for r in runs[1:]:
|
|||
|
|
r.text = ""
|
|||
|
|
runs[0].font.size = Pt(size)
|
|||
|
|
runs[0].font.color.rgb = color
|
|||
|
|
runs[0].font.bold = bold
|
|||
|
|
runs[0].font.name = FONT
|
|||
|
|
else:
|
|||
|
|
r = p.add_run()
|
|||
|
|
r.text = text
|
|||
|
|
r.font.size = Pt(size)
|
|||
|
|
r.font.color.rgb = color
|
|||
|
|
r.font.bold = bold
|
|||
|
|
r.font.name = FONT
|
|||
|
|
p.alignment = align
|
|||
|
|
|
|||
|
|
|
|||
|
|
def card(slide, x, y, w, h, line_color=MAIN):
|
|||
|
|
sh = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, Inches(x), Inches(y), Inches(w), Inches(h))
|
|||
|
|
sh.fill.solid()
|
|||
|
|
sh.fill.fore_color.rgb = WHITE
|
|||
|
|
sh.line.color.rgb = line_color
|
|||
|
|
sh.line.width = Pt(1.25)
|
|||
|
|
sh.shadow.inherit = False
|
|||
|
|
try:
|
|||
|
|
sh.adjustments[0] = 0.08
|
|||
|
|
except Exception:
|
|||
|
|
pass
|
|||
|
|
sh.text_frame.text = ""
|
|||
|
|
return sh
|
|||
|
|
|
|||
|
|
|
|||
|
|
def chip(slide, x, y, w, h, text, fill=MAIN, color=WHITE, size=12):
|
|||
|
|
sh = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, Inches(x), Inches(y), Inches(w), Inches(h))
|
|||
|
|
sh.fill.solid()
|
|||
|
|
sh.fill.fore_color.rgb = fill
|
|||
|
|
sh.line.fill.background()
|
|||
|
|
sh.shadow.inherit = False
|
|||
|
|
try:
|
|||
|
|
sh.adjustments[0] = 0.5
|
|||
|
|
except Exception:
|
|||
|
|
pass
|
|||
|
|
tf = sh.text_frame
|
|||
|
|
tf.word_wrap = False
|
|||
|
|
tf.vertical_anchor = MSO_ANCHOR.MIDDLE
|
|||
|
|
p = tf.paragraphs[0]
|
|||
|
|
p.alignment = PP_ALIGN.CENTER
|
|||
|
|
r = p.add_run()
|
|||
|
|
r.text = text
|
|||
|
|
r.font.name = FONT
|
|||
|
|
r.font.size = Pt(size)
|
|||
|
|
r.font.bold = True
|
|||
|
|
r.font.color.rgb = color
|
|||
|
|
return sh
|
|||
|
|
|
|||
|
|
|
|||
|
|
def textbox(slide, x, y, w, h, text, size=11, color=GREY, bold=False, line=1.25):
|
|||
|
|
box = slide.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h))
|
|||
|
|
tf = box.text_frame
|
|||
|
|
tf.word_wrap = True
|
|||
|
|
tf.margin_left = 0
|
|||
|
|
tf.margin_right = 0
|
|||
|
|
tf.margin_top = 0
|
|||
|
|
tf.margin_bottom = 0
|
|||
|
|
p = tf.paragraphs[0]
|
|||
|
|
p.alignment = PP_ALIGN.LEFT
|
|||
|
|
p.line_spacing = line
|
|||
|
|
r = p.add_run()
|
|||
|
|
r.text = text
|
|||
|
|
r.font.name = FONT
|
|||
|
|
r.font.size = Pt(size)
|
|||
|
|
r.font.bold = bold
|
|||
|
|
r.font.color.rgb = color
|
|||
|
|
return box
|
|||
|
|
|
|||
|
|
|
|||
|
|
def add_slide_like(prs, template, title):
|
|||
|
|
new = prs.slides.add_slide(template.slide_layout)
|
|||
|
|
for ph in list(new.placeholders):
|
|||
|
|
ph.element.getparent().remove(ph.element)
|
|||
|
|
sp_tree = new.shapes._spTree
|
|||
|
|
for shp in list(template.shapes)[:2]:
|
|||
|
|
sp_tree.insert_element_before(copy.deepcopy(shp.element), "p:extLst")
|
|||
|
|
set_text(new.shapes[1], title, 32, DARK, bold=True)
|
|||
|
|
return new
|
|||
|
|
|
|||
|
|
|
|||
|
|
def move_slide(prs, old_index, new_index):
|
|||
|
|
lst = prs.slides._sldIdLst
|
|||
|
|
items = list(lst)
|
|||
|
|
lst.remove(items[old_index])
|
|||
|
|
lst.insert(new_index, items[old_index])
|
|||
|
|
|
|||
|
|
|
|||
|
|
def main():
|
|||
|
|
prs = Presentation(F)
|
|||
|
|
tpl = prs.slides[4] # P5 两项目概览页
|
|||
|
|
|
|||
|
|
dims = [
|
|||
|
|
("个人资质", "湖南大学应用物理学本科 · 17 年激光控制研发 · 总工程师、省工程中心副主任 · 第一/主要发明人 2 项发明专利"),
|
|||
|
|
("研究方向产业契合度", "激光控制 ↔ 长沙“智能建造”产业链 · 湘江新区激光装备集群 · 高端激光控制国产替代、对标德国 ScanLab"),
|
|||
|
|
("科研成果水平", "2 项省级重点攻关项目 · 多轴同步 ≤1ms · 轮廓精度 ≤0.03mm · 15μm 级高精度拼接"),
|
|||
|
|
("成果转化与团队能力", "成果落地大族激光等行业龙头 · 国内最大消费级激光控制方案供应商 · 团队 117 人、年研发投入 1798 万"),
|
|||
|
|
("发展潜力与平台支撑", "湖南省激光控制系统工程中心 · 国防科大技术背景 · 3D 打印市场 630 亿(2030)"),
|
|||
|
|
("未来三年规划", "标准筑基 → 消费落地 → 芯片自主(EtherCAT / ESP32 / MCU+FPGA)· 开放 API/SDK · AI 就绪"),
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
s = add_slide_like(prs, tpl, "02 业绩贡献 - 科技创新类评审维度")
|
|||
|
|
|
|||
|
|
cw, ch = 3.77, 2.42
|
|||
|
|
x0, y0 = 0.8, 1.55
|
|||
|
|
for i, (name, desc) in enumerate(dims):
|
|||
|
|
col, row = i % 3, i // 3
|
|||
|
|
x = x0 + col * (cw + 0.2)
|
|||
|
|
y = y0 + row * (ch + 0.12)
|
|||
|
|
card(s, x, y, cw, ch)
|
|||
|
|
chip(s, x + 0.22, y + 0.2, 0.42, 0.42, str(i + 1), fill=MAIN, size=13)
|
|||
|
|
textbox(s, x + 0.78, y + 0.24, 2.8, 0.38, name, size=14, color=DARK, bold=True)
|
|||
|
|
textbox(s, x + 0.24, y + 0.78, cw - 0.48, ch - 0.95, desc, size=10.5, color=GREY, line=1.3)
|
|||
|
|
|
|||
|
|
# 移到 index 4(原 P5 之前)
|
|||
|
|
move_slide(prs, len(prs.slides._sldIdLst) - 1, 4)
|
|||
|
|
|
|||
|
|
prs.save(F)
|
|||
|
|
print("已保存,总页数:", len(prs.slides._sldIdLst))
|
|||
|
|
# 校验
|
|||
|
|
prs2 = Presentation(F)
|
|||
|
|
for i, sl in enumerate(prs2.slides, 1):
|
|||
|
|
txt = []
|
|||
|
|
for sh in sl.shapes:
|
|||
|
|
if sh.has_text_frame:
|
|||
|
|
t = " ".join(p.text for p in sh.text_frame.paragraphs).strip()
|
|||
|
|
if t:
|
|||
|
|
txt.append(t)
|
|||
|
|
print(f"P{i}: " + " | ".join(txt)[:70])
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
main()
|