Skip to content

feat(render3d): 前端接回 3D 资产创建入口 - #771

Merged
xiaocheny214 merged 1 commit into
mainfrom
feat/518-render3d-entry
Aug 27, 2026
Merged

feat(render3d): 前端接回 3D 资产创建入口#771
xiaocheny214 merged 1 commit into
mainfrom
feat/518-render3d-entry

Conversation

@johnnyzhang-eng

Copy link
Copy Markdown
Contributor

Summary

母版卡片上接回 3D 资产入口:absent 给建、awaiting_review 给放行/弃掉、building/rigging 给在跑的反馈、failed 展示后端文案。面板抽成 render3d-panel.tsxindex.tsx 已经 1900 行,且抽出来才能单独挂起来看渲染结果)。

后端加两道闸,都在花钱之前:体型不可绑骨、每账号资产数上限。

三条约束的落法

金额只从后端 cost 读。 建按钮上的 30 积分、确认里的 20+10 拆分、per_outfit_once 的说明,全部来自 GET 的返回。前端 grep 不到硬编码的积分数——档位会变,抄一份就会分叉。

awaiting_review 是人工闸。 只有「放行绑骨」和「弃掉重建」两个动作,没有任何自动放行路径。文案写明模型生成即最终、改不动,以及放行会再扣绑骨那 10 积分。

error 原样展示。 不在前端重拼文案。

体型闸:为什么是声明而不是检测

自动绑骨对非双足不报错——它漏认被遮挡的肢体,那条肢体每帧同姿势,而 motion_scale、死帧数、loop_seam、帧数时长成色全部正常,一道闸都不会红。用户拿到「有条腿是柱子」的动画且不知情。

原本想从包围盒判。量了全部 25 份归档 GLB:

X/Y 臂展 Z/Y 体长
狼(四足) 0.72 1.47
混元人形原始产物 2.11 ~ 2.62 3.19 ~ 4.47
减面后的人形 0.72 0.28

四足与人形完全重叠,且人形原始产物比狼更「长」——管线不同阶段的模型量纲不一样,几何上判不出来。所以 stance 由调用方声明,且必填无默认:给默认值等于把「没声明」当成「双足」放行,而「没声明」最可能出现在非双足角色上。

user_message() 原本把这条脱敏成通用的「生成没能完成,请稍后重试」。这条不能抹——重试一万次也不会变,四足永远绑不了;原文里只有体型与路线名,无内部信息,已加进白名单原样透出。

每账号上限

MAX_ASSETS_PER_USER = 2。数的是当前持有而不是历史建过多少次,弃掉一个释放一个名额:混元的模型生成即最终、不合格只能弃掉重建,名额若不释放,两个坏模型会把用户永久卡死在这条路线外面。

建造中的不计入。那需要逐造型去问资产存储,而并发建多个的代价本来就由积分挡着。

顺带修的一处

not_a_pose 超上限时的文案原本写「多半是模型躺着或轴向约定不对」。实测拦下带武器模型的正是这条闸(臂展/身高 4.10),而 has_accessory 一次都没触发——它靠 node/mesh 名里的武器词,真实产物的部件名是 node_0 / Material.001。原文案把用户往轴向上引,改成把手持物列在首位。

一处既有测试的断言方向被改了

身份母版只展示现有操作 里有 expect(screen.queryByText(/3D 资产/)).toBeNull(),是 #486 撤掉入口时锁进去的。#486 的理由是 worker 镜像缺 node/playwright、点进去必然抛错;依赖在 #517 补齐、出帧又在 #717 改到浏览器之后,那条理由不成立。断言改成要求该分组存在,并在代码里注明了原委。

不包含

Test plan

  • 后端 uv run ruff check . / export_openapi 无漂移 / uv run lint-imports / uv run pytest -q1773 passed, 14 skipped
  • 前端 format:check / lint / typecheck / test:coverage / build1213 passed, 81 files
  • 新增用例:后端 7 条(体型闸 4 条含双足控制样本、账号上限 3 条含「已有 1 个仍可建」与「弃掉释放名额」),前端 6 条面板 + 1 条 stance 传输
  • 六个状态在真浏览器里逐个渲过并看图确认(含扣费确认与选中四足后的拒绝态)

两侧命令都跑在最后一次编辑之后。

Closes #518

母版卡片上给出建 / 审 / 弃三个动作,金额与「只收一次」都从后端 cost 读。
体型由用户声明(几何上判不出来),非双足在花钱之前拒;每账号最多同时持有
两个 3D 角色,弃掉即释放名额。

Closes #518
@vercel

vercel Bot commented Aug 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
windup Ready Ready Preview Aug 26, 2026 9:37am

@codecov

codecov Bot commented Aug 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 82.24299% with 19 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...ntend/src/pages/workflow-editor/render3d-panel.tsx 77.77% 8 Missing and 8 partials ⚠️
...app/src/windup_app/server/orchestrator/executor.py 0.00% 1 Missing ⚠️
...windup_app/server/orchestrator/render3d_service.py 87.50% 1 Missing ⚠️
frontend/src/pages/workflow-editor/index.tsx 83.33% 0 Missing and 1 partial ⚠️

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #771      +/-   ##
==========================================
+ Coverage   82.66%   87.77%   +5.10%     
==========================================
  Files         102      281     +179     
  Lines        9162    20535   +11373     
  Branches     3034     3061      +27     
==========================================
+ Hits         7574    18024   +10450     
- Misses        759     1673     +914     
- Partials      829      838       +9     
Flag Coverage Δ
backend 91.97% <92.85%> (?)
frontend 82.62% <78.48%> (-0.04%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...app/src/windup_app/server/orchestrator/_failure.py 100.00% <100.00%> (ø)
...nd/packages/app/src/windup_app/web/api/render3d.py 95.93% <100.00%> (ø)
.../src/windup_framework/providers/render3d/checks.py 89.72% <ø> (ø)
frontend/src/entities/render3d/api.ts 96.25% <100.00%> (ø)
...app/src/windup_app/server/orchestrator/executor.py 78.35% <0.00%> (ø)
...windup_app/server/orchestrator/render3d_service.py 79.65% <87.50%> (ø)
frontend/src/pages/workflow-editor/index.tsx 83.49% <83.33%> (-0.01%) ⬇️
...ntend/src/pages/workflow-editor/render3d-panel.tsx 77.77% <77.77%> (ø)

... and 173 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the backend asset lifecycle/quota guards and the workflow-editor entry point. The stance gate and request wiring are consistent, but the new quota and failure UI paths leave users able to exceed the stated two-asset limit and unable to recover from some failed builds. Backend pytest could not be run because uv is unavailable; the frontend test command could not start because the local vitest binary is not executable.

Additional findings

  • frontend/src/pages/workflow-editor/render3d-panel.tsx:?: [P1] Make the failed-build reset action actually recover: The new failed-state button calls discardOutfitAsset, but the backend discard path only accepts assets whose builder state is AWAITING_REVIEW. If the initial image-to-3D call fails before a raw model is stored, the service reports failed while the builder state remains ABSENT; clicking the displayed 清掉重来 action therefore returns another 400 instead of clearing the failed job, so the user cannot retry that outfit through this panel. Add a reset path that also clears an initial-build failure (or hide/use a retry action that handles that state) before exposing this button for failed.

Comment thread backend/packages/app/src/windup_app/web/api/render3d.py

@xiaocheny214 xiaocheny214 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

没问题。

@xiaocheny214
xiaocheny214 merged commit 8a444a8 into main Aug 27, 2026
11 checks passed
johnnyzhang-eng added a commit that referenced this pull request Aug 27, 2026
三条都属于"任务照常 COMPLETED、帧数时长成色全对、零报错,只是产出的东西是错的"。
两位审查者(FennoAI + 内部审查)独立指出了其中两条。

① **位移轨恒为 None。** bake_driver.mjs 交回 meta 时已按片段名拆过一层
   (root_motion: rootMotion[clip] ?? null),sprite.py 原样透传 —— _root_motion_of
   再 .get(clip) 一次恒得 None。服务端渲那条 100% 走这里。
   顺带修脏数据守卫:原来只校验 x 且解包先于守卫,长度不对照样 ValueError、z 是脏值
   直接抛;守卫命中时又只丢那一条,让位移轨比帧数短、索引静默错位。改成整条作废。

② **并发串味。** ActionTaskExecutor 是进程级单例,strategy 被所有任务线程共用而
   action 并发默认 8:A 的 derive 写完、A 的 _finish 读之前,B 的 derive 会覆盖它。
   改用 ContextVar 按请求隔离,取完即清。不改 derive 的返回形状 —— 那是两个入口共用的。

③ **落库丢字段。** rig_facts / root_motion 只写进结果 JSON,没进 CharacterActionOutput
   也没进 _deserialize_result:查询接口与断线重连拿到的已完成任务缺这两样,而实时事件
   那条路有,两条路给出不同结果。同一个坑 geometry 已经踩过一次,注释就在它旁边。

配套修正测试:那个桩用的是 {"walk": {...}},比生产多包一层 —— ① 那个真 bug 就是这么
合进来的。改成 bake_driver 实际交回的形状。

另外 rebase 到 main(#771 已合)时解冲突,顺手清掉两处重复的 return 与一处把 strategy
当 render_spec 位置实参传的写法。
xiaocheny214 pushed a commit that referenced this pull request Aug 28, 2026
* feat(render3d): 出帧台读到的骨架事实与位移轨落进造型与动作

此前每渲一段都算一遍、算完即丢。骨架事实每造型一份,位移轨每动作一份,
取根骨动画轨而非交付帧像素反推。服务端渲与浏览器渲两条路交回同一批数。

Closes #774

* fix(render3d): 整改 #777 —— 位移轨恒空、并发串味、落库丢字段

三条都属于"任务照常 COMPLETED、帧数时长成色全对、零报错,只是产出的东西是错的"。
两位审查者(FennoAI + 内部审查)独立指出了其中两条。

① **位移轨恒为 None。** bake_driver.mjs 交回 meta 时已按片段名拆过一层
   (root_motion: rootMotion[clip] ?? null),sprite.py 原样透传 —— _root_motion_of
   再 .get(clip) 一次恒得 None。服务端渲那条 100% 走这里。
   顺带修脏数据守卫:原来只校验 x 且解包先于守卫,长度不对照样 ValueError、z 是脏值
   直接抛;守卫命中时又只丢那一条,让位移轨比帧数短、索引静默错位。改成整条作废。

② **并发串味。** ActionTaskExecutor 是进程级单例,strategy 被所有任务线程共用而
   action 并发默认 8:A 的 derive 写完、A 的 _finish 读之前,B 的 derive 会覆盖它。
   改用 ContextVar 按请求隔离,取完即清。不改 derive 的返回形状 —— 那是两个入口共用的。

③ **落库丢字段。** rig_facts / root_motion 只写进结果 JSON,没进 CharacterActionOutput
   也没进 _deserialize_result:查询接口与断线重连拿到的已完成任务缺这两样,而实时事件
   那条路有,两条路给出不同结果。同一个坑 geometry 已经踩过一次,注释就在它旁边。

配套修正测试:那个桩用的是 {"walk": {...}},比生产多包一层 —— ① 那个真 bug 就是这么
合进来的。改成 bake_driver 实际交回的形状。

另外 rebase 到 main(#771 已合)时解冲突,顺手清掉两处重复的 return 与一处把 strategy
当 render_spec 位置实参传的写法。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: 前端接回 3D 资产创建入口(后端契约已就绪)

2 participants