mirror of
https://github.com/barkeser2002/VMD-Motion-Optimizer.git
synced 2026-09-25 07:10:09 +03:00
.gitignore *.vmd dosyalarını engellediği için fixture'lar depoya konamıyor; tests/conftest.py spesifikasyona uygun VMD baytlarını çalışma anında üretiyor. Kapsam: kayıpsız gidiş-dönüş, interpolasyon eğrilerinin kare bazında korunması, kamera/gölge bloklarının hayatta kalması, VMD 1.0 başlığı, bozuk dosyanın hızlı reddi, çıktı==girdi reddi, atomik yazma, cp932 çevriyazısı, _moving_average uzunluğu, depth eğilim çıkarma, ground IK dışlama ve geri alma, morph sabit aralık sonu, normalize edilmemiş quaternionlar, RDP özyineleme derinliği, iptal ve GUI duman testleri.
98 lines
2.9 KiB
Python
98 lines
2.9 KiB
Python
# VMD Motion Optimizer by Barış Keser (barkeser2002)
|
||
# License: GNU General Public License v3.0 (GPL-3.0)
|
||
|
||
"""Sentetik VMD üreteci.
|
||
|
||
.gitignore `*.vmd` dosyalarını engellediği için depoya örnek dosya konamaz;
|
||
testler kendi fixture'larını bayt seviyesinde üretir.
|
||
"""
|
||
|
||
import os
|
||
import struct
|
||
import sys
|
||
|
||
import pytest
|
||
|
||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'scripts'))
|
||
|
||
SIG_V2 = b"Vocaloid Motion Data 0002"
|
||
SIG_V1 = b"Vocaloid Motion Data file"
|
||
|
||
# Ayırt edilebilir, sıfır olmayan bir interpolasyon bloğu
|
||
SAMPLE_INTERP = bytes(range(64))
|
||
|
||
|
||
def _name_field(name: str, size: int) -> bytes:
|
||
raw = name.encode('cp932')
|
||
assert len(raw) <= size, f"{name!r} {size} bayta sığmıyor"
|
||
return raw + b'\x00' * (size - len(raw))
|
||
|
||
|
||
def build_vmd(model_name: str = "TestModel",
|
||
bones=(),
|
||
morphs=(),
|
||
camera_frames=0,
|
||
light_frames=0,
|
||
shadow_frames=0,
|
||
version: int = 2,
|
||
trailing_blocks: int = 4) -> bytes:
|
||
"""Spesifikasyona uygun bir VMD bayt dizisi üretir.
|
||
|
||
bones: (name, frame, (x,y,z), (qx,qy,qz,qw), interp_bytes) demetleri
|
||
morphs: (name, frame, weight) demetleri
|
||
trailing_blocks: kaç adet kuyruk bloğu yazılacağı (0..4). Eski dosyaları
|
||
taklit etmek için 2 verilebilir.
|
||
"""
|
||
sig = SIG_V2 if version == 2 else SIG_V1
|
||
name_len = 20 if version == 2 else 10
|
||
out = bytearray()
|
||
out += sig + b'\x00' * (30 - len(sig))
|
||
out += _name_field(model_name, name_len)
|
||
|
||
out += struct.pack('<I', len(bones))
|
||
for name, frame, pos, quat, interp in bones:
|
||
out += _name_field(name, 15)
|
||
out += struct.pack('<I', frame)
|
||
out += struct.pack('<7f', *pos, *quat)
|
||
assert len(interp) == 64
|
||
out += interp
|
||
|
||
out += struct.pack('<I', len(morphs))
|
||
for name, frame, weight in morphs:
|
||
out += _name_field(name, 15)
|
||
out += struct.pack('<I', frame)
|
||
out += struct.pack('<f', weight)
|
||
|
||
blocks = [
|
||
(camera_frames, 61),
|
||
(light_frames, 28),
|
||
(shadow_frames, 9),
|
||
]
|
||
for i, (count, size) in enumerate(blocks):
|
||
if i >= trailing_blocks:
|
||
return bytes(out)
|
||
out += struct.pack('<I', count)
|
||
out += bytes(count * size)
|
||
if trailing_blocks >= 4:
|
||
out += struct.pack('<I', 0) # IK/görünürlük blok sayısı
|
||
return bytes(out)
|
||
|
||
|
||
def simple_bone(name="センター", frame=0, y=0.0, z=0.0, interp=None):
|
||
return (name, frame, (0.0, y, z), (0.0, 0.0, 0.0, 1.0), interp or SAMPLE_INTERP)
|
||
|
||
|
||
@pytest.fixture
|
||
def vmd_path(tmp_path):
|
||
"""Bayt dizisini diske yazıp yolunu döndüren yardımcı."""
|
||
def _write(data: bytes, name: str = "in.vmd") -> str:
|
||
p = tmp_path / name
|
||
p.write_bytes(data)
|
||
return str(p)
|
||
return _write
|
||
|
||
|
||
@pytest.fixture
|
||
def out_path(tmp_path):
|
||
return str(tmp_path / "out.vmd")
|