lecodes-assets 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -3
- package/dist/cli.js +4624 -1307
- package/dist/index.js +4624 -1307
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,16 @@ FBX → GLB converter + GLB doctor, one package on one glTF-Transform `Document`
|
|
|
4
4
|
`lecodes assets …` (optional peer of the `lecodes` CLI) or `bun src/cli.ts …` in the monorepo.
|
|
5
5
|
Plan/rationale: [docs/animation-plan.md §2.9](../../docs/animation-plan.md).
|
|
6
6
|
|
|
7
|
+
Install it the same way you installed the CLI — the peer is loaded by a bare `import` that resolves
|
|
8
|
+
upward from the CLI's own file, so a global `lecodes` only sees a global peer:
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
npm install -g lecodes-assets
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
KTX2 (`--ktx2`) additionally needs KTX-Software's `toktx` on PATH; `sharp` is an optional dependency
|
|
15
|
+
used to normalize exotic texture formats.
|
|
16
|
+
|
|
7
17
|
```
|
|
8
18
|
lecodes assets convert "Erika Archer.fbx" --clips Idle.fbx Walking.fbx "Fast Run.fbx" --drop-model-clips -o hero.glb
|
|
9
19
|
lecodes assets convert hero.glb --clips Jump.fbx # add clips to an existing GLB
|
|
@@ -12,9 +22,87 @@ lecodes assets convert hero.fbx --ktx2 # + textures
|
|
|
12
22
|
lecodes assets probe hero.fbx | hero.glb # skeleton / clips / meshes / textures / bind sanity / world size
|
|
13
23
|
lecodes assets doctor hero.glb [--fix] [--ktx2] [--dump] # the old tools/glb-reduce
|
|
14
24
|
lecodes assets doctor hero.glb --fix-scale # bake a unit fix (see Scale below)
|
|
25
|
+
lecodes assets doctor props/*.glb --lightmap-uv -o props/ # + TEXCOORD_1 lightmap UVs (xatlas) for `lecodes lightmap bake`
|
|
15
26
|
lecodes assets retarget fox.glb --from wolf.fbx --map auto # clips from a DIFFERENT skeleton (map from skeleton shape)
|
|
27
|
+
lecodes assets unpack pack.unitypackage -o out [--only re …] # stream a .unitypackage: unity-index.txt + assets (+ .meta)
|
|
28
|
+
lecodes assets convert Barrel_v1.FBX --list-nodes # multi-variant prop file: what is in it
|
|
29
|
+
lecodes assets convert Barrel_v1.FBX --keep Barrel_v1_LD1 -o barrel.glb # one variant, re-origined on the ground
|
|
30
|
+
lecodes assets convert Barrel_v1.FBX --split props/ # every top-level node → its own GLB
|
|
31
|
+
lecodes assets convert Body.FBX --merge-skins Shirt.FBX Gloves.FBX --clips … -o hero.glb # modular character → ONE skeleton
|
|
32
|
+
lecodes assets probe hero.glb --filmstrip # a CPU-skinned PNG strip per clip (no GPU)
|
|
33
|
+
lecodes assets scene Map_v8.unity -o level.json # Unity scene → placed prefabs with world TRS
|
|
16
34
|
```
|
|
17
35
|
|
|
36
|
+
## Props, modular characters, scenes — the Unity-pack workflow
|
|
37
|
+
|
|
38
|
+
**Multi-variant prop files.** Packs put every LOD, colour set and arrangement of a prop in one FBX
|
|
39
|
+
(`Barrel_v1.FBX` = 15 meshes). `--list-nodes [depth]` prints the scene's nodes with mesh/triangle
|
|
40
|
+
counts and world sizes; `--keep <node> …` keeps only those subtrees (ancestors stay as the path,
|
|
41
|
+
descendants come along) and **re-origins** the result — footprint centre at X/Z = 0, lowest vertex at
|
|
42
|
+
Y = 0 — so `Model.load` places the prop exactly where the node is (`--no-ground` keeps the file's
|
|
43
|
+
own placement); `--split [dir]` writes one re-origined GLB per top-level node. Both run before the
|
|
44
|
+
doctor passes, so `--keep X --ktx2 --max-size 1024` is the whole prop pipeline.
|
|
45
|
+
|
|
46
|
+
**Modular characters.** Body, shirt, gloves and boots each arrive as their own skinned FBX over the
|
|
47
|
+
same skeleton. `--merge-skins b.fbx c.glb …` moves the donors' skinned meshes onto the model's skin
|
|
48
|
+
(joints matched by name — exact, then the part after the last `:`/`|`, the Animator's own rule) and
|
|
49
|
+
drops their skeleton copies. That last part matters: a hand merge that leaves the donor skeleton in
|
|
50
|
+
gives every bone name a plain-node twin, and a name-bound `Animator` drives the twin while the mesh
|
|
51
|
+
freezes in its rest pose. `probe` and `doctor` now flag duplicate node names, and the doctor marks
|
|
52
|
+
the joint-shadowing case as BLOCKING. Donor bones the base lacks are reported with the skin weight
|
|
53
|
+
that sat on them (it moves to the root).
|
|
54
|
+
|
|
55
|
+
**Checking a clip without a GPU.** `probe` prints, per clip and skeleton root, how far the root
|
|
56
|
+
travels (root motion) and how far its first frame is from the rest pose — a root that starts ~90°
|
|
57
|
+
off is the double-axis-turn symptom of a clip from another export. `probe --filmstrip [out.png]`
|
|
58
|
+
renders a software-skinned PNG strip of every clip (`--clip` for one, `--frames N`, `--view
|
|
59
|
+
side|front|top|threequarter|lowfront`, `--bones` for the skeleton overlay, `--cell` px per frame);
|
|
60
|
+
`tools/filmstrip.ts` is the same renderer with reference frames from other files.
|
|
61
|
+
|
|
62
|
+
**Unity scenes.** `scene Map.unity [-o level.json]` reads a text-serialised scene: every
|
|
63
|
+
`PrefabInstance` (source prefab GUID, the position/rotation/scale/name/active overrides, its parent)
|
|
64
|
+
and every `MeshFilter` object, composes the scene's own `Transform` hierarchy on top (groups like
|
|
65
|
+
`Static/Roads`), and writes the instances with their **world** TRS plus a prefab table — path, use
|
|
66
|
+
count, and a guess at the FBX + `--keep` node that renders it (the FBX from the prefab's folder /
|
|
67
|
+
`Source/` whose stem prefixes the prefab name; most packs ship their prefabs binary-serialised, so
|
|
68
|
+
the prefab's own contents are not read). Frame: Unity is left-handed and its FBX importer mirrors X,
|
|
69
|
+
so the default undoes that — position `(−x, y, z)`, quaternion `(x, −y, −z, w)` — `--handedness z`
|
|
70
|
+
mirrors Z instead (content authored inside Unity), `none` keeps Unity's numbers. Subsets:
|
|
71
|
+
`--filter <re>` (prefab path / name / group), `--group <name>`, `--region x0,z0,x1,z1` (Unity
|
|
72
|
+
metres), `--include-inactive`. A parent that sits inside a binary prefab resolves to that prefab's
|
|
73
|
+
root and the instance is marked `approximate`. The output is plain JSON:
|
|
74
|
+
`{ prefabs: { guid: { path, name, fbx, node, count } }, instances: [ { name, prefab, group,
|
|
75
|
+
position, rotation, scale } ] }` — one `Model.load` per prefab, one `clone()` per instance.
|
|
76
|
+
|
|
77
|
+
## Unity packs — `unpack` + materials from `.mat`
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
lecodes assets unpack "Tactical FPS Animations.unitypackage" -o D:/Unity/tactical # everything
|
|
81
|
+
lecodes assets unpack pack.unitypackage -o out --only 'Meshes/' 'Animations/' 'Materials/' # regex subset
|
|
82
|
+
lecodes assets unpack pack.unitypackage -o out --list --only '\.wav$' # just list
|
|
83
|
+
lecodes assets convert out/Assets/Pack/Weapons/SKM_TR-15.FBX --clips … --ktx2 -o tr15.glb # .mat-aware
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
A `.unitypackage` is a gzip'd tar of one folder per asset GUID. `unpack` streams it twice (flat
|
|
87
|
+
memory on multi-GB packs): pass 1 writes `<out>/unity-index.txt` (GUID → path), pass 2 writes the
|
|
88
|
+
matching assets + their `.meta` sidecars under `<out>/Assets/…`.
|
|
89
|
+
|
|
90
|
+
`convert` then reads the **Unity `.mat` files** found next to (or one folder above) the FBX and
|
|
91
|
+
rewrites the glTF materials from them — an FBX exported for Unity usually references only a legacy
|
|
92
|
+
base colour and a normal map, while metallic / roughness / occlusion / emission and the alpha mode
|
|
93
|
+
live in the material. Matching is by name modulo `M_`/`MI_`/`MAT_` prefixes, then by containment
|
|
94
|
+
(`MI_Tshirt` ↔ `M_Shirt`), then by a shared base texture (`MI_Skin` ↔ `M_Body` through
|
|
95
|
+
`TX_Body_BaseColor`). Texture GUIDs resolve through `unity-index.txt` (found walking up from the
|
|
96
|
+
FBX, or `--unity-index`), else through `.meta` sidecars of a real Unity project. Slots understood:
|
|
97
|
+
`_BaseMap/_MainTex/_BaseColor/_BC`, `_BumpMap/_Normal`, a ready `_ORM`, HDRP/URP `_MaskMap`
|
|
98
|
+
(R metal, G AO, A smooth), Standard `_MetallicGlossMap` (R metal, A smooth), separate `_Metallic`,
|
|
99
|
+
`_Roughness` / `_Smoothness`, `_OcclusionMap`, `_EmissionMap` (+ `_EMISSION` keyword / colour),
|
|
100
|
+
`_Surface`/`_Mode`/`_AlphaClip`/`_Cutoff`/`_Cull`. Everything that is not already one texture is
|
|
101
|
+
**packed the glTF way** (R = AO, G = roughness, B = metallic; PNG/TGA/BMP decoded in-process, other
|
|
102
|
+
formats through `sharp`) — one metallic-roughness(+occlusion) texture per material. `--no-unity`
|
|
103
|
+
keeps the FBX references; `--unity-mats <dir>` looks elsewhere. Only YAML-serialised `.mat` files are
|
|
104
|
+
readable (Unity "Force Text", the asset-store default since 2017); binary ones are listed and skipped.
|
|
105
|
+
|
|
18
106
|
## Retarget — clips from a different skeleton
|
|
19
107
|
|
|
20
108
|
`convert --clips` binds tracks by BONE NAME and copies them verbatim; that only works when both
|
|
@@ -192,7 +280,16 @@ Both engines implement it: Filament through gltfio's `unlit_opaque` ubershader,
|
|
|
192
280
|
- **`--clips`**: each clip file's animations are re-targeted onto the model **by bone name** (exact,
|
|
193
281
|
then the part after the last `:`/`|` — the same rule the SDK `Animator` uses); clip meshes/skins
|
|
194
282
|
dropped; unbound tracks counted + warned. Naming: a single-clip file → its stack name unless generic
|
|
195
|
-
(`mixamo.com`, `Take 001`) → the file's basename; `--clip-names` overrides.
|
|
283
|
+
(`mixamo.com`, `Take 001`, `Unreal Take`) → the file's basename; `--clip-names` overrides.
|
|
284
|
+
**Parent-space correction**: a track is a LOCAL transform, and two exports of one rig need not
|
|
285
|
+
agree on what sits above a bone — Unreal/Unity clip exports put `root` straight under the scene
|
|
286
|
+
(rest −90°X, the Z-up→Y-up turn) while the skinned-mesh export parks that turn on an armature node
|
|
287
|
+
and leaves `root` at identity; copied verbatim the turn applies twice and the character plays lying
|
|
288
|
+
on the floor (KINEMATION's Tactical Shooter Pack, every UE5 Mannequin export). For each bound bone
|
|
289
|
+
the converter compares the world transform of its parent in both files — with the clip's first
|
|
290
|
+
frame written onto every bone it animates, so only the unanimated STRUCTURE above differs, never
|
|
291
|
+
the hair-width rest-pose drift between re-exports — and re-expresses the track in the model's
|
|
292
|
+
parent space (`local_model = inv(Pmodel)·Pclip·local_clip`). Reported as an ℹ line per clip file.
|
|
196
293
|
- Output goes through the same validation as the doctor (`validateGlb`) and a `probe` summary.
|
|
197
294
|
|
|
198
295
|
Skipped in v1: cameras/lights, morph targets, NURBS.
|
|
@@ -219,8 +316,11 @@ vendor/ufbx/ ufbx.h / ufbx.c (MIT)
|
|
|
219
316
|
src/wasm/fbxReader.js generated single-file Emscripten module (tracked; rebuild with build-wasm.sh)
|
|
220
317
|
src/fbx/ read.ts (wasm boundary + IR) · toDocument.ts · convert.ts (merge, write)
|
|
221
318
|
src/textures/ resolveImage.ts (sniff, TGA/BMP decode, PNG encode, external lookup)
|
|
222
|
-
src/doctor/ doctor.ts (analyze/apply/report) · reduceGlbBones · transcodeTextures · transcodeKtx2 · normalizeScale · validateGlb (mirror of viewer's)
|
|
319
|
+
src/doctor/ doctor.ts (analyze/apply/report) · reduceGlbBones · transcodeTextures · transcodeKtx2 · normalizeScale · lightmapUv (xatlas wasm) · validateGlb (mirror of viewer's)
|
|
223
320
|
src/retarget/ rig.ts (hierarchy + bind pose + clip sampling) · retarget.ts (reference poses, world-delta transfer, leg IK, bridges) · automap.ts (skeleton classification + chain pairing) · unityAnim.ts (Unity .anim YAML reader)
|
|
224
|
-
|
|
321
|
+
src/unity/ unpack.ts (.unitypackage streaming) · materials.ts (.mat → glTF materials, ORM packing) · png.ts · scene.ts (.unity → level JSON)
|
|
322
|
+
src/props.ts --list-nodes / --keep / --split (subtree carving + re-origin)
|
|
323
|
+
src/mergeSkins.ts --merge-skins (skinned meshes onto one skeleton) · duplicateNodeNames
|
|
324
|
+
src/filmstrip.ts CPU-skinned PNG filmstrip renderer (probe --filmstrip; tools/filmstrip.ts wraps it)
|
|
225
325
|
src/probe.ts · src/cli.ts · src/index.ts
|
|
226
326
|
```
|