mirror of
https://github.com/Comfy-Org/comfy-remote-nodes.git
synced 2026-11-04 23:37:22 +00:00
[PR #16] [MERGED] Add client-side multi-file 3D bundle decoder + BundledFile3D #16
Labels
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Comfy-Org/comfy-remote-nodes#16
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/Comfy-Org/comfy-remote-nodes/pull/16
Author: @Kosinkadink
Created: 5/17/2026
Status: ✅ Merged
Merged: 5/17/2026
Merged by: @Kosinkadink
Base:
main← Head:feat/model-3d-bundle-decoder📝 Commits (1)
c962fa5Add client-side multi-file 3D bundle decoder + BundledFile3D📊 Changes
4 files changed (+838 additions, -15 deletions)
View changed files
📝
client.py(+12 -0)➕
notes/run_model3d_bundle_decoder.py(+461 -0)📝
protocol.py(+61 -0)📝
serialization.py(+304 -15)📄 Description
What
Closes the client side of the multi-file 3D bundle envelope landed in comfy-rnp-server#38 (commit
50f9f4fonmain).Changes
Vendored
protocol.pymirrors the newCapability.MODEL_3D_BUNDLE_INLINE = "model_3d:bundle_inline"constant with a full docstring describing the wire shape,path/rolesemantics,primary_path/formatinvariants, capability negotiation discipline, and the planned futureMODEL_3D_BUNDLE_URIsibling.HEAVY_TYPESis unchanged (type="model_3d"already covers the new encoding).serialization.pyaddsBundledFile3Das aFile3Dsubclass built lazily via a_bundled_file3d_class()factory (so importing the heavycomfy_api.latest._utilpackage doesn't happen at module load — keeps the smoke-test stubbing pattern intact for callers that don't exercise the bundle path). The class stores the primary mesh file plus N companion files in memory as apath -> bytesdict and:get_data()/get_bytes()return the primary file's bytes without touching disk.get_source()lazily materialises the whole bundle into a temp directory using the producer-supplied relative paths, then returns the on-disk path to the primary file — so a.objthat references its.mtlby filename (and the.mtlthat referencestextures/albedo.png) resolves via the filesystem.save_to(dest)copies the whole bundle into the destination directory: the primary file under the caller-specified name, sidecars under their ORIGINAL relative paths so cross-file refs inside the.objthat point atmodel.mtlstill resolve when the user saves the model somewhere stable.primary_path,file_paths,file_bytes(path),file_role(path),paths_with_role(role), andmaterialize_to_temp_dir().shutil.rmtree(td, ignore_errors=True)in__del__).super().__init__(BytesIO(primary), primary_fmt)keeps the upstream_source/_formatattributes populated so existing downstream consumers that read those plain attributes still work.decode_model3d_envelopenow dispatches onencoding:glb_inlinefalls through to the existing single-file path unchanged;bundle_inlinewalks the new wire shape, decodes per-file base64data, hoistsrole/formathints into parallel dicts, and constructs aBundledFile3D. Malformed envelopes (missingprimary_path, missingfiles, primary not present in files, file entry missingdata) raiseRnpProtocolError(code=INTERNAL).client.pyadvertisesCapability.MODEL_3D_BUNDLE_INLINEinCLIENT_CAPABILITIESwith a full docstring matching the established pattern for the other 3D capabilities.Design decisions (from oracle consult on wire-shape surface)
model_3dheavy type with a new encoding (rather than introducingmodel_3d_bundleas a separate top-level type) — keepsHEAVY_TYPESunchanged, lets the single-fileglb_inlinedecoder stay untouched, and lets capability gating stay parallel.pathis the authoritative cross-file reference key (notrole) — roles are conceptually non-unique (multiple meshes, LODs, buffers, animations) soprimary_pathpins the entry point unambiguously.roleis an OPTIONAL producer hint, NOT a closed enum. Unknown roles round-trip without error.File3Ddirectly rather than introducing a new opaqueModelBundle3Dtype — existing downstream loaders that type-check or duck-type-check onFile3Dkeep working.isinstance(bundled, File3D)succeeds.datais base64 (mirrors the single-fileglb_inlinepayload mode). Mixeddata/uriinside one encoding was rejected per the parallel-encoding pattern already in use across the rest of the protocol; a future siblingCapability.MODEL_3D_BUNDLE_URIwould handle URL-fetch semantics.Smoke test
notes/run_model3d_bundle_decoder.py— 9 checks:is_enveloperecognisesbundle_inlineenvelopes.BundledFile3D(aFile3Dsubclass) + primary bytes round-trip viaget_data/get_bytes.get_sourcematerialises whole bundle on disk at producer-supplied paths.save_tocopies whole bundle: primary renamed, sidecars keep filenames.primary_path/file_paths/file_role/paths_with_role) work; unknown roles round-trip.RnpProtocolError(INTERNAL).make_model3d_bundle_envelopehelper (from comfy-rnp-server#38) — all files / roles / bytes preserve. Server-side path sanitization rejects..at envelope build time (defence-in-depth).CLIENT_CAPABILITIESadvertisesmodel_3d:bundle_inline.All 9 pass; PR #14's
notes/run_model3d_decoder.py(8 checks) and PR #15'snotes/run_dynamic_combo_parser.py(8 checks) also still pass — no regression on existing paths.What's deferred
Hunyuan3D / Tencent and future Meshy / Rodin / Tripo bundle providers land in a follow-up PR that consumes this decoder; this PR is protocol + client decoder only per the planned PR sequence.
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.