Files
2026-09-11 03:06:38 +08:00

543 lines
22 KiB
Python
Raw Permalink 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 -*-
"""
以《长沙市紧缺急需人才项目述职报告.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()