CommunityImagegithub.com

m4bwav/polywright

Start here whenever a task needs a 3D model and no sibling below clearly owns it: a model of a real or imagined thing for a game (Unity, Roblox, Unreal, three.js), a 3D print, AI video or AR, or another plugin asking for a mesh. Picks the route (find, capture, generate, code), scaffolds the asset folder and manifest other plugins read, and runs pw.py (inspect, render a sheet, convert, check a budget, bpy headless, match a picture's camera, army blockouts with depth and pose passes for AI...

What is polywright?

polywright is a Claude Code agent skill that start here whenever a task needs a 3D model and no sibling below clearly owns it: a model of a real or imagined thing for a game (Unity, Roblox, Unreal, three.js), a 3D print, AI video or AR, or another plugin asking for a mesh. Picks the route (find, capture, generate, code), scaffolds the asset folder and manifest other plugins read, and runs pw.py (inspect, render a sheet, convert, check a budget, bpy headless, match a picture's camera, army blockouts with depth and pose passes for AI...

Works with~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/m4bwav/polywright/tree/HEAD/skills/polywright

Installed? Explore more Image skills: steipete/songsee, affaan-m/frontend-design, mattpocock/improve-codebase-architecture · View all 6 →

Ask in your favorite AI

Open a new chat with this agent skill pre-loaded.

Documentation

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-m catches 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)

SituationRouteSkill
A common object; licence allows reusefind: 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/OpenSCADpolywright-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 scancapture: photogrammetry, splats, scanner, or model from measurementspolywright-capture
Imagined organic or one-off (statue, creature, prop)generate: reference sheet, then image-to-3Dpolywright-references, polywright-generate
Hero character that deformsgenerate or sculpt as a base, then retopology and rigpolywright-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)

  1. Blockout first at real size: primitives for silhouette and proportion. Fix scale here; it is cheap now.
  2. Numbers: pw.py inspect <file>: triangles, objects, size, materials, textures, UVs, watertight, parts. Read it before and after each destructive step.
  3. Picture: pw.py render <file> --out renders/<step>: six views and one sheet.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.
  4. Target: pw.py check <file> --target <t> --real-size-m X. Fail means fix before delivering; warn means say it in the hand-over.
  5. 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:

  1. Pick the picture for composition only: where people stand, the light, the aim. A plain stand-in prop is fine.
  2. 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 --depth so its width in pixels matches the stand-in's (width = f_px x diameter / depth; f_px is in the result). --hide drops the part behind a hand; --mask-add adds the hands to the mask.
  3. Read match_paste.png cropped at 3x: shape, size in frame, aim. Fix depth and pixels, re-run (seconds).
  4. Blend inside match_mask.png with 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).
  5. 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:

  1. pw.py spritecam <registry.json> --like <sprite> --out camera.json: the orthographic camera fitted from the game's ground diamonds (L-014). Set tile_m in it to the model's metres per tile.
  2. Build the model with a part prefix per object (wheel_, roof_...) in the scene property sprite_parts, moving parts under a pivot empty built in the pivot's space, and the motion in the scene property loop.
  3. pw.py sprite model.blend --camera camera.json --out renders/L1 --scale 10.6667 (1024 px for a 96 px sprite): read sheet-style the passes and the beauty at 1x beside the game's sprites; --frames N --frame-passes id,pos for 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 raise px_per_tile and move footprint_centre_px so 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).
  4. 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 groundBase stay exact, then check the painting against the ID and rim data in sprite.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.

  1. Build the scene around the model with a part prefix per object in the scene property scene_parts, wheels and discs in rings, key points or paths in scene_points. Banks, dykes and walls along a path are one sweep each (import it from scripts/pw_scene.py in a pw.py bpy script), not a row of boxes (L-020).
  2. 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).
  3. 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 and scene.json. Read clay and depth first; leave the depth range to the scene's own measurement unless a preset needs it fixed (L-019).
  4. 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:

  1. pw.py crowd --out work/crowd --action clash --preview (or march, advance, charge, hold; --view, --lens, --move), then read work/crowd/sheet.png.
  2. Drop --preview for 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).
  3. 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 bpy on 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.

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

m4bwav/polywright-blender

Use whenever Blender is scripted, automated or debugged, before writing any bpy code: headless runs that report errors (pw.py bpy), the bpy API traps models still write in Blender 5.x (removed importers, renamed sockets, EEVEE names), tested recipes (modifiers, booleans, remesh, UVs, bakes, turntables, depth passes, batch exports), Blender MCP servers and their risks, and other 3D tools per OS. Use on 'write a Blender script', 'automate Blender', 'batch export .blend', any bpy traceback or...

m4bwav/polywright-finish

Use whenever a mesh must be made ready for a game engine, the web or AR: is it game-ready, too many polygons, LODs, retopology, UVs, baking, PBR and normal-map conventions, collision, rigging, and axis, unit and format export for Roblox, Unity, Unreal, Godot, three.js (meshopt, KTX2), USDZ and video blockouts. Use on 'is this game-ready', 'make this work in Roblox/Unity', 'optimise for the web', 'reduce polygons', 'add LODs', 'retopologise', 'bake normals', 'imports sideways or tiny', 'flip...

m4bwav/polywright-generate

Use whenever an imagined or hypothetical 3D model must be made from a prompt, a drawing, concept art, photos or a blockout: image-to-3D through ComfyUI (TRELLIS.2, Pixal3D, Hunyuan3D-2 via pw.py gen) or hosted (Meshy, Tripo, Rodin), or code (bpy, build123d, CadQuery, OpenSCAD) when it has dimensions; then texturing and the licence record. Use on 'make me a 3D model of a dragon', 'turn this drawing or concept art into 3D', 'image to 3D', 'text to 3D', 'TRELLIS', 'Hunyuan3D', 'Meshy', 'model a...

m4bwav/polywright-references

Use whenever reference images, views or textures for a 3D model are needed, before modelling or image-to-3D: orthographic front, side and back views with a height bar, A-pose turnarounds, multi-view sets, blockout renders repainted into consistent views, seamless textures and quick normal/height/roughness maps (pw.py texture). Use on 'what references do I need', 'reference sheet', 'front side back views', 'turnaround', 'seamless texture', 'PBR maps from this photo'. Not for flat graphic...

Related Skills