543 lines
22 KiB
Python
543 lines
22 KiB
Python
# -*- coding: utf-8 -*-
|
||||
|
|
"""
|
|||
|
|
以《长沙市紧缺急需人才项目述职报告.pptx》为基底,
|
|||
|
|
合并 PPT 大纲中的新增内容(重大项目技术细节 / 三年底层逻辑主线 / EtherCAT 战略意义 / 总结与展望)。
|
|||
|
|
"""
|
|||
|
|
import copy
|
|||
|
|
import os
|
|||
|
|
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
|
|||
|
|
from pptx.enum.dml import MSO_COLOR_TYPE
|
|||
|
|
|
|||
|
|
SRC = r"C:\Users\chenyue\Desktop\紧缺急需人才\长沙市紧缺急需人才项目述职报告.pptx"
|
|||
|
|
DST = r"E:\SynologyDrive1\project\2026-06-09 长沙市紧缺急需人才项目申报\长沙市紧缺急需人才项目述职报告(整合大纲版).pptx"
|
|||
|
|
ASSETS = r"E:\SynologyDrive1\project\2026-06-09 长沙市紧缺急需人才项目申报\长沙市紧缺急需人才答辩\assets"
|
|||
|
|
|
|||
|
|
MAIN = RGBColor(0x40, 0xE0, 0xD0) # 主题青绿
|
|||
|
|
DARK = RGBColor(0x33, 0x33, 0x33) # 标题深灰
|
|||
|
|
GREY = RGBColor(0x55, 0x55, 0x55) # 正文灰
|
|||
|
|
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
|
|||
|
|
FONT = "Noto Sans SC"
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ---------- 基础工具 ----------
|
|||
|
|
def style_text(tf, size, color, bold=False, align=PP_ALIGN.LEFT, space_after=2, line=None):
|
|||
|
|
tf.word_wrap = True
|
|||
|
|
for p in tf.paragraphs:
|
|||
|
|
p.alignment = align
|
|||
|
|
p.space_after = Pt(space_after)
|
|||
|
|
if line:
|
|||
|
|
p.line_spacing = line
|
|||
|
|
for r in p.runs:
|
|||
|
|
r.font.name = FONT
|
|||
|
|
r.font.size = Pt(size)
|
|||
|
|
r.font.bold = bold
|
|||
|
|
r.font.color.rgb = color
|
|||
|
|
|
|||
|
|
|
|||
|
|
def set_text(shape, text, size=None, color=None, bold=None, align=PP_ALIGN.LEFT, anchor=MSO_ANCHOR.TOP):
|
|||
|
|
"""在保留原字体格式的前提下替换文本"""
|
|||
|
|
tf = shape.text_frame
|
|||
|
|
tf.vertical_anchor = anchor
|
|||
|
|
p = tf.paragraphs[0]
|
|||
|
|
runs = p.runs
|
|||
|
|
if runs:
|
|||
|
|
runs[0].text = text
|
|||
|
|
for r in runs[1:]:
|
|||
|
|
r.text = ""
|
|||
|
|
if size is not None:
|
|||
|
|
runs[0].font.size = Pt(size)
|
|||
|
|
if color is not None:
|
|||
|
|
runs[0].font.color.rgb = color
|
|||
|
|
if bold is not None:
|
|||
|
|
runs[0].font.bold = bold
|
|||
|
|
runs[0].font.name = FONT
|
|||
|
|
else:
|
|||
|
|
r = p.add_run()
|
|||
|
|
r.text = text
|
|||
|
|
if size is not None:
|
|||
|
|
r.font.size = Pt(size)
|
|||
|
|
if color is not None:
|
|||
|
|
r.font.color.rgb = color
|
|||
|
|
r.font.name = FONT
|
|||
|
|
p.alignment = align
|
|||
|
|
return shape
|
|||
|
|
|
|||
|
|
|
|||
|
|
def textbox(slide, x, y, w, h, text, size=12, color=GREY, bold=False,
|
|||
|
|
align=PP_ALIGN.LEFT, anchor=MSO_ANCHOR.TOP, 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.vertical_anchor = anchor
|
|||
|
|
tf.margin_left = 0
|
|||
|
|
tf.margin_right = 0
|
|||
|
|
tf.margin_top = 0
|
|||
|
|
tf.margin_bottom = 0
|
|||
|
|
p = tf.paragraphs[0]
|
|||
|
|
p.alignment = align
|
|||
|
|
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 card(slide, x, y, w, h, fill=WHITE, line_color=MAIN, line_w=1.0, radius=True):
|
|||
|
|
shape_type = MSO_SHAPE.ROUNDED_RECTANGLE if radius else MSO_SHAPE.RECTANGLE
|
|||
|
|
sh = slide.shapes.add_shape(shape_type, Inches(x), Inches(y), Inches(w), Inches(h))
|
|||
|
|
sh.fill.solid()
|
|||
|
|
sh.fill.fore_color.rgb = fill
|
|||
|
|
if line_color is None:
|
|||
|
|
sh.line.fill.background()
|
|||
|
|
else:
|
|||
|
|
sh.line.color.rgb = line_color
|
|||
|
|
sh.line.width = Pt(line_w)
|
|||
|
|
sh.shadow.inherit = False
|
|||
|
|
if radius:
|
|||
|
|
try:
|
|||
|
|
sh.adjustments[0] = 0.06
|
|||
|
|
except Exception:
|
|||
|
|
pass
|
|||
|
|
sh.text_frame.text = ""
|
|||
|
|
return sh
|
|||
|
|
|
|||
|
|
|
|||
|
|
def bar(slide, x, y, w, h, text, size=13, color=WHITE, bold=True, fill=MAIN, align=PP_ALIGN.LEFT):
|
|||
|
|
sh = card(slide, x, y, w, h, fill=fill, line_color=None, radius=True)
|
|||
|
|
tf = sh.text_frame
|
|||
|
|
tf.word_wrap = True
|
|||
|
|
tf.vertical_anchor = MSO_ANCHOR.MIDDLE
|
|||
|
|
tf.margin_left = Inches(0.18)
|
|||
|
|
tf.margin_right = Inches(0.18)
|
|||
|
|
p = tf.paragraphs[0]
|
|||
|
|
p.alignment = align
|
|||
|
|
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 sh
|
|||
|
|
|
|||
|
|
|
|||
|
|
def chip(slide, x, y, w, h, text, size=11, fill=MAIN, color=WHITE):
|
|||
|
|
"""小标签胶囊"""
|
|||
|
|
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
|
|||
|
|
tf.margin_left = 0
|
|||
|
|
tf.margin_right = 0
|
|||
|
|
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 picture(slide, x, y, w, h, filename):
|
|||
|
|
path = os.path.join(ASSETS, filename)
|
|||
|
|
return slide.shapes.add_picture(path, Inches(x), Inches(y), Inches(w), Inches(h))
|
|||
|
|
|
|||
|
|
|
|||
|
|
def add_slide_like(prs, template, title):
|
|||
|
|
"""复制模板页的背景与标题条,生成风格一致的新页"""
|
|||
|
|
new = prs.slides.add_slide(template.slide_layout)
|
|||
|
|
# 清理 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")
|
|||
|
|
# 新页 shapes[0]=背景 [1]=标题条
|
|||
|
|
new.shapes._spTree.remove(new.shapes[0]._element) if False else None
|
|||
|
|
set_text(new.shapes[1], title, size=32, color=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 build_tech_detail(prs, template):
|
|||
|
|
"""重大项目核心技术细节(左右两栏)"""
|
|||
|
|
s = add_slide_like(prs, template, "02 业绩贡献 - 重大项目核心技术细节")
|
|||
|
|
|
|||
|
|
blocks = [
|
|||
|
|
dict(
|
|||
|
|
x=0.8, name="激光增材制造多模态融合智能控制系统",
|
|||
|
|
items=[
|
|||
|
|
("① FPGA+MCU 异构架构集成控制硬件",
|
|||
|
|
"部件 3–4 套缩减至 1 套,核心硬件国产化率 ≥90%,单套成本较进口方案降 40%"),
|
|||
|
|
("② 多轴高精度协同控制",
|
|||
|
|
"自适应 S 形加减速 + FPGA 硬实时插补,激光与平台同步偏差 ≤1ms,轮廓精度 ≤0.03mm"),
|
|||
|
|
("③ 多源传感器融合闭环调控",
|
|||
|
|
"“温度–气体浓度–激光功率–运动状态”多维融合,故障预警准确率 ≥95%"),
|
|||
|
|
],
|
|||
|
|
img="pso.jpg",
|
|||
|
|
caption="实拍佐证:PSO 位置同步输出,触发间距稳定 19–21μm,波动 <1μm",
|
|||
|
|
kpi="关键指标:≥4 轴运动控制 · 多轴同步精度 ≤±0.03mm · 实时控制周期 ≤1ms · 轨迹偏差 ≤±0.03mm · 动态路径调整响应 ≤500μs",
|
|||
|
|
),
|
|||
|
|
dict(
|
|||
|
|
x=6.8, name="多振镜激光蚀刻除电路板油墨阻焊控制技术",
|
|||
|
|
items=[
|
|||
|
|
("① 大幅面 PCB 文件解析与分割",
|
|||
|
|
"Gerber 文件高精度解析,阻焊层开窗图形位置误差 ≤5μm,重复扫描率降 30%"),
|
|||
|
|
("② 高精度 CCD 振镜校正与视觉定位",
|
|||
|
|
"多点阵标定 + 畸变数字补偿,光学校准精度 ±3μm,PCB 加工精度 ≤15μm"),
|
|||
|
|
("③ 多振镜协同控制与拼接",
|
|||
|
|
"统一调度 + 坐标补偿 + 边缘校准,实现 15μm 级高精度拼接,消除间隙、重影"),
|
|||
|
|
],
|
|||
|
|
img="largesize.jpg",
|
|||
|
|
caption="设备实拍:多振镜大幅面加工,拼接无缝隙无重影(另附现场演示视频)",
|
|||
|
|
kpi="关键指标:机器视觉定位精度 ≤2μm · 激光扫描精度 ≤3μm · 拼接精度 ≤15μm · 整机加工精度 ≤±15μm",
|
|||
|
|
),
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
for b in blocks:
|
|||
|
|
x = b["x"]
|
|||
|
|
card(s, x, 1.5, 5.7, 5.3)
|
|||
|
|
bar(s, x + 0.12, 1.62, 5.46, 0.66, b["name"], size=16)
|
|||
|
|
y = 2.48
|
|||
|
|
for title, desc in b["items"]:
|
|||
|
|
textbox(s, x + 0.35, y, 5.0, 0.28, title, size=14, color=DARK, bold=True)
|
|||
|
|
textbox(s, x + 0.35, y + 0.3, 5.0, 0.62, desc, size=12, color=GREY, line=1.2)
|
|||
|
|
y += 1.02
|
|||
|
|
# 实拍图 + 说明
|
|||
|
|
picture(s, x + 0.35, 5.50, 1.5, 0.72, b["img"])
|
|||
|
|
textbox(s, x + 2.0, 5.52, 3.4, 0.7, b["caption"], size=10, color=GREY, line=1.15)
|
|||
|
|
bar(s, x + 0.35, 6.26, 5.0, 0.42, b["kpi"], size=10, bold=False, fill=MAIN)
|
|||
|
|
return s
|
|||
|
|
|
|||
|
|
|
|||
|
|
def build_ethercat(prs, template):
|
|||
|
|
"""EtherCAT 项目战略意义"""
|
|||
|
|
s = add_slide_like(prs, template, "03 未来规划 - EtherCAT 项目战略意义")
|
|||
|
|
|
|||
|
|
# 上半:历史规律 vs 未来趋势
|
|||
|
|
left = [
|
|||
|
|
("早期", "轴控卡均为专用控制卡方案"),
|
|||
|
|
("转折", "PLC 性能增强 + EtherCAT 总线出现"),
|
|||
|
|
("结果", "轴控功能逐渐集成到 PLC 中"),
|
|||
|
|
]
|
|||
|
|
right = [
|
|||
|
|
("现状", "振镜控制卡为公司当前主营业务"),
|
|||
|
|
("驱动", "芯片算力持续增强、成本不断下降"),
|
|||
|
|
("趋势", "振镜控制必然与 PLC 融合(重演规律)"),
|
|||
|
|
]
|
|||
|
|
card(s, 0.8, 1.5, 5.5, 2.15, fill=RGBColor(0xF6, 0xFB, 0xFA))
|
|||
|
|
textbox(s, 1.05, 1.62, 3.0, 0.32, "历史规律 · 轴控卡", size=14, color=GREY, bold=True)
|
|||
|
|
y = 2.02
|
|||
|
|
for k, v in left:
|
|||
|
|
chip(s, 1.05, y + 0.02, 0.62, 0.26, k, size=9, fill=RGBColor(0xB6, 0xB6, 0xB6))
|
|||
|
|
textbox(s, 1.78, y, 4.3, 0.3, v, size=11, color=DARK)
|
|||
|
|
y += 0.5
|
|||
|
|
|
|||
|
|
textbox(s, 6.4, 2.3, 0.5, 0.4, "⟹", size=22, color=MAIN, bold=True, align=PP_ALIGN.CENTER)
|
|||
|
|
|
|||
|
|
card(s, 7.0, 1.5, 5.5, 2.15, fill=WHITE, line_color=MAIN, line_w=1.75)
|
|||
|
|
textbox(s, 7.25, 1.62, 3.0, 0.32, "未来趋势 · 振镜卡", size=14, color=MAIN, bold=True)
|
|||
|
|
y = 2.02
|
|||
|
|
for k, v in right:
|
|||
|
|
chip(s, 7.25, y + 0.02, 0.62, 0.26, k, size=9, fill=MAIN)
|
|||
|
|
textbox(s, 7.98, y, 4.3, 0.3, v, size=11, color=DARK)
|
|||
|
|
y += 0.5
|
|||
|
|
|
|||
|
|
picture(s, 0.8, 1.5, 0, 0, "ethercat_card.jpg") if False else None
|
|||
|
|
|
|||
|
|
# 下半:战略结论
|
|||
|
|
card(s, 0.8, 3.95, 11.73, 2.85, fill=MAIN, line_color=None)
|
|||
|
|
textbox(s, 1.1, 4.12, 6.5, 0.36, "战略意义 · 主动顺应历史规律,抢占标准制定先机",
|
|||
|
|
size=15, color=WHITE, bold=True)
|
|||
|
|
items = [
|
|||
|
|
("主动作为", "公司 EtherCAT 项目正是主动顺应这一历史规律,由我们主动推动振镜卡融入 PLC 生态这一历史进程。"),
|
|||
|
|
("标准先机", "振镜控制尚未进入 PLCopen 标准——若我们主导振镜卡融入 PLC 生态,有望成为该领域的事实标准制定者。"),
|
|||
|
|
("普惠行业", "以标准制定者身份,推动振镜控制技术普惠全行业、赋能所有用户,让高端技术人人可用。"),
|
|||
|
|
]
|
|||
|
|
y = 4.62
|
|||
|
|
for k, v in items:
|
|||
|
|
chip(s, 1.1, y + 0.04, 1.0, 0.3, k, size=10, fill=RGBColor(0xFF, 0xE7, 0x9B),
|
|||
|
|
color=RGBColor(0x8A, 0x1A, 0x0F))
|
|||
|
|
textbox(s, 2.35, y, 9.9, 0.62, v, size=12, color=WHITE, line=1.2)
|
|||
|
|
y += 0.72
|
|||
|
|
return s
|
|||
|
|
|
|||
|
|
|
|||
|
|
def build_gallery(prs, template):
|
|||
|
|
"""关键成果实拍佐证图集"""
|
|||
|
|
s = add_slide_like(prs, template, "02 业绩贡献 - 关键成果实拍佐证")
|
|||
|
|
items = [
|
|||
|
|
("g_bitmap.jpg", "位图二维码打标工艺", "飞行打标效率领先,速度提升 30%"),
|
|||
|
|
("g_drilling.jpg", "20–30μm 激光钻孔工艺", "国内首家实现高质量、高一致性微加工"),
|
|||
|
|
("g_conversion.jpg", "LPKF 改造 · 红宝石改光纤", "盘活千万级老旧设备,切割效果达标"),
|
|||
|
|
("g_durability.jpg", "24h 稳定耐久性测试", "轨迹示波器量化对比,测试由定性转自动化"),
|
|||
|
|
("g_ethercat.jpg", "自研 EtherCAT 振镜控制卡", "工业总线国产替代,夯实新一代硬件基础"),
|
|||
|
|
("g_pso.jpg", "PSO 位置同步输出实测", "触发间距稳定 19–21μm,波动 <1μm"),
|
|||
|
|
]
|
|||
|
|
x0, y0 = 0.8, 1.55
|
|||
|
|
cw, ch = 3.77, 2.40
|
|||
|
|
for i, (img, title, desc) in enumerate(items):
|
|||
|
|
col, row = i % 3, i // 3
|
|||
|
|
x = x0 + col * 3.98
|
|||
|
|
y = y0 + row * 2.5
|
|||
|
|
card(s, x, y, cw, ch)
|
|||
|
|
picture(s, x + 0.12, y + 0.12, cw - 0.24, (cw - 0.24) / 2, img)
|
|||
|
|
textbox(s, x + 0.2, y + 1.92, cw - 0.4, 0.24, title, size=12, color=DARK, bold=True)
|
|||
|
|
textbox(s, x + 0.2, y + 2.16, cw - 0.4, 0.22, desc, size=10, color=GREY)
|
|||
|
|
return s
|
|||
|
|
|
|||
|
|
|
|||
|
|
def set_paragraphs(shape, texts, size=12, color=GREY, bold=False):
|
|||
|
|
"""替换多段文本(保持形状与字体)"""
|
|||
|
|
tf = shape.text_frame
|
|||
|
|
while len(tf.paragraphs) > len(texts):
|
|||
|
|
p = tf.paragraphs[-1]._p
|
|||
|
|
p.getparent().remove(p)
|
|||
|
|
for i, txt in enumerate(texts):
|
|||
|
|
if i < len(tf.paragraphs):
|
|||
|
|
p = tf.paragraphs[i]
|
|||
|
|
else:
|
|||
|
|
p = tf.add_paragraph()
|
|||
|
|
for r in list(p.runs):
|
|||
|
|
r._r.getparent().remove(r._r)
|
|||
|
|
r = p.add_run()
|
|||
|
|
r.text = txt
|
|||
|
|
r.font.name = FONT
|
|||
|
|
r.font.size = Pt(size)
|
|||
|
|
r.font.bold = bold
|
|||
|
|
r.font.color.rgb = color
|
|||
|
|
|
|||
|
|
|
|||
|
|
def find_shape(slide, keyword):
|
|||
|
|
for sh in slide.shapes:
|
|||
|
|
if sh.has_text_frame and keyword in sh.text_frame.text:
|
|||
|
|
return sh
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
|
|||
|
|
def build_summary(prs, template):
|
|||
|
|
"""总结与展望"""
|
|||
|
|
s = add_slide_like(prs, template, "总结与展望")
|
|||
|
|
|
|||
|
|
cards = [
|
|||
|
|
("全链条研发能力", "从激光控制、机器人控制系统,到工艺、硬件、系统集成,十几年的工作贯穿了整条链。"),
|
|||
|
|
("国产化突破", "入职长沙后主持两项省级攻关项目,推动省内激光装备核心控制技术国产化。"),
|
|||
|
|
("产业化落地", "接下来三年把产品做成标准化、系列化,带动产业链降本,配合长沙激光产业发展。"),
|
|||
|
|
]
|
|||
|
|
x = 0.8
|
|||
|
|
for i, (t, d) in enumerate(cards):
|
|||
|
|
card(s, x, 2.15, 3.77, 3.25)
|
|||
|
|
chip(s, x + 0.3, 2.4, 0.46, 0.46, str(i + 1), size=14, fill=MAIN)
|
|||
|
|
textbox(s, x + 0.95, 2.44, 2.6, 0.4, t, size=14, color=DARK, bold=True)
|
|||
|
|
textbox(s, x + 0.3, 3.05, 3.2, 2.1, d, size=12, color=GREY, line=1.3)
|
|||
|
|
x += 3.98
|
|||
|
|
|
|||
|
|
bar(s, 0.8, 5.7, 11.73, 0.85, "扎根长沙 · 把激光控制这件产品做扎实、做出规模",
|
|||
|
|
size=16, align=PP_ALIGN.CENTER)
|
|||
|
|
return s
|
|||
|
|
|
|||
|
|
|
|||
|
|
def set_fullscreen(movie):
|
|||
|
|
"""视频设为点击后全屏播放(p14:media 加 isFullScreen=1)"""
|
|||
|
|
P14 = 'http://schemas.microsoft.com/office/powerpoint/2010/main'
|
|||
|
|
media = movie._element.find(f'.//{{{P14}}}media')
|
|||
|
|
if media is not None:
|
|||
|
|
media.set('isFullScreen', '1')
|
|||
|
|
|
|||
|
|
|
|||
|
|
def replace_picture_with_video(slide, pic, video_name, poster_name, caption):
|
|||
|
|
"""将模板配图替换为可播放视频(16:9 适配、垂直居中、点击全屏播放)"""
|
|||
|
|
left, top, w, h = pic.left, pic.top, pic.width, pic.height
|
|||
|
|
new_w, new_h = w, int(w * 9 / 16)
|
|||
|
|
if new_h > h:
|
|||
|
|
new_h, new_w = h, int(h * 16 / 9)
|
|||
|
|
new_left = left + (w - new_w) // 2
|
|||
|
|
new_top = top + (h - new_h) // 2
|
|||
|
|
pic._element.getparent().remove(pic._element)
|
|||
|
|
movie = slide.shapes.add_movie(
|
|||
|
|
os.path.join(ASSETS, video_name), new_left, new_top, new_w, new_h,
|
|||
|
|
poster_frame_image=os.path.join(ASSETS, poster_name))
|
|||
|
|
set_fullscreen(movie)
|
|||
|
|
textbox(slide, Emu(left).inches, Emu(new_top + new_h + 9144).inches,
|
|||
|
|
Emu(w).inches, 0.26, caption, size=10, color=MAIN, bold=True)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def patch_missing_achievements(slide):
|
|||
|
|
"""P7 顶部空白补 3 项成果(统一 M4 平台 / 多机型机器人 / LPKF 改造)"""
|
|||
|
|
items = [
|
|||
|
|
("统一 M4 开发平台", "大幅缩短开发周期,解决项目碎片化痛点"),
|
|||
|
|
("多机型机器人控制系统", "兼容码垛、焊接、搬运、Scara 等"),
|
|||
|
|
("LPKF 激光切割系统改造", "红宝石改光纤,盘活千万级老旧设备"),
|
|||
|
|
]
|
|||
|
|
x = 0.8
|
|||
|
|
for t, d in items:
|
|||
|
|
card(slide, x, 1.5, 3.77, 0.88)
|
|||
|
|
textbox(slide, x + 0.22, 1.58, 3.4, 0.28, t, size=12, color=DARK, bold=True)
|
|||
|
|
textbox(slide, x + 0.22, 1.86, 3.4, 0.44, d, size=10, color=GREY, line=1.15)
|
|||
|
|
x += 3.98
|
|||
|
|
|
|||
|
|
|
|||
|
|
def normalize_colors(prs):
|
|||
|
|
"""统一文字配色:红→深青强调、杂灰→555555、深蓝黑→333333"""
|
|||
|
|
cmap = {
|
|||
|
|
'FF0000': '008B8B', # 强调数字/关键词统一为深青
|
|||
|
|
'4B5563': '555555', # 模板正文灰 → 统一正文灰
|
|||
|
|
'666666': '555555', # 图注灰 → 统一正文灰
|
|||
|
|
'1F2329': '333333', # 深蓝黑 → 统一标题深色
|
|||
|
|
}
|
|||
|
|
for s in prs.slides:
|
|||
|
|
for sh in s.shapes:
|
|||
|
|
if not sh.has_text_frame:
|
|||
|
|
continue
|
|||
|
|
for para in sh.text_frame.paragraphs:
|
|||
|
|
for r in para.runs:
|
|||
|
|
try:
|
|||
|
|
c = r.font.color
|
|||
|
|
if c.type == MSO_COLOR_TYPE.RGB:
|
|||
|
|
key = str(c.rgb)
|
|||
|
|
if key in cmap:
|
|||
|
|
c.rgb = RGBColor.from_string(cmap[key])
|
|||
|
|
except Exception:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
|
|||
|
|
def main():
|
|||
|
|
prs = Presentation(SRC)
|
|||
|
|
slides = prs.slides
|
|||
|
|
|
|||
|
|
# 0) 目录页金句、引进后总述去口号(模板自带 AI 感表述)
|
|||
|
|
m_dir = find_shape(slides[1], "以专业铸就基石")
|
|||
|
|
if m_dir is not None:
|
|||
|
|
set_paragraphs(m_dir, ["专注激光控制与工业自动化,把产品做扎实"], size=14, color=GREY)
|
|||
|
|
m_intro = find_shape(slides[7], "引入前沿工业控制技术")
|
|||
|
|
if m_intro is not None:
|
|||
|
|
set_paragraphs(m_intro, [
|
|||
|
|
"这两年引入 EtherCAT 总线技术,建立起标准化测试体系,把控制硬件和产品测试从定性做到量化,补上了公司产品可靠性的短板。",
|
|||
|
|
], size=12, color=GREY)
|
|||
|
|
|
|||
|
|
# 1) 修改现有页:底层逻辑条(原 P9)
|
|||
|
|
s9 = slides[8]
|
|||
|
|
bar(s9, 0.8, 1.62, 11.73, 0.5,
|
|||
|
|
"一条主线:硬件性能越来越强、门槛越来越低、成本越来越低 —— 按这条线分三步走:标准筑基 → 消费落地 → 芯片自主",
|
|||
|
|
size=12, bold=False, fill=MAIN)
|
|||
|
|
|
|||
|
|
# 1b) 改写原 P9「总体目标」三条(去口号,讲做什么)
|
|||
|
|
goal = find_shape(s9, "深耕激光控制核心")
|
|||
|
|
if goal is not None:
|
|||
|
|
set_paragraphs(goal, [
|
|||
|
|
"工业级:把激光控制核心技术做扎实,形成统一的标准化平台。",
|
|||
|
|
"消费级:把激光加工硬件下沉到个人用户,成本做低、门槛做低。",
|
|||
|
|
"自主:提前布局 MCU+FPGA 核心板,向自己造芯片的方向走。",
|
|||
|
|
], size=14, color=GREY)
|
|||
|
|
|
|||
|
|
# 1c) 改写原 P9 三个小卡描述(去「夯实/打造/普惠化」等口号词)
|
|||
|
|
for kw, txt in [
|
|||
|
|
("建立统一的激光控制协议", "建立统一的激光控制协议与接口标准,软硬件模块高度兼容,降低系统集成与维护成本,为规模化应用打基础。"),
|
|||
|
|
("引入AI算法与自适应控制", "引入 AI 算法与自适应控制,实时优化激光功率、光斑形态,提升加工精度与效率。"),
|
|||
|
|
("优化硬件架构与供应链", "优化硬件架构与供应链,推动核心部件国产化替代,把系统整体成本降下来,让激光加工更普及。"),
|
|||
|
|
]:
|
|||
|
|
sh = find_shape(s9, kw)
|
|||
|
|
if sh is not None:
|
|||
|
|
set_paragraphs(sh, [txt], size=12, color=GREY)
|
|||
|
|
|
|||
|
|
# 2) 修改现有页:2026 / 2027 阶段标签(原 P10,置于配图下方空白带)
|
|||
|
|
s10 = slides[9]
|
|||
|
|
chip(s10, 4.03, 5.10, 1.25, 0.32, "标准筑基", size=10)
|
|||
|
|
chip(s10, 10.00, 5.10, 1.25, 0.32, "消费落地", size=10)
|
|||
|
|
|
|||
|
|
# 2b) 改写原 P10「2026 战略意义」条,融入轴控卡趋势判断
|
|||
|
|
strat = find_shape(s10, "战略意义")
|
|||
|
|
if strat is not None:
|
|||
|
|
set_paragraphs(strat, [
|
|||
|
|
"战略意义:振镜卡与当年轴控卡一样,正面临与 PLC 融合的趋势;振镜控制尚未进入 PLCopen 标准,先做进去有机会成为事实标准制定者。",
|
|||
|
|
], size=12, color=GREY)
|
|||
|
|
|
|||
|
|
# 3) 修改现有页:2028 阶段标签(原 P11)
|
|||
|
|
s11 = slides[10]
|
|||
|
|
chip(s11, 11.0, 1.5, 1.3, 0.36, "芯片自主", size=10)
|
|||
|
|
|
|||
|
|
# 4) 修改现有页:补齐引进前 3 项成果(原 P7)
|
|||
|
|
patch_missing_achievements(slides[6])
|
|||
|
|
|
|||
|
|
# 4b) 嵌入现场演示视频(原 P8 旋转打标 / 原 P10 大幅面加工)
|
|||
|
|
s_rotary = slides[7]
|
|||
|
|
for sh in s_rotary.shapes:
|
|||
|
|
if sh.shape_type == 13 and Emu(sh.top).inches < 2.5 and Emu(sh.left).inches < 1.0:
|
|||
|
|
replace_picture_with_video(
|
|||
|
|
s_rotary, sh, "vid_rotary.mp4", "poster_rotary.jpg",
|
|||
|
|
"▶ 高速旋转打标 · 现场实拍(点击播放)")
|
|||
|
|
break
|
|||
|
|
s_2026 = slides[9]
|
|||
|
|
for sh in s_2026.shapes:
|
|||
|
|
if sh.shape_type == 13 and 2.5 < Emu(sh.top).inches < 3.5 and Emu(sh.left).inches > 3.5:
|
|||
|
|
replace_picture_with_video(
|
|||
|
|
s_2026, sh, "vid_largesize.mp4", "poster_largesize.jpg",
|
|||
|
|
"▶ 大幅面加工(点击播放)")
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
# 4c) 替换 2027 消费级卡配图(模板通用图 → 消费级打标场景图)
|
|||
|
|
for sh in s_2026.shapes:
|
|||
|
|
if sh.shape_type == 13 and Emu(sh.left).inches > 9.0:
|
|||
|
|
left, top, w, h = sh.left, sh.top, sh.width, sh.height
|
|||
|
|
sh._element.getparent().remove(sh._element)
|
|||
|
|
s_2026.shapes.add_picture(os.path.join(ASSETS, "consumer.jpg"), left, top, w, h)
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
# 5) 新增 2 页(技术细节 / 实拍图集)
|
|||
|
|
tpl = slides[4]
|
|||
|
|
build_tech_detail(prs, tpl)
|
|||
|
|
build_gallery(prs, tpl)
|
|||
|
|
|
|||
|
|
# 5b) 封底加一句收束语(替换英文 THANKS),代替原「总结与展望」页
|
|||
|
|
thanks = find_shape(slides[11], "THANKS")
|
|||
|
|
if thanks is not None:
|
|||
|
|
set_paragraphs(thanks, ["从工艺到系统集成,专注把激光控制做扎实"], size=14, color=GREY)
|
|||
|
|
|
|||
|
|
# 6) 调整顺序:A(技术细节)→5,D(图集)→9
|
|||
|
|
move_slide(prs, 12, 5)
|
|||
|
|
move_slide(prs, 13, 9)
|
|||
|
|
|
|||
|
|
# 7) 统一配色(红→深青强调、杂灰→统一灰、深蓝黑→统一深色)
|
|||
|
|
normalize_colors(prs)
|
|||
|
|
|
|||
|
|
prs.save(DST)
|
|||
|
|
print("saved:", DST)
|
|||
|
|
|
|||
|
|
# 校验
|
|||
|
|
prs2 = Presentation(DST)
|
|||
|
|
for i, s in enumerate(prs2.slides, 1):
|
|||
|
|
txt = []
|
|||
|
|
for sh in s.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)[:110])
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
main()
|