What does polywright do?
The hub for 3D model work. It decides how a model gets made, keeps every asset in one folder with a manifest, and proves each step with numbers and a picture. The sibling skills hold the depth.
Script: scripts/pw.py in this skill folder (Python 3.9+, standard library, one JSON object per command;
scripts/pw_bpy.py is its Blender half, never run by hand).
Run python <this folder>/scripts/pw.py doctor once per session: it finds Blender (any OS), the Python mesh
libraries, slicers and ComfyUI hosts, and says what is missing. Full command list: references/cli.md.
Step 1: the brief (ask only what you cannot infer)
- What: the object or place, real or imagined, and how recognisable it must be.
- Targets: one or more of
pw.py check --list(game-roblox, game-unity, game-unreal, web-threejs, ar-usdz, commerce-gltf, print-fdm, print-resin, video-blockout, cad-step). The target sets the budget. - Real size in metres (a bench is 1.8 m long; a person 1.7 to 1.8 m). Wrong scale is the most common
silent error;
check --real-size-mcatches it. - Inputs: a prompt, photos, a sketch, measurements, a map location, an existing mesh.
- Fidelity and budget: blockout, game-ready, hero; how much time or money a hosted generator may cost.
Scaffold: pw.py new <dir> --name <Name> --kind real|imagined|hybrid --route <route> --targets a,b --real-size-m X.
Folder: refs/ (references, each recorded as a source), work/ (.blend, intermediates), out/ (one deliverable
per target), renders/ (review sheets), brief.md, manifest.json. Contract for other plugins:
references/contract.md.
Step 2: pick the route (cheapest that meets the brief)
| Situation | Route | Skill |
|---|---|---|
| A common object; licence allows reuse | find: Poly Haven, Kenney, Quaternius (CC0), Sketchfab and print sites (check each licence) | this skill, references/routes.md |
| Has dimensions, parts that fit, sharp edges (bracket, case, building, furniture) | code (--route procedural): bpy, build123d/CadQuery/OpenSCAD | polywright-generate, polywright-blender |
| A real place (park, street, campus, terrain) | place: OSM + elevation (pw.py place) | polywright-capture |
| A real object you can photograph or scan | capture: photogrammetry, splats, scanner, or model from measurements | polywright-capture |
| Imagined organic or one-off (statue, creature, prop) | generate: reference sheet, then image-to-3D | polywright-references, polywright-generate |
| Hero character that deforms | generate or sculpt as a base, then retopology and rig | polywright-generate, polywright-finish |
Most good results mix routes: an OSM park with generated hero props, a code-built house with a generated statue. Record the route per part in the manifest.
Step 3: references before geometry
For anything not found ready-made, gather or generate references first (polywright-references): orthographic views on a shared ground line with a height bar, material swatches, scale references. Generated inputs decide most of an image-to-3D result.
Step 4: build, then prove it (every route)
- Blockout first at real size: primitives for silhouette and proportion. Fix scale here; it is cheap now.
- Numbers:
pw.py inspect <file>: triangles, objects, size, materials, textures, UVs, watertight, parts. Read it before and after each destructive step. - Picture:
pw.py render <file> --out renders/<step>: six views and onesheet.png. Read the sheet, not six screenshots. Compare against the references: silhouette, proportion, missing or floating parts, shading spikes, wrong axis (lying on its side), wrong scale. - Target:
pw.py check <file> --target <t> --real-size-m X. Fail means fix before delivering; warn means say it in the hand-over. - Record:
pw.py manifest <dir> --add-output out/<file> --target <t> --inspect, every source with--add-source <url> --licence <l> --author <a>, then--validate.
Plan, act and critique are separate passes: after building, look at the sheet as a critic would and list
defects before claiming done. Two rounds of fixing a traceback are worth it; past that, rethink the approach.
Save a numbered .blend per milestone (work/<name>_v03.blend) so a long job can restart from disk.
Compose a model into a picture
When an image or video model keeps drawing a designed prop wrong (a storyboard still, a set reference, a poster), take the composition from the picture and the shape from the model:
- Pick the picture for composition only: where people stand, the light, the aim. A plain stand-in prop is fine.
pw.py match model.blend --still pic.png --out work/match.png --anchor-px X,Y --anchor-local=X,Y,Z --aim-px X,Y --aim-local=X,Y,Z --depth M --horizon ROW --hide BOX --mask-add BOX: the model's anchor (a grip) lands on the hand, its aim point (a tip) on the exact pixel, rendered RGBA with the picture's lens and horizon. Set--depthso its width in pixels matches the stand-in's (width = f_px x diameter / depth;f_pxis in the result).--hidedrops the part behind a hand;--mask-addadds the hands to the mask.- Read
match_paste.pngcropped at 3x: shape, size in frame, aim. Fix depth and pixels, re-run (seconds). - Blend inside
match_mask.pngwith a masked low-denoise image edit (FLUX.2 Klein or any inpaint model: denoise 0.3 to 0.5, low cfg), then paste the result back through the mask so every pixel outside stays the picture's own. A whole-picture edit redraws everything (HDR colour, a bigger prop). - Check at full size; keep two or more seeds.
Reference sheets of the prop come from the model too (polywright-references, Step 2b).
Game sprites from a model
When an image model keeps getting a mechanism wrong in a game sprite (a wheel that is not round, a blade on the roof), build the model, render it through the game's own camera and let the image model only paint:
pw.py spritecam <registry.json> --like <sprite> --out camera.json: the orthographic camera fitted from the game's ground diamonds (L-014). Settile_min it to the model's metres per tile.- Build the model with a part prefix per object (
wheel_,roof_...) in the scene propertysprite_parts, moving parts under a pivot empty built in the pivot's space, and the motion in the scene propertyloop. pw.py sprite model.blend --camera camera.json --out renders/L1 --scale 10.6667(1024 px for a 96 px sprite): readsheet-style the passes and the beauty at 1x beside the game's sprites;--frames N --frame-passes id,posfor an animated part,--hide wheel_for a plate without it. Then check the size against the game's neighbours before painting:aw.py sprite <beauty at 96> --refs <same-class sprites>fails a coverage under 0.9 of their median. The diamond fit sets the ground scale, not how big the building reads: add massing and props first (taller walls, a lean-to, a yard), then raisepx_per_tileand movefootprint_centre_pxso the bounding box spans the canvas like the neighbours (L-016). Keep thin beams off a cylinder's silhouette: a level log over sloping rails reads as passing above and below them (L-017).- Paint the beauty with Canny of the clay as control (an image model with a ControlNet), cut it with the render's alpha so the
framing and
groundBasestay exact, then check the painting against the ID and rim data insprite.json.
Scene illustrations from a model
A loading screen, a key art or a cover around a model that already exists (a building, a machine): build the scene in Blender and let the image model paint it, so the mechanism and the layout stay right.
- Build the scene around the model with a part prefix per object in the scene property
scene_parts, wheels and discs inrings, key points or paths inscene_points. Banks, dykes and walls along a path are onesweepeach (import it fromscripts/pw_scene.pyin apw.py bpyscript), not a row of boxes (L-020). pw.py framecam problem.json --out cameras.json: world points with screen boxes, points kept in frame and out of the UI's text bands, a size measure to maximise; prints the best cameras as presets. Take the bands from the real UI file (its padding and anchors), not a rule of thumb (L-018).pw.py scene scene.blend --cameras cameras.json --camera framecam_1 --out renders/v1 --size 1280x720(the paint workflow's first-pass size): clay, beauty, depth, normal, id, line andscene.json. Read clay and depth first; leave the depth range to the scene's own measurement unless a preset needs it fixed (L-019).- Paint with control (depth or line), then gate the painting against
scene.json: ring rims, points, id colours.
Crowd and battle blockouts (AI video)
Video models break on crowds; Blender does not. Let Blender place the army and the video model paint the look:
pw.py crowd --out work/crowd --action clash --preview(or march, advance, charge, hold;--view,--lens,--move), then readwork/crowd/sheet.png.- Drop
--previewfor the full shot:depth.mp4(main control),pose.mp4(front ranks),mask.mp4,clay.mp4,camera.json; 832x480, 81 frames at 16 fps by default (Wan). - Repaint clay frame 1 into an approved start still; drive Wan VACE or Wan 2.2 Fun Control with the depth video and that still. Prompt costume, colour per side, light and haze, not the head count.
Details and what is untested: references/crowd-blockouts.md. Shot planning is cinewright's.
Two fighters: pw.py duel shot.json --preview poses two mannequins at their marks in the set, through the
shot's camera (examples/duel.json); read sheet.png, then drop --preview for each control's mp4.
More: crowd-blockouts.md, "Two fighters".
A small cast of real figures or toys (sides swapped, an extra figure, markings lost): mesh each from photos, fix
colour and painted markings with pw.py mark, give the video model pw.py turnaround pictures as references and a
pw.py lineup (teams on fixed sides, depth and mask) as its opening: references/figures-for-video.md.
Sets and props for a film shot: references/film-and-video.md.
Step 5: finish and hand over
Finishing per target (UVs, LODs, normal-map convention, axis and unit conversion, compression) is
polywright-finish; printing (watertight solid, walls, base, split, 3MF) is polywright-print. Hand the folder to
the plugin that uses it: three.js pages (threewright), film and video (cinewright), Roblox places
(roblox-agent), Unity projects (unity-agent), a printer-specific or slicer skill for print settings, an image
skill for more pictures. They read manifest.json and out/.
Hand-over message:
Model: <name> (<route>) -> out/<file> for <target>: <tris> tris, <size> m, <watertight/parts>
Check: <target> pass|warn (<rules>) Review: renders/<step>/sheet.png
Sources: <n> with licences; AI-made parts: <which, which model> Owed: <what is not done>
Rules
- Real size in metres in Blender and glTF (+Z up in Blender, +Y up in glTF); millimetres only in print files.
- Never claim a mesh is fine from its file name or a generator's word: inspect, render, check.
- Every downloaded or generated input gets a source line with its licence. Stores and print sites (Fab, Unity Asset Store, Sketchfab, MakerWorld, Printables, Steam) require disclosure of AI-made content; the manifest records it.
- Blender bridges over MCP run arbitrary Python: prefer
pw.py bpyon copies; use a live MCP session only when the user is watching (polywright-blender). - Google Photorealistic 3D Tiles may be viewed, never exported, cached or traced (their terms).
Maintenance
Evergreen unit, tier fast (generators and Blender move monthly). Companions: RESEARCH.md,
CHANGELOG.md, LEARNINGS.md, TESTS.md with evals/evals.json,
MAINTENANCE.md, state in evergreen.json. Read evergreen.json at each use; if due, do the
task, then refresh (evergreen-refresh). Write lessons to LEARNINGS.md as they happen.