660 lines
24 KiB
Python
660 lines
24 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
生成「物理模块 + 神经网络层 深度耦合」架构图的可编辑 PPT 素材。
|
||
每个子元素单独一页,文字均为可编辑文本框;另含一页完整拼装参考。
|
||
|
||
运行: python generate_physics_nn_diagram.py
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import math
|
||
import random
|
||
from pathlib import Path
|
||
|
||
from pptx import Presentation
|
||
from pptx.dml.color import RGBColor
|
||
from pptx.enum.dml import MSO_LINE_DASH_STYLE
|
||
from pptx.enum.shapes import MSO_CONNECTOR, MSO_SHAPE
|
||
from pptx.enum.text import MSO_ANCHOR, PP_ALIGN
|
||
from pptx.util import Inches, Pt
|
||
|
||
OUT_DIR = Path(__file__).resolve().parent
|
||
OUT_PPTX = OUT_DIR / "物理神经网络耦合图_可编辑素材.pptx"
|
||
|
||
# 配色(贴近原图)
|
||
C_BG = RGBColor(0x0A, 0x0A, 0x0A)
|
||
C_WHITE = RGBColor(0xFF, 0xFF, 0xFF)
|
||
C_BLACK = RGBColor(0x00, 0x00, 0x00)
|
||
C_BLUE = RGBColor(0x4A, 0x86, 0xC8)
|
||
C_BLUE_LT = RGBColor(0x6D, 0xA3, 0xE8)
|
||
C_TEAL = RGBColor(0x3D, 0xA8, 0x9A)
|
||
C_GREY = RGBColor(0xB0, 0xB0, 0xB0)
|
||
C_GREY_DK = RGBColor(0x55, 0x55, 0x55)
|
||
C_GREY_LT = RGBColor(0xD9, 0xD9, 0xD9)
|
||
C_LINE = RGBColor(0x88, 0x88, 0x88)
|
||
|
||
FONT = "Microsoft YaHei"
|
||
SLIDE_W = Inches(13.333)
|
||
SLIDE_H = Inches(7.5)
|
||
|
||
|
||
def new_prs() -> Presentation:
|
||
prs = Presentation()
|
||
prs.slide_width = SLIDE_W
|
||
prs.slide_height = SLIDE_H
|
||
return prs
|
||
|
||
|
||
def blank_slide(prs: Presentation, dark: bool = False):
|
||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||
if dark:
|
||
fill = slide.background.fill
|
||
fill.solid()
|
||
fill.fore_color.rgb = C_BG
|
||
return slide
|
||
|
||
|
||
def add_label(slide, left, top, width, height, text, size=14, bold=False,
|
||
color=C_BLACK, align=PP_ALIGN.CENTER, wrap=True):
|
||
box = slide.shapes.add_textbox(left, top, width, height)
|
||
tf = box.text_frame
|
||
tf.word_wrap = wrap
|
||
tf.vertical_anchor = MSO_ANCHOR.MIDDLE
|
||
p = tf.paragraphs[0]
|
||
p.text = text
|
||
p.alignment = align
|
||
p.font.name = FONT
|
||
p.font.size = Pt(size)
|
||
p.font.bold = bold
|
||
p.font.color.rgb = color
|
||
return box
|
||
|
||
|
||
def add_round_box(slide, left, top, width, height, fill=C_WHITE, line=C_BLACK,
|
||
line_w=1.0, radius_hint=0.08):
|
||
shape = slide.shapes.add_shape(
|
||
MSO_SHAPE.ROUNDED_RECTANGLE, left, top, width, height
|
||
)
|
||
shape.fill.solid()
|
||
shape.fill.fore_color.rgb = fill
|
||
shape.line.color.rgb = line
|
||
shape.line.width = Pt(line_w)
|
||
# 圆角
|
||
if hasattr(shape, "adjustments") and shape.adjustments:
|
||
shape.adjustments[0] = radius_hint
|
||
return shape
|
||
|
||
|
||
def add_oval(slide, left, top, size, fill, line=None, line_w=1.0):
|
||
shape = slide.shapes.add_shape(MSO_SHAPE.OVAL, left, top, size, size)
|
||
shape.fill.solid()
|
||
shape.fill.fore_color.rgb = fill
|
||
if line:
|
||
shape.line.color.rgb = line
|
||
shape.line.width = Pt(line_w)
|
||
else:
|
||
shape.line.fill.background()
|
||
return shape
|
||
|
||
|
||
def add_line(slide, x1, y1, x2, y2, color=C_LINE, width=0.75, dash=None):
|
||
conn = slide.shapes.add_connector(MSO_CONNECTOR.STRAIGHT, x1, y1, x2, y2)
|
||
conn.line.color.rgb = color
|
||
conn.line.width = Pt(width)
|
||
if dash:
|
||
conn.line.dash_style = dash
|
||
return conn
|
||
|
||
|
||
def add_arrow_down(slide, cx, y1, y2, color=C_WHITE, dash=False):
|
||
add_line(slide, cx, y1, cx, y2 - Inches(0.08), color, 1.2,
|
||
MSO_LINE_DASH_STYLE.DASH if dash else None)
|
||
tri = slide.shapes.add_shape(
|
||
MSO_SHAPE.ISOSCELES_TRIANGLE,
|
||
cx - Inches(0.06), y2 - Inches(0.12), Inches(0.12), Inches(0.12)
|
||
)
|
||
tri.rotation = 180
|
||
tri.fill.solid()
|
||
tri.fill.fore_color.rgb = color
|
||
tri.line.fill.background()
|
||
return tri
|
||
|
||
|
||
def add_arrow_up(slide, cx, y1, y2, color=C_WHITE):
|
||
add_line(slide, cx, y1 + Inches(0.08), cx, y2, color, 1.2)
|
||
tri = slide.shapes.add_shape(
|
||
MSO_SHAPE.ISOSCELES_TRIANGLE,
|
||
cx - Inches(0.06), y1, Inches(0.12), Inches(0.12)
|
||
)
|
||
tri.fill.solid()
|
||
tri.fill.fore_color.rgb = color
|
||
tri.line.fill.background()
|
||
|
||
|
||
def add_arrow_right(slide, x1, y, x2, color=C_WHITE):
|
||
add_line(slide, x1, y, x2 - Inches(0.08), y, color, 1.5)
|
||
tri = slide.shapes.add_shape(
|
||
MSO_SHAPE.ISOSCELES_TRIANGLE,
|
||
x2 - Inches(0.12), y - Inches(0.06), Inches(0.12), Inches(0.12)
|
||
)
|
||
tri.rotation = 90
|
||
tri.fill.solid()
|
||
tri.fill.fore_color.rgb = color
|
||
tri.line.fill.background()
|
||
|
||
|
||
def add_double_arrow_h(slide, x1, y1, x2, y2=None, color=C_WHITE):
|
||
if y2 is None:
|
||
y2 = y1
|
||
add_line(slide, x1 + Inches(0.1), y1, x2 - Inches(0.1), y2, color, 1.5)
|
||
for x, rot in ((x1, 270), (x2 - Inches(0.12), 90)):
|
||
tri = slide.shapes.add_shape(
|
||
MSO_SHAPE.ISOSCELES_TRIANGLE,
|
||
x, y1 - Inches(0.06), Inches(0.12), Inches(0.12)
|
||
)
|
||
tri.rotation = rot
|
||
tri.fill.solid()
|
||
tri.fill.fore_color.rgb = color
|
||
tri.line.fill.background()
|
||
|
||
|
||
def draw_mesh_icon(slide, cx, cy, w, h, color=C_GREY):
|
||
"""简化 3D 网格图标"""
|
||
pts = [
|
||
(cx - w * 0.35, cy + h * 0.2),
|
||
(cx, cy - h * 0.35),
|
||
(cx + w * 0.35, cy + h * 0.2),
|
||
(cx, cy + h * 0.35),
|
||
]
|
||
for i in range(4):
|
||
x1, y1 = pts[i]
|
||
x2, y2 = pts[(i + 1) % 4]
|
||
add_line(slide, x1, y1, x2, y2, color, 1.0)
|
||
add_line(slide, pts[0][0], pts[0][1], pts[2][0], pts[2][1], color, 0.8)
|
||
add_line(slide, pts[1][0], pts[1][1], pts[3][0], pts[3][1], color, 0.8)
|
||
for i in range(3):
|
||
t = (i + 1) / 4
|
||
mx = pts[0][0] + t * (pts[2][0] - pts[0][0])
|
||
my = pts[0][1] + t * (pts[2][1] - pts[0][1])
|
||
add_line(slide, mx, my, pts[1][0], pts[1][1], color, 0.6)
|
||
|
||
|
||
def draw_radar_icon(slide, cx, cy, size, color=C_GREY):
|
||
base = slide.shapes.add_shape(
|
||
MSO_SHAPE.OVAL, cx - size * 0.15, cy + size * 0.05,
|
||
size * 0.3, size * 0.15
|
||
)
|
||
base.fill.solid()
|
||
base.fill.fore_color.rgb = color
|
||
base.line.fill.background()
|
||
dish = slide.shapes.add_shape(
|
||
MSO_SHAPE.OVAL, cx - size * 0.35, cy - size * 0.25,
|
||
size * 0.7, size * 0.35
|
||
)
|
||
dish.fill.background()
|
||
dish.line.color.rgb = color
|
||
dish.line.width = Pt(1.2)
|
||
for ang in (-25, 0, 25):
|
||
rad = math.radians(ang - 90)
|
||
add_line(
|
||
slide, cx, cy,
|
||
cx + math.cos(rad) * size * 0.55,
|
||
cy + math.sin(rad) * size * 0.55,
|
||
color, 0.8, MSO_LINE_DASH_STYLE.DASH
|
||
)
|
||
|
||
|
||
def draw_field_icon(slide, cx, cy, w, h, color=C_GREY):
|
||
rect = slide.shapes.add_shape(
|
||
MSO_SHAPE.RECTANGLE, cx - w / 2, cy - h / 2, w, h
|
||
)
|
||
rect.fill.background()
|
||
rect.line.color.rgb = color
|
||
rect.line.width = Pt(1.0)
|
||
for i in range(4):
|
||
y = cy - h / 2 + h * (i + 1) / 5
|
||
add_line(slide, cx - w / 2, y, cx + w / 2, y, color, 0.5)
|
||
for i in range(4):
|
||
x = cx - w / 2 + w * (i + 1) / 5
|
||
add_line(slide, x, cy - h / 2, x, cy + h / 2, color, 0.5)
|
||
peak = add_oval(slide, cx - Inches(0.08), cy - Inches(0.08), Inches(0.16), C_GREY_DK)
|
||
|
||
|
||
def draw_wave_icon(slide, cx, cy, w, color=C_GREY):
|
||
for i, phase in enumerate((0, 0.4, 0.8)):
|
||
pts = []
|
||
steps = 20
|
||
for s in range(steps + 1):
|
||
t = s / steps
|
||
x = cx - w / 2 + w * t
|
||
y = cy + math.sin(t * math.pi * 2 + phase) * Inches(0.12)
|
||
pts.append((x, y))
|
||
for j in range(len(pts) - 1):
|
||
add_line(slide, pts[j][0], pts[j][1], pts[j + 1][0], pts[j + 1][1],
|
||
color, 1.0 if i == 1 else 0.7)
|
||
|
||
|
||
def draw_beam_icon(slide, cx, cy, size, color=C_GREY):
|
||
add_line(slide, cx - size * 0.3, cy, cx, cy, color, 1.2)
|
||
for ang in (-35, -15, 15, 35):
|
||
rad = math.radians(ang)
|
||
add_line(slide, cx, cy, cx + math.cos(rad) * size * 0.45,
|
||
cy + math.sin(rad) * size * 0.45, color, 1.0)
|
||
|
||
|
||
def draw_boundary_icon(slide, cx, cy, size, color=C_GREY):
|
||
add_line(slide, cx, cy - size * 0.35, cx, cy + size * 0.35, color, 1.2,
|
||
MSO_LINE_DASH_STYLE.DASH)
|
||
add_line(slide, cx - size * 0.35, cy, cx + size * 0.1, cy - size * 0.15, color, 1.2)
|
||
tri = slide.shapes.add_shape(
|
||
MSO_SHAPE.ISOSCELES_TRIANGLE,
|
||
cx + size * 0.05, cy - size * 0.22, Inches(0.1), Inches(0.1)
|
||
)
|
||
tri.rotation = 120
|
||
tri.fill.solid()
|
||
tri.fill.fore_color.rgb = color
|
||
tri.line.fill.background()
|
||
|
||
|
||
def draw_scale_icon(slide, cx, cy, w, color=C_GREY):
|
||
add_line(slide, cx, cy + Inches(0.15), cx, cy - Inches(0.25), color, 1.5)
|
||
add_line(slide, cx - w / 2, cy - Inches(0.25), cx + w / 2, cy - Inches(0.25), color, 1.5)
|
||
for dx in (-w / 2, w / 2):
|
||
pan = slide.shapes.add_shape(
|
||
MSO_SHAPE.OVAL, cx + dx - Inches(0.12), cy - Inches(0.05),
|
||
Inches(0.24), Inches(0.12)
|
||
)
|
||
pan.fill.background()
|
||
pan.line.color.rgb = color
|
||
pan.line.width = Pt(1.0)
|
||
|
||
|
||
def slide_title_bar(slide, title: str):
|
||
add_label(slide, Inches(0.4), Inches(0.15), Inches(12.5), Inches(0.45),
|
||
title, size=18, bold=True, color=C_GREY_DK, align=PP_ALIGN.LEFT)
|
||
|
||
|
||
# ---------- 各子元素 ----------
|
||
|
||
def make_top_box_1(slide):
|
||
slide_title_bar(slide, "01 · 顶部算子框 — 连续方程算子")
|
||
w, h = Inches(2.6), Inches(1.35)
|
||
x, y = Inches(5.4), Inches(2.8)
|
||
add_round_box(slide, x, y, w, h)
|
||
add_label(slide, x, y + Inches(0.08), w, Inches(0.4),
|
||
"连续方程算子", size=16, bold=True)
|
||
add_label(slide, x, y + Inches(0.45), w, Inches(0.75),
|
||
"∇·(ρv) + ∂ρ/∂t = 0", size=13, color=C_GREY_DK)
|
||
add_arrow_down(slide, x + w / 2, y + h, y + h + Inches(0.55), dash=True)
|
||
|
||
|
||
def make_top_box_2(slide):
|
||
slide_title_bar(slide, "02 · 顶部算子框 — 边界条件")
|
||
w, h = Inches(2.6), Inches(1.35)
|
||
x, y = Inches(5.4), Inches(2.8)
|
||
add_round_box(slide, x, y, w, h)
|
||
add_label(slide, x, y + Inches(0.08), w, Inches(0.4),
|
||
"边界条件", size=16, bold=True)
|
||
add_label(slide, x, y + Inches(0.45), w, Inches(0.75),
|
||
"B(u) = g 或 n×E = 0", size=13, color=C_GREY_DK)
|
||
add_arrow_down(slide, x + w / 2, y + h, y + h + Inches(0.55), dash=True)
|
||
|
||
|
||
def make_top_box_3(slide):
|
||
slide_title_bar(slide, "03 · 顶部算子框 — 传播/散射算子")
|
||
w, h = Inches(2.6), Inches(1.35)
|
||
x, y = Inches(5.4), Inches(2.8)
|
||
add_round_box(slide, x, y, w, h)
|
||
add_label(slide, x, y + Inches(0.08), w, Inches(0.4),
|
||
"传播/散射算子", size=16, bold=True)
|
||
add_label(slide, x, y + Inches(0.45), w, Inches(0.75),
|
||
"∇²E + k²E = 0", size=13, color=C_GREY_DK)
|
||
add_arrow_down(slide, x + w / 2, y + h, y + h + Inches(0.55), dash=True)
|
||
|
||
|
||
def make_top_box_4(slide):
|
||
slide_title_bar(slide, "04 · 顶部算子框 — 守恒约束")
|
||
w, h = Inches(2.6), Inches(1.35)
|
||
x, y = Inches(5.4), Inches(2.8)
|
||
add_round_box(slide, x, y, w, h)
|
||
add_label(slide, x, y + Inches(0.08), w, Inches(0.4),
|
||
"守恒约束", size=16, bold=True)
|
||
add_label(slide, x, y + Inches(0.45), w, Inches(0.75),
|
||
"能量 / 动量 / 质量守恒", size=13, color=C_GREY_DK)
|
||
add_arrow_down(slide, x + w / 2, y + h, y + h + Inches(0.55), dash=True)
|
||
|
||
|
||
def make_left_geometry(slide):
|
||
slide_title_bar(slide, "05 · 左侧 — 几何网格 / 材料分布")
|
||
w, h = Inches(1.55), Inches(3.2)
|
||
x, y = Inches(5.0), Inches(2.0)
|
||
add_round_box(slide, x, y, w, h)
|
||
add_label(slide, x, y + Inches(0.08), w, Inches(0.35),
|
||
"几何规则约束", size=11, bold=True)
|
||
draw_mesh_icon(slide, x + w / 2, y + h * 0.45, w, h * 0.55)
|
||
add_label(slide, x, y + h - Inches(0.55), w, Inches(0.45),
|
||
"几何网格\n材料分布", size=12, bold=True)
|
||
add_double_arrow_h(slide, x + w, y + h / 2, x + w + Inches(0.55), y + h / 2)
|
||
|
||
|
||
def make_neural_network(slide):
|
||
slide_title_bar(slide, "06 · 中部 — 神经网络层(6层)")
|
||
layer_colors = [C_BLUE, C_GREY_DK, C_TEAL, C_WHITE, C_GREY_DK, C_BLUE_LT]
|
||
neuron_colors = [C_BLUE, C_GREY, C_TEAL, C_GREY_LT, C_GREY, C_BLUE_LT]
|
||
n_layers = 6
|
||
n_neurons = 7
|
||
layer_w = Inches(0.55)
|
||
gap = Inches(0.18)
|
||
total_w = n_layers * layer_w + (n_layers - 1) * gap
|
||
x0 = (SLIDE_W - total_w) / 2
|
||
y0 = Inches(1.8)
|
||
layer_h = Inches(3.6)
|
||
neuron_r = Inches(0.11)
|
||
neuron_gap = (layer_h - Inches(0.5)) / (n_neurons - 1)
|
||
|
||
layer_centers = []
|
||
neuron_positions = []
|
||
|
||
for li in range(n_layers):
|
||
lx = x0 + li * (layer_w + gap)
|
||
add_round_box(slide, lx, y0, layer_w, layer_h,
|
||
fill=RGBColor(0xFF, 0xFF, 0xFF), line=layer_colors[li], line_w=1.5)
|
||
cx = lx + layer_w / 2
|
||
layer_centers.append(cx)
|
||
positions = []
|
||
ny = y0 + Inches(0.25)
|
||
for ni in range(n_neurons):
|
||
if ni == n_neurons // 2:
|
||
add_label(slide, cx - Inches(0.08), ny - Inches(0.05),
|
||
Inches(0.16), Inches(0.2), "⋮", size=14, color=C_GREY_DK)
|
||
ny += neuron_gap
|
||
continue
|
||
add_oval(slide, cx - neuron_r, ny - neuron_r, neuron_r * 2,
|
||
neuron_colors[li], line=layer_colors[li], line_w=0.8)
|
||
positions.append((cx, ny))
|
||
ny += neuron_gap
|
||
neuron_positions.append(positions)
|
||
|
||
random.seed(42)
|
||
for li in range(n_layers - 1):
|
||
for (x1, y1) in neuron_positions[li]:
|
||
for (x2, y2) in neuron_positions[li + 1]:
|
||
if random.random() < 0.55:
|
||
add_line(slide, x1, y1, x2, y2, C_LINE, 0.4)
|
||
|
||
|
||
def make_right_decoder(slide):
|
||
slide_title_bar(slide, "07 · 右侧 — 物理解码器(算子输出)")
|
||
w, h = Inches(1.55), Inches(3.2)
|
||
x, y = Inches(6.8), Inches(2.0)
|
||
add_round_box(slide, x, y, w, h)
|
||
add_label(slide, x, y + Inches(0.06), w, Inches(0.55),
|
||
"物理解码器\n(算子输出)", size=11, bold=True)
|
||
draw_radar_icon(slide, x + w / 2, y + Inches(1.05), Inches(0.9))
|
||
draw_field_icon(slide, x + w / 2, y + Inches(2.0), Inches(1.0), Inches(0.75))
|
||
add_label(slide, x, y + h - Inches(0.45), w, Inches(0.35),
|
||
"... 多源输出", size=11, color=C_GREY_DK)
|
||
add_arrow_right(slide, x - Inches(0.55), y + h / 2, x)
|
||
|
||
|
||
def make_bottom_box_1(slide):
|
||
slide_title_bar(slide, "08 · 底部算子 — 载荷频率算子")
|
||
w, h = Inches(1.7), Inches(1.15)
|
||
x, y = Inches(5.8), Inches(3.0)
|
||
add_round_box(slide, x, y, w, h)
|
||
draw_beam_icon(slide, x + w / 2, y + Inches(0.35), Inches(0.7))
|
||
add_label(slide, x, y + Inches(0.55), w, Inches(0.5),
|
||
"载荷频率\n算子", size=12, bold=True)
|
||
add_arrow_up(slide, x + w / 2, y, y - Inches(0.5))
|
||
|
||
|
||
def make_bottom_box_2(slide):
|
||
slide_title_bar(slide, "09 · 底部算子 — 边界条件")
|
||
w, h = Inches(1.7), Inches(1.15)
|
||
x, y = Inches(5.8), Inches(3.0)
|
||
add_round_box(slide, x, y, w, h)
|
||
draw_boundary_icon(slide, x + w / 2, y + Inches(0.35), Inches(0.75))
|
||
add_label(slide, x, y + Inches(0.55), w, Inches(0.5),
|
||
"边界条件", size=12, bold=True)
|
||
add_arrow_up(slide, x + w / 2, y, y - Inches(0.5))
|
||
|
||
|
||
def make_bottom_box_3(slide):
|
||
slide_title_bar(slide, "10 · 底部算子 — 传播/散射算子")
|
||
w, h = Inches(1.7), Inches(1.15)
|
||
x, y = Inches(5.8), Inches(3.0)
|
||
add_round_box(slide, x, y, w, h)
|
||
draw_wave_icon(slide, x + w / 2, y + Inches(0.35), Inches(0.85))
|
||
add_label(slide, x, y + Inches(0.55), w, Inches(0.5),
|
||
"传播/散射\n算子", size=12, bold=True)
|
||
add_arrow_up(slide, x + w / 2, y, y - Inches(0.5))
|
||
|
||
|
||
def make_bottom_box_4(slide):
|
||
slide_title_bar(slide, "11 · 底部算子 — 守恒约束")
|
||
w, h = Inches(1.7), Inches(1.15)
|
||
x, y = Inches(5.8), Inches(3.0)
|
||
add_round_box(slide, x, y, w, h)
|
||
draw_scale_icon(slide, x + w / 2, y + Inches(0.38), Inches(0.75))
|
||
add_label(slide, x, y + Inches(0.55), w, Inches(0.5),
|
||
"守恒约束", size=12, bold=True)
|
||
add_arrow_up(slide, x + w / 2, y, y - Inches(0.5))
|
||
|
||
|
||
def make_footer_title(slide):
|
||
slide_title_bar(slide, "12 · 底部标题条")
|
||
w, h = Inches(8.5), Inches(0.75)
|
||
x, y = Inches(2.4), Inches(3.2)
|
||
box = add_round_box(slide, x, y, w, h, fill=C_WHITE, line=C_TEAL, line_w=2.0)
|
||
add_label(slide, x, y, w, h,
|
||
"物理模块 + 神经网络层 深度耦合", size=22, bold=True, color=C_TEAL)
|
||
|
||
|
||
def make_connection_lines(slide):
|
||
slide_title_bar(slide, "13 · 连接线与反馈回路")
|
||
y = Inches(3.5)
|
||
add_line(slide, Inches(1.5), y, Inches(11.8), y, C_WHITE, 1.2,
|
||
MSO_LINE_DASH_STYLE.DASH)
|
||
add_arrow_down(slide, Inches(3.5), Inches(2.5), Inches(3.5), dash=False)
|
||
add_arrow_down(slide, Inches(6.5), Inches(2.5), Inches(6.5), dash=False)
|
||
add_arrow_down(slide, Inches(9.5), Inches(2.5), Inches(9.5), dash=False)
|
||
add_label(slide, Inches(1.0), Inches(4.0), Inches(11.0), Inches(0.5),
|
||
"(虚线底栏 + 各层虚线箭头,可按需复制到拼装页)",
|
||
size=12, color=C_GREY_DK)
|
||
|
||
|
||
def make_full_assembly(slide):
|
||
"""完整拼装参考页(深色背景)"""
|
||
fill = slide.background.fill
|
||
fill.solid()
|
||
fill.fore_color.rgb = C_BG
|
||
|
||
add_label(slide, Inches(0.3), Inches(0.1), Inches(12.7), Inches(0.4),
|
||
"完整拼装参考(所有元素可从此页复制,或从前页分别插入)",
|
||
size=14, bold=True, color=C_WHITE, align=PP_ALIGN.LEFT)
|
||
|
||
# 顶部四框
|
||
top_y = Inches(0.55)
|
||
top_specs = [
|
||
("连续方程算子", "∇·(ρv)+∂ρ/∂t=0"),
|
||
("边界条件", "B(u)=g"),
|
||
("传播/散射算子", "∇²E+k²E=0"),
|
||
("守恒约束", "能量/动量/质量守恒"),
|
||
]
|
||
tw, th = Inches(2.35), Inches(0.95)
|
||
tx0 = Inches(0.55)
|
||
tg = Inches(0.28)
|
||
top_xs = []
|
||
for i, (title, formula) in enumerate(top_specs):
|
||
tx = tx0 + i * (tw + tg)
|
||
top_xs.append(tx + tw / 2)
|
||
add_round_box(slide, tx, top_y, tw, th, fill=C_WHITE)
|
||
add_label(slide, tx, top_y + Inches(0.04), tw, Inches(0.32),
|
||
title, size=10, bold=True, color=C_BLACK)
|
||
add_label(slide, tx, top_y + Inches(0.36), tw, Inches(0.5),
|
||
formula, size=9, color=C_GREY_DK)
|
||
|
||
# 神经网络区域
|
||
nn_y0 = Inches(1.65)
|
||
nn_h = Inches(2.55)
|
||
layer_colors = [C_BLUE, C_GREY_DK, C_TEAL, C_WHITE, C_GREY_DK, C_BLUE_LT]
|
||
neuron_colors = [C_BLUE, C_GREY, C_TEAL, C_GREY_LT, C_GREY, C_BLUE_LT]
|
||
n_layers, n_neurons = 6, 5
|
||
layer_w = Inches(0.42)
|
||
gap = Inches(0.12)
|
||
nn_x0 = Inches(3.05)
|
||
neuron_positions = []
|
||
layer_centers = []
|
||
|
||
for li in range(n_layers):
|
||
lx = nn_x0 + li * (layer_w + gap)
|
||
add_round_box(slide, lx, nn_y0, layer_w, nn_h,
|
||
fill=RGBColor(0x18, 0x18, 0x18), line=layer_colors[li], line_w=1.2)
|
||
cx = lx + layer_w / 2
|
||
layer_centers.append(cx)
|
||
positions = []
|
||
ny = nn_y0 + Inches(0.18)
|
||
ngap = (nn_h - Inches(0.36)) / (n_neurons - 1)
|
||
nr = Inches(0.07)
|
||
for ni in range(n_neurons):
|
||
if ni == n_neurons // 2:
|
||
add_label(slide, cx - Inches(0.06), ny - Inches(0.04),
|
||
Inches(0.12), Inches(0.15), "⋮", size=10, color=C_GREY)
|
||
ny += ngap
|
||
continue
|
||
add_oval(slide, cx - nr, ny - nr, nr * 2, neuron_colors[li],
|
||
line=layer_colors[li], line_w=0.6)
|
||
positions.append((cx, ny))
|
||
ny += ngap
|
||
neuron_positions.append(positions)
|
||
|
||
random.seed(7)
|
||
for li in range(n_layers - 1):
|
||
for p1 in neuron_positions[li]:
|
||
for p2 in neuron_positions[li + 1]:
|
||
if random.random() < 0.5:
|
||
add_line(slide, p1[0], p1[1], p2[0], p2[1], C_LINE, 0.35)
|
||
|
||
# 顶部虚线箭头
|
||
for i, cx in enumerate([layer_centers[0], layer_centers[1],
|
||
layer_centers[3], layer_centers[5]]):
|
||
add_arrow_down(slide, cx, top_y + th, nn_y0, color=C_WHITE, dash=True)
|
||
|
||
# 左侧
|
||
lw, lh = Inches(1.15), Inches(2.55)
|
||
lx, ly = Inches(1.55), nn_y0
|
||
add_round_box(slide, lx, ly, lw, lh, fill=RGBColor(0x18, 0x18, 0x18), line=C_WHITE)
|
||
add_label(slide, lx, ly + Inches(0.04), lw, Inches(0.3),
|
||
"几何规则约束", size=8, bold=True, color=C_WHITE)
|
||
draw_mesh_icon(slide, lx + lw / 2, ly + lh * 0.45, lw, lh * 0.5, C_GREY)
|
||
add_label(slide, lx, ly + lh - Inches(0.42), lw, Inches(0.38),
|
||
"几何网格\n材料分布", size=9, bold=True, color=C_WHITE)
|
||
add_double_arrow_h(slide, lx + lw, ly + lh / 2, nn_x0, ly + lh / 2)
|
||
|
||
# 右侧
|
||
rx = Inches(9.65)
|
||
add_round_box(slide, rx, ly, lw, lh, fill=RGBColor(0x18, 0x18, 0x18), line=C_WHITE)
|
||
add_label(slide, rx, ly + Inches(0.04), lw, Inches(0.42),
|
||
"物理解码器\n(算子输出)", size=8, bold=True, color=C_WHITE)
|
||
draw_radar_icon(slide, rx + lw / 2, ly + Inches(0.95), Inches(0.65), C_GREY)
|
||
draw_field_icon(slide, rx + lw / 2, ly + Inches(1.75), Inches(0.7), Inches(0.5), C_GREY)
|
||
add_label(slide, rx, ly + lh - Inches(0.32), lw, Inches(0.28),
|
||
"... 多源输出", size=8, color=C_GREY)
|
||
add_arrow_right(slide, layer_centers[-1] + layer_w / 2 + Inches(0.05),
|
||
ly + lh / 2, rx)
|
||
|
||
# 底部四框
|
||
bot_y = Inches(4.55)
|
||
bw, bh = Inches(1.45), Inches(0.85)
|
||
bot_specs = [
|
||
("载荷频率\n算子", draw_beam_icon),
|
||
("边界条件", draw_boundary_icon),
|
||
("传播/散射\n算子", draw_wave_icon),
|
||
("守恒约束", draw_scale_icon),
|
||
]
|
||
bx0 = Inches(2.15)
|
||
bg = Inches(0.22)
|
||
bot_centers = []
|
||
for i, (txt, icon_fn) in enumerate(bot_specs):
|
||
bx = bx0 + i * (bw + bg)
|
||
bot_centers.append(bx + bw / 2)
|
||
add_round_box(slide, bx, bot_y, bw, bh, fill=C_WHITE)
|
||
if icon_fn == draw_wave_icon:
|
||
icon_fn(slide, bx + bw / 2, bot_y + Inches(0.28), Inches(0.65))
|
||
elif icon_fn == draw_scale_icon:
|
||
icon_fn(slide, bx + bw / 2, bot_y + Inches(0.3), Inches(0.6))
|
||
else:
|
||
icon_fn(slide, bx + bw / 2, bot_y + Inches(0.28), Inches(0.6))
|
||
add_label(slide, bx, bot_y + Inches(0.42), bw, Inches(0.4),
|
||
txt, size=9, bold=True, color=C_BLACK)
|
||
|
||
for cx, li in zip(bot_centers, [0, 1, 3, 5]):
|
||
add_arrow_up(slide, cx, bot_y, nn_y0 + nn_h, color=C_WHITE)
|
||
|
||
# 底栏虚线
|
||
dash_y = Inches(5.55)
|
||
add_line(slide, Inches(1.3), dash_y, Inches(12.0), dash_y, C_WHITE, 1.0,
|
||
MSO_LINE_DASH_STYLE.DASH)
|
||
|
||
# 底部标题
|
||
fw, fh = Inches(7.5), Inches(0.6)
|
||
fx = (SLIDE_W - fw) / 2
|
||
fy = Inches(6.15)
|
||
add_round_box(slide, fx, fy, fw, fh, fill=RGBColor(0x18, 0x18, 0x18),
|
||
line=C_TEAL, line_w=2.0)
|
||
add_label(slide, fx, fy, fw, fh,
|
||
"物理模块 + 神经网络层 深度耦合", size=18, bold=True, color=C_TEAL)
|
||
|
||
|
||
def make_usage_slide(slide):
|
||
slide_title_bar(slide, "使用说明")
|
||
items = [
|
||
"本文件共 15 页:第 1–13 页为独立子元素,第 14 页为完整拼装参考,第 15 页为说明。",
|
||
"所有中文、公式均为可编辑文本框,双击即可修改字体与内容。",
|
||
"建议做法:打开对应页 → Ctrl+A 全选 → 复制 → 粘贴到目标 PPT。",
|
||
"图标由 PPT 基本形状组合而成,可在「选择窗格」中单独调整。",
|
||
"若需公式编辑器格式,可选中文本后 插入 → 公式 重新录入。",
|
||
"神经网络连线为独立线条,可在选择窗格中批量删除或重画。",
|
||
]
|
||
y = Inches(1.2)
|
||
for item in items:
|
||
add_label(slide, Inches(0.8), y, Inches(11.8), Inches(0.55),
|
||
"• " + item, size=14, color=C_GREY_DK, align=PP_ALIGN.LEFT)
|
||
y += Inches(0.62)
|
||
|
||
|
||
def build():
|
||
prs = new_prs()
|
||
makers = [
|
||
make_top_box_1,
|
||
make_top_box_2,
|
||
make_top_box_3,
|
||
make_top_box_4,
|
||
make_left_geometry,
|
||
make_neural_network,
|
||
make_right_decoder,
|
||
make_bottom_box_1,
|
||
make_bottom_box_2,
|
||
make_bottom_box_3,
|
||
make_bottom_box_4,
|
||
make_footer_title,
|
||
make_connection_lines,
|
||
]
|
||
for fn in makers:
|
||
slide = blank_slide(prs, dark=False)
|
||
fn(slide)
|
||
|
||
full = blank_slide(prs, dark=True)
|
||
make_full_assembly(full)
|
||
|
||
usage = blank_slide(prs, dark=False)
|
||
make_usage_slide(usage)
|
||
|
||
prs.save(str(OUT_PPTX))
|
||
print(f"已生成: {OUT_PPTX}")
|
||
print(f"共 {len(prs.slides)} 页幻灯片")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
build()
|