Take a product's OUTER APPEARANCE (industrial design: form, proportion, panel split, CMF, controls layout) from a brief + AI-generated look images to a parametric three.js appearance model, a fixed-camera contact sheet, Blender Cycles finals, and a versioned ID report (md authority + Word snapshot) — driven by an AI coding agent, no CAD or Blender GUI muscle memory. Use when SHAPING how a device looks before/alongside structural CAD, when the owner iterates by sending a NEW RENDER ("make it look like this"), when YOU must produce the next look image by sending the current render back to an image model, when you must MEASURE numbers out of an AI image (px/mm, ratios, corner radii, hole pitch, colors), when you need consistent multi-view renders for review, or when producing an ID/CMF/small-batch report with a change log between versions. Key facts this skill encodes: part of the form is usually ALREADY DECIDED by a document nobody re-read (an algorithm spec's mounting assumption, an antenna keep-out, a power L
Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.
Quelldateien prüfen
Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
Take a product's OUTER APPEARANCE (industrial design: form, proportion, panel split, CMF, controls layout) from a brief + AI-generated look images to a parametric three.js appearance model, a fixed-camera contact sheet, Blender Cycles finals, and a versioned ID report (md authority + Word snapshot) — driven by an AI coding agent, no CAD or Blender GUI muscle memory. Use when SHAPING how a device looks before/alongside structural CAD, when the owner iterates by sending a NEW RENDER ("make it look like this"), when YOU must produce the next look image by sending the current render back to an image model, when you must MEASURE numbers out of an AI image (px/mm, ratios, corner radii, hole pitch, colors), when you need consistent multi-view renders for review, or when producing an ID/CMF/small-batch report with a change log between versions. Key facts this skill encodes: part of the form is usually ALREADY DECIDED by a document nobody re-read (an algorithm spec's mounting assumption, an antenna keep-out, a power LED the MCU cannot switch off) — dig those out before the first picture; the physics has to pass through the surface you drew (ordinary plastic is OPAQUE to LWIR; a window sunk t mm needs 2·t·tan(FOV/2) of extra clear width or the shell clips the field of view); AI images are LOOK, not engineering — measure them, then make the numbers self-consistent and tag every value's provenance ([ref]/[std]/[eye]/ [own]/[vN]) in ONE params file; the scene is generated by script (no CSG, outline × profile sweep, plates with real holes) and reviewed in the browser with an auto-export loop (`?sheet=1&save=1`); raster in the browser for iteration, Blender/Cycles for finals (an in-browser path tracer was tried and retired — not accurate enough, and it needed the interactive rAF loop paused during export or tiles silently rendered another view); GLB is the handoff to Blender (mm→m, front faces −Y, bake emissive strength); a hand-tuned `studio.blend` is EXTRACTED and reused so lighting/materials survive every model regeneration; light bars get a purpose-built material (camera-ray strength ≠ bounce strength) plus compositor bloom, and a dim-room shot pair (material overrides re-attached by object NAME); the report's authority is a markdown file, the .docx is a generated snapshot with a version-log table. This is APPEARANCE, not structure — no stack-up, bosses, draft; hand fit numbers to `vibe-cad`. Sibling of `vibe-cad`.
You own what it looks like: proportions, split lines, panel inset, control
placement, CMF. vibe-cad owns how it holds together. The two meet on a few
shared numbers (outer W×H×D, corner radii, apertures) that you decide here and
hand over.
The method has three sources of truth and one loop:
params.js — every dimension, once, with a provenance tag. Nobody types a
number anywhere else.
design-report.md — the written authority (ID · CMF · manufacturability ·
open questions · version log). The .docx is a generated snapshot of it.
studio.blend — the hand-tuned lighting/materials, extracted so they outlive
the model.
constraints ─► AI look image(s) ─► MEASURE ─► params.js ─► three.js scene ─► contact sheet ─► GLB ─► Blender shots ─► report vN
+ brief (Midjourney/ px/mm, [tag] each outline×profile 6 fixed views mm→m studio.blend md + docx
Imagen/gpt-image) ratios value + plates w/ holes raster (reused) + change log
▲ ▲ │ │
│ └───── you send the current render back ───┘ (edit = refine the form, generate = leave it) │
└──────────── owner sends a new image ("v+1: more like this") ◄─────────────────────────────────────────────┘
0. Constraints first: what already decided the form
Before the first picture, go and find the constraints that are already fixed — they
are rarely all in the brief. An algorithm spec that assumed a mounting height and derived
its thresholds from it; an ADR whose module needs an antenna keep-out; a human-machine
contract saying which two controls must never be confusable; a board contract with a power
LED the MCU cannot switch off. Extract them into the report's §0 before you have a
picture to fall in love with.
Then check that the physics passes through the surface you're about to draw: ordinary
plastic and glass are opaque to LWIR, metallised paint blinds an antenna, a recessed
window sunk t mm loses 2·t·tan(FOV/2) of clear width at each edge. Method, the traps
per band, and the worked failure (a desktop form that silently invalidated a
regression-tested detector because the spec said "wall-mounted at 1.5 m") are in
references/appearance-vs-physics.md.
The symptom of skipping this section is always the same:
the review argues about the
button colour while the form is wrong.
1. What the AI image is (and is not)
An AI-generated product render is a look: proportion, material feel, where
things sit, what reads as "one product". It is not a drawing — its own annotated
dimensions rarely agree with its pixels (a "170 × 95" caption on a 1.98:1 picture).
Rule: the picture wins, then make the numbers self-consistent. Pick one anchor
(usually the front width, from the volume you need or the board you must wrap),
derive px/mm, and measure everything else off that. Anything the picture doesn't
show (back, top, thickness) is your decision — say so in the report.
Measure with code, not by eye (scripts/measure_ref.py, method in
references/reference-image-measurement.md): brightness scans across edges for
the outer bbox and seams; a circle fit on a corner for R; the dark-run pitch along
a row + column for a perforation (check hex vs square: column pitch = 0.866× row
pitch means 60° honeycomb); mean RGB of flat regions for the CMF start values.
Write the pixel numbers into the params comments — the next person can re-derive.
You can also drive the picture. When the next look has to come from you rather than
from the owner, send the current render back to an image model as the reference — the
proposal then starts from true proportions instead of the model's imagination, and the
owner reviews a picture of their product. edit with your render refines within the
form; plain generate deliberately leaves it, and reaching for edit after the brief
moved just gives you the same box with a different button while everyone calls it
exploration. Getting the render to disk without a human, the three-part prompt (a literal
BASE of features that must survive · the one NEW element · a shared STYLE +
exclusions), why three prompts beat -n 3, and why "no text, no logos" is not optional:
references/ai-image-iteration.md.
2. One params file, tagged provenance
exportconst P = {
W: 154, // [v2] from the owner's v2 image (8.03 px/mm at "front = 154")H: 90, // [v2] 1237×726 px → 1.70; volume held equal to v1Rc: 10, // [v2] corner fit R≈80 pxRf: 2.0, // [v2] bezel outer-edge → panel gap only 17 pxusbW: 9.15, // [std] USB-C receptacle 8.94×3.16 + clearancebtnOD: 23, // [v2] 173 px = 21.5; rounded UP for elderly fingers — see report Q6boneRough: .78// [eye]
};
Tags: [ref] measured from an image · [std] a part/standard · [eye] tuned by
looking · [own] the owner decided it in the viewer · [vN] this version's image.
A value with no tag is a bug. Derived quantities (panel radius = Rc − Rf − inset)
are computed in the builder, never stored — storing them is how a second radius
sneaks in and the gap pinches at 45°.
3. The scene is generated, not modelled
three.js, no CSG, one file per concern (params / device / materials / textures / views / sheet / studio / pathtrace / main). The body is an outline × a side
profile: the outline gives the front corner radius, the profile gives the edge
fillet — two independent radii, which is what "very round front, thin soft
edge" needs and a single-radius rounded box cannot do. Straight runs of the
outline become true flat plates so they can carry real holes (ports,
buttons, light slots); arcs are swept in one piece. Panels sit in the shell with a
controlled gap; the gap and the seam are the design's only "lines".
Two disciplines that pay for themselves:
Textures are procedural (perforation, grain, decals drawn on canvas) — the
review often happens offline in a meeting room; and one texture can host several
decals (label + icon) via UV sub-rects.
One material per part (a materials ledger with the real-world counterpart in
the report's CMF table). Split parts = split materials; the ledger is the CMF spec.
The gotchas that cost sessions are in references/threejs-scene-gotchas.md
(holes silently dropped when they cross a seam, alpha-test perforation turning to
mush at 3 px/hole, shadow maps that only draw back faces, NaN cameras that never
recover, uv in millimetres, groove floors that vanish without AO…). Read it before
touching geometry.
4. Review in the browser; export by URL
Serve with scripts/serve.py (static + POST /save?name= → out/, and
Cache-Control: no-store — Chrome caches ES modules heuristically and you'll
"fix" the wrong file for an hour). The page:
draws six fixed cameras (front / side / back / top / bottom-lit / bottom-off)
framed by "how much must fit" (fitW/fitH), so a size change never re-frames;
top/bottom follow third-angle projection so left/right match the front (those
strips are what people compare against the board);
persists slider deltas in localStorage (?fresh=1 ignores them; "save params"
writes out/params.json — copy the values back into params.js, that is the
only place they count);
auto-exports: ?sheet=1&save=1&scale=2 (contact sheet), ?shot=1&view=…
(one camera), and for bisecting a broken sheet ?tile=<view> + ?compose=1.
Trigger the export from setTimeout, not rAF — background tabs suspend rAF and
the image simply never appears.
The browser is for iteration (raster, seconds per sheet). Finals come from
Blender (§4). An in-browser GPU path tracer was vendored for a while and then
retired: small emissive meshes get no importance sampling (the bar could not
light the wall without a fake area light), and it needed the interactive rAF loop
paused during every export — otherwise the tracer yields, the loop sees
needsRender, re-applies the default camera, and the remaining samples accumulate
onto another view/material (a face goes dark grey, the light bar prints the grille
texture, a bottom tile contains a whole front view — all look like
material-index corruption, only when the tab is visible). If you add any
multi-frame renderer to the page, keep an exporting flag that makes the rAF
loop return early. Once Cycles was stable the browser tracer bought nothing.
5. GLB → Blender: the studio outlives the model
Export only the device (hide additive glow cards — fake bloom is wrong in an
offline renderer), with emissives baked to real strengths (light bar ~8, indicator
dots ~5) via KHR_materials_emissive_strength; three.js units are mm, glTF is m —
scale 0.001 on import; the front ends up facing −Y. Then
(references/blender-pipeline.md, scripts in scripts/blender_*.py):
blender_render.py — new scene or open studio.blend if present, import the
GLB under a device-root empty, re-attach hand-tuned materials from the
override table, pack, save out/*.blend, render. Use Khronos PBR Neutral,
not AgX (AgX desaturates safety orange to salmon).
Tune by hand once (HDRI, walls, lights, materials, camera). Then
blender_extract_studio.py deletes the device, records {object name → material names} for anything hand-materialled, packs, and saves studio.blend. From now
on every regeneration inherits the studio; renamed parts print an
[override] warning instead of silently losing their material.
blender_shots.py — a camera table (front / hero / side / back / top /
bottom-off / bottom-on + a dim-room pair): the camera moves, the wall doesn't;
unseen faces are shown by rotating the device. Filter emissives by material
name — once the front carries lit dots too, "turn the light off" must not
turn those off. Emissive detection must test strength × colour: Principled's
Emission Strength defaults to 1.0 with black colour, so "strength > 0" flags
every material. For plain emissives, unlit = strength 0 and base colour
swapped to opal (a black-based emissive at strength 0 is a black plastic bar).
Light bars need their own material (scenelib.lightbar_material). The
GLB's black-base emissive ×8 is a faintly glowing plastic strip in Cycles: it
can't beat the studio HDRI (doesn't read as a lamp) and can't light the wall
(too weak) — and those two wants contradict physically (wall-lighting strength
is in the hundreds; at that level the bar itself is a white smear). Split them
with Light Path → Is Camera Ray: ~7 for camera rays (warm, slightly
over-exposed, edge still visible), ~160 for bounce rays (a real pool of light
on the wall — Cycles importance-samples mesh lights, no fake area light
needed). Base = milky PMMA + coat, so unlit is just lit = 0. Then add
compositor Glare/Bloom (enable_bloom, threshold 1.0): the path tracer
computes where light goes, not the halo the lens paints around a bright
source — that is post. Show the bar in a dim-room pair (studio HDRI + lamps
× 0.15, lights off / on): that is what the light actually looks like.
Look at the HDRI direction — the bright side of an environment map is often
behind the device on first use; a 180° Z on the World Mapping node fixes it.
6. The report and the version loop
design-report.md is the authority: §0 reference basis + per-version change
list (what changed, v(n−1) → v(n), the pixel evidence, why), §1 ID (placement
principles, form language, dimension table, face→element allocation, per-element
table), §2 CMF (palette with rendered hex + real-world start point + gloss, parts ×
material × finish, optics), §3 small-batch manufacturing (A/B/C tiers, SLA
execution plan + per-unit parts list + consumables, feature notes, risks R1…),
§4 open questions for the owner (Q1…), §5 next steps. The .docx is generated by
a script you commit (release/make-docx.py; python-docx; embed the 10 MB
sheet as JPEG) — mirror the md section-for-section, end with a version-log table,
keep older versions in release/.
When the owner sends the next look image: save it to refs/NN-<version>-<view>.png,
measure, add a [vN] block to params.js, re-render sheet + shots, add the change
list, regenerate the docx, and name what the picture doesn't decide (button
size vs. finger, LED count vs. driver channels, sensor moved → board respin) as
open questions rather than silently choosing. What is not in the picture stays
unchanged and says so.
Run it
python3 skills/vibe-industrial-design/scripts/serve.py <scene-dir> 5181 # static + POST /save → out/
open "http://127.0.0.1:5181/?sheet=1&save=1&scale=2"# raster sheet# in the page console: ID.exportGLB() → out/<name>.glb
B=/Applications/Blender.app/Contents/MacOS/Blender; SK=skills/vibe-industrial-design/scripts
$B --background --python $SK/blender_render.py -- out/<name>.glb out/front.png 128
$B --background --python $SK/blender_extract_studio.py -- out/<name>.blend # after hand-tuning$B --background --python $SK/blender_shots.py -- out/<name>.glb 128 [front hero hero-dim-on …]
ID_LIGHTBAR_CAM=7 ID_LIGHTBAR_LIGHT=160 $B --background --python $SK/blender_shots.py -- out/<name>.glb 48 hero-dim-on # tune the bar
python3 skills/vibe-industrial-design/scripts/measure_ref.py refs/30-v2-front.png --anchor-w 154
When to reach for this vs others
Proportion / panel split / CMF / "does it look like one product" → here.
Wall thickness, bosses, snap fits, board fit → vibe-cad (take W×H×D, Rc/Rf,
apertures from params.js).
Rendering an existing STEP → the cad-viewer skill; this skill's renders are
for appearance review, before there is a STEP.
Keeping this current (living doc)
Fold every new gotcha (a Blender importer quirk, a path-tracer race, a measurement
trick) back into references/ with the work that found it. The scripts are
generic on purpose — keep them so; product designs go in examples/.