- 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>
243 lines
14 KiB
TypeScript
243 lines
14 KiB
TypeScript
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);
|
||
}
|