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/.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; export type AvailableCourseId = keyof typeof COURSES; export function isAvailableCourseId(value: string | null): value is AvailableCourseId { return value !== null && Object.prototype.hasOwnProperty.call(COURSES, value); }