|
|
|
@@ -0,0 +1,137 @@ |
|
|
|
/** |
|
|
|
* 采集原型 DEMO 的模型工程出厂基线,作为「正式环境要和 DEMO 一致」的唯一基准物。 |
|
|
|
* |
|
|
|
* 产物落到 `unreal_tran_web/scripts/fixtures/bridge-erector-demo-baseline.json`, |
|
|
|
* 由 `unreal_tran_web/scripts/emit-model-bridge-erector-seed.mjs` 读取生成数据库种子。 |
|
|
|
* |
|
|
|
* 为什么必须从浏览器采集,不能从 profile 推导: |
|
|
|
* 文档里 `model.objects`(187 个部件语义 + 114 条材质)与 |
|
|
|
* `environment.objects`(70 个环境对象布局)是 `captureModelObjects()` / |
|
|
|
* `captureOutdoorScene()` 从活的 three.js 场景里采下来的, |
|
|
|
* `equipmentModelProfiles.ts` 只有机构轨道,推不出这两块。 |
|
|
|
* 丢了它们,下游 `applyModelEditorOverrides` 就回放不出部件语义与 PBR 质感。 |
|
|
|
* |
|
|
|
* 关键点:必须用全新 browser context。 |
|
|
|
* 原型启动流程是 loadModel -> restoreAutoSave()(读 dt-editor-project-v2), |
|
|
|
* 有存档就恢复存档、没存档才走 createBaselineProject()。 |
|
|
|
* 带着上一次的 localStorage 打开,采到的是那份存档而不是出厂基线。 |
|
|
|
* 报告里的 `hadAutosaveBeforeDump` 必须是 false 才算有效采集。 |
|
|
|
* |
|
|
|
* 前置:`unreal_tran_web` 的 dev server 或 preview 在 6180 上跑着, |
|
|
|
* 且 `public/legacy-model-editor/` 已由 stage-legacy-model-editor.mjs 生成。 |
|
|
|
* |
|
|
|
* 用法(在 ute2e 下): |
|
|
|
* node tools/dump-model-demo-baseline.mjs |
|
|
|
* node tools/dump-model-demo-baseline.mjs # 自定义地址用 TARGET 环境变量 |
|
|
|
* |
|
|
|
* 只覆盖模型制作,不涉及场景制作。 |
|
|
|
*/ |
|
|
|
import { chromium } from '@playwright/test' |
|
|
|
import { mkdirSync, writeFileSync } from 'node:fs' |
|
|
|
|
|
|
|
const URL = process.env.TARGET || 'http://127.0.0.1:6180/legacy-model-editor/editor.html' |
|
|
|
const OUT = 'artifacts/model-demo-baseline' |
|
|
|
/** 基准物落在 Web 项目的 fixtures 下,由种子生成器读取。 */ |
|
|
|
const FIXTURE = '../unreal_tran_web/scripts/fixtures/bridge-erector-demo-baseline.json' |
|
|
|
mkdirSync(OUT, { recursive: true }) |
|
|
|
|
|
|
|
const browser = await chromium.launch({ |
|
|
|
args: ['--enable-unsafe-swiftshader', '--use-gl=angle', '--use-angle=swiftshader'], |
|
|
|
}) |
|
|
|
// 全新 context,天然没有 localStorage / IndexedDB 残留,拿到的是出厂基线工程 |
|
|
|
const context = await browser.newContext({ viewport: { width: 1920, height: 1080 } }) |
|
|
|
const page = await context.newPage() |
|
|
|
|
|
|
|
const failures = [] |
|
|
|
page.on('requestfailed', (r) => failures.push(`${r.method()} ${r.url()} :: ${r.failure()?.errorText}`)) |
|
|
|
page.on('response', (r) => { if (r.status() >= 400) failures.push(`HTTP ${r.status()} ${r.url()}`) }) |
|
|
|
|
|
|
|
const report = {} |
|
|
|
try { |
|
|
|
await page.goto(URL, { waitUntil: 'domcontentloaded', timeout: 60000 }) |
|
|
|
await page.waitForSelector('.editor-shell', { timeout: 60000 }) |
|
|
|
await page.waitForSelector('#loading-overlay.hidden', { timeout: 180000 }) |
|
|
|
// 基准动作是在 loadModel 之后注入的,等一拍确保时间轴已就位 |
|
|
|
await page.waitForTimeout(1500) |
|
|
|
|
|
|
|
const dump = await page.evaluate(() => { |
|
|
|
const app = window.editorApp |
|
|
|
if (!app) throw new Error('window.editorApp 不存在') |
|
|
|
return { |
|
|
|
project: app.serializeProject(), |
|
|
|
runtime: { |
|
|
|
activeDescriptorId: app.activeDescriptor?.id ?? null, |
|
|
|
activeDescriptorName: app.activeDescriptor?.name ?? null, |
|
|
|
timelineTrackCount: app.timeline?.tracks?.length ?? 0, |
|
|
|
blueprintNodeCount: app.blueprint?.nodes?.length ?? 0, |
|
|
|
blueprintEdgeCount: app.blueprint?.edges?.length ?? 0, |
|
|
|
environmentMeta: { ...(app.outdoorSceneMeta ?? {}) }, |
|
|
|
modelTargetKeys: [...(app.modelTargets?.keys?.() ?? [])], |
|
|
|
}, |
|
|
|
// 存档路径也一起记下来,确认这次确实走的是出厂基线而不是恢复存档 |
|
|
|
hadAutosave: Boolean(window.localStorage.getItem('dt-editor-project-v2')), |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
report.runtime = dump.runtime |
|
|
|
report.hadAutosaveBeforeDump = dump.hadAutosave |
|
|
|
const project = dump.project |
|
|
|
|
|
|
|
if (dump.hadAutosave) { |
|
|
|
throw new Error('采集到的是 localStorage 存档而不是出厂基线,请用全新 context 重跑') |
|
|
|
} |
|
|
|
|
|
|
|
writeFileSync(`${OUT}/demo-project.json`, `${JSON.stringify(project, null, 2)}\n`, 'utf8') |
|
|
|
writeFileSync(`${OUT}/demo-project.compact.json`, JSON.stringify(project), 'utf8') |
|
|
|
// 缩进版进仓库:122 KB 的单行 JSON 没法 code review,缩进后 diff 可读 |
|
|
|
writeFileSync(FIXTURE, `${JSON.stringify(project, null, 2)}\n`, 'utf8') |
|
|
|
|
|
|
|
report.summary = { |
|
|
|
schema: project.schema, |
|
|
|
version: project.version, |
|
|
|
name: project.name, |
|
|
|
modelId: project.modelId, |
|
|
|
meta: project.meta, |
|
|
|
timeline: { |
|
|
|
duration: project.timeline?.duration, |
|
|
|
currentTime: project.timeline?.currentTime, |
|
|
|
loop: project.timeline?.loop, |
|
|
|
zoom: project.timeline?.zoom, |
|
|
|
trackCount: project.timeline?.tracks?.length, |
|
|
|
trackIds: project.timeline?.tracks?.map((t) => t.id), |
|
|
|
trackTargets: project.timeline?.tracks?.map((t) => t.targetId), |
|
|
|
keyframeTotal: project.timeline?.tracks?.reduce((s, t) => s + t.keyframes.length, 0), |
|
|
|
}, |
|
|
|
blueprint: { |
|
|
|
name: project.blueprint?.name, |
|
|
|
viewport: project.blueprint?.viewport, |
|
|
|
nodeCount: project.blueprint?.nodes?.length, |
|
|
|
edgeCount: project.blueprint?.edges?.length, |
|
|
|
nodes: project.blueprint?.nodes?.map((n) => ({ id: n.id, type: n.type, title: n.title, params: n.params })), |
|
|
|
edges: project.blueprint?.edges, |
|
|
|
}, |
|
|
|
camera: project.camera, |
|
|
|
modelResource: project.modelResource, |
|
|
|
model: project.model ? { objectKeys: Object.keys(project.model.objects ?? {}).length } : null, |
|
|
|
environment: { |
|
|
|
...project.environment, |
|
|
|
objects: `<${Object.keys(project.environment?.objects ?? {}).length} 个对象,已折叠>`, |
|
|
|
}, |
|
|
|
runtime: project.runtime, |
|
|
|
publish: project.publish, |
|
|
|
topLevelKeys: Object.keys(project), |
|
|
|
} |
|
|
|
report.failedRequests = failures |
|
|
|
report.bytesCompact = Buffer.byteLength(JSON.stringify(project), 'utf8') |
|
|
|
report.ok = failures.length === 0 && project.modelId === 'bridge-erector-vehicle' |
|
|
|
} catch (error) { |
|
|
|
report.error = error?.message || String(error) |
|
|
|
report.failedRequests = failures |
|
|
|
report.ok = false |
|
|
|
} finally { |
|
|
|
await browser.close() |
|
|
|
} |
|
|
|
|
|
|
|
writeFileSync(`${OUT}/dump-report.json`, `${JSON.stringify(report, null, 2)}\n`, 'utf8') |
|
|
|
process.stdout.write(`报告已写入 ${OUT}/dump-report.json ok=${report.ok}\n`) |
|
|
|
process.exit(report.ok ? 0 : 1) |