- C# 74.1%
- Python 25.3%
- Batchfile 0.6%
| kkunity_converter | ||
| kkunity_plugin | ||
| .gitignore | ||
| build_converter.bat | ||
| build_plugin.bat | ||
| build_release.bat | ||
| convert.py | ||
| gui.py | ||
| README.md | ||
| requirements.txt | ||
| run_gui.bat | ||
Koikatsu → Unity Exporter
A two-part pipeline that turns a Koikatsu / Koikatsu Sunshine character into Unity-ready assets:
kkunity_plugin/— an in-game BepInEx plugin (the exporter). Runs inside KK / KKS, bakes the character's materials and skeleton, and dumps an export folder (model.pmx+ baked textures +KK_*.jsonsidecars + screenshots).kkunity_converter/+convert.py/gui.py— a pure-Python converter. Reads that export folder and produces:- a skinned, morph-target
.glb(geometry + rig + blendshapes), and - the loose
unity_exportfolder (per-material textures, JSON sidecars, body masks) that a custom Unity runtime loader reads, optionally packed into a single.kkmarchive.
- a skinned, morph-target
The converter is pure Python — no Blender, no game, no Unity required to run the conversion. Everything (texture compositing, rig prep, morph math, GLB writing) is done with numpy / Pillow / scipy / pygltflib. The plugin is the only part that needs the game.
Koikatsu (game) ──[kkunity_plugin]──▶ export folder ──[kkunity_converter]──▶ .glb / unity_export / .kkm
Part 1 — The exporter plugin (kkunity_plugin/)
A BepInEx plugin (GUID kkunity.plugin) that adds a "kkunity Exporter" panel to
the character maker. It bakes each coordinate's materials into flat albedo
composites, captures the skeleton and morphs, and writes the export folder the
converter consumes.
What it produces
For the selected character it writes Export_PMX/<timestamp>_<name>/ containing:
Outfit 00/
model.pmx that coordinate's mesh + skeleton + morphs
baked materials/ one composited (lit/dark-shaded) albedo PNG per material
cf_m_body_AM_00.png per-coordinate body alpha mask
screenshot.png front preview (transparent bg, auto-cropped)
screenshot back.png back preview
Outfit 01/ ... one folder per exported coordinate
card_data.png the character card
KK_*.json sidecars (see below)
The base/body pass bakes the shared body mesh neutral so per-outfit pushup
morphs (pushup_outfit_NN) can deform it.
Sidecars written (lean mode, the 12 the converter and its Unity runtime loader consume):
| Sidecar | Contains |
|---|---|
KK_MaterialDataComplete.json |
One record per mesh-renderer material slot: the renderer's path/name, coordinate index, and each material's shader name plus render state (cull / cutoff / stencil / ZWrite / alpha mode). The file materials are reconstructed from. |
KK_Coordinates.json |
Per-outfit manifest: each coordinate's name and the list of worn clothing + accessory item names (only the running game can resolve these). |
KK_MaterialData.json |
Per-material shader property table — declared property names and types (Texture / Color / Float) with their values, including _ST tiling transforms and clothing-state floats. |
KK_TextureData.json |
Per-texture UV settings (wrap mode, offset, tiling scale), keyed by texture filename. |
KK_DynamicBoneData.json |
Per-coordinate dynamic-bone (sway physics) chains: root bone plus its damping / elasticity / stiffness curve settings. |
KK_DynamicBoneColliderData.json |
Per-coordinate colliders the dynamic bones collide against: name, shape (radius / height / direction), center, offset rotation, and scale. |
KK_CoordinateBoneModifiers.json |
Per-outfit ABMX bone changes (position / rotation / scale) that differ from the base coordinate, on the shared skeleton. |
KK_EditBoneInfo.json |
Every bone's local and world transform (TRS + matrix) in the game's bone frame; used to orient the exported skeleton so a runtime bone driver sees identity. |
KK_ChaFileCustomBody.json |
Body-customization values: shape sliders, skin/detail IDs and colors, bust softness/weight. |
KK_CharacterInfoData.json |
Character name plus face/eye parameters: personality, pupil size / offset / scale, and eye-highlight offsets. |
KK_BlendShapeInfo.json |
Expression blendshape patterns (Eye / Mouth / …) mapping each named expression to the raw PMX morphs the converter combines. |
KK_AibuHitColliderData.json |
H-scene "Aibu" touch-zone colliders: name, tag / body zone, collider type, and local transform. |
In-game options
The exporter panel exposes: Export All Outfits, Enable Pushups, Lean export (kkunity only), Bulk export all cards in folder, and the Export Model for kkunity button.
- Lean export (default on) ships only what the converter reads — per-material
_AM(alpha mask) and_NMP(normal) textures plus the per-coordinate body_AM, and the 12 sidecars above — skipping the bulk of raw texture maps and 11 legacy sidecars. Turn it off for a full dump. - Enable Pushups captures a per-outfit body morph so each coordinate's pushup is reproducible on the shared body.
Building
Visual Studio / dotnet build against the .sln, with two configurations:
| Config | Framework | Game | Accessory DLLs (in deps/) |
|---|---|---|---|
NET35 |
net35 | Koikatsu (KK) | KK_Accessory_States.dll, KK_Pushup.dll |
NET46 |
net46 | Koikatsu Sunshine (KKS) | KKS_Accessory_States.dll, KKS_Pushup.dll |
# Koikatsu Sunshine
dotnet build kkunity_plugin\kkunity_plugin.sln -c NET46
# Koikatsu
dotnet build kkunity_plugin\kkunity_plugin.sln -c NET35
Game/BepInEx assemblies come from NuGet (IllusionLibs.*, BepInEx,
IllusionModdingAPI). The KK*_Accessory_States / KK*_Pushup reference DLLs
are not redistributed here — drop them in kkunity_plugin/deps/ (see the
placeholder file there). create_release.bat packages both DLLs into a release
zip.
Part 2 — The converter (kkunity_converter/)
Input
Point the converter at an export folder produced by the plugin:
<export>/
model.pmx body mesh + full skeleton + face morphs
KK_*.json material / bone / blendshape / texture sidecars
baked materials/ one composited albedo PNG per body/face/eye material
cf_m_body_AM*.png per-coordinate body alpha masks
cf_m_*_NMP.png raw (packed) normal maps
Outfit 00/
model.pmx that coordinate's clothing + hair
baked materials/ composited albedo for this outfit's materials
screenshot.png preview (used by the GUI)
Outfit 01/ ... one folder per coordinate
Each material's base color comes from the baked materials/ composite (the
exporter's flattened albedo). The gag-eye and tears overlays aren't composited
there, so they fall back to their raw _MT_CT.png.
Output
Modes:
- Single GLB (CLI default): one
.glbwith textures embedded. Self-contained, good for quick viewing / generic glTF tools. - Unity export (
--unity-export/--fbx/--kkm): a<char>.glb— or, with--fbx, a binary<char>.fbxconverted from the same built GLB (identical geometry/rig/blendshapes) — with no embedded textures, plus loose sidecar files the Unity runtime loader rebuilds materials from:<char>.ht— Unity HumanTemplate with the humanoid bone mapping (in the editor's Avatar Configuration use Mapping → Load to apply it)<material>.png/.jpg— per-material main textures (deduplicated by content; aKK_TextureManifest.jsonaliases duplicates back to the one file that was actually written)<material>_NMP.png— raw normal maps, shipped byte-for-byteKK_*.json— the sidecars the loader and its components consume<char>_Outfit_NN_body_AlphaMask.png— per-coordinate body masks- with
--kkm(GLB-based), everything is zipped into a flat<char>.kkmand the loose files are removed — only the bundle remains.
Usage
GUI
run_gui.bat
Pick an export folder; if it has multiple outfits, check which to export (thumbnails come from each outfit's screenshots). Pick the output Format — .kkm bundle (packed, no loose files, the default), GLB + sidecar files, or FBX + sidecar files — toggle the options, pick an output folder, and Export. Other GUI defaults: Ground feet on, Blank unused atlas regions on, JPEG opaque textures off, Include gag eyes & tears off (JPEG quality is fixed at 90).
CLI
# single embedded-texture GLB
python convert.py "path\to\export" -o output\model.glb
# loose unity_export folder (GLB + sidecars)
python convert.py "path\to\export" --unity-export -o output\char
# loose unity_export folder with an FBX instead of the GLB
python convert.py "path\to\export" --fbx -o output\char
# packed .kkm (loose files removed after packing)
python convert.py "path\to\export" --kkm -o output\char
Options:
| flag | effect |
|---|---|
--unity-export |
emit the loose folder instead of a single GLB |
--fbx |
ship the loose folder's model as a binary FBX instead of a GLB (implies --unity-export; incompatible with --kkm) |
--kkm |
zip the folder into <char>.kkm and remove the loose files (implies --unity-export; GLB-based) |
--jpeg-opaque |
ship opaque textures as JPEG (~85% smaller); textures with real transparency stay PNG |
--jpeg-quality N |
JPEG quality for --jpeg-opaque (default 90) |
--mask-unused |
flatten atlas texels a material's UVs never cover, so a material on a sliver of a shared 4096² sheet stops shipping the whole sheet |
--no-ground-feet |
keep KK's original height (don't drop the rig so the feet rest on the floor) |
--gag-tears |
include the gag-eye / tears overlay meshes and their shape keys (off by default) |
How it works
kkunity_converter/
read_export/ parse the PMX + KK_*.json + locate textures
mesh/ combine partial PMX morphs into named blendshapes; build the
gag/tears reveal + per-outfit pushup morphs
armature/ reduce the raw skeleton, rename to Unity humanoid, orient bones
to the game frame from EditBoneInfo
export/ composite/grade/mask textures and write the GLB/FBX + unity_export
read_export — A from-scratch PMX 2.0/2.1 reader (parse_pmx → PMX:
vertices with skin weights and extra UVs, faces, materials with their face
spans, bones, vertex morphs) and an ExportFolder helper that resolves the JSON
sidecars, the baked materials/ composites, normal maps, and per-material
shader properties (alpha-mask transforms, clothing-state floats, the
_AlphaMaskuv UV set).
mesh — combine_morphs merges the partial vertex morphs into named
expression blendshapes following KK_BlendShapeInfo.json (with the eye/mouth
filtering rules), build_pushup_morphs builds the per-outfit pushup morphs, and
build_tear_gag_morphs / build_gag_eye_hide reproduce the gag-eye / tears
visibility trick in morph space: the overlays are pushed back into the head at
rest and a reveal morph moves them forward.
armature — reduce_skeleton cuts the raw PMX skeleton to a clean body rig
(drops IK-target cf_t_*, clothing-parent ct_* and outfit object-root
pseudo-bones o_*/n_*/clothes_*; promotes Center/Hips to roots),
rename_bones maps bones to the Unity humanoid convention (deduping name
collisions by appending _0, _1, … suffixes), and
orient_skeleton re-orients each bone to the game's bone frame from
KK_EditBoneInfo.json so a runtime expression-bone driver sees an identity
correction.
export —
- Textures (
lut.py,unity_export.py): sources each material'sbaked materials/composite, applies KK's Amplify Color LUT grade (vendored as the pre-bakedamplify_lut_table.npy), bakes clothing/bra alpha masks into the texture alpha by rasterizing the mask through the mesh (the mask is sampled in a second UV set, so it can't be combined in texture space), and dedups identical images by content (blake2b hash →KK_TextureManifest.json). Normal maps ship verbatim (any pixel touch would corrupt the packed X/Y encoding). - GLB (
glb.py,build_glb): one self-contained, deduplicated mesh per group (Body / Hair / Outfit / Gag / Tears) with its own vertex buffer, sparse morph targets, and a shared skin; PMX (left-handed) is converted to glTF (right-handed) by negating Z and reversing winding; the rig is optionally grounded so the feet rest on the floor. - FBX (
fbx.py,glb_to_fbx): a pure-Python binary FBX 7.4 writer that translates the finished GLB — same hierarchy, skin clusters, and blendshape channels — using the Blender-style convention Unity expects (Y-up, centimeters,UnitScaleFactor1). Materials are name-only; textures come from the sidecars, same as the GLB path.
Setup
python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
Requires Python 3.10+ (uses X | Y type syntax). Core dependencies: numpy,
Pillow, scipy, pygltflib.
Scope
This targets a clean, animation-ready Unity avatar export: geometry, skinning, blendshapes, materials, and the dynamic-bone / collider sidecars the runtime loader wires up.