Skip to content

Add model bundle export (Cast + textures) for DS2#38

Open
UncleRito wants to merge 1 commit into
ShadelessFox:masterfrom
UncleRito:feature/model-bundle-export
Open

Add model bundle export (Cast + textures) for DS2#38
UncleRito wants to merge 1 commit into
ShadelessFox:masterfrom
UncleRito:feature/model-bundle-export

Conversation

@UncleRito

Copy link
Copy Markdown

Summary

When you export a model as Cast today, you get the geometry and per-material mesh pieces — but the actual textures live behind ~10 ref hops the user has to walk by hand for every model. This change adds two new export formats — Cast + DDS textures and Cast + PNG textures — that ship the Cast file alongside every TextureSet transitively reachable from the source object, preserving the surviving directory tree from TextureSetTextureDesc.path:

<export-root>/<ObjectType_g_i>/
  model.cast
  ds/models/characters/fgl_fragile/fgl_textures/textures/
    fgl_body_boots_v00_clr.dds
    fgl_face_fuzzdeferred_v00_clr.dds   ← variation textures included
    ...

Variation TextureSets (e.g. muddy face via ArtPartsVariationReplaceTextureSetResource) come along automatically because they're reachable via outgoing refs.

Design notes

  • No duplicated serialization. The bundle exporters delegate to the existing single-file Cast / DDS / PNG exporters by id via the Exporter SPI. AbstractModelBundleExporter is the shared orchestration; concrete subclasses only declare which texture exporter id to use and which extension to emit.
  • Lazy texture walk. ModelBundle holds a memoized Supplier<List<TextureSet>> rather than an eager list. ModelToModelBundleConverter.convert() only does the Scene conversion (delegating to the existing converter) and packages the supplier — the BFS runs only when bundle.textureSets() is called inside the exporter. This matters because anything that probes converters eagerly (action visibility checks, viewer discovery, etc.) shouldn't pay the walk cost.
  • DS2-only. HFW lacks ArtPartsDataResource and its model containers differ enough that a separate implementation is warranted later. The intermediate ModelBundle type lives in odradek-game so a future HFW converter can reuse it.
  • Dedup. Same source-texture path is written at most once per bundle.
  • No material binding in Cast. Cast supports it; the existing exporter writes geometry only. Out of scope here — the per-material mesh split means manual assignment in Blender is one selection per material, which is bearable.

Test plan

  • ./mvnw clean package succeeds against JDK 25.
  • Manual: export an ArtPartsDataResource as "Cast + DDS textures" produces model.cast plus the expected ds/models/... tree.
  • Manual: variation TextureSets are bundled (verified by checking that a character with body-state variants gets multiple albedo maps).
  • Manual: app remains responsive when opening model editors — the walk is deferred.

🤖 Generated with Claude Code

Exporting a model as Cast today drops just the geometry — material slots
are split per-mesh but the actual textures live behind 10+ ref hops the
user has to walk by hand. This change adds a "bundle" export that emits
a Cast file alongside every TextureSet transitively reachable from the
source object, preserving each TextureSet's surviving directory tree
(work:ds/models/.../foo.psd → ds/models/.../foo.dds).

Two new exporters appear in the Export As popup whenever a model is
selected: "Cast + DDS textures" and "Cast + PNG textures". Both walk
outgoing refs from the source via PathTypeVisitor, collect every
reachable TextureSet (which catches variation textures like
ArtPartsVariationReplaceTextureSetResource — muddy face etc.), and
delegate to the existing single-file Cast / DDS / PNG exporters via the
Exporter SPI by id, so there's no duplicated serialization logic.

The ref walk is deferred until the exporter actually asks for the
textures — convert() only does the Scene conversion (which the existing
pipeline already does) and packages a memoized supplier. This keeps the
new converter free during anything that probes converters eagerly (Show
As menu visibility, etc.).

DS2 only for now; HFW model containers differ enough that a separate
implementation is warranted later.
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.

1 participant