Repository navigation
Conversation
0096660 to
7fcef17
Compare
- Enable the Director Stage entry in the shared creation toolbar and open the stage editor automatically after creating the node. - Pin node, document, and keyframe identity across editor.save, undo, redo, and storage reload with a service-level command test. - Add a browser acceptance flow covering creation, editing, undo, redo, and reopen, runnable in en-US and zh-CN via DIRECTOR_STAGE_LOCALE. - npm test (676 passing at this commit's tree state). - .venv/bin/python -m pytest tests/test_creation_director.py. - node tests/browser/director_stage_entry.mjs plus the DIRECTOR_STAGE_LOCALE=zh-CN run, against the isolated creation_server.py fixture. Refs: ThinkFlowLab#241
## summary - Extend StageObject with kind 'gltf' carrying a registered AssetRef, a character or scene role, a glb or gltf container format, and the animation clip names discovered in the imported asset. - Reject incomplete or incompatible imports at the contract boundary: missing asset, role, or format, non-file assets, motion clips on scene assets, motion without animation data, mismatched animation flags, and blank animation names. - Prove the persistence boundary with a command-level test: forged asset references are rejected without state changes and replays return the original receipt. ## validation - .venv/bin/python -m pytest tests/test_director_stage_assets.py tests/test_creation_contracts.py (89 passing at this commit's tree state). - .venv/bin/python scripts/generate_contracts.py --check. - npm test (676 passing) and npm run build against the old projection reading the regenerated contract fixture. Refs: ThinkFlowLab#241
## summary - Import GLB and self-contained GLTF files as stage objects with a character or scene role; FBX files and GLTF resources with external dependencies are rejected with bilingual capability notices. - Keep blob preview URLs out of persisted identity: saves register the imported file as a project asset and the stage object pins its AssetRef, so imported objects survive reopen, undo, and redo. - Surface per-object capability boundaries in the editor: static use without animation data, discovered clip names, and motion clips only when the asset carries animation data. ## validation - npm test (679 passing) and .venv/bin/python -m pytest tests/test_director_stage_assets.py tests/test_creation_contracts.py tests/test_creation_director.py. - .venv/bin/python scripts/generate_contracts.py --check and npm run check:i18n (2888 keys, 0 problems). - node tests/browser/director_stage_entry.mjs plus the DIRECTOR_STAGE_LOCALE=zh-CN run against the isolated creation_server.py fixture. Refs: ThinkFlowLab#241
## summary - Detect GLB and GLTF containers from actual bytes during upload probing, record model/gltf-binary or model/gltf+json MIME types, and mark whether every buffer and image is embedded in a new selfContained metadata flag. - Reject director documents whose imported stage objects pin assets that are not self-contained models of the declared container format, so no entry point can smuggle external-resource files into a stage document. ## validation - Verified on the full model import and animation tree: .venv/bin/python -m pytest tests (1096 passed, 6 skipped) and .venv/bin/python scripts/generate_contracts.py --check. Refs: ThinkFlowLab#241
…mations ## summary - Probe import bytes in the editor: the container format comes from the GLB header or GLTF document, not the file extension, and corrupt or external-resource files are rejected with localized notices before anything reaches the project. - Map procedural motion presets onto imported animation clips through shared name hints in stageMotion, so preview, stills, and MP4 rendering pick the same clip from the same stage sampling. - Cover the flow end to end with an animated GLB fixture: clip discovery, motion binding at the playhead, playhead scrub animation, and a 1280x720 MP4 rendered through the shared sampler. ## validation - Verified on the full model import and animation tree: npm test (680 passing), npm run build, npm run check:i18n, and the isolated browser checks tests/browser/director_stage_entry.mjs (en-US and zh-CN) and tests/browser/director_stage_animation.mjs. Refs: ThinkFlowLab#241
…l capture ## summary - renderVideo parsed the stage into a local `snapshot` that shadowed the component's `snapshot()` still-capture helper; rename the local to `exportStage` so the two are no longer confused.
## summary - stopAllAction leaves imported skeletons frozen at the last sampled pose; capture the authored rest pose when a runtime binding is created and restore it whenever a sample leaves a motion clip. - Build preview and export bindings through one createImportedRuntime helper, and clone export runners from the pristine cached scene so frames outside clips render the authored rest pose too.
…sources ## summary - A GLB whose header is valid but whose embedded JSON chunk is missing, unparsable, or not a glTF document was reported as an unsupported external-resource import; probeStageAsset now returns a distinct corrupt result with its own bilingual message. - Move the byte probe next to the stage document format and export it so container checks are testable without a browser.
## summary
- The director projection derived upload names as `${name}.${format}`;
renaming a stage object to "hero/v2" made the whole save fail on the
backend path-character check. Derive through stageUploadTitle, which
replaces path separators and control characters, trims, caps the
length under the 255-character upload limit, and falls back to the
localized model title for blank names.
- Cover the sanitization with Node tests.
## validation
- node --test tests/directorStageAssets.test.ts
## summary - The .gltf probe read whole files into memory under only the generic 512 MiB asset cap. inspect_file now rejects GLB and .gltf uploads above MAX_STAGE_MODEL_BYTES (100 MiB, matching the browser import limit) with a bilingual 413 before any full read. - Oversized JSON without a .gltf suffix is no longer parsed as a document and stays a generic file instead of spiking memory. - Cover oversized GLB, oversized .gltf and oversized plain JSON. ## validation - .venv/bin/python -m pytest tests/test_director_stage_assets.py -q
## summary - The load effect cached resolved scenes only; while a slow load was in flight, a re-run fetched the same asset again and the loser's scene was overwritten in the cache without ever being disposed, leaking its WebGL buffers. Cache the in-flight promise under the cache key so concurrent runs share one load. - Evict a promise from the cache when it rejects so later runs can retry, and dispose resolved scenes on unmount through the promise.
## summary
- StageObject.animationNames and hasAnimation were client-attested and
never compared with the pinned asset. The upload probe now records the
animation names a model container declares (declared names trimmed,
unnamed clips falling back to animation_{index}, matching what the
browser loader derives) as MediaMetadata.animationNames, and snapshot
validation rejects stage objects whose animation metadata disagrees
with the pinned asset bytes.
- Align the front-end unnamed-clip fallback with the same convention.
- Regenerate contracts, extend the fixture and cover forged names and
the fallback naming with Python tests.
## validation
- .venv/bin/python -m pytest tests/test_director_stage_assets.py
tests/test_creation_contracts.py -q
- .venv/bin/python scripts/generate_contracts.py --check
- npm exec tsc -- --noEmit
## summary - Exported stage JSON keeps pinned asset identity but carries no model bytes, so importing it into another project produced objects that could neither preview nor apply: the editor showed an unavailable asset and every save failed the backend asset check until the objects were deleted by hand. - The workspace now passes the open project's registry to the stage editor (same consistency the backend snapshot check demands), and import rejects up front with a bilingual message naming the objects whose assets are not registered in this project. - Cover the foreign/registered/unregistered distinction with Node tests.
## summary - Python: a GLB with a valid header whose declared length exceeds the file stays a generic file asset, and stage objects pinning it are rejected. - Node: byte-level cases for the shared stage asset probe (self-contained and external models, truncated GLB, corrupt or non-document JSON chunks), and a projection read where the pinned model's content is unavailable — the reopened stage keeps its registered asset identity and stays a valid document without the preview URL. ## validation - .venv/bin/python -m pytest tests/test_director_stage_assets.py -q - node --test tests/directorStageAssets.test.ts - npm exec tsc -- --noEmit
## summary - Add zh-CN text to all seven StageObject import validation errors in the domain contract, matching the dual-language pattern already used by the stage checks in project.py. - Chinese wording follows the stage terminology in the editors message catalogs (舞台对象, 素材, 动作片段), so receipt issues stay readable in both locales. - Validation rules and error conditions are unchanged; only the message text now carries both languages. ## validation - .venv/bin/python -m pytest tests/test_director_stage_assets.py tests/test_creation_director.py tests/test_creation_contracts.py (98 passed, working tree identical to the staged content)
…d pin procedural rejects ## summary - Deliver the missing-or-inconsistent pinned asset reference rejection in both Chinese and English, matching the other snapshot boundary messages. - Cover procedural stage objects that carry imported asset or animation metadata, which the stage object contract rejects. ## validation - Verified on the full still and MP4 registration tree: .venv/bin/python -m pytest tests/test_director_stage_assets.py tests/test_creation_contracts.py. Refs: ThinkFlowLab#241
…daries
- Probe glTF documents for Draco, meshopt and KTX2 extensions and reject
them with a bilingual notice for needing decoders the browser loader
cannot provide, instead of mislabeling the bytes as corrupt.
- Route stage and layer JSON imports through a text boundary that reports
unparsable files (including binary model bytes fed to the JSON picker)
with a localized notice instead of an engine SyntaxError quoting them.
- Keep cached imported scenes resident while any live clone references
them, and dispose them only after the last one is gone.
- Share one selection-highlight pass across placeholder and imported
clones so unlit materials without emissive survive selection.
- Preserve the loader's animation_{index} fallback for unnamed imported
clips in committed metadata, and stream-scan leading whitespace before
glTF JSON heads in the upload probe (also catching pathological deeply
nested JSON via RecursionError).
- Verified on the staged tree: node_modules/.bin/tsc --noEmit,
node --test tests/canvasStage.test.ts tests/directorStageAssets.test.ts
tests/layerDocument.test.ts (32 passing), and
PYTHONPATH=packages/core/src .venv/bin/python -m pytest
tests/test_director_stage_assets.py (26 passed).
… retargeting
## summary
- Route every stage numeric field through one NumberField text-to-value
boundary: only canonical decimal text commits, so leading zeros ("03"),
trailing dots, exponents and empty input never become values and a
cleared field can no longer silently write 0.
- Replace the read-only time readout with an editable current-time field,
clamped to [0, duration] and quantized to hundredths so the playhead,
the slider and keyframe identity share one time grid.
- Extract stageCameraAt as the single shot-resolution source shared with
the sampler: choosing an edit-target camera or duplicating a camera no
longer flips cameraSource off storyboard cuts, and in storyboard mode
the selection retargets the shot covering the playhead instead.
## validation
- Verified on the staged tree: node_modules/.bin/tsc --noEmit, node --test
tests/canvasStage.test.ts (14 passing, including stageCameraAt boundary,
gap and clamping coverage), and npm run check:i18n (2896 keys,
0 problems).
- node tests/browser/director_stage_entry.mjs covers the numeric contract,
clamped quantized playhead entry, and storyboard-preserving camera
selection end to end in both locales.
## summary - Split Director Stage scrolling into two regions — subjects with the stage and timeline on one side, the inspector on the other — so neither column drags the other along. Region scrollbars stay thin and quiet until the region is hovered or dragged; narrow layouts (<=1100px) keep the previous single shared scroll. - Size the stage frame to a comfortable centered width (900px for 16:9) shared by the viewport, transport, playhead track, keyframes and storyboard, so the draggable track lines up exactly with the picture instead of overrunning it, and portrait stages stay height-capped and centered. - Pin the footer hint below the two regions instead of letting it ride inside the layout container, where it could drift off the page once the layout became position-managed. ## validation - Verified on the staged tree: node_modules/.bin/tsc --noEmit and npm test (693 passing). - Measured in the browser across 1920x1080, 1440x900, 1229x691, 1280x720, 1366x640, 1100x700 and 1000x620 viewports: the frame stays centered with the track exactly matching its width, both regions scroll independently, the bottom action bar and hint remain inside the window, and narrow viewports fall back to one scroll. - node tests/browser/director_stage_entry.mjs passes in en-US and zh-CN after the restructure.
7fcef17 to
3e7188d
Compare
Codex Review Summary: ✅ CompletedThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d4fde9f582
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| selfContained: bool | None = Field(default=None, description='Whether a model container embeds all its buffers and images.') | ||
| animationNames: tuple[str, ...] | None = Field(default=None, description='Animation clip names declared by a parsed model container, in document order.') |
There was a problem hiding this comment.
Omit unused new fields from persisted fingerprint inputs
When opening a pre-change project containing a generation job with media references, these defaults add two null keys to every ResolvedReference.metadata dump. InputSnapshot.fingerprint() consequently changes, Job.execution_identity() rejects the saved hash, and CommitLog._record() reports the existing commit as corrupt, making the project unavailable even if it never used the stage. The new defaulted StageObject fields likewise invalidate historical editor command fingerprints checked by CommitRecord.consistent(). Omit unused new fields from serialization, following the existing timeline/audio extensions, to preserve AGENTS.md's stable-replay requirement.
AGENTS.md reference: AGENTS.md:L67-L67
Useful? React with 👍 / 👎.
| const renderImported = new Map<string, ImportedRuntime>(); | ||
| for (const [id, binding] of previewRuntime.imported) { |
There was a problem hiding this comment.
Wait for imported models before freezing the MP4 scene
On reopening a stage with a model still loading, ready already enables MP4 rendering, but previewRuntime.imported contains only completed loads. This loop therefore skips the pending model and leaves its placeholder in the cloned export scene. Finishing the preview load during onPrepareRender cannot repair that already-cloned scene, so export reports success while rendering boxes instead of the saved models. Await every imported object required by exportStage before cloning the export scene, and reject export if a required model cannot load.
Useful? React with 👍 / 👎.
| if obj.kind == 'gltf': | ||
| asset = assets[obj.asset.assetId] | ||
| if asset.metadata.mimeType != STAGE_MODEL_MIME[obj.format] or asset.metadata.selfContained is not True: | ||
| raise ValueError('Imported stage objects require a matching self-contained GLB or GLTF asset. 导入的舞台对象需要匹配的自包含 GLB 或 GLTF 素材。') |
There was a problem hiding this comment.
Localize stage rejections with dedicated issue codes
For HTTP editor.create/editor.save rejections caused by incompatible assets or animation metadata, creation_publication assigns these validation failures the generic invalid_input code. localized_receipt consequently replaces the stage-specific reason with generic request text for en-US, while returning the full English/Chinese concatenation for zh-CN. English callers lose the actionable reason and Chinese callers receive mixed-language text, contrary to AGENTS.md's requirement that “neither leaks the other language.” Add dedicated issue codes and per-locale presentation lookups instead of concatenating translations in these exceptions.
AGENTS.md reference: AGENTS.md:L86-L89
Useful? React with 👍 / 👎.
| const next = parseLocalStageDocument(await file.text(), t); | ||
| const foreign = isRegisteredAsset | ||
| ? unregisteredStageAssets(next, isRegisteredAsset) | ||
| : []; |
There was a problem hiding this comment.
Rehydrate pinned model URLs when importing stage JSON
After exporting a saved stage and importing its JSON into another stage in the same project, registration validation succeeds, but every imported model lacks source because the exporter deliberately removes it. This path installs the parsed objects without the media-URL hydration performed by CreationProjection.editorData(). The loading effect then reports assetUnavailable and leaves placeholders, so preview and MP4 rendering cannot use assets that are actually registered and available until the user applies and reopens the editor. Resolve each pinned asset to a fresh project media URL before installing the imported document.
Useful? React with 👍 / 👎.
| if (material instanceof THREE.MeshStandardMaterial) | ||
| material.emissive.set(highlighted ? "#1e1536" : "#000000"); |
There was a problem hiding this comment.
Preserve authored emissive colors when highlighting models
For a valid imported model with a nonzero emissiveFactor, this overwrites the authored emission with purple when selected and black when deselected. The export material-cloning path also forces emission to black, so the change is not merely a temporary selection effect: emissive surfaces render incorrectly in both the preview and final MP4. Preserve the original emissive values and remove only the selection highlight when deselecting or exporting.
Useful? React with 👍 / 👎.
| : undefined; | ||
| if (binding.activeClip !== clip) { | ||
| binding.mixer.stopAllAction(); | ||
| if (clip) binding.mixer.clipAction(clip).reset().play(); |
There was a problem hiding this comment.
Apply motion amplitude to imported animation actions
For imported animated characters, the inspector exposes and persists the amplitude control, but applyImportedAnimation() never reads value.motion.amount. The action plays at its default full weight, so reducing amplitude—even to zero—has no effect in the preview, screenshots, or MP4 output. Apply the amount to the animation action's effective weight, including when the currently active clip remains unchanged, or stop exposing an unsupported control.
Useful? React with 👍 / 👎.
Refs #241
Summary
The Director Stage can import GLB and self-contained GLTF models: the backend probes the uploaded bytes and pins the import to a registered project asset, the frontend drives import, animation playback, and boundary rejections, and the stage editor's interaction details are brought in line. Slice 2 of 4 delivering #241, stacked on #328.
Contracts side:
editor.save(packages/core/src/opendirector/domain/creation/editors.py,src/contracts/creation.schema.json).creation_uploads.py).animation_{index}fallback shared with the browser loader.Frontend side:
src/canvas/DirectorStage.tsximports GLB/self-contained GLTF, drives imported animations, restores the rest pose when leaving a motion clip, and shares one in-flight asset load across effect runs.f6c46292,08509657).Linked issue
Labels
type:feat,area:canvasTest plan
npx tsc --noEmitclean;npm test688/688;npm run check:i18n0 problems;.venv/bin/python -m pytest tests/test_director_stage_assets.py tests/test_creation_director.py tests/test_creation_contracts.py102 passed;node tests/browser/director_stage_entry.mjsand thezh-CNvariant both pass;node tests/browser/director_stage_animation.mjspasses (animated GLB import discovers clips, unnamed clips keep the loader fallback, motion clip binds the model, playhead scrub animates, MP4 renders through the same sampling, metadata persists).Evidence that it works
Provider verification
Bilingual UI
check:i18nreports 0 problems.