Paperdoll Renderer
Desktopeditor voor .ppd-projecten, bone-gebonden equipment en geanimeerde 3D-preview, met productie-export naar paperdoll-maps.
Starten
Rust 1.96.0 staat in rust-toolchain.toml. Op Debian/Ubuntu gebruik je voor bouwen en het venster pkg-config, libxcb1-dev, libx11-dev, libxkbcommon-dev, libxkbcommon-x11-dev en libgl1-mesa-dev. De native filepicker gebruikt XDG Desktop Portal met een GTK/KDE-backend, of Zenity. Live 3D vereist OpenGL 3.3.
cargo run
cargo run -- "/pad met spaties/character.ppd"
cargo run -- --preview-fixture
cargo run -- --status
Project en bodies
Het startvenster biedt Project laden en Nieuw project, beide met een systeembestandkiezer. Nieuw project maakt character.ppd en de mappen assets/body/, assets/equipment/, presets/, cache/ en renders/. De CC0-testlichamen worden in assets/body/ voorbereid. --preview-fixture maakt een tijdelijk voorbeeldproject en opent de mannelijke body.
De eerste projectweergave toont de bodies. Kies man / Kies vrouw selecteert een model. Een extern bestand wordt naar de projectmap gekopieerd; bij een bestaande naam krijgt de kopie een eigen naam. Modellen met bijbestanden kun je met hun relatieve mappenstructuur in de projectmap zetten en daar selecteren. Importoverzicht toont meshes, materialen, textures, joints en clips van glTF/GLB, FBX en OBJ+MTL. FBX-lichamen worden bij het laden één keer genormaliseerd (rechtshandig Y-up, meters) en daarna net als glTF/GLB in de live 3D-preview en de productierender getekend, met al hun animatiestacks als clips.
Open man / Open vrouw opent de body. Een geskind glTF/GLB- of FBX-model, of een statisch lichaam (zie hieronder), is in de live preview zichtbaar:
- Sleep om te orbiten. Vast renderkader tonen toont het gele orthografische exportkader; stel per man/vrouw Vaste renderhoogte, Centrum Y (beeldcentrum) en Pivot Y (orbitpunt) in en sla op. Zonder handmatige pivot gebruikt de editor de rustpose-nekhoogte (of een fallback met diagnose); Auto nek wist een handmatige override. Body en equipment delen exact dezelfde camera. Uitvinken geeft vrije perspectief-preview met scrollzoom; die zoom verandert de export niet. Een echte 24-bits depthbuffer houdt voorste oppervlakken vooraan.
- Kies een animatieclip en gebruik Pauze / Speel.
- Yaw − / Yaw + volgen twaalf hoeken van 30°; pitch -60 / -30 / 0 / 30 / 60 kiezen de hoogte. Yaw 0 kijkt naar de voorkant van de testlichamen (−Z).
- Render alle hoeken rendert alle animaties × alle frames op project-fps × 60 hoeken (5 pitches × 12 yaws). Na de losse maps schrijft hij per maptype een atlas plus een
.ppda-bestand (zie Atlassen en .ppda). De preview wordt direct vervangen door iedere voltooide pass: highres → detail → target/diffuse → depth → normal → specular. Alle passes behouden hun aspect met gecentreerde letterboxing en nearest-neighbor weergave. Voortgang telt echte passes; Annuleer render behoudt geldige gedeeltelijke uitvoer. Live 3D keert terug naar het model; een volgende voltooide renderpass kan de preview weer vervangen. - Stel breedte, hoogte en fps in de Bodies-weergave in. Nieuwe projecten beginnen op 4 fps; een bestaand
.ppdhoudt zijn opgeslagen fps. - Zoek onderbroken runs (ook in de equipment-editor) toont geannuleerde, mislukte of afgebroken runs van dezelfde body/equipmentset met het aantal vastgelegde PNG's. Hervat gaat alleen verder als bronbestanden en instellingen exact gelijk zijn aan die run; anders staat erbij wat er verschilt. Na annuleren verschijnt de run direct in deze lijst. Zie Hervatten hieronder.
!Bodyweergave met animatie- en camerabediening
Statische lichamen (OBJ, glTF/FBX zonder skin)
Een lichaam zonder skin kan ook als man- of vrouwmodel gekozen worden: OBJ+MTL (met PNG-textures naast het model), glTF/GLB zonder skins en FBX zonder skin deformer. Zo'n lichaam wordt getoond en gerenderd zoals het is opgeslagen, zonder skelet of animatie:
- OBJ en FBX lopen door dezelfde rigide lezer als equipment (FBX met dezelfde as-/eenheidsnormalisatie, één keer); bij glTF worden de node-transformaties van de scène één keer in de geometrie verwerkt (gespiegelde transformaties draaien de driehoeksvolgorde om). Animaties in een glTF zonder skin worden niet toegepast; de melding noemt hoeveel.
- De bodyweergave meldt dat het lichaam statisch is. De animatiekeuze en Speel/Pauze zijn uitgeschakeld.
- Pivot Y: zonder nekbot gebruikt de camera Centrum Y als orbitpunt (diagnose Statisch lichaam zonder skeleton); een handmatige Pivot Y werkt zoals bij geskinde lichamen. Vaste renderhoogte/zoom per lichaam blijft gelijk.
- Render alle hoeken maakt één bindpose-sample
clip000-bind× 60 hoeken met dezelfde vier maps (diffuse als onbelichte albedo, normal, depth, specular) op doelresolutie, plus atlassen en.ppda; de set opent viaexport_seten de 2D-preview. - Equipment heeft een bot nodig: in de equipment-editor zijn Skeleton / botten en Render equipment · alle hoeken uitgeschakeld met uitleg, en de live view tekent geen equipment op een statisch lichaam. Een equipmentrender op een statisch lichaam wordt geweigerd.
2D-preview
Vanuit Body of de bodyweergave opent 2D-preview de gerenderde compositor over voltooide exports in renders/. De preview leest iedere export via zijn .ppda en de vier atlassen (gevalideerd, zie Atlassen en .ppda), niet via de losse PNG's. Runs zonder geldige .ppda worden overgeslagen; het aantal staat onder de exportteller, met de reden bij hover. De gedecodeerde atlassen van de huidige selectie blijven in het geheugen tot je een andere selectie kiest of Vernieuw exportsets gebruikt.
- Kies Man / Vrouw. Alleen exportsets met dezelfde project-/rig-/exportcontract (
CompatKey) zijn combineerbaar; equipment verschijnt als multiselect wanneer die contracten overeenkomen. - Kies een clip uit de body-exportinventaris. Speel / Pauze, snelheid en export-fps sturen een looping frame-tijd (elapsed × fps); ontbrekende maps/frames tonen een diagnose in plaats van een stille vervanging.
- Yaw − / Yaw + wrappen over 0…330° (stap 30). Pitch − / Pitch + kiezen −60 / −30 / 0 / +30 / +60 met klemmen (start op 0). Hoekwijzigingen behouden de animatietijd.
- Kies weergavestand Resultaat (standaard; belichte compositie), Alleen diffuse, Alleen normal map, Alleen depth map of Alleen specular map. Mapstanden tonen de diepte-gecompositeerde winnende laag onbelicht (depth als grijswaarde, normal als opgeslagen RGB). Playback, hoek en equipment werken in elke stand; licht alleen in Resultaat.
- Houd Azimut / Elevatie ingedrukt voor continue lichtrotatie in °/s (delta-time); azimut wrapt over 360° zonder klem, elevatie blijft −80…+80. Loslaten, focusverlies of pointer-annulering stopt de beweging. Belichting is alleen voor Resultaat; opgeslagen diffuse blijft unlit albedo.
- Playback loopt over exact
frames / fpsseconden, zodat ieder geëxporteerd frame (inclusief het laatste) even lang duurt (1/fps). - Het samengestelde frame gebruikt nearest-neighbor weergave met uniforme schaal en gecentreerde letterboxing, gelijk aan de productie-passpreviews.
!2D-preview op pitch +60 met een echte FBX-export via .ppda en atlassen
# Native GUI-smoke (Xvfb): synthetische export, losse PNG's verwijderd, preview via .ppda/atlassen
cargo run --example preview2d_smoke --locked -- /tmp/preview2d-smoke
# Zelfde smoke op een bestaand project met echte exports
cargo run --example preview2d_smoke --locked -- /tmp/preview2d-real /tmp/fbx-live
Equipment en opslaan
Vanuit een body opent Equipment de lijst. Kies een definitie of Nieuw.
1. Geef een unieke naam van maximaal 64 kleine letters/cijfers/-/_, zonder spaties; body is gereserveerd. 2. Kies Man / Vrouw voor het lichaam dat je bekijkt; Geldig voor bepaalt op welke geslachten het preset van toepassing is. 3. Onderdeel toevoegen (OBJ/FBX) opent de systeembestandkiezer. Rigide OBJ-onderdelen worden inclusief MTL en relatieve PNG-bijbestanden ingesloten. Rigide (niet-geskinde) FBX-onderdelen krijgen dezelfde as- en eenheidsnormalisatie als FBX-lichamen; in de FBX ingebedde PNG-textures en relatieve PNG-textures naast het model worden in het preset ingesloten. Een gekoppelde diffuse texture vervangt de FBX-diffusekleur. Geskinde FBX-meshes, morph targets en niet-PNG-textures worden met een melding geweigerd. De preview gebruikt diffuse kleur/texture en alpha-cutout (grens 0,5). Bronbestanden mogen de modelmap niet verlaten. 4. Vouw Skeleton / botten uit en kies het doelbot. Stel relatieve positie XYZ en rotatie XYZ in graden in (Rz·Ry·Rx). Het onderdeel beweegt met dat bot tijdens walk/attack of een andere geïmporteerde bodyclip. Iedere sekse heeft een eigen botbinding; de numerieke offsets zijn gedeeld. 5. Kies shinymap (PNG) moduleert de instelbare glanssterkte. Wis shinymap keert terug naar een uniforme glanssterkte. Vul desgewenst Herkomst / licentie in. Meerdere onderdelen kunnen verschillende botten gebruiken. 6. Opslaan bewaart het project plus zelfbevattende binaire .ppe-snapshots in presets/ (geometrie, bronpayloads, materialen/textures, transformaties, bindingen, seksevlaggen en herkomst). Er worden geen bronpaden buiten het project nodig om die opnieuw te laden.
Preset importeren leest een .ppe uit bijvoorbeeld een ander project. Bij een andere rig moet je de botten opnieuw kiezen; er wordt niet stil geretarget. Onvolledige configuraties kunnen worden opgeslagen, maar onopgeloste onderdelen worden niet op een gegist bot getekend. Verwijder onderdeel haalt een onderdeel uit het werkpreset; Verwijder equipmentdefinitie haalt de definitie uit de projectlijst. Oude project-snapshots blijven voor herstel bewaard. Opslaan vereist een bestandssysteem met hardlinks; bij een fout wordt een bestaande snapshot niet overschreven.
Een preset is begrensd op 32 MiB, 32 onderdelen/bestanden en textures van maximaal 2048×2048. Een project heeft maximaal 64 presets en 64 MiB aan presetbestanden. De volledige containerbeschrijving staat in design.md.
# Zelfbevattend CC0-zwaardvoorbeeld in een nieuw tijdelijk project
cargo run --example equipment_fixture --locked
!Werkelijke equipment-editor met geanimeerd lichaam en handgebonden OBJ
Bij sluiten of Ander project / Nieuw project met wijzigingen kies je Opslaan, Niet opslaan of Annuleren. Als opslaan mislukt, blijft het project open. Annuleren in de bestandkiezer behoudt je huidige werk.
Productie-output
De bodyknop rendert alleen het lichaam; Render equipment alle hoeken in de equipment-editor rendert het geselecteerde preset, geanimeerd op het gekozen lichaam maar zonder body-occlusie of bodypixels. De vaste camera van dat geslacht blijft gelijk, ook als je in de live preview vrij hebt ingezoomd.
Per beeld worden alleen vier target-res PNGs opgeslagen: diffuse, normal, depth, specular. Diffuse is onbelichte albedo (basiskleur/texture × vertexkleur, alpha behouden), nooit ingebakken lighting/schaduw/glans. Depth is lineaire 16-bit grayscale+alpha met gemeenschappelijke near/far; normal is view-space RGB+alpha; specular is lineaire sterkte (body nul, equipment inclusief ingestelde shinymap). Highres en detail zijn uitsluitend zichtbare hulpstappen en worden niet opgeslagen.
renders/run-<tijd>-<pid>/male/body/clip001-walk/f000012/p-60_y330_diffuse.png
renders/run-<tijd>-<pid>/male-body-diffuse-atlas.png (en -normal, -depth, -specular)
renders/run-<tijd>-<pid>/male-body-atlas.ppda
Ieder nieuw renderverzoek krijgt een eigen runmap; bestaande uitvoer wordt niet overschreven. render.json (schema 4) beschrijft projectidentiteit, bronfingerprints (SHA-256 van bodybestand en optioneel encoded equipment), camera/pivot/sample/materiaalcontract (pitches −60…+60, yaws 0…330), clipinventaris en telling. Alleen complete.json (zelfde schema, met per-PNG SHA-256 en de namen en SHA-256 van atlassen en .ppda) betekent een volledige set. Exports met een ouder schema worden door de lezers geweigerd. cancelled.json/failed.json markeren een gedeeltelijke run zonder compleet-marker. De bibliotheekmodule export_set opent uitsluitend voltooide runs met overeenkomende identiteit en geverifieerde checksums, en leest de echte RGBA8-/depth16-maps met strikte pad-/formaatvalidatie. De map-/camera-/pixelcontracten staan in design.md.
Atlassen en .ppda
Als alle losse maps klaar en geverifieerd zijn, ook na hervatten, schrijft de render per maptype één atlas in de runmap: <geslacht>-<settype>-<maptype>-atlas.png (bijv. male-sword-normal-atlas.png). Daarna volgt <geslacht>-<settype>-atlas.ppda, en pas daarna complete.json.
- Een cel is exact de doelresolutie, zonder rand of schaling. Iedere rij is één (clip, frame) in bronvolgorde. De 60 kolommen zijn pitch −60→+60 en daarbinnen yaw 0→330. De vier atlassen hebben dezelfde celcoördinaten. Diffuse houdt het sRGB-kenmerk; depth blijft 16-bit grijs+alpha.
- De atlassen worden rij voor rij uit de geverifieerde losse PNG's opgebouwd, atomair gepubliceerd en overschrijven niets. Staat er na een crash al een atlas, dan blijft die staan als hij byte-gelijk is aan de nieuwe; anders wordt hij als
.corrupt-aNNN-Kbewaard en opnieuw geschreven. - Wordt een atlas groter dan 16384 pixels in breedte of hoogte, dan weigert de render vóór de start met een duidelijke melding (verlaag fps, resolutie of animatieduur). Atlassen worden nooit gesplitst.
.ppda(PaPerDollAtlas) is strikte UTF-8 JSON metformat_version1,sex,settype,project(id/naam),identity(body- en equipment-SHA-256, framing, pivotbron, near/far, cameraafstand, normal- en depthcodering),fps,cell_width/cell_height,pitches,yaws,maps(per maptype bestandsnaam, SHA-256, breedte, hoogte) enclips(bronindex, naam, token,frame_count,durationen per frame alle 60 hoeken als{pitch, yaw, x, y, w, h}in atlaspixels).- De bibliotheeklezer
atlas::AtlasSetweigert onbekende velden of versies, bestandsnamen die niet de verwachte naam in dezelfde map zijn, symlinks, afwijkende atlasafmetingen of kleurtypes, celcoördinaten die niet op het raster liggen en SHA-256-fouten.load()decodeert de vier atlassen met dezelfde pixelregels als de losse maps;cell()knipt één hoek eruit.atlas::cross_check_runvergelijkt iedere atlascel met de losse PNG.
Hervatten
Een hervatte run schrijft in dezelfde runmap verder; andere runs blijven onaangeroerd.
- Elke run houdt
progress.jsonlbij: per gepubliceerde PNG het pad en de SHA-256.run.lockvoorkomt dat twee renders tegelijk in dezelfde run schrijven. - Hervatten kan alleen als
render.jsonexact overeenkomt met wat de huidige job zou schrijven: project, geslacht/set, SHA-256 van body en equipment, target, fps, framing/pivot, camera, clips en maps. Een voltooide, vergrendelde of afwijkende run wordt geweigerd. - Vóór het renderen wordt iedere geplande PNG gecontroleerd. Bestanden met een kloppende checksum blijven ongewijzigd en worden niet opnieuw gerenderd. Ontbrekende bestanden worden opnieuw gemaakt. Beschadigde bestanden worden hernoemd naar
<naam>.corrupt-aNNN-Ken daarna opnieuw gemaakt; ze worden niet verwijderd. Een bestaand bestand zonder journalregel (crash tussen schrijven en vastleggen) blijft staan als de opnieuw gerenderde bytes gelijk zijn. Achtergebleven.part-*-bestanden worden niet aangeraakt. - Na de losse maps worden de atlassen en
.ppdageschreven (zie hierboven). - De voortgang begint bij het werk dat al klopt; overgeslagen beelden heten al aanwezig, checksums kloppen. Je kunt opnieuw annuleren en later weer hervatten. Een vorige
cancelled.json/failed.jsonwordt bewaard alsattempt-NNN-…json, enresume.jsonllegt per poging vast wat geverifieerd, ontbrekend en in quarantaine gezet is. Pas als alles klopt, wordtcomplete.jsonmet alle checksums geschreven.
!Onderbroken run hervatten in de bodyweergave
2D depth-compositor (bibliotheek)
De module compositor combineert een body-export met nul of meer compatibele equipment-exports (zelfde project-id, geslacht, body-fingerprint, target, fps, framing, near/far en clipinventaris, gelezen uit export_set of uit de .ppda). Per pixel wint het dichtstbijzijnde geldige oppervlak (depth gray dichterbij = kleiner; epsilon DEPTH_EPSILON_U16 = 1 depthcode; bij gelijke diepte wint de laagste laagindex — geef body eerst, daarna equipment op vaste naamvolgorde). Diffuse, normal en specular komen altijd uit dezelfde winnende laag. Soft alpha van die winnaar blijft behouden; verliezende lagen mixen niet mee (single-depth kan geen willekeurige doorschijnendheid reconstrueren).
relight / compose_and_relight past lineaire ambient + Lambert diffuse + Blinn-Phong specular toe. De lichtvektor staat al in view-space, gelijk aan de geëxporteerde normal maps. Er is geen randlicht: de preview toont alleen deze relight in stand Resultaat. Opgeslagen diffuse blijft elders onbelichte albedo.
use paperdoll_renderer::compositor::{
assert_compatible, compose_and_relight, LayerMaps, Light,
};
use paperdoll_renderer::export_set::ExportRun;
let body = ExportRun::open(&body_run_dir)?;
let sword = ExportRun::open(&sword_run_dir)?;
assert_compatible(&[&body, &sword])?;
let layers = [
LayerMaps::from_export_run(&body, "body", "clip000-walk", 0, 0, 0)?,
LayerMaps::from_export_run(&sword, "sword", "clip000-walk", 0, 0, 0)?,
];
let (maps, lit_rgba) = compose_and_relight(&layers, &Light::default())?;
// Zelfde lagen via de consumentenvorm (.ppda + atlassen):
use paperdoll_renderer::atlas::AtlasSet;
let body = AtlasSet::open_run(&body_run_dir)?.load()?;
let layer = LayerMaps::from_atlas(&body, "body", "clip000-walk", 0, 60, 330)?;
Highres is exact 8× beide assen; batch-targets zijn momenteel 1–256 pixels per as (highres max 2048). Grotere projectinstellingen geven een duidelijke renderfout, geen stil verkleinde supersampling. Maximaal 500000 PNGs / 16 GiB geschatte ongecomprimeerde targetdata per run; output vereist hardlinks. Productie rendert de eerste skinned glTF/GLB-mesh met driehoekprimitieven, basiskleur/8-bit albedotextures en vertexkleuren. Een FBX-lichaam loopt via de genormaliseerde scene (fbx_scene) door dezelfde batch: alle animatiestacks × frames × 60 hoeken, dezelfde vaste camera, nekpivot en vaste zoom per lichaam, en dezelfde vier maps. Equipment hangt op de geanimeerde botten van dat FBX-skelet. Statische lichamen renderen één bindpose-sample per hoek (zie Statische lichamen). Live placement blijft een belichte, vereenvoudigde weergave.
Openen met
- Windows: start de uitgepakte
.exeeenmaal om de editor voor het huidige account aan Openen met toe te voegen. Kies daarna bij een.ppdOpenen met → Paperdoll Renderer (of selecteer de.exevia Bladeren). - Debian/Arch: de pakketten installeren de
.ppd-MIME-koppeling, desktopstarter en iconen. Kies de editor in Openen met. - Linux AppImage/losse binary: bewaar het bestand op zijn vaste plek en voer
"/pad/Paperdoll.AppImage" --install-desktopuit. Hiervoor zijnshared-mime-infoendesktop-file-utilsnodig. Daarna staat de editor in Openen met. Herhaal dit na verplaatsen van de AppImage.
Het venster, de Linux-starter en de Windows-executable gebruiken Tims applicatie-icoon.
Tests
cargo fmt --check
cargo check --workspace --all-targets --locked
cargo clippy --workspace --all-targets --locked -- -D warnings
cargo test --workspace --locked
# Linux GPU-regressie: echte depth-occlusie, normal/depth/specular en lichtonafhankelijke albedo
xvfb-run -a cargo run --example preview_depth_probe --locked
# Productie op echte GPU (body of equipment); controleert daarna iedere atlascel tegen de losse PNG
xvfb-run -a cargo run --example production_fixture --locked -- /tmp/prod tests/fixtures/bodies/test-body-male.glb tests/fixtures/bodies/test-body-female.glb male 32 48 4
# Hervatten op echte GPU: setup, render met crash (abort) of annulering, hervatten, export_set- en atlascontrole
cargo run --release --example resume_fixture --locked -- setup /tmp/resume tests/fixtures/bodies/test-body-male.glb tests/fixtures/bodies/test-body-female.glb 32 32 2
xvfb-run -a cargo run --release --example resume_fixture --locked -- render /tmp/resume male --abort-after=200
xvfb-run -a cargo run --release --example resume_fixture --locked -- resume /tmp/resume male
cargo run --release --example resume_fixture --locked -- verify /tmp/resume
# FBX op echte GPU: live view (lichaam + FBX-zwaard) en volledige productie van body- en equipmentset
xvfb-run -a cargo run --example fbx_live_fixture --locked -- /tmp/fbx-live /tmp/fbx-live-proof 32 48 4
# Statische lichamen op echte GPU: OBJ-man en glTF-vrouw zonder skin, live view, equipment-weigering, productie en export_set
xvfb-run -a -s '-screen 0 1280x1024x24' cargo run --example static_body_fixture --locked -- /tmp/static-body /tmp/static-body-proof 32 48 4
De testlichamen komen uit scripts/generate_test_bodies.py. Het FBX-testbestand wordt met Blender via scripts/convert_test_body_to_fbx.py gemaakt. De rigide FBX-zwaarden (sword-fbx.fbx met ingebedde PNG, sword-fbx-external.fbx met PNG ernaast) komen uit scripts/make_fbx_equipment_fixture.py (Blender, Z-up geëxporteerd). De statische lichamen in tests/fixtures/bodies/static/ (OBJ+MTL+PNG en een GLB zonder skin) komen uit scripts/generate_static_bodies.py.