# -*- coding: utf-8 -*- """ Generate work-summary PPT styled like 组会/yh -散射边界条件.pptx Run: python generate_summary_ppt.py """ from pathlib import Path import subprocess import sys from pptx import Presentation from pptx.util import Pt from pptx.enum.text import PP_ALIGN ROOT = Path(__file__).resolve().parent ASSETS = ROOT / "ppt_assets" REF_PPT = Path(r"e:\组会\yh -散射边界条件.pptx") REF_IMGS = Path(r"c:\Users\Administrator\Desktop\ppt_assets_ref") OUT_PATH = ROOT / "OpticsFEM_2D_to_3D_Summary.pptx" LAYOUT_TITLE_CONTENT = 11 # 5_标题和内容 — same as reference deck def delete_all_slides(prs): while len(prs.slides) > 0: sld_id = prs.slides._sldIdLst[0] r_id = sld_id.rId prs.part.drop_rel(r_id) del prs.slides._sldIdLst[0] def set_title(slide, text): if slide.shapes.title: slide.shapes.title.text = text def add_textbox(slide, left, top, width, height, text, size=14, bold=False): box = slide.shapes.add_textbox(left, top, width, height) tf = box.text_frame tf.word_wrap = True p = tf.paragraphs[0] p.text = text p.font.size = Pt(size) p.font.bold = bold return box def add_bullets(slide, left, top, width, height, items, size=13): box = slide.shapes.add_textbox(left, top, width, height) tf = box.text_frame tf.word_wrap = True for i, item in enumerate(items): p = tf.paragraphs[0] if i == 0 else tf.add_paragraph() p.text = item p.font.size = Pt(size) p.space_after = Pt(6) def add_picture(slide, path, left, top, width=None, height=None): path = Path(path) if not path.exists(): print(f" [warn] missing image: {path}") return None if width and height: return slide.shapes.add_picture(str(path), left, top, width=width, height=height) if width: return slide.shapes.add_picture(str(path), left, top, width=width) if height: return slide.shapes.add_picture(str(path), left, top, height=height) return slide.shapes.add_picture(str(path), left, top) def ensure_assets(): ASSETS.mkdir(parents=True, exist_ok=True) # charts subprocess.run([sys.executable, str(ASSETS / "make_charts.py")], check=True, cwd=ROOT) # normE slices if not present mesh = ROOT / "scat3D/Release/SBCmesh.dat" outdir = ROOT / "scat3D/Release/OutFile" plot_script = ROOT / "tools/plot_normE_slice.py" if mesh.exists() and (outdir / "normE").exists() and plot_script.exists(): for plane, coord, name in [("z", 0.5, "normE_z050.png"), ("z", 0, "normE_z000.png"), ("y", 0.5, "normE_y050.png")]: out = ASSETS / name if not out.exists(): subprocess.run([ sys.executable, str(plot_script), "--mesh", str(mesh), "--outdir", str(outdir), "--plane", plane, "--coord", str(coord), "--output", str(out), ], cwd=ROOT, check=False) def build(prs): delete_all_slides(prs) # emu helpers — reference deck is 13.33" wide from pptx.util import Inches W = prs.slide_width # --- 1 封面(仿参考第1页布局)--- slide = prs.slides.add_slide(prs.slide_layouts[LAYOUT_TITLE_CONTENT]) set_title(slide, "组会汇报") add_textbox( slide, Inches(0), Inches(2.9), W, Inches(1.6), "OpticsFEM 二维→三维扩展工作总结\n" "一阶 Nedelec 边元 · 散射边界条件与本征频率问题\n" "汇报人: 时间:2026年6月", size=22, bold=False, ) add_picture(slide, REF_IMGS / "image5.png", Inches(7.2), Inches(1.5), width=Inches(5.5)) # --- 2 背景与目标 --- slide = prs.slides.add_slide(prs.slide_layouts[LAYOUT_TITLE_CONTENT]) set_title(slide, "项目背景与目标") add_bullets(slide, Inches(0.35), Inches(1.35), Inches(5.8), Inches(5.5), [ "原有 OpticsFEM:2D 三角网格 + 一阶边元(FemType 0–3)", "目标:扩展至 3D 四面体网格,对标 MATLAB 参考代码", "算例:SBC 立方体模型,εr=1.0/1.5,λ₀=0.8 m", "散射:assembly_equ + out + inc → Ax=b", "本征:assembly_equ → A·x=λ·B·x", "交付:C++ 散射全流程 + 本征矩阵与 MATLAB 对齐", ]) add_picture(slide, REF_IMGS / "image6.png", Inches(6.3), Inches(1.6), width=Inches(6.5)) # --- 3 SBC 物理(仿参考“散射边界条件模型”)--- slide = prs.slides.add_slide(prs.slide_layouts[LAYOUT_TITLE_CONTENT]) set_title(slide, "散射边界条件(SBC)物理模型") add_textbox(slide, Inches(0.35), Inches(1.8), Inches(5.5), Inches(4.5), "散射边界条件(Scattering Boundary Condition)\n\n" "• 出射边界:模拟只有沿外法向传播的出射波\n" "• 入射激励:平面波 Einc=(1,0,0)\n" "• 体积分:curl-curl − k₀²ε\n" "• C++ 实现:Assemble_SBC + assembly_inc/out\n" "• 与 MATLAB main.m 流程一致", size=14) add_picture(slide, REF_IMGS / "image8.png", Inches(1.5), Inches(4.8), width=Inches(3.5)) add_picture(slide, REF_IMGS / "image17.png", Inches(6.0), Inches(1.5), width=Inches(6.8)) # --- 4 2D vs 3D --- slide = prs.slides.add_slide(prs.slide_layouts[LAYOUT_TITLE_CONTENT]) set_title(slide, "二维与三维扩展对照") add_picture(slide, ASSETS / "status_table.png", Inches(0.4), Inches(1.4), width=Inches(12.0)) add_textbox(slide, Inches(0.5), Inches(5.0), Inches(12), Inches(1.2), "新增 FemType 4(3D 散射)与 5(3D 本征);共用 Mesh_3D + SBCmesh.dat", size=13) # --- 5 软件架构 --- slide = prs.slides.add_slide(prs.slide_layouts[LAYOUT_TITLE_CONTENT]) set_title(slide, "软件整体架构") add_picture(slide, ASSETS / "workflow.png", Inches(0.5), Inches(1.35), width=Inches(12.2)) add_bullets(slide, Inches(0.5), Inches(5.2), Inches(12), Inches(1.5), [ "模块:mesh · material · phy · kernel · solver · post · Interface", "构建:scat3D/(散射)与 eigen3D/(本征)独立 CMake 目录", ], size=12) # --- 6 散射方程与流程(仿参考“方程”页)--- slide = prs.slides.add_slide(prs.slide_layouts[LAYOUT_TITLE_CONTENT]) set_title(slide, "三维散射问题(FemType = 4)") add_picture(slide, REF_IMGS / "image3.png", Inches(0.4), Inches(1.5), width=Inches(5.5)) add_picture(slide, REF_IMGS / "image4.png", Inches(6.2), Inches(1.5), width=Inches(6.5)) add_textbox(slide, Inches(0.5), Inches(5.5), Inches(12), Inches(1.2), "方程 A·x=b | 配置 sbc3d.json | 返回码 4 | complexsolver SparseLU", size=13, bold=True) # --- 7 散射数值结果 --- slide = prs.slides.add_slide(prs.slide_layouts[LAYOUT_TITLE_CONTENT]) set_title(slide, "散射仿真结果 |E| 分布") add_picture(slide, ASSETS / "normE_z050.png", Inches(0.4), Inches(1.35), width=Inches(6.0)) add_picture(slide, ASSETS / "metrics.png", Inches(6.6), Inches(1.35), width=Inches(6.2)) add_textbox(slide, Inches(0.5), Inches(5.6), Inches(12), Inches(0.8), "z=0.5 m 切面 | |E| min=0.72 max=1.36 V/m | 残差 |r|≈6.2×10⁻¹³", size=13) # --- 8 多切面对比 --- slide = prs.slides.add_slide(prs.slide_layouts[LAYOUT_TITLE_CONTENT]) set_title(slide, "散射场多切面 |E|(C++ Post 输出)") add_picture(slide, ASSETS / "normE_z000.png", Inches(0.3), Inches(1.4), width=Inches(4.0)) add_picture(slide, ASSETS / "normE_z050.png", Inches(4.5), Inches(1.4), width=Inches(4.0)) add_picture(slide, ASSETS / "normE_y050.png", Inches(8.7), Inches(1.4), width=Inches(4.0)) add_textbox(slide, Inches(0.3), Inches(1.2), Inches(4), Inches(0.3), "z = 0", size=11, bold=True) add_textbox(slide, Inches(4.5), Inches(1.2), Inches(4), Inches(0.3), "z = 0.5 m", size=11, bold=True) add_textbox(slide, Inches(8.7), Inches(1.2), Inches(4), Inches(0.3), "y = 0.5 m", size=11, bold=True) add_textbox(slide, Inches(0.5), Inches(5.8), Inches(12), Inches(0.6), "可与 COMSOL 切面对比:compare/compare_normE_zslice.m", size=12) # --- 9 本征问题 --- slide = prs.slides.add_slide(prs.slide_layouts[LAYOUT_TITLE_CONTENT]) set_title(slide, "三维本征频率问题(FemType = 5)") add_bullets(slide, Inches(0.35), Inches(1.4), Inches(5.5), Inches(5.5), [ "广义本征:A·x = λ·B·x", "A = curl-curl(刚度),B = ε 质量矩阵", "边界:全 PMC(与 MATLAB 3D一阶本征问题2 一致)", "C++:Assemble + Test_OutputMatrix", "导出:Ai/Aj/Av、Bi/Bj/Bv(COO,0-based)", "求解:MATLAB eigs(A,B) + get_ele 绘图", "待接:C++ Run/Post", ]) add_picture(slide, REF_IMGS / "image27.png", Inches(6.0), Inches(1.5), width=Inches(6.8)) # --- 10 本征矩阵验证 --- slide = prs.slides.add_slide(prs.slide_layouts[LAYOUT_TITLE_CONTENT]) set_title(slide, "本征矩阵组装验证(C++ vs MATLAB)") add_picture(slide, ASSETS / "metrics.png", Inches(0.4), Inches(1.4), width=Inches(6.5)) add_bullets(slide, Inches(6.8), Inches(1.5), Inches(6), Inches(5), [ "Av.txt 约 1.29×10⁶ 行非零元", "C++ OutFile 与 MATLAB OutFile 逐行一致", "Bi/Bj/Bv 约 43632 非零(质量矩阵)", "FemType=5 返回码:5", "下一步:接入 Solver_EigenFreq", ], size=14) # --- 11 求解器 --- slide = prs.slides.add_slide(prs.slide_layouts[LAYOUT_TITLE_CONTENT]) set_title(slide, "求解器体系") add_picture(slide, REF_IMGS / "image12.png", Inches(0.4), Inches(1.5), width=Inches(5.5)) add_bullets(slide, Inches(6.2), Inches(1.5), Inches(6.5), Inches(5), [ "solver/:接口层(编进 OpticsFEM.exe)", "散射:interface.cpp → complexsolver.exe", "complexsolver:Eigen SparseLU / BiCGSTAB", "路径:Release/complex/complexsolver.exe", "本征 3D:当前 MATLAB eigs 验证", "优势:无需 PETSc,CMake 一键编译", ], size=13) # --- 12 总结 --- slide = prs.slides.add_slide(prs.slide_layouts[LAYOUT_TITLE_CONTENT]) set_title(slide, "总结与下一步") add_bullets(slide, Inches(0.4), Inches(1.4), Inches(6), Inches(5.5), [ "【已完成】2D→3D 一阶 Nedelec 框架扩展", "【已完成】3D SBC 散射端到端可运行、数值可靠", "【已完成】3D 本征 A/B 组装与 MATLAB 严格对齐", "【待完成】本征 C++ 求解与后处理接入", "【待完成】PBC、二阶基等扩展(MATLAB 已有)", "【待完成】Test_Main 支持命令行指定 JSON", ], size=14) add_picture(slide, ASSETS / "normE_z050.png", Inches(6.5), Inches(1.5), width=Inches(6.0)) # --- 13 致谢 --- slide = prs.slides.add_slide(prs.slide_layouts[LAYOUT_TITLE_CONTENT]) set_title(slide, "下一步工作") add_textbox( slide, Inches(1.5), Inches(2.5), Inches(10), Inches(2.5), "1. COMSOL 数值对比出图(compare 脚本)\n" "2. FemType=5 接入 Run/Post,完成 C++ 本征闭环\n" "3. 扩展 PBC / 二阶基边界条件\n" "4. 算例自动化与文档完善\n\n" "谢谢!", size=22, bold=False, ) def main(): print("Preparing assets...") ensure_assets() if not REF_PPT.exists(): raise FileNotFoundError(f"Reference PPT not found: {REF_PPT}") print(f"Loading template: {REF_PPT}") prs = Presentation(str(REF_PPT)) build(prs) prs.save(str(OUT_PATH)) print(f"Saved: {OUT_PATH}") if __name__ == "__main__": main()