Files
workinf_Blender_Wasm/web/app/src/tutorial/course.ts
mes123456 d5dd647881
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
feat(web): finish RE 5000PBR lesson video and fix non-ASCII GLB import
- Add 11:07 Chinese-narrated lesson video, VTT, poster and 13 chapter screenshots.
- Regenerate 20-render WebGL2 matrix and 15 operation captures on RTX 4060.
- Fix WASM staging for non-ASCII import filenames (猴头.glb); rebuild web_engine.
- Harden lesson video recording pipeline; fix stale lesson-link E2E selector.
- Add RE PBR derived libraries, lesson page, evidence and status report.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-11-12 21:49:24 +08:00

243 lines
14 KiB
TypeScript
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.
import { PARTS_TUTORIAL_REQUIRED_STEPS, PARTS_TUTORIAL_STEPS, PARTS_TUTORIAL_STORAGE_KEY } from "./parts/curriculum";
import { REDUCER_TUTORIAL_REQUIRED_STEPS, REDUCER_TUTORIAL_STEPS, REDUCER_TUTORIAL_STORAGE_KEY } from "./reducer/curriculum";
import { PBR_TUTORIAL_REQUIRED_STEPS, PBR_TUTORIAL_STEPS, PBR_TUTORIAL_STORAGE_KEY } from "./pbr/curriculum";
import type { TutorialStep } from "./parts/types";
export type CourseId = "parts" | "reducer" | "pbr";
export interface TutorialCourseCard {
name: string;
note: string;
icon: string;
}
export interface TutorialGalleryItem {
step: string;
title: string;
note: string;
/** Path relative to assetDir; default is steps/<step>.jpg. */
image?: string;
}
/** Everything a course needs that was previously hardcoded in the parts UI. */
export interface TutorialCourse {
id: CourseId;
title: string;
heading: string;
steps: readonly TutorialStep[];
requiredSteps: readonly TutorialStep[];
storageKey: string;
opfsFile: string;
assetDir: string;
lessonUrl?: string;
startUrl: string;
saveStepId: string;
summaryStepId: string;
/** Checkpoint every N advances; heavy courses can checkpoint less often. */
checkpointEvery?: number;
/** Set only when the course must own a deterministic OPFS project id. */
defaultProjectId?: string;
defaultProjectName: string;
pythonFileName: string;
overlayTitle: string;
reopenAria: string;
heroTitle: string;
heroAccent: string;
eyebrow: string;
lead: string;
partsLead: string;
galleryLead: string;
bottomCta: string;
bottomCtaContinue: string;
parts: readonly TutorialCourseCard[];
summaryParts: readonly string[];
summaryPartsTitle?: string;
gallery: readonly TutorialGalleryItem[];
skills: readonly string[];
}
const PARTS_CARDS: readonly TutorialCourseCard[] = [
{ name: "底座", note: "立方体 + 倒角 + 布尔孔", icon: "base" },
{ name: "轴", note: "圆柱 + 编辑模式 + 平滑", icon: "shaft" },
{ name: "密封圈", note: "贝塞尔曲线 + 倒角深度", icon: "ring" },
{ name: "垫圈", note: "布尔差集 + 阵列", icon: "washer" },
{ name: "螺栓", note: "六角头 + 合并 + 文字 M8", icon: "bolt" },
{ name: "支架", note: "实体化 + 镜像 + 细分", icon: "bracket" },
];
const PARTS_GALLERY: readonly TutorialGalleryItem[] = [
{ step: "base-holes", title: "底座 · 布尔打孔", note: "四个圆柱合并成刀具,再用 Boolean 差集切出安装孔。" },
{ step: "base-detail", title: "底座 · 编辑模式沉台", note: "在顶面内插并向下挤出,做出装配用的沉台。" },
{ step: "shaft-create", title: "轴 · 圆柱与倒角", note: "圆柱参数化创建,加 Bevel 修改器并平滑着色。" },
{ step: "gasket-curve", title: "密封圈 · 曲线建模", note: "贝塞尔圆环设置 Bevel Depth,再转换为网格。" },
{ step: "washer-boolean", title: "垫圈 · 布尔内孔", note: "外圆柱减去内圆柱,再用 Array 叠放三片。" },
{ step: "bolt-shaft", title: "螺栓 · 头杆合并", note: "六角头与螺栓杆 Join 成单个对象,再加倒角。" },
{ step: "bracket-subsurf", title: "支架 · 镜像与细分", note: "Solidify 加厚度,Mirror 对称,Subsurf 圆润边缘。" },
{ step: "assembly-hierarchy", title: "装配 · 集合与父子", note: "零件归入机械零件集合,并以 Empty 作为装配总成。" },
];
const PARTS_SKILLS: readonly string[] = [
"创建与变换:基础体、曲线、文字、Empty,移动/旋转/缩放与数值输入",
"编辑模式:内插、挤出、倒角、选择与法线",
"修改器与布尔:Bevel、Boolean、Mirror、Array、Solidify、Subsurf",
"层级、材质与导出:集合、父子、材质颜色、保存 .blend、导出 GLB",
];
export const COURSES = {
parts: {
id: "parts",
title: "机械零件套装",
heading: "从零制作机械零件套装",
steps: PARTS_TUTORIAL_STEPS,
requiredSteps: PARTS_TUTORIAL_REQUIRED_STEPS,
storageKey: PARTS_TUTORIAL_STORAGE_KEY,
opfsFile: "parts-progress.json",
assetDir: "/tutorial/parts",
startUrl: "/?tutorial=parts&start=1",
saveStepId: "save-export",
summaryStepId: "summary",
checkpointEvery: 1,
defaultProjectName: "机械零件套装.blend",
pythonFileName: "PartsTutorial.py",
overlayTitle: "零件建模互动教程",
reopenAria: "展开零件建模教程",
heroTitle: "从零制作",
heroAccent: "机械零件套装",
eyebrow: "互动教学 · 初学者 · 约 60–90 分钟",
lead: "在真实运行的 Web Blender 里动手完成一套零件:底座、轴、密封圈、垫圈、螺栓与支架。每个步骤都会实时检查你的场景,卡住时可以一键演示。",
partsLead: "从基础体到布尔、曲线与文字,覆盖零件创建的主要手法。",
galleryLead: "以下画面由有头 Chromium 实际执行每一步“演示本步”后截取,不是示意图。",
bottomCta: "进入教程",
bottomCtaContinue: "继续上次进度",
parts: PARTS_CARDS,
summaryParts: ["底座", "轴", "密封圈", "垫圈", "螺栓", "螺栓标记", "支架"],
gallery: PARTS_GALLERY,
skills: PARTS_SKILLS,
},
reducer: {
id: "reducer",
title: "二级减速器",
heading: "从零制作二级减速器",
steps: REDUCER_TUTORIAL_STEPS,
requiredSteps: REDUCER_TUTORIAL_REQUIRED_STEPS,
storageKey: REDUCER_TUTORIAL_STORAGE_KEY,
opfsFile: "reducer-progress.json",
assetDir: "/tutorial/reducer",
startUrl: "/?tutorial=reducer&start=1",
saveStepId: "save-export",
summaryStepId: "summary",
checkpointEvery: 3,
defaultProjectId: "tutorial-reducer",
defaultProjectName: "二级减速器.blend",
pythonFileName: "ReducerTutorial.py",
overlayTitle: "二级减速器互动教程",
reopenAria: "展开二级减速器教程",
heroTitle: "从零制作",
heroAccent: "二级减速器",
eyebrow: "互动教学 · 初学者 · 约 90–120 分钟",
lead: "在真实 Web Blender 中完成一台二级减速器:箱体、箱盖、三根轴、四个齿轮、六套轴承与端盖,最后用集合和 Empty 建立完整装配层级。每一步都会实时检查你的场景。",
partsLead: "从箱体到齿轮,覆盖实体建模、布尔、阵列与标准件的完整流程。",
galleryLead: "以下画面由有头 Chromium 实际执行每一步“演示本步”后截取,不是示意图。",
bottomCta: "进入减速器教程",
bottomCtaContinue: "继续上次进度",
parts: [
{ name: "箱体", note: "立方体 + Solidify + 布尔轴承孔", icon: "base" },
{ name: "箱盖", note: "内插止口 + Mirror 螺栓孔", icon: "bracket" },
{ name: "齿轮", note: "Array 对象偏移 + Boolean 切齿", icon: "bolt" },
{ name: "轴", note: "圆柱 + 轴颈 + 布尔键槽", icon: "shaft" },
{ name: "轴承", note: "Torus + 滚球 + Join", icon: "ring" },
{ name: "附件", note: "通气帽 / 油标 / 放油塞 / 铭牌", icon: "washer" },
],
summaryParts: ["箱体", "箱盖", "输入轴", "中间轴", "输出轴", "齿轮1–4", "轴承1–6", "端盖1–6", "键1–4", "法兰螺栓", "通气帽", "油标", "放油塞", "铭牌"],
gallery: [
{ step: "housing-bores", title: "箱体 · 布尔轴承孔", note: "六个圆柱 Join 成孔刀,再用 Boolean 差集切穿两端壁。" },
{ step: "gear1-cutter", title: "齿轮 · 阵列齿槽刀", note: "齿槽刀原点放在齿轮中心,Empty 旋转 360/17,Array 用对象偏移复制。" },
{ step: "gear2", title: "齿轮组 · 参数化齿数", note: "同一套阵列+布尔流程生成 z=34 的大齿轮。" },
{ step: "bearings-set", title: "轴承 · Torus 与滚球", note: "内外套圈、滚球合并后复制到三根轴两端。" },
{ step: "shaft-assemblies", title: "装配 · 轴组件 Empty", note: "每根轴的零件 parent 到对应 Empty,实时装配树同步更新。" },
{ step: "collections", title: "层级 · 集合归类", note: "箱体组件、传动组件、附件三个子集合。" },
{ step: "materials-assign", title: "材质 · 四种表面", note: "铸铁、钢、黄铜、蓝漆分配到不同零件。" },
{ step: "save-export", title: "交付 · 保存与 GLB", note: "工程写入 OPFS,并导出 GLB 供外部使用。" },
],
skills: [
"零件创建与变换:立方体、圆柱、Torus、Text、Empty,应用缩放与旋转",
"编辑模式:删面、内插、挤出、倒角与平滑着色",
"修改器与布尔:Solidify、Bevel、Boolean 并集/差集、Array 对象偏移、Mirror、Subsurf",
"传动件建模:轴颈与键槽、齿轮齿槽阵列、轴承套圈与滚珠",
"装配与交付:集合、Empty 父子层级、实时装配树、材质、链接复制、保存与 GLB 导出",
],
},
pbr: {
id: "pbr",
title: "PBR 渲染入门",
heading: "从零开始 PBR 渲染",
steps: PBR_TUTORIAL_STEPS,
requiredSteps: PBR_TUTORIAL_REQUIRED_STEPS,
storageKey: PBR_TUTORIAL_STORAGE_KEY,
opfsFile: "pbr-progress.json",
assetDir: "/tutorial/pbr",
lessonUrl: "/tutorial/pbr/render-lesson.html",
startUrl: "/?ui=web&tutorial=pbr&start=1",
saveStepId: "download-save",
summaryStepId: "summary",
checkpointEvery: 1,
defaultProjectName: "PBR渲染入门.blend",
pythonFileName: "PbrTutorial.py",
overlayTitle: "PBR 渲染互动教程",
reopenAria: "展开 PBR 渲染教程",
heroTitle: "从零开始",
heroAccent: "PBR 渲染",
eyebrow: "互动教学 · 初学者 · 约 30–45 分钟",
lead: "在真实 Web Blender 里完成 3 个模型 × 4 种材质的 12 张渲染图:追加 Suzanne、Cube、Torus,分配金属、玻璃、木纹与自发光材质,最后用场景相机渲染并下载 PNG。",
partsLead: "从资产库、材质球到 Web PBR 渲染,覆盖初学者最常用的渲染流程。",
galleryLead: "以下画面由有头 Chromium 实际操作并渲染生成,不是示意图。",
bottomCta: "进入渲染教程",
bottomCtaContinue: "继续上次进度",
parts: [
{ name: "模型", note: "Suzanne / Cube / Torus", icon: "shaft" },
{ name: "材质", note: "金属 / 玻璃 / 木色 / 自发光", icon: "ring" },
{ name: "灯光", note: "三点面光 + 世界环境", icon: "washer" },
{ name: "渲染", note: "1024 场景相机 → PNG", icon: "bolt" },
],
summaryParts: ["PBR Suzanne · 拉丝金属", "PBR Cube · 玻璃", "PBR Torus · 暖色木质", "PBR Studio Ground · 青色自发光"],
summaryPartsTitle: "本课完成的组合",
skills: [
"Shading 工作区与 PBR 资产库:搜索、预览、双击追加模型与分配材质",
"PBR 材质参数:Metallic、Roughness、Transmission、IOR、Emission 与颜色",
"摄影棚场景:相机、三点面光、世界环境与地面",
"渲染交付:分辨率、场景相机、PNG 下载、OPFS 保存与 .blend 保存",
],
gallery: [
{ step: "workspace-shading", title: "资产库 · Shading 与 PBR 卡片", note: "打开 Shading 工作区,浏览 6 个模型与 14 个材质。" },
{ step: "load-studio", title: "摄影棚 · 相机与三点光", note: "载入带相机、面光与世界环境的固定场景。" },
{ step: "add-suzanne", title: "模型 · 追加 PBR Suzanne", note: "双击资产卡片,模型自动出现在原点并选中。" },
{ step: "add-cube", title: "模型 · 追加 PBR Cube", note: "用同一入口添加第二个模型。" },
{ step: "add-torus", title: "模型 · 追加 PBR Torus", note: "三个模型准备完成。" },
{ step: "assign-metal", title: "材质 · 拉丝金属", note: "Metallic 1.0、Roughness 0.30。" },
{ step: "assign-glass", title: "材质 · 透射玻璃", note: "Transmission 1.0、IOR 1.45。" },
{ step: "assign-wood", title: "材质 · 暖色木质", note: "暖色 Base Color、Roughness 0.52。" },
{ step: "assign-emission", title: "材质 · 青色自发光", note: "Emission Color 青色、Strength 5.0。" },
{ step: "render-setup", title: "渲染 · 1024 场景相机", note: "Rendering 工作区选择分辨率与场景相机。" },
{ step: "download-save", title: "交付 · PNG 与 .blend", note: "下载渲染图并保存工程。" },
{ step: "render-suzanne-metal", image: "renders/suzanne-metal-brushed.jpg", title: "Suzanne · 拉丝金属", note: "金属高光与拉丝粗糙度。" },
{ step: "render-suzanne-glass", image: "renders/suzanne-glass.jpg", title: "Suzanne · 玻璃", note: "透射与折射对比。" },
{ step: "render-suzanne-wood", image: "renders/suzanne-wood.jpg", title: "Suzanne · 暖色木质", note: "暖色哑光材质在曲面上的表现。" },
{ step: "render-suzanne-emission", image: "renders/suzanne-emission-cyan.jpg", title: "Suzanne · 自发光", note: "青色自发光强度 5.0。" },
{ step: "render-cube-metal", image: "renders/cube-metal-brushed.jpg", title: "Cube · 拉丝金属", note: "平面上的金属反射。" },
{ step: "render-cube-glass", image: "renders/cube-glass.jpg", title: "Cube · 玻璃", note: "厚立方体的透射效果。" },
{ step: "render-cube-wood", image: "renders/cube-wood.jpg", title: "Cube · 暖色木质", note: "平面上的暖色哑光表面。" },
{ step: "render-cube-emission", image: "renders/cube-emission-cyan.jpg", title: "Cube · 自发光", note: "自发光立方体轮廓。" },
{ step: "render-torus-metal", image: "renders/torus-metal-brushed.jpg", title: "Torus · 拉丝金属", note: "环面高光。" },
{ step: "render-torus-glass", image: "renders/torus-glass.jpg", title: "Torus · 玻璃", note: "曲面透射与焦散近似。" },
{ step: "render-torus-wood", image: "renders/torus-wood.jpg", title: "Torus · 暖色木质", note: "环面上的暖色哑光材质。" },
{ step: "render-torus-emission", image: "renders/torus-emission-cyan.jpg", title: "Torus · 自发光", note: "环面自发光。" },
],
},
} satisfies Record<CourseId, TutorialCourse>;
export type AvailableCourseId = keyof typeof COURSES;
export function isAvailableCourseId(value: string | null): value is AvailableCourseId {
return value !== null && Object.prototype.hasOwnProperty.call(COURSES, value);
}