3dicon skill

3dicon — Demo/output image published by the author (skill folder): contact_sheet.png

Illustration · not a resultFrom the samyost1/3dicon README — shows the whole collection, not only this skill.source ↗

Turn a prompt or a still image into a looping animated icon with real transparency — GPT Image / Nano Banana for the art, Seedance via OpenRouter for the motion, exact-unpremultiply matting for the alpha, animated WebP out.

by samyost1·MIT license·★ 562 Stars on the repo·GitHub ↗

Use now

Files of 3dicon

samyost1/main1 file shown
SKILL.md
Show the full text231 lines

3dicon

Generate a still, animate it into a seamless loop, key the background out properly, and encode it as an animated WebP with true soft alpha — the format expo-image, Chrome and Safari all render natively, with no Lottie conversion and no new native dependency.

Before anything

  1. pip install -r requirements.txt (or uv pip install -r requirements.txt).
  2. cp .env.example .env and fill in the keys for the backend you want.
  3. ffmpeg must be on PATH.

Check the keys are present before running — animate costs money, and a run that dies halfway has already spent it.

Always stop after the still

One still, then ask. Never generate several takes to choose from, and never run through to the animation without the user seeing the art first.

  1. Generate exactly ONE still.
  2. Show it to the user and ask plainly whether they are happy with the static design before anything else happens.
  3. Only on a yes, animate it.

Why this is a hard rule, not a nicety: the still is 13 cents and the full run is 48 cents and four minutes, and every later stage inherits the still's object, colour and weight. Animating art the user has not approved means paying twice. run therefore stops after the still on purpose.

If they are not happy, adjust the prompt and generate one replacement — not a grid of options. Variants exist behind --variants but are off by default and should stay that way unless the user asks to compare.

How to report back

Keep the chat almost empty. The user is looking at an image, not reading a report — everything they need is either in the picture or in one line under it.

After the still, say only this, then stop and wait:

Still ready. (show the image) Happy with it, or change something?

Do not explain the prompt, list what you did, or describe the image back to them. They can see it.

Then propose the motion and stop again. Do not animate on the strength of the still being approved — the motion is the second decision and it is the one that costs four minutes. Propose exactly one:

Motion — (one plain sentence: what actually happens, in physical terms) event · lively · --emit

Agree, or describe the motion you want?

One sentence and the flags. No reasoning, no alternatives, no explanation of what the flags mean.

If they describe their own motion, translate it. They will use ordinary words; your job is to turn those into the flags and one physical sentence, not to pass their phrasing through. Work out:

  • what does this object do when left alone? → --strategy
  • how much should the object itself move? → --energy
  • would the action throw something off? → --emit
  • what literally happens to the material? → --motion

Then show the translated proposal in the same format and confirm before running. If what they asked for is not supported — a camera move, something entering the frame, the object travelling away — say so in one line and offer the nearest thing that is.

animate --dry-run composes the full prompt and spends nothing. Use it if you need to check what the flags expand to; do not paste the result into the chat.

After the animation, one line of numbers and the file:

Done — flame.webp · 857 KB · 122f @ 24fps (show the contact sheet)

If verify fails, one line on what failed and one on what you propose:

Motion too weak — step 0.9, needs 1.0. Retry as event with --emit?

Never paste raw CLI output, stage-by-stage progress, the composed motion prompt, or a summary of the pipeline. If a stage succeeds, its output is the file — say the filename and move on. Save the detail for when something breaks and the user has to choose what to do next.

The pipeline

python -m iconloop --out out still   --prompt "a 3D stopwatch, sage green and cream, soft matte plastic"
python -m iconloop --out out animate --motion "the stopwatch rocks gently while its hand sweeps clockwise"
python -m iconloop --out out matte
python -m iconloop --out out encode  --sweep
python -m iconloop --out out encode  --size 288 --name timer
python -m iconloop --out out verify

run is just still plus the reminder to stop and ask. The stages are separate because each one is a place to look before spending the next thing.

Choosing the motion

Before animating, answer one question: what does this object do when left alone? The answer picks the strategy, and picking it wrong is the main cause of a dull or generic result.

answer strategy what you get
It moves by itself — flows, burns, breathes, ticks native continuous motion from its own physics
Nothing. It is inert until used event it performs its function once, then rests
Nothing, but part of it is loose, hinged or light part the body anchors, one small piece moves
Nothing, and it has no moving parts at all surface light or material travels across a fixed form

Most objects people ask for are inert. Asking an inert object to move naturally is what produces a turntable spin or an aimless bob — it has no natural motion, so the model supplies a generic one. Reach for event first unless the thing genuinely moves on its own.

Add --emit when the action would realistically throw something off — a fragment, a droplet, a spark, a glint. For an inert object that emission is often the entire reason the animation reads at icon size.

event and part also permit the object to be temporarily altered — something removed, opened, split, filled — as long as it returns to its opening state so the loop closes. Without that permission an object can only ever jiggle.

How much it moves

--energy decides whether the object itself moves, separately from what it is doing. Body motion is not the enemy — aimless body motion is. A squash, a recoil, a quick shake, an anticipation before an action: that is most of the vocabulary that makes something feel alive, and forbidding it to avoid a turntable spin throws away the whole language.

energy the object
still holds completely rigid
calm leans, settles, breathes a little
lively squashes, tilts, recoils, shakes, springs back
playful anticipates, overshoots, wobbles, hops in place

Defaults follow the strategy — native and event get lively, part gets calm, surface gets still — and an explicit --energy always wins. If a result feels lifeless, raise it before changing anything else.

Only two things stay forbidden at every level: rotating as a whole, and drifting. Everything else is available.

Small and many beats large and one. A single big motion of the whole object is the obvious choice and the worst one: it risks the frame edge, it reads as generic, and it says nothing about what the object is. Motion distributed over the object's own details cannot leave the frame and is specific to that object by construction. Where it has several of the same element, they must move out of step with each other — unison is the loudest tell that something was animated rather than observed.

Everything that goes out comes back. Anything that extends, rises, spreads or is thrown off must retract, settle, be reabsorbed or fade. Stated as physics rather than as a boundary, because a boundary is something the model can cross without noticing — and a round trip is what the seamless loop needs anyway.

Animate the effect, not its cause. The commonest way a good result still comes out wrong: the model reaches for whatever would normally cause the motion, and that cause is usually the object being moved by something. The physics ends up correct and the icon ends up bobbing, because at icon size the cause is invisible and only the displacement reads. Describe the effect by itself and let gravity, heat, air, tension or the object's own forces drive it.

Then say in --motion what the specific action is, in plain physical language. Describe what happens to the material, not what the animation should feel like.

Rules that matter

Resolution is the quality lever, not the WebP quality setting. Measured on one icon: quality 50 to 90 cut colour error from 2.96 to 1.84 out of 255 and added 500KB — nothing you can see. The visible loss is the downscale from a 1440px render. Defaults are now a 512px master and a 384px output; raise --size before you touch --quality, and never above the master, which only upscales.

Encode at the source frame rate. Kling returns 24fps. Sampling down to 8fps to hit a size target is the single most common way to ruin one of these — it produces judder that looks like a bad render, bad matting, or a bad player, and is none of those. --sweep prints what each rate actually costs; choose with the numbers in front of you. Smooth, slow motion suffers worst, so the icons that most want animating are the ones a low frame rate hurts most.

The last frame is set to the first frame. That is what makes the model return to its opening pose and the loop close. It is handled for you in kling.py on both backends; do not remove it and try to cross-fade instead.

Motion runs through OpenRouter on Seedance 2.0 by default. --model picks another — Seedance 2.5, Veo 3.1, Hailuo 3 and Wan 2.7 all accept a first and last frame. Not Kling 3.0: it caps prompts at 2500 characters and the composed motion clause is longer, so it rejects the request outright. --via replicate is the fallback and stops at Kling 2.5.

The backing colour is load-bearing. The still is flattened onto a known mid-grey before it is sent. Because the colour is known exactly, the matte stage solves C = (F - (1-a)*BG)/a for the true foreground rather than estimating it — measured, that cut edge colour error from 26.9 to 15.9. Do not switch it to chroma-key green or magenta: those spill onto glossy edges.

Believe the verifier, not the contact sheet. A render can look animated in stills and be almost entirely static — it has happened, 1 moving frame out of 121. verify measures motion inside the object and exits non-zero. If it says the render is static, re-prompt; do not encode it.

Never temporally smooth alpha. It seems like the obvious anti-flicker move and it smears a ghost ring around anything that moves.

What it cannot do

See docs/limitations.md. The short version: the soft contact shadow does not survive matting, file size scales with frame count, transparent MP4 needs an x265 build most distros do not ship, and an animated WebP cannot honour reduced-motion — ship the still alongside it.

1---
2name: 3dicon
3description: Turn a prompt or a still image into a looping animated icon with real transparency — GPT Image / Nano Banana for the art, Seedance via OpenRouter for the motion, exact-unpremultiply matting for the alpha, animated WebP out. Use when asked to animate an app icon, make a 3D icon loop, produce a transparent animated asset, or add motion to flat icon art.
4---
5 
6# 3dicon
7 
8Generate a still, animate it into a seamless loop, key the background out
9properly, and encode it as an animated WebP with true soft alpha — the format
10`expo-image`, Chrome and Safari all render natively, with no Lottie conversion
11and no new native dependency.
12 
13## Before anything
14 
151. `pip install -r requirements.txt` (or `uv pip install -r requirements.txt`).
162. `cp .env.example .env` and fill in the keys for the backend you want.
173. `ffmpeg` must be on PATH.
18 
19Check the keys are present *before* running — `animate` costs money, and a run
20that dies halfway has already spent it.
21 
22## Always stop after the still
23 
24One still, then ask. Never generate several takes to choose from, and never
25run through to the animation without the user seeing the art first.
26 
271. Generate exactly ONE still.
282. **Show it to the user** and ask plainly whether they are happy with the
29 static design before anything else happens.
303. Only on a yes, animate it.
31 
32Why this is a hard rule, not a nicety: the still is 13 cents and the full run is
3348 cents and four minutes, and every later stage inherits the still's object,
34colour and weight. Animating art the user has not approved means paying twice.
35`run` therefore stops after the still on purpose.
36 
37If they are not happy, adjust the prompt and generate one replacement — not a
38grid of options. Variants exist behind `--variants` but are off by default and
39should stay that way unless the user asks to compare.
40 
41## How to report back
42 
43Keep the chat almost empty. The user is looking at an image, not reading a
44report — everything they need is either in the picture or in one line under it.
45 
46**After the still**, say only this, then stop and wait:
47 
48> **Still ready.**
49> *(show the image)*
50> Happy with it, or change something?
51 
52Do not explain the prompt, list what you did, or describe the image back to
53them. They can see it.
54 
55**Then propose the motion and stop again.** Do not animate on the strength of
56the still being approved — the motion is the second decision and it is the one
57that costs four minutes. Propose exactly one:
58 
59> **Motion** — *(one plain sentence: what actually happens, in physical terms)*
60> `event` · `lively` · `--emit`
61>
62> Agree, or describe the motion you want?
63 
64One sentence and the flags. No reasoning, no alternatives, no explanation of
65what the flags mean.
66 
67**If they describe their own motion, translate it.** They will use ordinary
68words; your job is to turn those into the flags and one physical sentence, not
69to pass their phrasing through. Work out:
70 
71- *what does this object do when left alone?* → `--strategy`
72- *how much should the object itself move?* → `--energy`
73- *would the action throw something off?* → `--emit`
74- *what literally happens to the material?* → `--motion`
75 
76Then show the translated proposal in the same format and confirm before
77running. If what they asked for is not supported — a camera move, something
78entering the frame, the object travelling away — say so in one line and offer
79the nearest thing that is.
80 
81`animate --dry-run` composes the full prompt and spends nothing. Use it if you
82need to check what the flags expand to; do not paste the result into the chat.
83 
84**After the animation**, one line of numbers and the file:
85 
86> **Done** — `flame.webp` · 857 KB · 122f @ 24fps
87> *(show the contact sheet)*
88 
89**If `verify` fails**, one line on what failed and one on what you propose:
90 
91> **Motion too weak** — step 0.9, needs 1.0.
92> Retry as `event` with `--emit`?
93 
94**Never** paste raw CLI output, stage-by-stage progress, the composed motion
95prompt, or a summary of the pipeline. If a stage succeeds, its output is the
96file — say the filename and move on. Save the detail for when something breaks
97and the user has to choose what to do next.
98 
99## The pipeline
100 
101```bash
102python -m iconloop --out out still --prompt "a 3D stopwatch, sage green and cream, soft matte plastic"
103python -m iconloop --out out animate --motion "the stopwatch rocks gently while its hand sweeps clockwise"
104python -m iconloop --out out matte
105python -m iconloop --out out encode --sweep
106python -m iconloop --out out encode --size 288 --name timer
107python -m iconloop --out out verify
108```
109 
110`run` is just `still` plus the reminder to stop and ask. The stages are
111separate because each one is a place to look before spending the next thing.
112 
113## Choosing the motion
114 
115Before animating, answer one question: **what does this object do when left
116alone?** The answer picks the strategy, and picking it wrong is the main cause
117of a dull or generic result.
118 
119| answer | strategy | what you get |
120|---|---|---|
121| It moves by itself — flows, burns, breathes, ticks | `native` | continuous motion from its own physics |
122| Nothing. It is inert until used | `event` | it performs its function once, then rests |
123| Nothing, but part of it is loose, hinged or light | `part` | the body anchors, one small piece moves |
124| Nothing, and it has no moving parts at all | `surface` | light or material travels across a fixed form |
125 
126Most objects people ask for are **inert**. Asking an inert object to move
127naturally is what produces a turntable spin or an aimless bob — it has no
128natural motion, so the model supplies a generic one. Reach for `event` first
129unless the thing genuinely moves on its own.
130 
131Add `--emit` when the action would realistically throw something off — a
132fragment, a droplet, a spark, a glint. For an inert object that emission is
133often the entire reason the animation reads at icon size.
134 
135`event` and `part` also permit the object to be **temporarily altered** —
136something removed, opened, split, filled — as long as it returns to its
137opening state so the loop closes. Without that permission an object can only
138ever jiggle.
139 
140## How much it moves
141 
142`--energy` decides whether the object itself moves, separately from what it is
143doing. Body motion is not the enemy — aimless body motion is. A squash, a
144recoil, a quick shake, an anticipation before an action: that is most of the
145vocabulary that makes something feel alive, and forbidding it to avoid a
146turntable spin throws away the whole language.
147 
148| energy | the object |
149|---|---|
150| `still` | holds completely rigid |
151| `calm` | leans, settles, breathes a little |
152| `lively` | squashes, tilts, recoils, shakes, springs back |
153| `playful` | anticipates, overshoots, wobbles, hops in place |
154 
155Defaults follow the strategy — `native` and `event` get `lively`, `part` gets
156`calm`, `surface` gets `still` — and an explicit `--energy` always wins. If a
157result feels lifeless, raise it before changing anything else.
158 
159Only two things stay forbidden at every level: rotating as a whole, and
160drifting. Everything else is available.
161 
162**Small and many beats large and one.** A single big motion of the whole object
163is the obvious choice and the worst one: it risks the frame edge, it reads as
164generic, and it says nothing about what the object is. Motion distributed over
165the object's own details cannot leave the frame and is specific to that object
166by construction. Where it has several of the same element, they must move out
167of step with each other — unison is the loudest tell that something was
168animated rather than observed.
169 
170**Everything that goes out comes back.** Anything that extends, rises, spreads
171or is thrown off must retract, settle, be reabsorbed or fade. Stated as physics
172rather than as a boundary, because a boundary is something the model can cross
173without noticing — and a round trip is what the seamless loop needs anyway.
174 
175**Animate the effect, not its cause.** The commonest way a good result still
176comes out wrong: the model reaches for whatever would normally *cause* the
177motion, and that cause is usually the object being moved by something. The
178physics ends up correct and the icon ends up bobbing, because at icon size the
179cause is invisible and only the displacement reads. Describe the effect by
180itself and let gravity, heat, air, tension or the object's own forces drive it.
181 
182Then say in `--motion` what the specific action is, in plain physical language.
183Describe what happens to the material, not what the animation should feel like.
184 
185## Rules that matter
186 
187**Resolution is the quality lever, not the WebP quality setting.** Measured on
188one icon: quality 50 to 90 cut colour error from 2.96 to 1.84 out of 255 and
189added 500KB — nothing you can see. The visible loss is the downscale from a
1901440px render. Defaults are now a 512px master and a 384px output; raise
191`--size` before you touch `--quality`, and never above the master, which only
192upscales.
193 
194**Encode at the source frame rate.** Kling returns 24fps. Sampling down to 8fps
195to hit a size target is the single most common way to ruin one of these — it
196produces judder that looks like a bad render, bad matting, or a bad player, and
197is none of those. `--sweep` prints what each rate actually costs; choose with
198the numbers in front of you. Smooth, slow motion suffers worst, so the icons
199that most want animating are the ones a low frame rate hurts most.
200 
201**The last frame is set to the first frame.** That is what makes the model
202return to its opening pose and the loop close. It is handled for you in
203`kling.py` on both backends; do not remove it and try to cross-fade instead.
204 
205**Motion runs through OpenRouter on Seedance 2.0 by default.** `--model` picks
206another — Seedance 2.5, Veo 3.1, Hailuo 3 and Wan 2.7 all accept a first and
207last frame. Not Kling 3.0: it caps prompts at 2500 characters and the composed
208motion clause is longer, so it rejects the request outright. `--via replicate`
209is the fallback and stops at Kling 2.5.
210 
211**The backing colour is load-bearing.** The still is flattened onto a known
212mid-grey before it is sent. Because the colour is known exactly, the matte
213stage solves `C = (F - (1-a)*BG)/a` for the true foreground rather than
214estimating it — measured, that cut edge colour error from 26.9 to 15.9. Do not
215switch it to chroma-key green or magenta: those spill onto glossy edges.
216 
217**Believe the verifier, not the contact sheet.** A render can look animated in
218stills and be almost entirely static — it has happened, 1 moving frame out of
219121. `verify` measures motion inside the object and exits non-zero. If it says
220the render is static, re-prompt; do not encode it.
221 
222**Never temporally smooth alpha.** It seems like the obvious anti-flicker move
223and it smears a ghost ring around anything that moves.
224 
225## What it cannot do
226 
227See `docs/limitations.md`. The short version: the soft contact shadow does not
228survive matting, file size scales with frame count, transparent MP4 needs an
229x265 build most distros do not ship, and an animated WebP cannot honour
230reduced-motion — ship the still alongside it.
231 

Discussion

Alternatives

Anime celMake Japanese TV-anime style videos (MP4) with code — cel-shaded characters with tapered line art, big detailed eyes, hair clumps with a specular band, comedic anime reactions (sweat drop, anger vein, sparkle eyes, ≥< eyes, dot eyes, tears), painted skies with cumulus clouds, school/street/classroom/rooftop sets with time-of-day palettes, speed/focus lines, impact frames, dramatic zooms, smears, sakura petals and anime subtitles (CJK). Canvas 2D rendered frame by frame in headless Chrome, encoded with ffmpeg. Use when a reference video or brief calls for anime / 動畫 / 日系動畫 / アニメ / cel-shaded / slice-of-life / school romance / shōnen-style reactions, or when the video-clone router picks the anime-cel style. 2D only; characters are original (Hina, Ren) or new ones built on the same rig.Creator · MITLemo opuscarDirect and produce a short film made entirely in code, in one of the styles of the Lemo-Opuscar library (e.g. Impasto Oil Painting 油画厚涂, Watercolor Brush 水彩笔刷, Ukiyo-e 浮世绘, Satellite Briefing 卫星简报, Exploded Teardown 产品拆解, Microscope Story 显微叙事). Use when the user asks for a video, film, short, promo, explainer or animation (视频、短片、动画、宣传片) in a named style, including data, science and product explainers built from real material such as satellite imagery, laser scans, CAD files or archive scans (数据解说、科普、产品拆解); when they want a film made about their own topic and haven't chosen a style (help them choose); or when they ask which film styles there are. Not for editing or converting existing video files.Creator · MITXiaohei illustrationsGenerate Ian-style Xiaohei illustrations for articles, posts, blogs, Notion documents, workflows, methods, processes, structures, states, metaphors, and opinions. Use when the user asks for weird, hand-drawn, Xiaohei, article, editorial, body illustrations, illustration ideas, shot lists, title removal, image edits, or image regeneration. By default, all image text and annotations are English, even when the source article is Chinese; only use another language when the user explicitly asks.Creator · MITScenario game assetsUse when creating game art through Scenario MCP: sprites, sprite sheets, icons, props, loot, tilesets and autotile terrain transitions, seamless tiles, isometric buildings, top-down maps, pixel art, UI buttons and panels, parallax background layers or depth planes for side-scrollers, and character or concept art; or when assets need transparent backgrounds, background removal, style-consistent variation batches, upscaling, pixel-grid cleanup, or PNG export for Unity, Godot, or Unreal.Creator · MIT