Files
IOT_SIM/runtime/Sim.WinForms/Resources/project8.py
T
xuyueandClaude Opus 5.5 65a1b3a81e
CI/CD / test (push) Canceled after 0s
CI/CD / deploy (push) Canceled after 0s
项目 8 智慧校园管理平台:5 个任务的示例、自绘素材、WisdomCampus 数据库
- 示例 p8-t1…t5(代码逐个累加):主界面、校园环境界面、环境采集与分析(路灯手动 / 自动控制、
  LED 推送、自动推送火情、7 天温度图表)、校园安防(入侵提示、摄像机方向、拍照入库)、历史图片
  (按日期查询、左右切换),各有评分规则
- 按书补全参考工程里空着的"推送LED信息""自动控制",给"自动推送火情"绑定 CheckedChanged
  (书上第 23 步写成了 ch_autoLamp);任务 5 每次查询前清空 Keys
- 素材:项目 8 的 20 多张图片全部自绘,名字和尺寸与教材一致(project8.py 生成 SVG,
  build-gallery.sh 渲染成 PNG;源文件移到 svg/)
- 数据库 WisdomCampus 按教材的表结构建(EnvironmentData、SecurityData、Users),预置 7 天环境数据
- 测试:参考答案全部通过;没绑定"自动推送火情"、开灯换错背景图片时报出对应原因
- 文档:教材 8 个项目全部有示例,共 23 个

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 15:15:00 +08:00

171 lines
9.2 KiB
Python
Raw 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.
#!/usr/bin/env python3
"""
生成教材项目 8(智慧校园管理平台)用到的素材 SVG:名字和尺寸与教材配套资源一致,内容全部自绘(扁平插画)。
运行后用 build-gallery.sh 渲染成 PNG:python3 project8.py && sh build-gallery.sh
"""
from pathlib import Path
OUT = Path(__file__).parent / "svg"
OUT.mkdir(exist_ok=True)
FONT = "Source Han Sans CN, Noto Sans CJK SC, sans-serif"
def save(name, width, height, body):
(OUT / f"{name}.svg").write_text(
f'<svg xmlns="http://www.w3.org/2000/svg" width="{width}" height="{height}" viewBox="0 0 {width} {height}">\n{body}\n</svg>\n',
encoding="utf-8",
)
# ---------- 标题图:智慧校园 ----------
save("ico_title", 226, 54, f"""
<g transform="translate(8,7)">
<circle cx="20" cy="20" r="19" fill="#ffffff" opacity="0.95"/>
<path d="M7 27 L20 10 L33 27 Z" fill="#3593ed"/>
<rect x="13" y="22" width="14" height="9" fill="#3593ed"/>
<rect x="18" y="25" width="4" height="6" fill="#ffffff"/>
</g>
<text x="56" y="37" font-family="{FONT}" font-size="27" font-weight="bold" fill="#ffffff" letter-spacing="3">智慧校园</text>
""")
# ---------- 校园建筑:主界面背景、路灯场景共用的元素 ----------
def building(x, y, w, h, color, window="#cfe3f5", rows=4, cols=6, roof="#b36b45"):
parts = [f'<rect x="{x}" y="{y}" width="{w}" height="{h}" fill="{color}"/>',
f'<rect x="{x-8}" y="{y-14}" width="{w+16}" height="16" fill="{roof}"/>']
gap_x = w / cols
gap_y = (h - 30) / rows
for r in range(rows):
for c in range(cols):
parts.append(f'<rect x="{x + c*gap_x + gap_x*0.22:.1f}" y="{y + 18 + r*gap_y:.1f}" width="{gap_x*0.56:.1f}" height="{gap_y*0.55:.1f}" fill="{window}"/>')
return "\n".join(parts)
def tree(x, y, s=1.0, color="#4f9a45"):
return (f'<rect x="{x-6*s}" y="{y}" width="{12*s}" height="{60*s}" fill="#7a5230"/>'
f'<circle cx="{x}" cy="{y-30*s}" r="{48*s}" fill="{color}"/><circle cx="{x-32*s}" cy="{y-5*s}" r="{34*s}" fill="{color}"/>'
f'<circle cx="{x+32*s}" cy="{y-5*s}" r="{34*s}" fill="#468c3e"/>')
save("bg_home", 1440, 748, f"""
<defs>
<linearGradient id="sky" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#5aa7ea"/><stop offset="1" stop-color="#cfe8fb"/></linearGradient>
<linearGradient id="lawn" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#8cc66d"/><stop offset="1" stop-color="#5f9e45"/></linearGradient>
</defs>
<rect width="1440" height="748" fill="url(#sky)"/>
<circle cx="1260" cy="120" r="60" fill="#fff3b0" opacity="0.9"/>
<g fill="#ffffff" opacity="0.85">
<ellipse cx="260" cy="120" rx="90" ry="30"/><ellipse cx="320" cy="105" rx="60" ry="28"/>
<ellipse cx="880" cy="90" rx="110" ry="32"/><ellipse cx="950" cy="80" rx="70" ry="28"/>
</g>
<g opacity="0.55" fill="#9fb8cf">
<rect x="40" y="300" width="160" height="260"/><rect x="1230" y="280" width="180" height="280"/><rect x="230" y="340" width="120" height="220"/>
</g>
{building(380, 260, 680, 300, "#f2e3c9")}
<rect x="660" y="190" width="120" height="70" fill="#f2e3c9"/><rect x="650" y="178" width="140" height="16" fill="#b36b45"/>
<circle cx="720" cy="225" r="20" fill="#ffffff" stroke="#8a6a4a" stroke-width="4"/>
<rect x="680" y="470" width="80" height="90" fill="#8a6a4a"/>
{building(1080, 360, 220, 200, "#e8d6bd", rows=3, cols=4)}
{building(150, 380, 210, 180, "#e8d6bd", rows=3, cols=4)}
<rect x="0" y="560" width="1440" height="188" fill="url(#lawn)"/>
<path d="M560 748 L680 560 L760 560 L880 748 Z" fill="#d9c9a8"/>
{tree(120, 600, 1.1)}{tree(330, 620, 0.9)}{tree(1130, 610, 1.0)}{tree(1350, 600, 1.2)}
<g fill="#3f8a3a"><ellipse cx="480" cy="572" rx="90" ry="22"/><ellipse cx="960" cy="572" rx="90" ry="22"/></g>
""")
def street(lit):
sky = ('<stop offset="0" stop-color="#2b3d66"/><stop offset="1" stop-color="#e8a76b"/>' if lit
else '<stop offset="0" stop-color="#6bb0ea"/><stop offset="1" stop-color="#d6ecfb"/>')
window = "#ffd37a" if lit else "#cfe3f5"
lamps = []
for x in (180, 520, 860, 1200):
lamps.append(f'<rect x="{x}" y="380" width="12" height="260" fill="#4b5563"/>'
f'<path d="M{x+6} 386 Q{x+6} 356 {x+46} 356 H{x+70}" stroke="#4b5563" stroke-width="10" fill="none"/>'
f'<rect x="{x+56}" y="348" width="44" height="16" rx="5" fill="#374151"/>')
if lit:
lamps.append(f'<path d="M{x+60} 364 L{x+20} 640 H{x+140} L{x+96} 364 Z" fill="#fff3b0" opacity="0.35"/>'
f'<ellipse cx="{x+78}" cy="366" rx="22" ry="7" fill="#fff8d0"/>')
else:
lamps.append(f'<ellipse cx="{x+78}" cy="366" rx="18" ry="5" fill="#9ca3af"/>')
return f"""
<defs><linearGradient id="sky" x1="0" y1="0" x2="0" y2="1">{sky}</linearGradient></defs>
<rect width="1440" height="716" fill="url(#sky)"/>
{'<circle cx="1180" cy="110" r="36" fill="#fff6c8"/>' if lit else '<circle cx="1220" cy="110" r="50" fill="#fff3b0" opacity="0.9"/>'}
{building(80, 220, 560, 330, "#e9d8bf", window=window, rows=5, cols=7)}
{building(760, 260, 580, 290, "#dcc7a8", window=window, rows=4, cols=7)}
<rect x="0" y="550" width="1440" height="40" fill="#7fb06a"/>
<rect x="0" y="590" width="1440" height="126" fill="#5b6472"/>
<g fill="#f8fafc">{''.join(f'<rect x="{x}" y="648" width="70" height="8"/>' for x in range(30, 1440, 140))}</g>
{''.join(lamps)}
{tree(700, 560, 0.8, "#3f7f45")}{tree(1390, 560, 0.9, "#3f7f45")}
"""
save("day_01", 1440, 716, street(lit=True))
save("day_02", 1440, 716, street(lit=False))
# ---------- 主界面的三个功能图标 ----------
def tile(name, color, icon, label):
save(name, 180, 210, f"""
<rect x="6" y="6" width="168" height="198" rx="18" fill="{color}"/>
<rect x="6" y="6" width="168" height="198" rx="18" fill="#ffffff" opacity="0.08"/>
<g transform="translate(90,82)" fill="#ffffff" stroke="#ffffff">{icon}</g>
<text x="90" y="178" font-family="{FONT}" font-size="24" font-weight="bold" fill="#ffffff" text-anchor="middle">{label}</text>
""")
tile("icon_02", "#2e9e5b", """
<rect x="-6" y="10" width="12" height="40" stroke="none"/>
<circle cx="0" cy="-12" r="34" stroke="none"/><circle cx="-26" cy="8" r="22" stroke="none"/><circle cx="26" cy="8" r="22" stroke="none"/>
<path d="M-4 -2 L0 -30 L4 -2" fill="#2e9e5b" stroke="none"/>""", "校园环境")
tile("icon_03", "#2f7fe0", """
<path d="M0 -48 L40 -32 V2 C40 28 20 44 0 52 C-20 44 -40 28 -40 2 V-32 Z" stroke="none"/>
<path d="M-16 0 L-4 12 L18 -14" fill="none" stroke="#2f7fe0" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>""", "校园安防")
tile("icon_04", "#e2803a", """
<path d="M-10 -44 H-40 V44 H-10" fill="none" stroke-width="10" stroke-linejoin="round"/>
<path d="M-12 0 H40 M22 -18 L40 0 L22 18" fill="none" stroke-width="10" stroke-linecap="round" stroke-linejoin="round"/>""", "退 出")
# ---------- 面板背景(半透明深蓝) ----------
for name, width in (("bg_from1", 555), ("bg_from2", 425)):
save(name, width, 670, f"""
<rect x="8" y="8" width="{width-16}" height="654" rx="12" fill="#14304f" opacity="0.88"/>
<rect x="8" y="8" width="{width-16}" height="654" rx="12" fill="none" stroke="#7fb3e6" stroke-width="2" opacity="0.6"/>
<rect x="24" y="22" width="{width-48}" height="4" rx="2" fill="#4fa3ff" opacity="0.8"/>
""")
# ---------- 监控:无画面时的图标、方向盘底座、方向键 ----------
save("icon_monitor", 280, 280, """
<g fill="none" stroke="#c9ced6" stroke-width="10" stroke-linejoin="round" stroke-linecap="round">
<rect x="40" y="70" width="160" height="110" rx="14"/>
<path d="M200 105 L245 80 V170 L200 145"/>
<path d="M90 180 L80 225 M150 180 L160 225 M65 225 H175"/>
</g>
<circle cx="120" cy="125" r="26" fill="none" stroke="#c9ced6" stroke-width="10"/>
""")
save("panel_video_ctrl", 500, 500, """
<defs><radialGradient id="pad" cx="0.5" cy="0.45" r="0.6"><stop offset="0" stop-color="#f4f6f9"/><stop offset="1" stop-color="#c7cdd6"/></radialGradient></defs>
<circle cx="250" cy="250" r="240" fill="url(#pad)" stroke="#aab3bf" stroke-width="6"/>
<circle cx="250" cy="250" r="95" fill="#e9edf2" stroke="#aab3bf" stroke-width="4"/>
<circle cx="250" cy="250" r="40" fill="#d4dae2"/>
""")
ROTATE = {"up": 0, "right": 90, "down": 180, "left": 270}
for direction, angle in ROTATE.items():
for state, (fill, stroke) in {"normal": ("#ffffff", "#5b6472"), "press": ("#2f7fe0", "#ffffff")}.items():
save(f"btn_arrow_{direction}_{state}", 28, 28, f"""
<circle cx="14" cy="14" r="13" fill="{fill}" stroke="#aab3bf" stroke-width="1"/>
<path d="M8 16.5 L14 10 L20 16.5" fill="none" stroke="{stroke}" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" transform="rotate({angle} 14 14)"/>
""")
for direction, angle in (("left", 270), ("right", 90)):
save(f"arrow_{direction}_normal", 60, 60, f"""
<circle cx="30" cy="30" r="27" fill="#000000" opacity="0.45"/>
<path d="M19 35 L30 23 L41 35" fill="none" stroke="#ffffff" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" transform="rotate({angle} 30 30)"/>
""")
save("windowstate_closed_hover", 36, 36, """
<rect x="2" y="2" width="32" height="32" rx="4" fill="#e81123"/>
<path d="M12 12 L24 24 M24 12 L12 24" stroke="#ffffff" stroke-width="3" stroke-linecap="round"/>
""")