Remotion: Krallen und Daumen als eigene Teile, weicherer Schal am Hals, Pinos Kopfgelenk, Noa im VerformTest (8 s)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Pascal Scholz 2026-10-28 20:28:02 +00:00
parent 6a29198c32
commit c2e5a95846
37 changed files with 124 additions and 17 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 823 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 712 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 601 B

After

Width:  |  Height:  |  Size: 577 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 379 B

After

Width:  |  Height:  |  Size: 361 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 363 B

After

Width:  |  Height:  |  Size: 413 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 806 B

After

Width:  |  Height:  |  Size: 792 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,016 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 980 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 976 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 751 B

After

Width:  |  Height:  |  Size: 901 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 923 B

After

Width:  |  Height:  |  Size: 986 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 923 B

After

Width:  |  Height:  |  Size: 950 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3 KiB

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 639 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 600 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 545 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 386 KiB

After

Width:  |  Height:  |  Size: 388 KiB

View file

@ -9,7 +9,7 @@ const BILD = {fps: 30, width: 1920, height: 1080} as const;
export const Root: React.FC = () => ( export const Root: React.FC = () => (
<> <>
<Composition id="LaterneTest" component={LaterneTest} durationInFrames={420} {...BILD} /> <Composition id="LaterneTest" component={LaterneTest} durationInFrames={420} {...BILD} />
<Composition id="VerformTest" component={VerformTest} durationInFrames={180} {...BILD} /> <Composition id="VerformTest" component={VerformTest} durationInFrames={240} {...BILD} />
<Composition id="HintergrundAbend" component={HintergrundAbend} durationInFrames={1} {...BILD} /> <Composition id="HintergrundAbend" component={HintergrundAbend} durationInFrames={1} {...BILD} />
<Composition id="VordergrundAbend" component={VordergrundAbend} durationInFrames={1} {...BILD} /> <Composition id="VordergrundAbend" component={VordergrundAbend} durationInFrames={1} {...BILD} />
<Composition id="PapierStruktur" component={PapierStruktur} durationInFrames={1} {...BILD} /> <Composition id="PapierStruktur" component={PapierStruktur} durationInFrames={1} {...BILD} />

View file

@ -17,7 +17,7 @@ export const RIGS: Record<string, Record<string, RigTeil>> = {
"eltern": "rumpf", "eltern": "rumpf",
"um": [ "um": [
124, 124,
244 252
] ]
}, },
"kopf": { "kopf": {
@ -123,8 +123,8 @@ export const RIGS: Record<string, Record<string, RigTeil>> = {
"kopf": { "kopf": {
"eltern": "koerper", "eltern": "koerper",
"um": [ "um": [
400, 405,
465 450
] ]
}, },
"ohr_links": { "ohr_links": {
@ -176,6 +176,34 @@ export const RIGS: Record<string, Record<string, RigTeil>> = {
600 600
] ]
}, },
"krallen_hand_links": {
"eltern": "arm_links",
"um": [
366,
524
]
},
"krallen_hand_rechts": {
"eltern": "arm_rechts",
"um": [
452,
526
]
},
"krallen_fuss_links": {
"eltern": "fuss_links",
"um": [
332,
590
]
},
"krallen_fuss_rechts": {
"eltern": "fuss_rechts",
"um": [
526,
580
]
},
"bommel": { "bommel": {
"eltern": "muetze", "eltern": "muetze",
"um": [ "um": [
@ -234,6 +262,20 @@ export const RIGS: Record<string, Record<string, RigTeil>> = {
172 172
] ]
}, },
"daumen_links": {
"eltern": "hand_links",
"um": [
68,
300
]
},
"daumen_rechts": {
"eltern": "hand_rechts",
"um": [
186,
296
]
},
"bein_links": { "bein_links": {
"eltern": "rumpf", "eltern": "rumpf",
"um": [ "um": [

View file

@ -1,12 +1,13 @@
import React from 'react'; import React from 'react';
import {AbsoluteFill, Img, staticFile, useCurrentFrame} from 'remotion'; import {AbsoluteFill, Img, staticFile, useCurrentFrame} from 'remotion';
import {feder, kreis, kurve} from '../bewegung'; import {feder, kreis, kurve} from '../bewegung';
import {LULU, PINO} from '../figuren'; import {LULU, NOA, PINO} from '../figuren';
import {RIGS} from '../rigs'; import {RIGS} from '../rigs';
import {VerformFigur, type Knochen} from '../teile/VerformFigur'; import {VerformFigur, type Knochen} from '../teile/VerformFigur';
// Test für weiche Animation (6 s, ohne Ton): Lulu beugt den Hals zu Pino // Test für weiche Animation (8 s, ohne Ton) mit möglichst vielen Bewegungsarten:
// hinunter, Pino freut sich und hüpft. Alles über Gitter-Verformung mit den // Lulu beugt den Hals, schüttelt den Kopf, stampft; Pino hüpft, winkt, tippt mit dem
// Fuß; Noa winkt, wippt, hüpft, die Zöpfe schwingen nach. Alles über Gitter-Verformung mit den
// Körperteil-Masken aus werkzeuge/rig.py und Federn; Hintergrund und Papier // Körperteil-Masken aus werkzeuge/rig.py und Federn; Hintergrund und Papier
// sind vorberechnete Bilder. // sind vorberechnete Bilder.
@ -37,8 +38,9 @@ const ohrZucken = (f: number, bei: number[]) => (bei.some((b) => f >= b && f < b
const luluKnochen = (f: number): Knochen[] => { const luluKnochen = (f: number): Knochen[] => {
const hals = feder(halsKurve, f, {steifheit: 0.11, daempfung: 0.2}); const hals = feder(halsKurve, f, {steifheit: 0.11, daempfung: 0.2});
const halsTraege = feder(halsKurve, f, {steifheit: 0.05, daempfung: 0.16}); const halsTraege = feder(halsKurve, f, {steifheit: 0.05, daempfung: 0.16});
const kopf = feder((g) => -halsKurve(g) * 0.35 + neugierKurve(g), f, {steifheit: 0.07, daempfung: 0.18}); const schuetteln = (g: number) => (g > 160 && g < 200 ? 8 * Math.sin((g - 160) * 0.45) * Math.sin(((g - 160) / 40) * Math.PI) : 0);
const ohrL = feder((g) => -26 * ohrZucken(g, [20, 98]), f, {steifheit: 0.25, daempfung: 0.12}) + (hals - halsTraege) * 0.6; const kopf = feder((g) => -halsKurve(g) * 0.35 + neugierKurve(g) + schuetteln(g), f, {steifheit: 0.12, daempfung: 0.2});
const ohrL = feder((g) => -26 * ohrZucken(g, [20, 98, 182]), f, {steifheit: 0.25, daempfung: 0.12}) + (hals - halsTraege) * 0.6;
const ohrR = feder((g) => 22 * ohrZucken(g, [60, 104]), f, {steifheit: 0.25, daempfung: 0.12}) - (hals - halsTraege) * 0.5; const ohrR = feder((g) => 22 * ohrZucken(g, [60, 104]), f, {steifheit: 0.25, daempfung: 0.12}) - (hals - halsTraege) * 0.5;
const schwanz = -6 + 10 * Math.sin(f * 0.085) + feder((g) => halsKurve(g) * 0.5, f, {steifheit: 0.04, daempfung: 0.1}) - halsKurve(f) * 0.5; const schwanz = -6 + 10 * Math.sin(f * 0.085) + feder((g) => halsKurve(g) * 0.5, f, {steifheit: 0.04, daempfung: 0.1}) - halsKurve(f) * 0.5;
const schal = (halsTraege - hals) * 0.9 + 2.5 * Math.sin(f * 0.07); const schal = (halsTraege - hals) * 0.9 + 2.5 * Math.sin(f * 0.07);
@ -48,7 +50,8 @@ const luluKnochen = (f: number): Knochen[] => {
gelenk('lulu', 'ohr_rechts', ohrR), gelenk('lulu', 'ohr_rechts', ohrR),
gelenk('lulu', 'kopf', kopf), gelenk('lulu', 'kopf', kopf),
gelenk('lulu', 'hals', hals), gelenk('lulu', 'hals', hals),
gelenk('lulu', 'schwanz', schwanz), gelenk('lulu', 'schwanz', schwanz + (f > 200 ? 8 * Math.sin(f * 0.3) : 0)),
gelenk('lulu', 'bein_vorne_rechts', feder((g) => kurve(g, [[198, 0], [206, 14], [214, 0], [218, 6], [224, 0]]), f, {steifheit: 0.3, daempfung: 0.3})),
gelenk('lulu', 'schal_ende', schal), gelenk('lulu', 'schal_ende', schal),
{name: 'atmen', art: 'stauchen', um: [175, 330], sx: 1 - atmen * 0.006, sy: 1 + atmen * 0.012, gewicht: kreis(180, 300, 50, 110)}, {name: 'atmen', art: 'stauchen', um: [175, 330], sx: 1 - atmen * 0.006, sy: 1 + atmen * 0.012, gewicht: kreis(180, 300, 50, 110)},
]; ];
@ -79,21 +82,54 @@ const pinoWerte = (f: number) => {
const ohr = feder((g) => (g > SPRUNG.auf && g < SPRUNG.auf + 3 ? -14 : 0), f, {steifheit: 0.25, daempfung: 0.12}); const ohr = feder((g) => (g > SPRUNG.auf && g < SPRUNG.auf + 3 ? -14 : 0), f, {steifheit: 0.25, daempfung: 0.12});
const atmen = Math.sin(f * 0.11); const atmen = Math.sin(f * 0.11);
const knochen: Knochen[] = [ const knochen: Knochen[] = [
gelenk('pino', 'krallen_fuss_rechts', f > 190 && f < 226 ? -8 * Math.sin((f - 190) * 0.7) : 0),
gelenk('pino', 'krallen_hand_links', freude(f) * 10),
gelenk('pino', 'krallen_hand_rechts', -freude(f) * 10),
gelenk('pino', 'bommel', bommel + 3 * Math.sin(f * 0.06)), gelenk('pino', 'bommel', bommel + 3 * Math.sin(f * 0.06)),
gelenk('pino', 'ohr_links', ohr), gelenk('pino', 'ohr_links', ohr),
gelenk('pino', 'ohr_rechts', -ohr * 0.7), gelenk('pino', 'ohr_rechts', -ohr * 0.7),
gelenk('pino', 'arm_links', freude(f) * 12), gelenk('pino', 'arm_links', freude(f) * 12),
gelenk('pino', 'arm_rechts', -freude(f) * 10), gelenk('pino', 'arm_rechts', -freude(f) * 10),
gelenk('pino', 'kopf', feder(pinoKopfKurve, f, {steifheit: 0.08, daempfung: 0.2})), gelenk('pino', 'kopf', feder((g) => pinoKopfKurve(g) + (g > 150 && g < 176 ? 4 * Math.sin((g - 150) * 0.5) : 0), f, {steifheit: 0.1, daempfung: 0.2})),
gelenk('pino', 'fuss_rechts', f > 190 && f < 226 ? -10 * Math.abs(Math.sin((f - 190) * 0.35)) : 0),
{name: 'atmen', art: 'stauchen', um: [420, 600], sx: 1 + atmen * 0.01, sy: 1 + atmen * 0.014, gewicht: kreis(420, 540, 70, 170)}, {name: 'atmen', art: 'stauchen', um: [420, 600], sx: 1 + atmen * 0.01, sy: 1 + atmen * 0.014, gewicht: kreis(420, 540, 70, 170)},
]; ];
return {stauch, hoch, knochen}; return {stauch, hoch, knochen};
}; };
// ---------- Noa ----------
const noaKopfKurve = (f: number) => kurve(f, [[0, 0], [20, 0], [40, 8], [120, 8], [135, 0], [215, 0], [225, -5], [232, 4], [240, 0]]);
const noaHuepfer = (f: number) => (f > 140 && f < 176 ? Math.abs(Math.sin(((f - 140) / 36) * Math.PI * 2)) : 0);
const noaNeigen = (f: number) => kurve(f, [[0, 0], [180, 0], [192, 3], [204, -3], [216, 0]]);
const noaWerte = (f: number) => {
const kopf = feder(noaKopfKurve, f, {steifheit: 0.1, daempfung: 0.2});
const arm = feder((g) => kurve(g, [[0, 0], [58, 0], [74, -75], [118, -75], [134, 0]]), f, {steifheit: 0.15, daempfung: 0.25});
const winken = f > 76 && f < 118 ? 18 * Math.sin((f - 76) * 0.45) : 0;
const hoch = noaHuepfer(f) * 22;
// Zöpfe hängen nach: reagieren auf Kopf, Neigen und Hüpfen
const schwung = (g: number) => -noaKopfKurve(g) * 1.2 - noaNeigen(g) * 3 + noaHuepfer(g) * 10;
const zopf = feder(schwung, f, {steifheit: 0.06, daempfung: 0.08}) + 2 * Math.sin(f * 0.07);
const atmen = Math.sin(f * 0.1);
const knochen: Knochen[] = [
gelenk('noa', 'zopf_links', -zopf),
gelenk('noa', 'zopf_rechts', zopf),
gelenk('noa', 'daumen_rechts', winken * 0.8),
gelenk('noa', 'hand_rechts', winken),
gelenk('noa', 'arm_rechts', arm + winken * 0.3),
gelenk('noa', 'arm_links', 3 * Math.sin(f * 0.06) + noaHuepfer(f) * 8),
gelenk('noa', 'kopf', kopf),
{name: 'atmen', art: 'stauchen', um: [115, 300], sx: 1 + atmen * 0.006, sy: 1 + atmen * 0.01, gewicht: kreis(115, 240, 40, 120)},
];
const stauch = feder((g) => (noaHuepfer(g) > 0 && noaHuepfer(g) < 0.15 ? 0.95 : 1), f, {steifheit: 0.3, daempfung: 0.3});
return {knochen, hoch, stauch, neigen: feder(noaNeigen, f, {steifheit: 0.1, daempfung: 0.2})};
};
export const VerformTest: React.FC = () => { export const VerformTest: React.FC = () => {
const f = useCurrentFrame(); const f = useCurrentFrame();
const zoom = 1.04 - f * 0.0002; const zoom = 1.04 - f * 0.0002;
const pino = pinoWerte(f); const pino = pinoWerte(f);
const noa = noaWerte(f);
const luluNeigen = kurve(f, [[30, 0], [74, -2.5], [118, -2.5], [150, 0]]) + Math.sin(f * 0.05) * 0.5; const luluNeigen = kurve(f, [[30, 0], [74, -2.5], [118, -2.5], [150, 0]]) + Math.sin(f * 0.05) * 0.5;
const abend = 'brightness(0.86) saturate(0.95)'; const abend = 'brightness(0.86) saturate(0.95)';
@ -101,6 +137,17 @@ export const VerformTest: React.FC = () => {
<AbsoluteFill style={{backgroundColor: '#24325c', overflow: 'hidden'}}> <AbsoluteFill style={{backgroundColor: '#24325c', overflow: 'hidden'}}>
<AbsoluteFill style={{transform: `scale(${zoom})`}}> <AbsoluteFill style={{transform: `scale(${zoom})`}}>
<Img src={staticFile('hintergrund/abend.png')} style={{position: 'absolute', inset: 0, width: '100%', height: '100%'}} /> <Img src={staticFile('hintergrund/abend.png')} style={{position: 'absolute', inset: 0, width: '100%', height: '100%'}} />
<VerformFigur
daten={NOA}
hoehe={430}
x={560}
y={BODEN + 4 - noa.hoch}
drehung={noa.neigen}
stauchX={2 - noa.stauch}
stauchY={noa.stauch}
knochen={noa.knochen}
filter={abend}
/>
<VerformFigur <VerformFigur
daten={PINO} daten={PINO}
hoehe={210} hoehe={210}

View file

@ -38,7 +38,7 @@ FIGUREN_DEF = {
"knochen": [ "knochen": [
("rumpf", None, (130, 292), (232, 296), 0, ("rumpf", None, (130, 292), (232, 296), 0,
[(120, 245), (170, 238), (230, 250), (248, 290), (245, 345), (200, 352), (150, 347), (105, 337), (95, 300)]), [(120, 245), (170, 238), (230, 250), (248, 290), (245, 345), (200, 352), (150, 347), (105, 337), (95, 300)]),
("hals", "rumpf", (124, 244), (112, 156), 14, ("hals", "rumpf", (124, 252), (112, 156), 70,
[(78, 150), (132, 140), (142, 178), (128, 205), (85, 205), (72, 176)]), [(78, 150), (132, 140), (142, 178), (128, 205), (85, 205), (72, 176)]),
("kopf", "hals", (111, 150), (86, 72), 10, ("kopf", "hals", (111, 150), (86, 72), 10,
[(10, 60), (40, 45), (60, 47), (80, 25), (110, 30), (140, 52), (146, 90), (136, 135), (126, 152), [(10, 60), (40, 45), (60, 47), (80, 25), (110, 30), (140, 52), (146, 90), (136, 135), (126, 152),
@ -84,7 +84,7 @@ FIGUREN_DEF = {
"knochen": [ "knochen": [
("koerper", None, (230, 430), (400, 560), 0, ("koerper", None, (230, 430), (400, 560), 0,
[(20, 350), (150, 200), (245, 200), (262, 300), (275, 445), (300, 560), (200, 640), (60, 560)]), [(20, 350), (150, 200), (245, 200), (262, 300), (275, 445), (300, 560), (200, 640), (60, 560)]),
("kopf", "koerper", (400, 465), (420, 320), 26, ("kopf", "koerper", (405, 450), (420, 320), 60,
[(245, 150), (300, 115), (420, 120), (520, 180), (554, 240), (554, 455), (470, 465), (380, 462), [(245, 150), (300, 115), (420, 120), (520, 180), (554, 240), (554, 455), (470, 465), (380, 462),
(300, 450), (268, 400), (262, 300), (240, 240)]), (300, 450), (268, 400), (262, 300), (240, 240)]),
("ohr_links", "kopf", (290, 258), (256, 222), 6, "kreis", (272, 240, 27)), ("ohr_links", "kopf", (290, 258), (256, 222), 6, "kreis", (272, 240, 27)),
@ -92,13 +92,21 @@ FIGUREN_DEF = {
("bauch", "koerper", (330, 600), (430, 470), 20, ("bauch", "koerper", (330, 600), (430, 470), 20,
[(300, 450), (470, 460), (485, 520), (480, 600), (470, 640), (400, 645), (360, 610), (300, 545)]), [(300, 450), (470, 460), (485, 520), (480, 600), (470, 640), (400, 645), (360, 610), (300, 545)]),
("arm_links", "koerper", (322, 462), (370, 540), 10, ("arm_links", "koerper", (322, 462), (370, 540), 10,
[(322, 456), (345, 470), (380, 505), (392, 540), (372, 556), (345, 548), (330, 515), (318, 480)]), [(322, 456), (345, 470), (382, 505), (400, 535), (398, 562), (372, 564), (345, 552), (330, 515), (318, 480)]),
("arm_rechts", "koerper", (458, 468), (455, 545), 10, ("arm_rechts", "koerper", (458, 468), (455, 545), 10,
[(445, 465), (472, 470), (478, 520), (470, 556), (442, 556), (440, 510)]), [(445, 465), (472, 470), (478, 520), (474, 562), (440, 564), (424, 550), (422, 515), (430, 498), (442, 490)]),
("fuss_links", "koerper", (310, 568), (320, 645), 10, ("fuss_links", "koerper", (310, 568), (320, 645), 10,
[(305, 565), (355, 563), (360, 600), (350, 655), (315, 658), (305, 620)]), [(300, 560), (360, 556), (364, 600), (350, 655), (315, 658), (300, 620)]),
("fuss_rechts", "koerper", (495, 600), (540, 575), 10, ("fuss_rechts", "koerper", (495, 600), (540, 575), 10,
[(495, 560), (540, 548), (554, 575), (545, 640), (505, 640), (490, 600)]), [(495, 556), (540, 540), (554, 560), (554, 640), (505, 640), (490, 600)]),
("krallen_hand_links", "arm_links", (366, 524), (372, 540), 4,
[(348, 524), (366, 514), (395, 520), (395, 546), (352, 548)]),
("krallen_hand_rechts", "arm_rechts", (452, 526), (454, 542), 4,
[(434, 528), (455, 522), (474, 528), (472, 546), (436, 546)]),
("krallen_fuss_links", "fuss_links", (332, 590), (332, 568), 4,
[(306, 566), (330, 558), (358, 562), (356, 594), (308, 594)]),
("krallen_fuss_rechts", "fuss_rechts", (526, 580), (540, 556), 4,
[(512, 560), (532, 546), (554, 548), (554, 578), (516, 582)]),
], ],
"accessoires": [ "accessoires": [
{ {
@ -128,6 +136,10 @@ FIGUREN_DEF = {
[(35, 180), (62, 165), (78, 180), (80, 290), (60, 300), (35, 295), (28, 240)]), [(35, 180), (62, 165), (78, 180), (80, 290), (60, 300), (35, 295), (28, 240)]),
("arm_rechts", "rumpf", (166, 172), (183, 272), 12, ("arm_rechts", "rumpf", (166, 172), (183, 272), 12,
[(160, 170), (180, 180), (192, 230), (195, 275), (178, 280), (165, 240), (158, 190)]), [(160, 170), (180, 180), (192, 230), (195, 275), (178, 280), (165, 240), (158, 190)]),
("daumen_links", "hand_links", (68, 300), (78, 310), 3,
[(67, 292), (76, 294), (81, 304), (79, 315), (70, 315), (66, 302)]),
("daumen_rechts", "hand_rechts", (186, 296), (192, 281), 3,
[(184, 279), (192, 278), (199, 286), (198, 298), (188, 299), (183, 290)]),
("bein_links", "rumpf", (92, 322), (92, 350), 6, ("bein_links", "rumpf", (92, 322), (92, 350), 6,
[(78, 320), (108, 320), (108, 352), (80, 352)]), [(78, 320), (108, 320), (108, 352), (80, 352)]),
("bein_rechts", "rumpf", (128, 322), (132, 350), 6, ("bein_rechts", "rumpf", (128, 322), (132, 350), 6,
@ -291,6 +303,12 @@ def baue(figur, d):
labels[rest] = koerper[rest] labels[rest] = koerper[rest]
# 3. abgeschnittene Inseln über die ganze Figur auffüllen # 3. abgeschnittene Inseln über die ganze Figur auffüllen
labels = ausbreiten(labels, figur_maske) labels = ausbreiten(labels, figur_maske)
# 4. Teile, die an einem Accessoire hängen (z. B. Daumen am Fäustling), nehmen sich ihre Fläche daraus
accessoire_namen = {t[0] for g in d["accessoires"] for t in g["teile"]}
for name, eltern, _, _, _, *flaeche in d["knochen"]:
if eltern in accessoire_namen and flaeche:
f = flaechen_pixel(flaeche, form) & figur_maske & (labels == nummer[eltern])
labels[f] = nummer[name]
ziel = os.path.join(FIGUREN, figur) ziel = os.path.join(FIGUREN, figur)
os.makedirs(os.path.join(ziel, "teile"), exist_ok=True) os.makedirs(os.path.join(ziel, "teile"), exist_ok=True)