scenario-zbrush-character-creature
Use when sculpting a realistic head, face, portrait, body or creature in ZBrush (anatomy, landmarks, proportions, "the face looks off", eyes, lips, jaw), adding skin, pores, wrinkles or reptile and dragon scales with alphas, Surface Noise, layers or morph targets, planning subdiv
Install
npx skills add https://github.com/scenario-labs/skills/tree/main/skills/dcc/zbrush/scenario-zbrush-character-creature
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install scenario-labs-skills@llmmart
git clone https://github.com/scenario-labs/skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole scenario-labs/skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Realistic characters and creatures (ZBrush 2026)
Expert level here means every form has a cause (skull, mass, tension, compression) before any pore exists, and every detail pass is reversible, regional and judged in renders from several views and lights. The agent works the way Kingslien and Costa do: find the upstream cause, fix it at the lowest level that holds it, and keep detail on layers or behind a stored morph target. If a sibling skill named here is missing from your available skills, ask the user to install it (npx skills add scenario-labs/skills --skill <name>); unattended, proceed from tool schemas and flag the gap.
REQUIRED BACKGROUND: scenario-zbrush-expert (bridge, stroke engine, review loop, 2026 traps). Toolkit: scripts/zb_character.py (stroke plans, ZBrush-side wrappers via zb_character.call(...), OBJ, alpha and render checks) on the lead's toolkit.
Stance (the expert delta)
- Cause before form, in planes. Kingslien: cranial versus facial mass, then the Bridgeman masses (forehead box, flat cheek, maxilla cylinder, triangular jaw), then features sculpted through the forms around them (nostril through the alar facial juncture, eye through the nasojugal groove). "Almost everything right or wrong in this face is right or wrong back in the beginning." A region touched twice means replan (
MoveBudget). His single separating lines suit synthesized strokes. - Detail is worthless on a weak base. Costa: judge primaries blurred, from afar, upside down and under three lights from the first sketch; keep level 1 alive for likeness edits until export.
- Asymmetry lives in the big forms. FlippedNormals break symmetry in the mid-frequency pass; J Hill again in the last 25 percent (nose tip off the axis, one ear, a lip corner, one eye's scale); Lazov pushes the whole creature from a stored morph target. Creature big forms: skull mass, one orbit rim, the jaw corner, a nostril, a horn's angle. Scars and broken scales come on top, never instead.
- Skin: scale first, regions second, breakup third. J Hill sets pore scale globally for age and sex first; lids and lips stay clean; even skin between details reads as plastic. Lines lie across the compression, in short connected segments (Costa: wrinkles connect pores, perpendicular to the circular muscles). Cracks on horns and tusks go large to small in stages (Starkie).
- Scales follow the anatomy; alphas never finish a surface. Henning Sanden: generic tile, mid value 50, tested on the model at least three times while you build it, then a hand unify pass; smooth inside a scale, never across its border. Starkie: a close-up hero gets individual scales (he gives no numbers). Berger: story, then biology, then cool; reference animals blended with no seams.
- Sharp enough to survive the render. FlippedNormals: carve deeper than feels right, because SSS and soft light eat sharpness; J Hill: only the sharp detail survives in the highlights. Over finished detail ClayBuildup erases what is under it: enhance with Standard, make directional passes with Elastic.
- Folds and poses have causes. Grassetti: every fold starts at a tension point, ends in another fold or an edge, is heavier near the tension; tightness picks drop, zigzag, X or Y. Eaton: only the force chain is tense; active muscles run straight between landmarks; scapula about 30 degrees at 90 degrees of arm elevation.
- Agent limits [added]. Transfer well: palette operations, feature masks plus Deformation sliders, layers and morph targets, two-point alpha stamps, short lines, spray loops, curves-API horns. Stay weak: Costa's thin ClayBuildup layering, groom and Morph-brush feel, anything needing Ctrl or Shift held, and HD Geometry (the A key cannot be sent). Say so in the report.
Establish first
| Input | Changes | Default when silent |
|---|---|---|
| Purpose and shot | stack, map path | film close-up still, 32-bit displacement into Maya/Arnold [added] |
| Driver | stack route | SDK only: regular levels, no HD; computer use: HD split |
| Subject and reference | canon, blend | human: A4S canon, Costa's millimeters; creature: two or three animals blended (Berger) |
| Age, sex, weight | pore scale, fat pads, gonial angle | adult, written down first |
| Pose | symmetry strategy | neutral: symmetric until the mid-frequency pass; posed: symmetry off (Eaton) |
| Base mesh | where detail starts | ZRemeshed quads with UVs and interior polygroups (eyes, mouth, nostrils) from scenario-zbrush-retopology-export |
Workflow
- Brief and maps. Design pyramid (Berger); rest versus rich zones (Lazov); scale flow over the anatomy (Henning). GATE: all three written down.
- Masses (from scenario-zbrush-sculpting, or skull first here, Lazov). Head: cranial versus facial mass, then Bridgeman masses on a stroke budget (Kingslien: 16 Move strokes; C2). Creature: skull, jaw hinge, horns (C17), open and closed mouth on layers (Berger). Figure: landmarks first (Eaton). GATE: sheet with front, profile, three-quarter and below (
view_facing("-Y"));head_ratio_checkorscapula_checkpasses or the deviation is deliberate; no retouched region. - Face critique pass in Kingslien's order (
references/critique.md). The first failing step is the fix: a separating line (C3), a masked move (C4) or plane strokes. GATE:face_reportmoves the right way against the previous stage. - Topology, stack, projection. Detail only on a clean, UV'd base. Choose the route before the first Divide (C5): SDK only,
subdiv_plan(route="regular")to about 20 to 25M, micro detail in a tiled map at render; computer use, 3 to 4 regular levels (top 1 to 2M) then 3 to 4 DivideHD. Never add HD over a 6 or 7-level stack: you cannot go below the HD entry (J Hill). Reproject withzb_ops.project_all(checkpoint, morph target and top-level layer built in), eye, mouth and nostril interiors masked first; then walk every level (C19). GATE:stack_checkok;spike_reportok in every danger-zone group; points per level logged. - Secondary forms: folds, wrinkles, fat pads, lids, lips; Costa's thin ClayBuildup (Z 2 to 3, many short strokes), Kingslien's lines; symmetry off for the big-form mid-frequency pass. Cloth: plan folds (C13) or simulate, then sculpt (C14, C15). GATE: blurred and thumbnail renders; no form "screams".
- Tertiary detail, one layer per frequency (
layer_new, agent-side ledger). Aim first:aim_atthe region, stamps insideregion_on_canvas(C9). Pore or scale bed: Surface Noise (C7) or alpha stamps (C8, C9), each alpha tested on the model while built. Wrinkles across the compression (C10); cracks withcrack_tree, largest first; breakup (C11); hand unify. GATE: raking close-ups;alpha_check(anddots_checkif dots were used); no tiling; lids and lips clean; each layer seen at intensity 0 and 1. - Asymmetry pass (C12), big forms first. GATE:
asymmetry_reportfinds at least two big-form regions broken; posed figures skip it. - Fur and hair (C16). Mask, preset from a file, Preview, Accept, Fast Preview PRE Vis 6 to 8. GATE: coverage render with a contrasting fill.
- Final review and handoff (C20). Costa's eight steps under three lights, upside down, blurred;
eye_report;detail_survivalon the SSS render;map_request_check; versioned ZTL. GATE: scorecard passes, or open items listed.
Numbers
| Item | Value | Source |
|---|---|---|
| Stack, SDK only | regular to about 20 to 25M (6,240 faces reach 25.5M at SDiv 7); "40 million is high"; micro detail tiled at render | J Hill; Costa |
| Stack, computer use | 3 to 4 regular levels, top 1 to 2M, then 3 to 4 DivideHD (Costa: 1.1M top, 3 DivideHD, 70M); HD ceiling 1 billion, so 3 DivideHD over 16M is already at it | Costa; J Hill; HD doc |
| Reprojection | target at about 3M; Store MT and a layer at the top level; PA Blur lowered | FlippedNormals |
| Face block-in | under about 360k points with Sculptris Pro | Kingslien |
| Head canon | crown to hairline 1/6 H, then thirds of 5/6 H; eyes at 1/2 H | Anatomy For Sculptors |
| Portrait scale | head 229 mm, eyeball 24 mm, IPD 63 mm, eyes 3 to 10 degrees outward | Costa |
| Wrinkles | Standard + Alpha 39, sharpened curve; fine: Standard, Spray, Alpha 60, Z 3 | Costa; J Hill |
| Pores | DragRect, Focal Shift -100, one size per zone; Surface Noise strength negative | Costa; J Hill |
| Alpha tile | plane, Smt off, about 1M polys (a 1K map), document 1024, perspective off, +50/-50 dots taller than any detail, MidValue 50; tested at 11, 26, 41 minutes | Henning |
| Folds | Standard Z 25 valleys, Clay Z 80 ridges, big Move for sag, SDiv 2 to 4 first | Grassetti (frames) |
| Cloth | Gravity 1 to 2.5; iterations 100 to 1000; Firmness 1 to 2 silky, 5 to 6 leather; collision about 1024; Extract Thick 0.01 cloth, 0.03 leather | Pavlovich; Maxon docs |
| Fur | Max Fibers 11 (thousands), Segments 6, PRE Vis 6 to 8, Profile 1 | Pablo; FiberMesh docs |
| Horns | detail straight, radial counts 8, 5, 3, then bend; decimate a copy to 20 % (arrays 5 % then 1 %); curve Res 10, Imbed 0 | Pablo |
Quality gates
- Measured:
stack_check; points per level and layer count;spike_reportper polygroup at every level;head_ratio_checkwithin 5 percent of H [added];face_reportdeltas;mirror_deviationandasymmetry_report;alpha_check,dots_check; foldfree_endpoints= 0,evenness> 0; area drift under 5 percent after a sim [added];eye_report;detail_survivalratio at least 0.5 [added];map_request_check; volume before and after every stroke batch. - Seen:
zb_review.reviewsheets in MatCap Gray (front, profile, three-quarter, below, back), raking close-ups per region and per danger zone after projection,blurred,thumbnail,upside_down, and the three-light SSS render (highlights keep the sharp detail).references/critique.mdsays what to look for.
Common mistakes
| Mistake | Looks like | Fix |
|---|---|---|
| Features on an undivided egg | no facial plane in profile | cranial versus facial mass, Bridgeman masses (C2) |
| Eye centered in the orbit | straight rim, lumps under the eye | eye high, margin hooked into the orbit, check from below (C3) |
| HD on a deep regular stack | 6 or 7 regular levels then HD; no smoothing room; 1 billion faces | route chosen first, stack_check (C5) |
| Projecting without nets | spikes in lids, lips, nostrils, fingers; hard lines in the displacement | project_all nets, interiors masked, level walk; morph_repair, never smooth (C19) |
| Symmetry broken only in details | scars on a mirrored skull | move the big forms (C12) |
| Uniform detail | plastic, noisy, no rest | region map; lids and lips clean; beat-up at three sizes |
| Alpha built blind | soft, tiled, bordered on the model | test on the model while building; dots taller than any detail; hand unify (C8) |
| ClayBuildup over finished pores | detail erased | Standard to enhance, Elastic for direction |
| Micro detail too soft | gone in the SSS render | carve deeper; detail_survival |
| One long wrinkle line | drawn, not skin | short connected segments across the compression (C10) |
| Lone or even folds | dangling ends, ladder of copies | termination and unevenness rules (C13) |
| Sim stretch or ghost collider | long polygons at contacts; cloth hugging nothing | more iterations or less gravity; Recalc after visibility changes |
| Fibers lost | groom vanished, FiberUV grayed | neutral settings before a groom; groom first, Divide last |
Handoffs
- Receives from scenario-zbrush-sculpting: a versioned ZTL with the primary forms, polygroups per region,
stats()and the last sheet. From scenario-zbrush-retopology-export: the clean quad base with UVs (UDIMs), interior polygroups for eyes, mouth and nostrils, and the projected stack (rx.project_stack). - Delivers to scenario-zbrush-retopology-export: the detailed ZTL with level 1 intact, eyes as separate SubTools, layers baked or the ledger, a morph target at the export level, and a map request that passes
map_request_check(Adaptive identical across maps, SubDiv below the top, Mid equal to the renderer's zero, UDIM tile names with a dot, Merge Maps off with EXR; Costa's film values inreferences/procedures.mdC20). UVs relaid outside ZBrush come back by Store MT, Switch, Import at the export level, no Divide, Switch MT on (Costa). To scenario-zbrush-paint-render: the same ZTL, region polygroups, grayscale approval renders and the three-light SSS request. - Related: scenario-zbrush-stylized, scenario-zbrush-pose-print, scenario-maya-groom (XGen).
ZBrush 2026 notes
- Not scriptable: HD entry (A key;
press_key"does not work"), layer names (the macros rename through ZFileUtils), mask painting, Shift-smoothing, the NoiseMaker window (set once by computer use, save a .ZNM). Brush:Dam_Standarddoes not resolve on 2026.2.1: select brushes throughzb_ops.select_brush, runbrush_checkonce per session. Orb_Cracks, RK_ and Antro_ brushes do not ship: Dam_Standard, Slash3, ClayBuildup, TrimAdaptive stand in.- Since 2026.1 custom alphas and brushes live in the Asset Directory (
alpha_library_dir()). NoiseMaker 2.0 (2026.0) rotates a tiling alpha to follow each region's flow. Redshift inside ZBrush runs the SSS check; Substance Bridge (2026.2) is the Painter route. - Decimate a duplicate, never the master (
rx.decimate_copy); a scripted pre-process may end the calling script [verify]. - Unconfirmed: layer intensity path, NoiseMaker blocking, scripted Run Simulation stop (live_c01, c03, c05).
References
references/expert-notes.md: principles by expert with timestamps, conflicts with their deciding condition. Load when planning a stage.references/procedures.md: bridge procedures C1 to C20, full code, live tests. Load before writing bridge code.references/critique.md: Kingslien's face checklist, Costa's evaluation, skin, creature, projection, cloth and fur rubrics. Load at every gate.references/gui-paths.md: palettes, hotkeys, brushes, settings for a computer-use agent.references/sources.md: sources, credentials, URLs, best timestamps, revision log.
Files (skills)
-
references
-
critique.md 34.9 KB
# Critique rubric: realistic characters and creatures Use at every gate. Each row says what to measure in code, what to judge on which render, and how to fix it. Metrics marked [added] are this skill's own translation of an expert rule and are not calibrated: compare a stage with the previous stage, a before copy or a reference head exported the same way, and let the renders decide. Severity: **blocker** (fix before the next stage: later stages inherit it), **major** (fix in this stage), **minor** (note it, fix when passing by). Standard renders: - The review sheet: `zb_review.review`, MatCap Gray, with front, right (profile), `view_facing("-Y")` (below), three-quarter and back. - A raking-light close-up per region. - `blurred`, `thumbnail` (128 px) and `upside_down` copies of the front view. - For skin: the three-light SSS render from scenario-zbrush-paint-render (portrait key, from below, flat front: Costa j5XLtLMN0P8 00:19:51, 00:31:12). ## 1. The face, in Kingslien's order (6wiDxO-ZADg) Walk the steps in order and stop at the first failure: "almost everything that's right or wrong in this face is also right or wrong back in the beginning" (01:37:20). Ears are not in his order: he names them only as a premature detail (00:31:33). | # | Step | Measure | Judge on | Fix (procedure) | Severity | | --- | ---------------------------------------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | 1 | Cranial versus facial mass (00:31:15-00:34:15) | `brow_step_deg` small and the profile one smooth arc = egg [added] | profile: the facial plane reads as a flat face set into the cranium ("Pacman"), not features on an egg | Move-only pushes on a budget, then clay (C2) | blocker | | 2 | Facial plane (00:36:00) | none reliable | three-quarter: the plane runs along cheek, zygoma, depressor anguli and brow | ClayBuildup Z 20 strokes on the plane border (C3) | blocker | | 3 | Flat of the cheek (00:38:45) | two-line fit of the half section at zygoma height beats a circle [added] | front and three-quarter: a side plane going back in space | short strokes on the side plane (C3) | major | | 4 | Jaw (00:38:50, 00:46:42) | gonial corner visible in `front_profile` [added] | profile: triangular jaw, back of the jaw sculpted, angle present | two-point strokes on jaw line and ramus (C3), Move (C4 A) | major | | 5 | Forehead box (00:40:39-00:42:58) | `forehead_box` below the previous stage [added] | front and top: a box that breaks down into the brow, separated from the round cranium | TrimAdaptive Z 100, Focal Shift -83 on the front plane; bevel the corner at the external angular process | major | | 6 | Maxilla cylinder, chin, jaw separation (00:43:31-00:46:42) | `maxilla_sagitta` above the previous stage [added] | below: the column round; front: a groove between cheek and muzzle | one long clay stroke per side with symmetry, rounding strokes, dig the chin (C3) | blocker | | 7 | Nose (00:49:14-00:50:59) | bridge narrower than the alar base on `front_section` [added] | front and below: bridge smaller than the base; the procerus "capital" at the root | masked or auto-masked Move pull; Size X on the base (C4) | major | | 8 | Nostril around the wing (00:54:07-00:57:33) | none | three-quarter and below: an alar facial juncture triangle; no bump, no cartoon hole | two or three short strokes beside the wing, not on it, Imbed lowered | minor | | 9 | Form-clue line around the wing (00:59:02) | none | front against a before copy: more "form, shape, distinction" (01:00:38) | one line, then a trim stroke (C3 trim) | minor | | 10 | Eye socket (01:03:55-01:09:00) | deepest orbit point in the upper half of the orbit [added] | below: the infraorbital margin shows "a one curve and a two curve"; front: nasojugal groove with depth; eye high and protected | curved strokes whose ends hook into the orbit; remove any lump ("tumor") | blocker | | 11 | Brow (01:09:55-01:11:45) | `brow_step_deg` [added] | profile: superciliary arch with the orbit carved beneath; a small male hook; temporal line | short carve strokes plus one temporal-line stroke | major | | 12 | Mouth barrel (01:12:20-01:18:32) | `lip_dominance` > 0; `chin_slope_deg` > 0 ("quite sloped") [added] | profile: upper lip over the lower; front: marionette line tucks the lower lip | ordered lines: mouth line (Standard, Dots, Z 25), lip planes, lower-lip columns, depressor nodule, marionette, upper-lip fiber line, five pillows | major | | 13 | Mouth corner as a donut (01:19:56) | none | three-quarter: the corner is blended into chin and depressor anguli, not pasted on | fan of short strokes out from the corner (Sculptris Pro off), then Inflat Z 10 | minor | | 14 | Masseter and cheek (01:22:46) | none | front: cheek edges moved together, cheek not "resolved" | strokes on the borders only | minor | | 15 | Malar line (01:24:31) | none | three-quarter: cheekbones read | a band stroke, then a trim along its lower edge | minor | | 16 | Upper lid (01:28:37) | none | front: one simplified lid line; small gap between lid lines; parts tucked up | one curved stroke per lid, a small upward Move | major | | 17 | Nasolabial area and nostrils (01:33:42-01:36:13) | nostrils inside the alar crease in the front silhouette [added] | three-quarter: planes delimited from both sides, a trough kept; nostrils tucked under | plane strokes from both borders, then a low blend | major | | 18 | Process (00:20:29) | `MoveBudget.report`: no retouched region, within budget | the stroke log | undo to the stage's morph target and replan | major | Canon ratios, from Anatomy For Sculptors, feed step 1. Check them with `head_ratio_check` on landmarks from `profile_landmarks` or markers: - crown to hairline 1/6 H; - then three equal thirds; - the lower third split in three; - eyes at 1/2 H. Tolerance is plus or minus 5 percent of H [added]. Stylized heads: record which ratio is broken on purpose (`deliberate=`). ## 2. Costa's form evaluation (j5XLtLMN0P8 00:04:44) Run it after every secondary stage and at delivery. It is visual; the derived images do the work. | # | Check | Image | Pass when | | --- | ----------------------------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | | 1 | Blur-eyed read (00:09:18) | `blurred` front and profile | head shape, distances and planes right with no detail visible | | 2 | Context hierarchy (00:09:51) | region close-ups | in each region, the primary form dominates and small forms are subordinate | | 3 | Collision and ripple (00:12:26) | front and three-quarter | forms in an expression move together (a smile lifts the nose corner, nasolabial and lower lid into a squint) | | 4 | Weight and age (00:14:10) | three-quarter, from below | fat pads, bone landmarks and gravity match the chosen age; not every muscle shown | | 5 | Subtlety and flow (00:17:12) | raking light | transitions flow; nothing "screams" | | 6 | Distance from the subject (00:17:26) | `thumbnail` and a 100 % crop | detail density matches the shot, and the tertiary stays tight enough for specular | | 7 | Physically accurate renders under several lights (00:19:51) | three-light SSS set | no volume looks off under any of the lights | | 8 | Refine the eye (00:20:24) | early versus late sheets side by side; `upside_down` (Lfen-BSwWcE 00:29:26) | the model still reads as right when seen fresh | "Injecting life" (00:24:07): - mood and intention decided; - gaze slightly up and aside; - eyes 3 to 10 degrees outward (`eye_report`: no `cross_eyed_risk`); - lighting that tells the story; - the details that draw the eye (eyes, nasolabial, lips) are right; - asymmetry present; - a subtle pose. ## 3. Skin (J Hill HlHoIGE2Ocs; Costa Lfen-BSwWcE, j5XLtLMN0P8) | Check | Measure | Judge | Fix | | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | Pore scale fits age and sex (J Hill 00:15:05) | pore spacing in px per zone logged | raking close-up of cheek and forehead against same-age reference | Surface Noise scale before Apply; smaller for young and female (C7) | | Region rules (Costa j5 00:41:28; J Hill 00:22:16) | none | nose shallow and sparse with a polished tip; clogged pores are bumps; lids and lips clean; cheek pores spread | Morph brush with the polygroup auto-mask on lids and lips; DragDot bumps (C7, C10) | | Direction (Costa 00:46:18; J Hill 00:17:38) | orientation of `connect_the_dots` segments versus the field | lines lie across the compression: perpendicular to the circular muscles around the mouth; circular on the lids; horizontal on the forehead; on creatures across each bend (throat, jaw hinge, joints) [added generalization] | rebuild chains with the right field (C10) | | Wrinkles are short and connected (Costa 00:46:50) | chain lengths within 2 to 4 segments | no long, drawn lines; secondary folds around soft skin | break chains, add a hierarchy of secondary folds | | Not plastic (J Hill 00:18:42) | none | highlights break up irregularly on cheeks and the nose tip | beat-up at three sizes, push and pull (C11) | | Even pore depth (J Hill 00:16:10) | depth histogram spread on a height crop [added] | pores not all the same depth | Morph brush variation, heavy then dial back | | Lips (J Hill 00:24:10-00:27:23) | none | Dam_Standard sections; catching arcs in alternating directions and lengths with Y shapes; medium folds strong enough to break the silhouette | arc families, then Elastic volume between deep lines | | Sharp enough for SSS (J Hill 00:34:53; FlippedNormals 0PaYUUvgwYM 00:19:49-00:20:22) | `detail_survival` ratio at least 0.5 in the highlights, same camera and crop [added, uncalibrated] | the sharp detail survives in the highlights of the SSS render | carve deeper than feels right with Standard or Dam_Standard; less soft micro relief | | Brush order over detail (FlippedNormals TpS0QdlfHWU 00:07:44; Costa j5 00:52:22; J Hill 00:20:53) | stroke log: no ClayBuildup or Clay pass after the pore layer | finished pores intact after later passes | Standard to enhance, cloned Elastic on Spray for direction, Elastic at very low intensity for volume between lines | | Asymmetry (J Hill 00:29:42; Costa j5 00:35:03; FlippedNormals G2o6fdoACIQ 00:06:19) | `asymmetry_report`: at least two big-form regions (nose tip, ear, lip corner, eye) broken, not only details | the face is believable, and it is a likeness when there is a subject | C12, big forms first | | Stamps (Costa 00:39:01) | one size per zone in the plan | no repeated or mixed-size stamps side by side | rotate by alternating drag direction; keep one size per zone | ## 4. Creatures | Check | Source | Judge | Fix | | ---------------------------------------------------------------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | | Story, then biology, then cool | Berger PcuC8K-bz44 00:04:24 | every feature has a reason (riding, feeding, habitat) | cut features that break the story, even pretty ones (the dorsal spines, 00:25:21) | | Earth-like metaphor at the broad strokes, alien details up close | Berger 00:14:13 | the silhouette reads as a known animal type; close-ups reward the eye | push the metaphor in the masses and keep the alien in the tertiary | | Blended references, no seams | Berger 00:12:33 | you cannot tell where one animal ends | re-blend at the mass level | | Open and closed mouth | Berger 00:20:53 | both states resolved, no articulation problem | jaw on a layer (C6) | | Grayscale for anatomy approval | Berger 00:37:47 | the anatomy reads in gray, without color | review in MatCap Gray; color is a separate approval | | Scale flow | Henning TuRIf92oMCY 00:00:34 | scales follow the muscles and cross over where muscles cross | re-stamp with the right flow angle per region (C9) | | Individual yet unified | Henning 00:07:10 | each scale separate, the patch a unity; no tiling, no stamp border; some scales lifted | unify by hand with Standard; mid-frequency lift (C9, C11) | | Alpha tested while built | Henning 00:14:15, 00:21:52 | at least three test stamps on the model during the tile build (about 11, 26, 41 minutes); dots taller than any detail (`dots_check`) or `recenter_alpha` | test, read (soft? uniform? tiling? border?), fix the tile (C8) | | Hero close-up | Starkie (Maxon interview) | each scale has depth and form, with no overlaps | per-scale placement, not a tiled patch | | Tusks, spikes, horns | Starkie; Pablo TN9ARiC_82w | bone or rock with hard broken edges; cracks staged large to small; tapered, twisted, not obviously repeated; attached to the skull | `crack_tree` levels played largest first; radial detail (8, 5, 3) on the straight tube, then bend (C17) | | Rest zones | Lazov AQsmWcXLxk8 00:31:12; Rakan FRZtVXpAokc 00:06:40 | rich areas against clean rest areas | clean the rest zones; concentrate detail | | Expression | Lazov 00:23:40 | the eyes and brow carry it | small targeted moves at the brow | | Asymmetry | Lazov 00:56:58-00:58:04; J Hill 00:29:42 | the big forms are not mirrored: skull mass, one orbit rim, the jaw corner, a nostril, a horn; scars and broken scales come on top | `asymmetry_report`; C12 | | Silhouette | Lazov 00:28:36; Berger 00:33:48 | interesting, never flat; schooling creatures survive repetition | reshape the masses; tile a thumbnail to check repetition [added] | ## 5. Bodies (Eaton Ale6SXXbJMM; Anatomy For Sculptors) - **Force chain.** Only the muscles in the force chain are tense; the antagonists are soft. A generalist's "super bodybuilder deltoid" is the error (00:13:39-00:15:29). Judge on a three-quarter back render. - **Landmarks first.** Sternum, clavicle, acromion, spine of the scapula, C7, iliac crest, ASIS, PSIS, deltoid insertion (00:35:59-00:44:08). They read as depressions on a muscular body and as bumps on a lean one (A4S). - **Scapula.** About 30 degrees of rotation at 90 degrees of humerus elevation (`scapula_check`). - **Straight active muscles.** They run point to point; check from under the armpit (00:54:07). - **A4S ratios:** - adult hand length = chin to hairline; - middle finger = half the hand; - deltoid tuberosity a bit above mid-humerus; - the butt never wider than the hips (greater trochanter). - **Hands.** Arched palm, knuckles not aligned, square fingers, webbing between them (A4S). ## 6. Cloth | Check | Measure | Judge | Fix | | ------------------------------------------------------------ | ------------------------------------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------ | | Tension points identifiable (Grassetti gNx4v0WVVHo 00:00:31) | pins in the plan | every fold family relates to a pin | re-plan from pins (C13) | | No fold ends alone (00:02:35) | `free_endpoints` = 0 | raking light: folds end in folds, seams, hems or edges | `snap_free_ends` | | Never even (00:03:08) | `evenness` of lengths and spacings > 0.05 [added] | heavier and overlapping near the pin, relaxing away from it | taper segments, vary counts | | Type matches tightness (00:04:26) | `fold_type_for` per region | drop on loose areas, zigzag between two holds, X on tight compressed bands, Y at splits | change the family | | Reads from every side (00:12:06) | none | back and profile renders | sculpt pass after any sim | | Sim stretch (Pavlovich m5O_sBag_iA 00:14:54) | area drift under 5 percent [added] | PolyFrame at contact points: no long polygons | more iterations or less gravity; a smaller mesh | | Gap and penetration (nqoCyOME8Jo 00:06:40) | none | Transparency: garment close to the body, not inside | Collision Inflate lower; resolution about 1024; Recalc | | Fabric weight (m5O_sBag_iA 00:17:59) | Firmness logged | silk: many folds; leather: few, thick folds | Firmness 1 to 2 or 5 to 6 | ## 7. Fur and hair (Pablo lgTA_ebLGDw; FiberMesh doc; Pavlovich QCcDTuDT2HM) - **Coverage.** With a contrasting fill (Color > FillObject on the fibers) there are no gaps. - **Roots.** No floating fibers and no cushion gap around the scalp; roots are pinned with Mask By Fibers before any simulation. - **Groom.** It reads as intentional, with clumps and partings that read as a hairstyle. - **Still a FiberMesh.** FiberUV enabled means grooming is still possible. - **Polycount.** Within budget; Profile 1 unless exporting. ## 8. Game readiness (Rakan Khamash, Blizzard, FRZtVXpAokc) - **"Say it or don't".** At `thumbnail` size, the character reads from the back of the room (00:02:16, 00:05:00). - **Curves and lines.** S and C curves; no parallel lines; no noisy curves (00:06:07). - **70/30.** Main breaks sit away from the middle; `split_ratio` flags 0.45 to 0.55 [added band]. - **Rest areas.** Rest areas exist, and details push the character's intent (00:08:53-00:10:32). - **Blockout.** It is 90 to 95 percent of the low poly (00:21:37). - **Loops and floaters.** Straps sit on loops; floaters stay separate; the face topology matches the shared template (vertex count equal before projecting). - **Budget.** About 16k for a hero including hair variants (unit not stated in the audio; the research manifest reads it as triangles). ## 9. Stack and projection (J Hill HlHoIGE2Ocs; Costa Lfen-BSwWcE; FlippedNormals Zp07GW3rND0) | Check | Measure | Judge | Fix | Severity | | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | -------- | | Route chosen before the first Divide (J Hill 00:13:22; Costa 00:17:53) | `stack_check` ok for the driver: SDK only = regular to about 20 to 25M; computer use = 3 to 4 regular (top 1 to 2M) + 3 to 4 HD | none | replan; a 6 or 7-level regular stack stays regular | blocker | | Micro detail has a home (Costa 01:10:58) | SDK route: a tiled micro-displacement map requested from scenario-zbrush-paint-render | close-up render shows pore grain | add the tile to the handoff | major | | Safety nets before Project All (00:02:13) | `zb_ops.project_all` result: `morph_target` stored, `layer` created, a checkpoint ZTL, no "safety net missing" warning | none | rerun with the defaults (`store_mt=True`, `layer=True`) | blocker | | Danger zones protected (00:01:41, 00:05:31) | interior polygroups (eyes, mouth, nostrils) masked before projecting | none | mask the grown interior polygroup (C19) | major | | No spikes at any level (00:03:49, 00:07:10) | the lead's bbox gate (`gate.ok`) plus `spike_report` ok per polygroup at every level (interior spikes stay inside the bbox); displacement outliers against the pre-projection export | close-ups of lids, lips, nostrils, fingers, armpits, crotch at each level, layer at 0 and 1 | `zb_ops.morph_repair` or masked local reprojection, never smoothing (C19) | blocker | | Detail came back (retopology digest) | `rx.projection_verdict`: bbox within 0.5 %, volume within 2 % | raking light: no soft patches | project again (Dist 0.1) or a local pass | major | ## 10. Report format (end of every stage) ``` stage: <name> file: <versioned ZTL> sheet: <path> measured: <stack_check, points per level, layers ledger, spike_report per group, face_report deltas, asymmetry_report, alpha_check / dots_check, detail_survival, fold checks> first failing step: <section and row> -> fix planned: <procedure> not verified: <every [verify] the stage relied on> ``` -
expert-notes.md 42.9 KB
# Expert notes: realistic characters and creatures The depth behind SKILL.md: principles and judgment by expert, with source and timestamp. Notes live in the project's `notes/anatomy/`, `notes/character/`, `notes/creature/` and `notes/hair-fibers/` (digests `_digest_face_anatomy_cloth_visual.md`, `_digest_character_creature_studio.md`, `_digest_realism_portrait_skin_visual.md`, `_digest_fibermesh_dynamics.md`). Timestamps are from the videos; doc sections are named. Numbers read off frames are marked "frame"; [?] means the frame was hard to read; [added] marks this skill's own inference. ## 1. The head and face ### Ryan Kingslien, "Common Mistakes When Sculpting The Face" (6wiDxO-ZADg, ZBrush 2021.6.2) **Principles** - **Efficiency is the measure of learning.** Count your moves in the Undo History and cut them: the head wedge went from 48 moves to 13, with a 16-move target (00:20:29-00:24:47, 00:28:04). A region touched twice is a red flag: go back and solve it upstream (00:21:01, 00:27:32). - **The first separation.** It is the cranial mass versus the facial mass, and it comes before the ears, the SCM, the frontal eminence or the orbit (00:31:15-00:34:15). The profile becomes "a little bit like a Pacman" (00:34:15). - **Bridgeman's masses**, one per step (00:38:12-00:47:17): - the flat of the cheek going back in space; - the triangular jaw, with its back sculpted; - the forehead box, broken down and separated from the round cranium, cut with TrimAdaptive (frame: Z 100, Focal Shift -83); - the maxilla cylinder, "the shape everybody misses": a line from beside the nose toward the jaw, dug in ("you can always put it back in"), the column rounded, the chin dug, the jaw separated. Round or square masses change the character (00:47:17). - **Structure before fat.** Fat pads are elements sculpted into each other (00:48:39, 01:46:50). - **Nose.** The bridge is smaller than the base whatever the nose type; widen the base, lift the bottom; the procerus rounds the root into a "capital" (00:49:45-00:50:59). Sculpt the nostril around it, through the alar facial juncture triangle, with Imbed lowered, "without touching the nostril" (00:54:07-00:57:33). A form-clue line around the wing marks the turn into shadow: draw it, then trim it (00:59:02-00:59:34). - **Eye.** - The eyeball sits high in the orbit ("up, protected") (01:05:21). - The infraorbital margin curls into the orbit at both ends; from below it shows "a one curve and a two curve" (01:06:02-01:07:39). - The nasojugal groove has real depth (01:04:49). - Only the upper lid is drawn, as one simplified line with a small gap, tucked up underneath (01:28:37-01:30:33). - Deep-set eyes read without a caruncula (the Prague bronze, 01:27:25). - **Brow.** A mark and carve at the external angular process, beveling the edge of the forehead box, with a small male "hook", then the temporal line (01:09:55-01:11:45). - **Mouth.** Muzzle; mouth line (Standard, Dots, Z 25, frame); lip planes first, even if they look like Dr Doom; lower-lip columns; depressor anguli nodule; the marionette line tucks the lower lip; a line separating a bundle of upper-lip fibers; then the five pillows (01:12:20-01:18:32). The mouth corner is a donut: radiate strokes out, turn Sculptris Pro off, blend chin and depressor with Inflat at Z 10 (01:19:45-01:21:56). - **Cheek.** Don't resolve the cheek: move the surrounding edges together; trim a malar band back for cheekbones (01:23:54-01:25:44). - **Nasolabial area.** Planes delimited from both sides, connected, blended, trough kept; nostrils tucked under (01:33:42-01:36:13). - **Planes over curves.** "Stay with the planes until you know what the next plane is, don't just put a tumor in there" (01:09:00). "Humans don't understand curves ... we understand planes" (01:34:15). **Numbers** - Primitive of 1,538 points (frame 00:16:08). The whole block-in stays under about 354k points with Sculptris Pro (frames 00:35:12-01:35:29). - Move: Dots, Z 51, Focal Shift 0, Draw Size 1.8 to 33 (frames). - RK_BallStylus (his own Gumroad brush, not shipped): FreeHand, Z 20, Focal Shift -56 (frames). **Agent reading [added]** - His decisive moves are single lines: the best stroke-synthesis targets. - Sculptris Pro needs a mesh without levels. - Morph targets replace his Undo History as the restore point, because `merge_undo` is not implemented. ### Kris Costa, Realistic Portraiture (Lfen-BSwWcE, 2018) and Realism Idealized (j5XLtLMN0P8, 2021) **Principles** - **Primaries first.** "Detail is nothing if you don't have a solid mesh base" (Lfen 00:28:17). Primaries are what scans hand you and why scans teach nothing (00:36:48). - **The hierarchy is context.** Primary, secondary and tertiary depend on the region you approach: a "fractal progression" (j5 00:08:12-00:10:25). - **Sketch, then fix by resolution.** Sketch primary and secondary together to catch the expression, then fix proportions through the lower levels without touching it (j5 00:10:40-00:11:45). - **ClayBuildup only** ("it respects the boundaries between the different forms"), at intensity 2 to 3 in very thin layers; forms must not "scream"; Standard only for veins (j5 00:12:58-00:17:12). - **Collision and ripple.** A smile lifts the nose corner, the nasolabial and the lower lid into a squint. Age shrinks fat pads and reveals the zygomatic and the skull. Never show every muscle flexed (j5 00:11:54-00:15:17). - **Evaluate physically.** Accurate renders under several lights from the first sketch: "a volume correct under one light is off under another" (j5 00:18:46-00:19:51). Turn the model upside down to "press the reset button in your brain" (Lfen 00:29:26). - **Pores** (Lfen 00:31:18-00:39:01): - a pore is a star-shaped "quilted button"; some are cuts with no depth; - pores stay symmetric until all are placed, then symmetry goes off for wrinkles and characteristic features (00:34:04); - single-pore stamps on hero zones, multi-pore stamps on the forehead and neck; - one size per zone, rotation varied by alternating drag direction and dragging diagonally. - **Wrinkles connect pores** (Lfen 00:45:09-00:47:26): short segments, never one long line, broken by stepping sideways, perpendicular to the circular muscles. The brush is Standard with Alpha 39 and a sharpened curve; not Dam_Standard, which pinches and distorts the motion. - **Directional pass with a cloned Elastic.** Spray, Alpha 16, intensity about 20 to 30 [?], because Standard "obliterates" finished pores on repeated passes (j5 00:51:50-00:52:22). - **Leather micro texture.** Spray in small circles at low intensity without revisiting; then Inflat at 1 to 2 with a big draw size (Lfen 00:42:21-00:44:37). - **Skin by region** (j5 00:40:24-00:42:33): - around the mouth, micro wrinkles perpendicular to the orbicularis oris: vertical at the lips, then diagonal, then horizontal toward the chin; - lip-edge wrinkles grow into lip lines; - nose: shallow pores, blackheads, peach fuzz; - cheeks, muzzle and chin: pores spread out; - forehead: horizontal lines. Wrinkles are "a memory of the facial expression". - **Likeness.** Asymmetry "is the only way you're going to be able to actually capture likeness" (j5 00:35:03). Gaze slightly up and aside; eyes rotated outward (j5 00:28:36-00:30:16). - **Keep level 1 alive.** Likeness edits happen on the primaries for months (Lfen 00:20:37). **Numbers** - Useful Measurements slide (j5 00:38:12): head 229 mm, eyeball 24 mm, pupillary distance 63 mm, eyes 3 to 10 degrees outward, about 5. - Stack: 17,328 points at level 1 to 1.108M at level 4, then 3 DivideHD, about 70M for head and torso (Lfen 00:17:53-00:18:25; frames). A whole face in HD at about 40 to 45M (j5 00:48:33). - Multi Map Exporter, from frames 00:20:31-00:23:31: - 8192, Border 4, FlipV, Switch MT on; - SubDiv 2, Adaptive, DPSubPix 4, SmoothUV; - Mid 0, 32-bit, EXR, Scale 1. Five 8K UDIMs export in 7.5 minutes (00:18:58). - Switch MT trick after an external base edit: delete the morph targets, StoreMT at level 2, Switch, Import, no Divide, export with Switch MT on (Lfen 00:22:17-00:23:59). He suspects newer builds no longer need it [verify]. **Hand-off to paint-render** - Blackheads are brown, never black. - Redness comes from micro veins. - Paint the albedo to be desaturated before SSS. - A painted micro-displacement control map is black on lids and lips (Lfen 00:40:07, 00:51:22, 01:10:58; j5 00:49:06-00:56:54). ### Anatomy For Sculptors (Uldis Zarins and team, blog articles) - **Head canon.** Cut the top sixth (crown to hairline), split the rest into thirds, then split the lower third in three; the eye line sits at half the head height ("Adding detail"). - **Skull.** Braincase to face about 60/40 in adults. The widest head point is the parietal eminence, the widest face point the zygomatic arch (except very brachycephalic heads). - **Sex differences.** Males: stronger brow, flatter back-slanted forehead, square chin, steeper gonial angle. Females: more vertical forehead, rounder orbits, narrower jaw, softer temporal line. These are averages, "not rules or ideals". - **Hand.** Length equals chin to hairline in adults; the middle finger is half the hand; the nail is half the distal phalanx. Fingers are square, not cylinders, joined by webbing; knuckles never align. - **Deltoid and hips.** The deltoid tuberosity sits a bit above mid-humerus (Eaton agrees). The butt is never wider than the hips (greater trochanter). - **Landmarks by body type.** Bony landmarks are depressions on a muscular body and protrusions on a lean one. ## 2. Skin detail ### J Hill, "Sculpting Skin Details" (HlHoIGE2Ocs, ZBrush 2021) - **Scans or hand work.** Studios with the camera on the skin use photo scans (ZWrap, Mari); hand sculpting is for personal hyper-real work (00:00:33-00:02:13). Use reference of the same age and sex, and test on a patch when unsure (00:05:26). - **Stack split.** 3 to 4 regular levels plus 3 to 4 HD, not 6 or 7 regular plus 1 or 2 HD, because "you are not able to go below where you started": inside HD the lowest level you can step to is the HD entry, and smoothing needs lower levels. A 6,240-face base reaches 25.5M at SDiv 7, and one HD level on top of that is 102M; games stop at 20 to 40M, "40 million is high" (00:10:37-00:13:22). Real-time work does not need 100M: tiling detail textures carry the sharpness (00:11:10-00:11:43). [added] Each level multiplies by 4, so HD over a deep regular stack explodes: 3 DivideHD over 16M reaches the 1 billion HD ceiling. - **HD regions behave like new meshes.** UVs differ, the morph target must be stored again on every entry, layers do not survive leaving HD, symmetry does not work. Aim front-on and slightly forward for a "butterfly" region covering both sides (00:12:16-00:14:26). - **Pore bed.** A custom 16-bit tiling height map in Surface Noise (NoiseMaker), Mix Basic Noise 0, strength negative so pores go in (frames: Noise Scale 1, alpha scale about 0.015 [?], Strength about -0.02 [?]). Preview in 3D, then Apply; female means smaller pores (00:14:31-00:15:38). "If you were to do the wrong scale in the beginning then everything would be off" (00:15:05). - **HD projection.** In HD the noise projects from the camera at the moment you pressed A: re-aim per area. The Morph brush blends seams, varies pore depth and removes pores around eyes and lips (00:15:38-00:16:42). - **Heavy then dial back.** Store MT, go too strong, Switch, paint back with the Morph brush (set to Spray for breakup) (00:17:05-00:20:21). - **Fine wrinkles** follow the compression, circular around the eyes (00:17:38-00:18:10). Default-brush recipe (frame 00:08:53): Standard, Spray, Alpha 60, Z 3, LazyStep 0.02. - **Not plastic.** Beat the surface up with push and pull at several sizes including medium: "the skin shouldn't feel perfectly smooth" (00:18:10-00:19:14). - **Elastic at very low intensity** builds volume between wrinkles; Clay smooths them away (00:20:21-00:20:53). - **Crisp lines.** Dam_Standard or a smallest-line brush for memory creases (nasolabial, frown, surprise) (00:21:17). - **Nose.** Shallower, sparser pores than the cheeks; a polished tip; clogged pores as bumps with DragDot so the highlight breaks; horizontal wrinkles where the tip meets the bridge (00:22:16-00:23:54). - **Lips** (00:24:10-00:27:23; frame 00:25:36: Dots, LazyStep 0.04, LazyRadius 4, Z 33): - Dam_Standard sections, then sprayed vertical and horizontal cues; - "catching arc" lines in alternating directions and lengths with Y shapes; - volume between the deep lines; - medium folds strong enough to break the silhouette; - fully asymmetric. - **Smoothing at a lower level** softens forms and keeps the sharp detail: use it to fade pores out of the soft eye skin (00:27:57-00:28:31). - **Body skin** carries fine wrinkles and breakup, few deep pores (00:29:03). - **Asymmetry in the last 25 percent**, on big forms: nose tip off the Z axis, one ear, a lip corner, the septum, one eye's scale (00:29:42-00:30:47). - **Export and render.** Multi Map Exporter captures HD detail: 8K, 32-bit EXR displacement, an 8K normal map that is very clean "because it's baking to itself" (00:33:16-00:33:48). In the SSS render only the sharp detail survives in the highlights (00:34:53). ### Kris Costa on skin See section 1: pores, wrinkles, Elastic and Spray. ## 3. Bodies and poses ### Scott Eaton, Dynamic Figure Sculpture (Ale6SXXbJMM, Summit 2015) - **Why anatomy.** Anatomy is "the tool to give you the ability to stylize realistically" (00:06:28). - **Force chain.** In a dynamic pose only the muscles in the force chain are tense; antagonists are relaxed "like a marshmallow". The error is a striated "super bodybuilder deltoid" (00:13:39-00:15:29, 00:28:42). Tension can change within one muscle (the rear deltoid "like a belt") and between sides (the side lifting more weight) (00:14:56, 00:28:56). - **Base and stack.** A sparse posed quad base with one loop per finger joint. Divide with smoothing off for the first two levels so knuckles do not become "sausage fingers", about 6 levels and 1.7M for a full figure (00:32:01-00:33:08). - **Reversible edits.** Store a morph target on the smooth divided mesh. Put proportion changes on layers: a head scaled up 20 percent, dialed back to 15 with the layer (00:33:33-00:34:39). - **Landmarks first, then connect the dots.** The landmarks: sternum, clavicle (an elongated weak S), acromion, spine of the scapula, C7, iliac crest, PSIS, ASIS, deltoid insertion (00:35:59-00:44:08). Resolve the humerus, then the scapula (about 30 degrees at 90 degrees of elevation), then the back muscles (00:39:23-00:39:55). - **Active muscles run point to point.** Check the construction from under the armpit (00:54:07-00:54:39). ClayTubes strokes follow the fibers (00:49:05). Smooth with Smooth Directional in the same direction (01:09:27); it ships in `Lightbox/Brushes/Smooth` (local 2026-09-24). - **Volume at low levels.** Change volumes at low levels by moving a few vertices, not at the top (00:47:27-00:48:00). - **Symmetry on posed figures.** Sculpt them without symmetry; use Posable Symmetry for the face and for toe curls (00:36:34). ## 4. Creatures ### Zachary Berger, Avatar: The Way of Water (PcuC8K-bz44, Summit 2023) - **The pyramid.** Story at the base, then biology and ecosystem ("these are not monsters they are animals"), then appeal for ages 8 to 80 (00:04:24-00:06:01). - **References.** Blend many reference animals so no seam shows (00:03:52, 00:12:33). An Earth-like metaphor at the broad strokes, alien details up close (00:14:13). - **Three approvals.** Form and anatomy, color and pattern, bioluminescence, each separate. Present grayscale when only anatomy is discussed (00:06:33, 00:37:47). - **Layers as design tools.** Jaw articulation; always open and closed mouth states; an evolution between an old and a new head so the director can stop "in between" (00:13:39, 00:18:05, 00:20:53). - **Three versions.** What was asked, what the designers pitch, and one pushed too far (00:16:59). - **Readability.** Schooling creatures need a silhouette that survives repetition (00:33:48). Reds vanish first underwater, oranges and blues survive (00:41:07). Too intricate patterns go noisy (00:55:43). - **Brushes.** Four: Dam_Standard, Standard, Move, ClayBuildup (00:45:35). ### Henning Sanden, creature scale alphas from scratch (TuRIf92oMCY, FlippedNormals 2025) - **Flow.** Scales flow with the anatomy: triceps into forearm, crossover patterns on the forearm, another direction on the thigh (00:00:34-00:01:38). - **Hand work and alphas.** Hand sculpting belongs in hero areas; an alpha speeds coverage, but "there's no way to get this perfect with just alphas" (00:01:38, 00:03:52). - **The tile.** A plane with Smt off, about 1M polys ("a million polies is equivalent to a 1K map"), on a layer (00:02:11-00:02:47). - Rough in the directionality with a clay brush; keep sizes uniform and the pattern generic, or it tiles visibly (00:03:20-00:04:25, 00:18:38). - Keep everything inside the borders (00:06:04). - Smooth, then Dam_Standard for separation: broken lines of varying weight (00:06:36-00:08:16). - **Smoothing is blurring.** Smooth inside each scale, never across the separating lines (00:20:47-00:21:19). - **Photos lie about height.** Shadows become recesses and speculars become bumps; use photos as a template only (00:17:33-00:18:05). - **Test on the model early.** At 11, 26 and 41 minutes: Document 1024 x 1024, perspective off, Actual, GrabDoc, DragRect, Focal Shift -100 (00:14:15-00:15:21). - **Mid value 50 calibration.** Mask a small circle in one corner and Inflate it to +50, typed, not dragged; do the same at -50 in another corner. The dots must be taller and deeper than anything in the alpha. Grab, set MidValue 50, paint the dots out (00:25:09-00:27:51). The agent substitute is `recenter_alpha`, which rescales so the flat border sits at 0.5 [added]. - **Applying it.** DragRect on a fresh layer; Focal Shift -100 for the full tile or 0 for a faded edge (00:27:51-00:28:23). - **Unify.** A hand pass so individual scales fit (00:28:55-00:29:29). Mid-frequency: mask scale groups, blur, invert, blur, Inflate some scales out; Alt on the border to break it (00:30:02-00:30:35). - **Cost.** About 1 hour per alpha; one to two weeks of alpha making for five characters (00:10:27, 00:11:32). A whole creature at about 21M polys, where a 1K alpha is already overkill (00:08:49). ### Luke Starkie, God of War: Ragnarok creatures (Maxon interview 2023) - **Head first.** The head is the focal point; its aesthetic sets the body. - **Tusks and spikes.** Cracks in stages with Orb Cracks and Dam_Standard, main shapes split into secondary ones, Clay brushes for a bone and rock feel with hard broken edges, small Orb Cracks last. Orb brushes are a third-party pack, not in the 2026.2.1 install (local 2026-09-24): use Dam_Standard, Slash3 and TrimDynamic. - **Wrinkles by hand.** From elephant and rhino reference: a modified Standard and a low-intensity ClayBuildup, then alphas only to break big wrinkles into smaller ones. - **No numbers.** The interview gives no brush values. A low ClayBuildup intensity in numbers is Costa's (Z 2 to 3, j5XLtLMN0P8 00:12:58-00:17:12), not Starkie's: attribute it that way. - **Hero scales.** On the close-up Nidhogg every scale was made by hand, each slotted next to another with no overlaps, after alphas "didn't feel right". - **Anatomy first.** "Once you memorize those shapes, you can play around with anatomy freely." ### Marko Lazov, creature concept speed sculpt (AQsmWcXLxk8, Summit 2025) - **Start.** Skull first (00:15:58). DynaMesh 128 plus Sculptris Pro whenever topology degrades (00:17:07). - **Don't get attached.** Smooth an hour away and redo it (00:29:38). - **Mask by Cavity, then a light Inflate** pulls detail on cheekbones and forehead. Mask, invert, Inflate gives happy accidents (00:22:37-00:25:56). - **Detail zones.** Green rich zones and red rest zones painted before detailing (00:31:12). - **Silhouette window** open constantly (00:28:36). - **Asymmetric growths.** Duplicate a shell, inflate it inward, sculpt growths on it, DynaMesh them in (00:41:03). - **Asymmetry pass.** Store MT, push and pull, restore parts with the Morph brush with symmetry off (00:56:58). "Asymmetry is the best thing for a creature" (00:58:04). - **Eyes.** A Toy Plastic sphere, black painted on the upper part as fake occlusion (00:43:15-00:45:59). - **Edit mode.** He dropped out of Edit mode into 2.5D near the end; the agent must check Edit before strokes (00:58:04). ### Pablo Munoz Gomez, four ways to create horns (TN9ARiC_82w, 2022) - **Detail straight, then bend.** Detail a straight tube with radial symmetry, then bend it into a horn; keep a straight master and duplicate it for each variant (00:23:04, 00:28:42). Odd radial counts layered 8, 5, 3 look less regular (00:21:49). - **Decimate before arraying.** 850k to 42k (5 %) to 8k (1 %) for arrays; a single horn at 20 % (00:05:14-00:06:22, 00:23:34). - **Curve IMM.** Stretch on, curve Resolution 10, max bend 45 to 50, Size falloff flipped, Imbed 0, Lock Start (00:13:24-00:17:21). - **Deformers.** They "deal with the volume and not necessarily the continuity of the topology". Keep the gizmo aligned to the straight horn for Bend Curve; 5 or 6 points are enough (00:25:29-00:26:48). - **Spiral3D.** Parametric thickness, coverage, radius and twist, then Make PolyMesh3D (00:29:23-00:32:39). ## 5. Cloth ### Rafael Grassetti, how to sculpt cloth folds (gNx4v0WVVHo, 2020) - **Tension first.** Every fold starts at a tension point; think first about "how this actually falls down from this tension point" (00:00:31). Mark tension points with small spheres (frames 00:00:45, 00:03:00, 00:07:32). - **Termination.** "You should never have a fold that just by itself terminates by itself" (00:02:35). - **Unevenness.** Near the tension, folds are heavier, thicker and overlapping; they relax further away (00:03:08). - **Four types by tightness.** Loose gives drop or curtain folds; two holds give diaper or zigzag; tight compressed fabric gives X; a split tension line gives Y. Types morph along a sleeve (00:04:26-00:06:36). - **Brushes** (frames): Standard (Dots, Z 25) "deforms the edges and pulls to the front", pushed in for drops; Clay (Z 80) for X ridges and elbow rolls; Move (Draw Size about 372, Z 51) for the edge sag; Smooth (Z 100, Focal Shift -55) to fade. - **Density.** Planes at SDiv 2 to 4, the sleeve at DynaMesh 32 first. Folds come out "way too big" on the first pass: subdivide and split (00:08:14-00:08:47). - **Read reference as tension points plus fold types.** A tight T-shirt splits Y folds from the armpit; a loose shirt is a ladder of drops tied by zigzags; a rolled sleeve makes X diamonds (00:08:50-00:11:36). - **Simulation.** It is fine when faster, but "none of those softwares will get you a hundred percent there" (00:12:06). ### Michael Pavlovich, Dynamics lessons (m5O_sBag_iA, F7bcjQAK0Wc, s2qVeTnx89M, gyaMwGSsrb8, nqoCyOME8Jo; ZBrush 2021) - **What the solver does.** It keeps edge lengths and surface area on every movement. Stretch means too little computation for the movement: raise Simulation Iterations or lower Gravity Strength or brush speed. "Iterations high, speed low" for tight fabric (m5O 00:14:54-00:20:55). - **Iterations stiffen.** Very high iterations with high Firmness never compresses (m5O 00:20:24). - **Fabric weight.** Firmness 1 is silky, 2 soft, 4 medium, 5 to 6 leather (m5O 00:17:59-00:19:48). - **Defaults.** Gravity Strength 10 and iterations 100 on screen in 2021; clean drapes at gravity 1, or at gravity 10 with iterations 1000 on simple meshes (F7bc 00:03:34-00:05:09). The 2026 doc says iterations default 50. - **Simulate low, preview high.** Simulate the low-resolution mesh and preview with Dynamic Subdiv; Apply only when the drape is right (m5O 00:05:18). - **The collision volume** is a voxel copy of every visible, non-active SubTool, held in memory until Recalc or until CollisionVolume goes off (nqoC 00:01:10-00:04:10). - Resolution: 16 unusable, 128 blocky, about 1024 good, 2048 available (nqoC 00:07:17-00:09:54). - Close holes on a duplicate collider (nqoC 00:01:58). - Collision Inflate: 0 touches, about 0.9 keeps clear; Dynamic Thickness Offset 100 makes the simulated surface the inside (nqoC 00:06:08-00:07:17). - **Inflate versus Expand.** Inflate follows normals; Expand and Contract act on edges and fight the solver to make wrinkles. Axis toggles are world axes. Masking pins (s2qV 00:00:32-00:10:26). - **Leftover switches.** Everything switched on in Dynamics also runs during cloth brushes and TransposeCloth: a leftover Contract collapses the mesh (s2qV 00:14:26; gyaM 00:00:31). - **Cloth brushes.** ClothPinchTrails needs Trails 10 to 16 (1 fades, 100 is too strong) (gyaM 00:03:09). Uniform dimples: ClothDimple on DragDot, then wiggle in place (gyaM 00:12:34). ### Maxon docs: Dynamics, cloth brushes, Mesh Extract - **Stopping and pinning.** Stop a simulation with a click in the document or the Spacebar; mask, then resume: masks fix areas in place. - **Limits.** Max Simulation Points defaults to 250 (thousands). - **Morph target as area reference.** A stored morph target is the surface area to maintain. - **MDD.** Record Deformation Animation writes an MDD file. - **Extract.** Thick 0.01 for cloth, 0.03 for leather; clear the mask on the source afterwards. ## 6. Fur and hair ### Pablo Munoz Gomez, FiberMesh for hair and fur (lgTA_ebLGDw, 2020) - **Groom or settings, not both.** Grooming overrides twist, revolve radius and gravity (00:01:24, 00:05:47). - **See what you groom.** White base and tip colors; FillObject the fibers in a contrasting color to expose gaps (00:06:53-00:09:32). For shimmering fibers, Document > Double then AAHalf (00:07:24). - **Creature fur.** Max Fibers 11 (thousands), Segments 6; Fast Preview with PRE Vis 6 to 8 grooms through guides (00:08:28-00:10:04). - **Masks select clumps.** Masking any point masks the whole fiber, so masks plus Ctrl+W make clump and parting polygroups (00:12:11-00:13:16). ### Maxon FiberMesh docs and Pavlovich hair simulation (QCcDTuDT2HM) - **Mask drives growth.** Mask intensity modulates density and length. Fibers take color from PolyPaint only. Generate in several passes. - **What survives Accept.** Preview settings stop mattering after Accept, except the BPR options. - **Profile 1** while working; get volume from BPR Sides or Dynamic Thickness. - **Topology changes convert.** Subdividing or slicing turns a FiberMesh into a polymesh, and FiberUV grays out. - **Human head.** 80k to 140k hairs. - **Before a hair simulation.** Pin the roots with Mask By Fibers (FiberMask with a root-only profile), then CollisionVolume against the head, Self Collision 1 and a slightly lower Collision Inflate (QCcD 00:03:23-00:04:37). - **Cards.** FiberMesh can make hair cards (Width Profile max, Coverage max, Profile 1, BPR Sides 2); every fiber is UV'd root to tip (QCcD 00:00:00-00:02:09, 00:07:01). - **Custom strands.** Swap fibers for designed strands with Frame Mesh plus an IMM curve brush, or MicroMesh plus Convert BPR To Geo (QCcD 00:14:12, 00:17:00). - **Curve Flat** is Curve Tube with Brush Modifier 0: the scriptable hair-card route through the curves API (goZavCi515k 00:02:20-00:02:50). ## 7. Game production rules (Rakan Khamash, Overwatch 2, FRZtVXpAokc, Summit 2023) - **Readability.** "Say it or don't": readable from the back of the room (00:02:16, 00:05:00). - **The blockout** is 90 to 95 percent of the low poly (00:21:37). - **Shape language.** S and C curves; no parallel lines; no noisy curves; 70/30, never 50/50; flare at the top, taper at the bottom (00:06:07-00:08:53, 00:16:37). - **Rest areas.** An armored arm next to a bare one (00:06:40). - **Rig first.** Skins share the hero's face topology and loop counts; straps snap to loops (00:11:04-00:11:37). - **Floaters** for anything that may change (00:13:16). - **Folds** only where pressure and joints demand; fold brushes sculpted on a plane and stamped (00:19:55-00:21:00). - **Design on the body.** Polypaint the design on the nude body, decimate with polypaint kept, and trace the color borders as topology (00:37:33). - **1:1 bakes.** Project the high poly onto the final low-poly face with polygroups per loop (00:41:27-00:42:31). - **Wear physics.** Dirt from the bottom up; chips at the first contact point, never mid-panel (00:25:34-00:26:42). ## 8. Projection, frequency stages, sharpness and brush order (FlippedNormals) ### Henning Sanden and Morten Jaeger, "Reprojecting Details" (Zp07GW3rND0, 2018) - **Both nets first, at the top level.** Store Morph Target, then a new Layer, then Project All: "It's important that you do all of this on the highest level" (00:02:13, 00:02:47). PA Blur lowered. Target subdivided to "a few million", about 3M (00:01:02). - **Danger zones.** Parts that sit close together (eyes, mouth, armpits, fingers) grab the wrong surface (00:01:41). Keep a polygroup inside the eye, grow it a little past the lid, mask by it, then project (00:05:31-00:06:03); pre-shape the high-res where it differs a lot (the stomach), or project in stages from the lowest level upward (00:03:17). - **Walk every level.** Toggle the layer and step through the levels (00:03:49). Armpits, between the fingers, butt and crotch fail most; artifacts there become hard lines in displacement maps "down the pipe" (00:07:10-00:07:43). - **Repair without smoothing.** Rogue vertices "go to infinity"; smoothing "doesn't work" on busted areas: Morph brush back to the stored target, or mask, invert and reproject locally (00:04:27, 00:06:36, 00:09:27). - **Timing.** Stop the concept sculpt before micro detail when retopology is coming (00:08:22). - Agent reading [added]: `spike_report` per polygroup at each level replaces the eye on spikes; the eye-interior mask is a MaskLasso polygon from `region_on_canvas` [verify] or a computer-use polygroup isolation. ### "Top Tips for Sculpting" (G2o6fdoACIQ, 2020) - **Never skip a frequency.** The mid-frequency pass, "shapes within shapes", is the backbone of detailing; pores before it look like decoration on a statue (00:05:13-00:05:46, 00:08:17). - **Symmetry breaks at the mid-frequency stage**, on the big shapes (the mouth), before any pore (00:06:19). - **Silhouette stability.** The less the silhouette changes between later stages, the more solid the design (00:17:27). ### "Learn to Sculpt Like a Pro" (0PaYUUvgwYM) and "The Only 6 Brushes You Need" (TpS0QdlfHWU) - **Carve deeper than feels right.** Shading, subsurface scattering and soft light eat sharpness later (0P 00:19:49-00:20:22). J Hill says the same from the render side: only the sharp detail survives in the SSS highlights (HlHoIGE2Ocs 00:34:53). - **Brush order.** ClayBuildup replaces what is under it; Standard pushes and pulls what is there. ClayBuildup belongs before pores, Standard after (TpS0 00:03:55, 00:07:44). Costa: repeated Standard passes also "obliterate" finished pores, so his directional pass uses a cloned Elastic on Spray with Alpha 16 (j5XLtLMN0P8 00:51:50-00:52:22); J Hill builds volume between lines with Elastic at very low intensity, because Clay smooths them away (00:20:21-00:20:53). ## 9. Maxon docs that set the rules - **3D Layers.** - New layers only at the top level; Record turns on by itself and can only be turned off at the top level. - Intensity 1 as sculpted, above 1 exaggerates, negative inverts, always 1 while recording. - Bake All bakes at current intensities. A single-layer bake (StoreMT, delete, Switch) loses polypaint and masking: Split first. - Erase layer content locally: hide the layer, StoreMT, unhide, paint with the Morph brush. - **Morph Targets.** One per SubTool; destroyed by topology changes; the typical use is restoring the base right before map generation. - **Surface Noise.** - It is a render effect until Apply To Mesh, and it shows only with Quick 3D Edit on. - Keep Strength low; SNorm 100 at high scale and strength; divide enough before applying. - NoiseMaker Mask Mixing: Magnify by Mask and Strength by Mask. - NoiseMaker settings save and load as files; since 2026.0 NoiseMaker rotates, scales and offsets the alpha, so a scale tile can follow each region's flow. - Open NoisePlug with Strength at 0.5 or -0.5 to see the preview; Snake Skin needs Scale Variability and "especially some Amplitude"; UV projection can show seams. - **Alphas.** Flattened 16-bit grayscale without compression. GrabDoc makes a true 16-bit alpha from canvas depth. - **HD Geometry.** Up to 1 billion polygons (the ceiling that 3 DivideHD over a 16M regular stack already reaches). - Only the region is live, sized by MaxPolyPerMesh (100 at startup on this Mac, Activity log 2026-09-24; units [verify]). - BPR renders HD with Render Properties > HDGeometry. - XTractor captures detail into alphas with the G key: GrabDoc on a plane is the scriptable substitute. - **Displacement.** Mid 50 by default (0 for 32-bit, per the Multi Map Exporter doc); normal and displacement maps must share the Adaptive setting. - **Multi Map Exporter.** UV tile ID format UDIM (Mari numbering) and UDIM output lists since 2021.7; Merge Maps writes TIFF, so it is off with EXR and each object gets its own tile (FlippedNormals -ThBTEc8L_M 00:04:43-00:05:41, 00:11:08; [verify on 2026]); a dot before the tile number makes the files an image sequence (00:06:36); ESC during export can lose the UVs: save first (MME doc Warning). - **Decimation Master.** Decimate a duplicate, never the master; the result reflects the model as it was at pre-process time; a scripted Pre-process ended the calling ZScript in a forum report, so run it as its own bridge call [verify] (Decimation doc). ## 10. Disagreements and deciding conditions | Choice | Positions | Decide by | | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Hand scales, alphas or procedural | Starkie: every scale by hand on a close-up hero; Henning: custom alpha plus a mandatory unify pass; docs: NoiseMaker Scales and Snake Skin | hero close-up with budget: by hand (per-scale stamps for the agent). Coverage seen closer than gameplay: alpha plus unify. Tertiary, previews, concept: procedural | | Pore bed | Costa: hand stamps, one size per zone; J Hill: Surface Noise tiling map, then hand fill and Morph-brush variation | scale uncertain or agent-driven: Surface Noise first (a slider) [added]; authorship on hero zones: stamps | | Wrinkle brush | Costa: Standard + sharpened Alpha 39 (Dam_Standard pinches); J Hill: Dam_Standard for lip sections and crisp memory creases | soft wrinkles grown from pores: Standard. Deep crisp creases standing above the rest: Dam_Standard or a tiny line brush | | Stack | Costa and J Hill: regular to 1 to 2M, then HD; J Hill for games: regular only to 20 to 40M | decided before the first Divide by the driver: a computer-use agent and a film close-up: 3 to 4 regular + 3 to 4 HD from the start. Bridge only: regular to about 20 to 25M, micro detail as a tiled map at render (Costa's own last step, Lfen 01:11:31) [added default]. Never HD over 6 or 7 regular levels (`stack_check`) | | Displacement Mid | Costa: Mid 0 with 32-bit for Arnold; J Hill's panel appears 0.5 [?]; MME doc: 0.5 for 16-bit, 0 for 32-bit | the renderer's zero decides; record it with the map | | Export level | Costa: level-2 mesh, exporter SubDiv 2; J Hill: SDiv 3 with an 8K normal in Marmoset | match the exporter's SubDiv to the mesh the renderer receives | | Detail strength control | J Hill: heavy then back with the Morph brush; Costa: thin layers at low intensity | subtle secondary forms: build up slowly; uncertain tertiary, and inside HD where layers do not survive: heavy then back | | Symmetry | Eaton: none on posed figures, Posable Symmetry for face and toes; FlippedNormals: broken at the mid-frequency pass; J Hill: big forms in the last 25 percent; Lazov: symmetric, then Morph-brush asymmetry; Costa: pores symmetric until placed, wrinkles asymmetric | posed: asymmetric from the start; neutral or concept: big forms broken at mid-frequency and again late, pores may stay mirrored until placed | | Displacement Adaptive | Costa: Adaptive on, DPSubPix 4; FlippedNormals: Adaptive off, "negligible" gain for a huge time cost (100 UDIMs: about 1 hour versus 24) | many UDIMs or iteration speed: off. Few tiles and a hero still: on. Either way identical in the normal map that combines with it (HD doc) | | Switch MT after an external UV edit | Costa: Store MT, Switch, Import, no Divide, Switch MT on, or big forms leak into the displacement; FlippedNormals: Switch MT "currently broken" in their build | test it on one tile in 2026 before relying on it [verify]; if it leaks, re-import and re-project instead | | Smoothing | Kingslien: Shift-smooth lumps under Sculptris Pro; Grassetti: smooth to fade folds; Henning: smoothing is blurring, inside scales only; J Hill: smooth at a lower level | block-in lumps: smooth. Detailed surfaces: resculpt, or smooth at a lower level or under a cavity mask | | Simulate or sculpt cloth | Kingslien lists a Marvelous recipe; Grassetti: sim if faster, finish by sculpting | drape and garment volume: sim. Hero folds and views without reference: sculpt. Both: sim base, sculpt pass | | Scapula | Eaton: about 30 degrees at 90 degrees of elevation; A4S: rotation only above shoulder level | use Eaton's number near horizontal arms; read A4S as "obvious above shoulder level" | | Color early or late | Lazov and Blizzard paint early to read the design; Berger and the Keos team keep anatomy approvals gray | design exploration: color; anatomy approval: gray | -
gui-paths.md 12 KB
# GUI paths: the same procedures by hand (for a computer-use agent or a human) Palette paths, hotkeys, brushes and settings behind procedures C1 to C20. Hotkeys come from the source videos (2018 to 2025 builds); treat any hotkey as [verify] on 2026.2.1. On macOS, ZBrush docs write "Ctrl" for Command in most places, but Control+W assigns a polygroup and **Cmd+W quits ZBrush** since 2023.0.1: never send Cmd+W. Brush files named below exist in `/Applications/Maxon ZBrush 2026/ZData/BrushPresets` or `Lightbox/Brushes` (local listing 2026-09-24) unless marked "not shipped". In the palette the brush file DamStandard.ZBP shows as Dam_Standard, but the item path `Brush:Dam_Standard` did not resolve through the SDK (README): a script selects it through `zb_ops.select_brush`. ## Navigation and review (C1) - **Views.** Rotate with Shift for snapped views. P toggles perspective. F frames. Transform > PolyF shows the wireframe. - **Materials.** MatCap Gray for form, SkinShade4 for folds (Pavlovich), Flat Color for silhouettes. A raking light comes from Light palette placement. - **Before copy.** Tool > SubTool > Duplicate, then Tool > Geometry > X Position to park it beside the original (Kingslien judges beside a duplicate). - **Upside down.** Rotate the model 180 degrees on screen (Costa). - **Renders.** BPR with Shift+R; Redshift in Render > Redshift Renderer (scenario-zbrush-paint-render), the in-ZBrush route for the three-light SSS check: sharp detail must survive in the highlights. - **Painter route.** Texture > Substance Bridge (2026.2): Send to Painter, Low & High, Auto-Bake Maps; Force UV Auto-Unwrap off when UVs exist. ## Masses and face (C2 to C4) - **Block-in.** - Move is B, M, V: Dots, Z 51, Focal Shift 0. - Clay block-in: ClayBuildup is B, C, B, at Z 20 and Focal Shift -56 as a stand-in for RK_BallStylus (not shipped). - TrimAdaptive, Z 100, Focal Shift -83, for the forehead box. - Standard is B, S, T: Dots, Z 25, for the mouth line. - Inflat, Dots, Z 10, for the mouth-corner blend. (Kingslien frames.) - **Carving.** Alt inverts ZAdd to ZSub. - **Sculptris Pro.** The top-bar S icon, or Stroke > Sculptris Pro; delete levels first (Tool > Geometry > Del Lower and Del Higher). Brush > Depth > Imbed lowered for the nostril. - **Masks.** - Hold Ctrl for MaskPen or MaskLasso; Ctrl+Alt removes mask. - Ctrl-click on the masked model blurs the mask (Pablo); Ctrl-click on empty canvas inverts it [verify]. - Tool > Masking > BlurMask, Inverse, Clear. - Mask By Cavity, with its Blur and Intensity sliders and Cavity Profile curve; Mask By Smoothness (Range, Falloff); Mask PeaksAndValleys; Mask Ambient Occlusion. - **Polygroup isolation.** Ctrl+Shift click isolates a polygroup; Control+W groups the visible part. - **Auto-masking.** Brush > Auto Masking > Mask By Polygroups at 100 keeps a stroke inside the polygroup it starts on (shipped macro "Toggle Mask By Polygroups"). - **Deformation sliders.** Tool > Deformation: Offset (percent of the unit radius), Size (100 doubles), Inflat, Smooth, Polish, Rotate, Bend, SBend, Twist, Taper, Gravity. Click the X, Y, Z letters inside a slider to constrain it to world axes. - **Symmetry.** Transform > Activate Symmetry, >X<; posed figures: Transform > Use Posable Symmetry (Eaton). ## Stack and layers (C5, C6) - **Divide.** Tool > Geometry > Divide (Ctrl+D), with Smt off for the first levels when joints must stay sharp (Eaton). SDiv slider; Lower Res and Higher Res; Del Lower and Del Higher. - **HD Geometry.** Tool > Geometry HD > DivideHD, only on a stack planned for it from the start: 3 to 4 regular levels (top 1 to 2M), then 3 to 4 DivideHD (J Hill, Costa). On a 6 or 7-level regular stack, stay regular. - Hide the polygroups you do not need before entering (Costa); Auto Groups With UV gives one group per UDIM. - Enter a region: A over the mesh, with perspective off and aimed off the front axis (Costa). - Preview the HD model: A with the cursor away from the model. - Solo shows only the HD region. - Region size comes from Preferences > Mem > MaxPolyPerMesh. - BPR of HD: Render > Render Properties > HDGeometry. - **Layers.** - Tool > Layers > New (at the top level; turns REC on). - The REC circle on the layer row turns recording off, at the top level only. - Eye icon, Intensity slider. - Bake All, Split, Duplicate, Delete. - Rename (a text prompt). - Record Deformation Animation writes an MDD file. - **Morph targets.** Tool > Morph Target > StoreMT, Switch, DelMT. The Morph brush is B, M, O. ## Reprojection with danger zones (C19) - At the top level: Tool > Morph Target > StoreMT, then Tool > Layers > New (FlippedNormals). - Only source and target visible (Solo, or the SubTool eye icons). - Eye, mouth and nostril interiors: Ctrl+Shift click the interior polygroup to isolate it, Tool > Visibility > Grow once or twice until it passes the lid, mask the visible part (Ctrl-click on the model, or Tool > Masking), Tool > Visibility > ShowPt. - Tool > SubTool > Project: PA Blur lowered, then Project All. - Walk the levels with the SDiv slider, toggling the layer's eye icon; look at eyes, mouth, nostrils, armpits, between the fingers, crotch. - Repair with the Morph brush, or mask, Tool > Masking > Inverse and Project All again on the region. Do not smooth. ## Skin and scales (C7 to C11) - **Surface Noise.** Tool > Surface > Noise opens NoiseMaker the first time. - NoisePlug opens the generators (Scales, Snake Skin, Voronoi...). Set Strength to 0.5 or -0.5 before opening it, to see the preview. - Alpha mode loads a 16-bit tiling height map. - Strength by Mask and Magnify by Mask; Open and Save presets. - Apply To Mesh; SNorm 100 at high strength. - Transform > Quick 3D Edit must be on for the preview. - Snake Skin: raise Scale Variability and Amplitude (doc). UV projection can seam; check a close-up. - Alpha Angle, X/Y Scale and Offset (NoiseMaker 2.0, 2026.0) turn the tile to each region's flow; paint a region mask first, then Magnify by Mask and Strength by Mask 1. - **Alpha tile** (Henning): - Tool > Plane3D, Make PolyMesh3D, Smt off, Divide to about 1M. - Document > Width and Height 1024 > Resize, Ctrl+N, redraw the plane, F, perspective off, Actual. - Alpha > Transfer > GrabDoc; Alpha > Modify > MidValue 50 (he records a macro on Alt+A); Alpha > Export. - Calibration dots: mask a small circle in a corner, Tool > Deformation > Inflate typed to 50 (not dragged), -50 in another corner. Both taller and deeper than any detail. - Test on the model three times while building: grab, Alpha > Modify > Surface, switch to the creature, new layer, DragRect with Focal Shift -100, look, delete the layer. - Save finished alphas in the Asset Directory: Preferences > Asset Directory > Open Directory, then LightBox > Alphas (2026.1+). - **Stamping.** - Stroke > DragRect, DragDot or Spray; Draw > Focal Shift -100 (full stamp) or 0 (faded). - Alpha palette; Alpha > Modify curve for pore profiles (Costa). - Spray settings: Placement, Scale, Color, Flow (Costa's stubble: color near 0, flow toward 0). - Brush > Modifiers > Imbed. - **Pores and wrinkles.** - Pores: Standard + a pore alpha on DragRect (Costa's Antro_ brushes are not shipped). - Wrinkles: Standard + Alpha 39, alpha curve sharpened (Costa). - Fine wrinkles: Standard, Spray, Alpha 60, Z 3, LazyStep 0.02 (J Hill). - Lips: Dam_Standard (B, D, S), Dots, LazyMouse on, LazyStep 0.04, LazyRadius 4, Z 33 (J Hill). - Elastic, cloned, on Spray with Alpha 16 for the directional pass (Costa). - Inflat Z 1 to 2 with a big Draw Size for organic micro texture. - **Smoothing.** Hold Shift; pick the Shift brush in the Brush palette while holding Shift. Smooth Stronger and SmoothDirectional are in Lightbox > Brushes > Smooth. - **Scale brushes that ship:** Lightbox > Brushes > Scales (ScalesLizard, ScalesSnake1 and 2, ScalesFish...). Alphas: Lightbox > Alphas (Scaly Skin, Leathery Skin, Bumpy Skin). - **Color passes** (scenario-zbrush-paint-render): Rgb on, low Rgb Intensity 5 to 10, Spray (Costa). ## Asymmetry (C12) - X toggles symmetry off, then use a large Move brush (J Hill). - Or: Tool > Morph Target > StoreMT, sculpt, then Morph brush strokes with symmetry and RGB off (Lazov). - Deformation > SmartReSym restores symmetry from a masked side. ## Cloth (C13 to C15) - **Fold brushes.** Standard (Z 25), Clay (B, C, L, Z 80), Move (big Draw Size about 372), Smooth (Z 100, Focal Shift -55) (Grassetti frames). Tension points are small appended spheres. - **Dynamics palette** (dock it in a tray): - Simulation Iterations, Strength, Firmness, On Masked, On Brushed, Fade Border, Self Collision, Floor Collision, Allow Shrink, Allow Expand. - Gravity, Gravity Strength, Liquify, Set Direction. - Inflate, Deflate, Expand, Contract, each with an Amount and X, Y, Z buttons. - Collision Volume group: CollisionVolume, Recalc, Resolution, Inflate. - Run Simulation, stopped by a click in the document or the Spacebar; Max Simulation Points. - Draw > Elv sets the floor height (-1 follows the lowest SubTool). - **Dynamic Subdiv.** Tool > Geometry > Dynamic Subdiv: Dynamic (D on, Shift+D off), SmoothSubdiv, Thickness, Offset, Apply. - **Cloth brushes** (B, C, then a letter): ClothHook (B, C, K), ClothPull, ClothMove, ClothFold, ClothPinch, ClothPinchTrails (Brush > Modifiers > Trails 10 to 16), ClothSlide, ClothTwister, ClothWind, ClothDimple (DragDot, then wiggle in place), ClothInflate, ClothBall, ClothNudge, TransposeCloth (B, T, C, with the Gizmo). - **Mesh Extract.** Mask, then Tool > SubTool > Extract (Thick 0.01 cloth, 0.03 leather; S Smt), Accept, then clear the mask on the source. - **Close holes.** Tool > Geometry > Modify Topology > Close Holes, on a duplicate of an open collider. ## Fur and hair (C16) - **FiberMesh.** Tool > FiberMesh > Preview, Accept, Open and Save presets (Lightbox > FiberMeshes; presets such as Fibers5.ZFP ship). - Modifiers: Max Fibers (thousands), Length, Coverage, Gravity (set the model orientation first), Twist, Revolve, Segments, Base and Tip Color, Profile (keep 1), Imbed, By Area, Clumps. - Width, Length and Color Profiles are curve editors. - Preview Settings: Fast Preview, PRE Vis. - BPR Settings: Sides, Anisotropic. - **Groom brushes** (Brush > Groom): GroomHairToss, GroomerStrong, GroomBlower, GroomClumps, GroomTurbulence, GroomSpike, GroomSpinKnot and more. Turn RGB off while grooming. - **Visibility tricks.** Color > FillObject on the fibers in a contrasting color; Document > Double, then AAHalf, for anti-aliased fibers. - **Masking fibers.** Tool > Masking > Mask By Fibers: FiberMask, FiberUnmask, FiberMask Profile (left side = root). - **Hair cards.** Brush > CurveFlat or CurveFlatSnap: drag a card, tap off to commit; Curve Res in Brush > Modifiers. ## Horns (C17) - **Curve IMM** (Pablo): - Brush > Create InsertMesh > New from a straight side view with perspective off. - Stroke > Curve Mode. - Brush > Modifiers: Stretch, Curve Res 10, max bend 45 to 50. - Stroke > Curve Modifiers: Size on, falloff flipped. - Brush > Depth: Imbed 0; Lock Start; Brush > Save As. - **Tube route.** - Tool > Cylinder3D, DynaMesh (Ctrl-drag on empty canvas re-meshes). - Transform > Activate Symmetry, Y, (R) radial with Radial Count 8, then 5, then 3. - Keep the straight, detailed tube as a master: Tool > SubTool > Duplicate, and decimate only the copy. - Zplugin > Decimation Master: Pre-process Current, % of decimation, Decimate Current. - Cracks on tusks and spikes in stages, large to small: Dam_Standard at full size, then smaller; Clay for a bone and rock feel (Starkie). Orb_Cracks is third-party and not shipped. - Gizmo 3D (W, then the gear): Taper, Bend Arc, Twist, Bend Curve with 5 or 6 points. Alt-click resets the gizmo orientation; do not do this after placing the horn. - **Spiral3D.** Tool > Spiral3D, then Tool > Initialize sliders, then Make PolyMesh3D. ## Eyes and scale (C18) - **Eyes.** Macro > Append Eyes (shipped): Insert Sphere3D, then Tool > Geometry > X, Y, Z Position and XYZ Size, Mirror And Weld on X, Material > ToyPlastic. - **World scale.** Zplugin > Scale Master sets millimeters (scenario-zbrush-retopology-export owns export scale). -
procedures.md 50.3 KB
# Bridge procedures: realistic characters and creatures (ZBrush 2026.2.1) Status, stated once for the whole file: **no procedure below has run in ZBrush yet.** Every block is "not yet run in ZBrush". What is proven is the ground they stand on (scenario-zbrush-expert v01 to v03, 2026-09-24): synthesized V02 strokes sculpt, `Document:Export` and `Tool:Export` write files without a dialog, and `set()` on a missing path is silent. The logic of `zb_character` passes 54 offline tests (`tests/code/zbrush-character-creature/test_zb_character.py`, `run_offline.py`), including a round trip through the real bridge server against a fake `zbrush.commands` and, since the 2026-09-24 refactor, the stack, map-request, crack, aiming, region, spike, asymmetry, calibration-dot, SSS-survival and Asset Directory checks. The live tests that will settle each procedure are named in its header; `run_live.sh` runs them in risk order. | Live test | Settles | | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `live_c01_layers_morph.py` | path inventory of every `CHAR_PATHS` candidate; `brush_check` (Dam_Standard under its 2026 name); `layer_new` at the top level; the layer intensity path; StoreMT and Switch; Bake All | | `live_c02_masks_stamps.py` | Mask By Cavity protects cavities from Inflate; MaskLasso selected without Ctrl; stroke-type names; DragRect drag length sets stamp size; DragDot; Mask By Polygroups auto-mask | | `live_c03_alpha_noise.py` | Plane3D with Smt off to 1M; GrabDoc, MidValue, Alpha:Export by path; `alpha_check` on the export; Surface Noise pop-up (blocking or not); Apply To Mesh | | `live_c04_face_review.py` | `insert_sphere` and Geometry Position versus OBJ coordinates; multi-group OBJ; the view from below on the sheet; `face_report` and `mirror_deviation` on a real export | | `live_c05_dynamics_fibers.py` | Dynamics paths; whether Run Simulation returns and how it stops; Extract and Accept; FiberMesh Preview and Accept | | `live_c06_curves_horn.py` | CurveTube, IMM Curve and CurveFlat with script curves; curve points in OBJ units | ## Preamble (every procedure) ```python import json, os, random, sys SK = "skills" sys.path[:0] = [SK + "/scenario-zbrush-character-creature/scripts", SK + "/scenario-zbrush-expert/scripts", SK + "/scenario-zbrush-retopology-export/scripts"] import zb_launch as zl, zb_review, zb_audit, zb_stroke import zb_character as zc import zb_retopology_export as rx # reprojection stack, decimate_copy, MME (its owner) OUT = "/abs/out/creature" # every artifact under a versioned folder zl.start() # or reuse a running bridge session (scenario-zbrush-expert) W, H = zl.run("result = [zbc.get('Document:Width'), zbc.get('Document:Height')]") ``` Calls: `zl.call("zb_ops", ...)` for the lead's wrappers, `zc.call(...)` for this toolkit's ZBrush-side functions, `zl.run(code, modules=("zb_ops", "zb_stroke"))` for inline code. Before any stroke, confirm Edit mode (`stamp` calls `zb_ops.ensure_edit`), set symmetry on purpose, and save a versioned ZTL before anything destructive (`zl.call("zb_ops", "save_ztl", path)`). Once per session, `BR = zc.call("brush_check")`: which planned brush resolves to a palette item, to a file to load, or only to a fuzzy name. `Brush:Dam_Standard` does not exist under that name on 2026.2.1 (README); every procedure selects brushes by name through `zb_ops.select_brush` (called by `stamp`), which tries the DamStandard spellings and loads the .ZBP otherwise. Never `zbc.press("Brush:Dam_Standard")` directly: a missing path fails silently. --- ## C1. Face review loop (measure, render, compare) Source: Kingslien checks front, profile, three-quarter and from below, and judges a change on a duplicate beside the original (6wiDxO-ZADg 01:00:06, 01:07:39); Costa reads blurred, from afar and upside down (Lfen-BSwWcE 00:28:50; j5XLtLMN0P8 00:09:18). Test: `live_c04_face_review.py`. ```python def face_review(stage, chin_y, prev=None): d = f"{OUT}/{stage}" obj = zl.call("zb_ops", "export_obj", f"{d}/head.obj", overwrite=True)["path"] crown_y = zb_audit.audit(obj)["bbox_max"][1] prof = zc.front_profile(obj) lm = zc.profile_landmarks(prof, chin_y, crown_y) # heuristic: confirm on the profile tile rep = zc.face_report(obj, lm) rep["mirror"] = zc.mirror_deviation(obj) views = ["front", "right", zc.view_facing("-Y"), "threequarter", "back"] rv = zb_review.review(d, views=views, title=f"{stage} | MatCap Gray") front = rv["views"][0]["path"] derived = [zc.blurred(front, f"{d}/front_blur.png"), zc.thumbnail(front, f"{d}/front_thumb.png"), zc.upside_down(front, f"{d}/front_flip.png")] return {"landmarks": lm, "report": rep, "sheet": rv["sheet"], "derived": derived, "delta": zc.compare_reports(prev, rep) if prev else None} r1 = face_review("stage1_masses", chin_y=-0.92) # chin from a marker (C18) or the profile tile ``` Then open the sheet and the three derived images and walk `critique.md` section 1 in order: the first failing step is the fix, because later steps inherit its error (Kingslien 01:37:20). `chin_y` is not the bbox minimum on a bust (the neck is lower). The below view is `view_facing("-Y")` under the unfitted camera convention: check on the tile that the chin faces the camera. ## C2. Mass block-in on a stroke budget (Kingslien) Source: Move only (Dots, Z Intensity 51, Focal Shift 0), cranium plus face wedge in 13 to 16 moves, undo and redo when a region is touched twice (6wiDxO-ZADg 00:20:29-00:24:47, 00:28:04). Morph target as the undo point, since `merge_undo` is not implemented. Test: `live_c01` (StoreMT, Switch) and `live_c02` (strokes). ```python zl.call("zb_ops", "set_symmetry", True, "x") zc.call("morph_store") # restore point for this pass mb = zc.MoveBudget({"mass": 16}) plan = [ # (region, start px, end px) read off the last front render; one decision per move ("face_wedge", (0.50 * W, 0.47 * H), (0.50 * W, 0.53 * H)), ("jaw_under", (0.47 * W, 0.66 * H), (0.47 * W, 0.61 * H)), ("cranium_back", (0.62 * W, 0.30 * H), (0.60 * W, 0.28 * H)), ] for region, a, b in plan: zc.call("stamp", [zb_stroke.line(a, b, 6)], brush="Move", size=180, z_intensity=51, focal=0) mb.log("mass", region) rep = mb.report("mass") if rep["retouched"] or rep["over_budget"]: zc.call("morph_switch") # back to the stored shape zc.call("morph_store") # and replan with fewer, better moves open(f"{OUT}/move_budget.json", "w").write(mb.to_json()) ``` Next, clay block-in (his RK_BallStylus is a Gumroad brush; the stock stand-in is ClayBuildup at Z 20, Focal Shift -56 [added]), Sculptris Pro only after deleting levels (`zl.call("zb_ops", "del_levels")`). The Sculptris Pro toggle path is [verify] (Stroke > Sculptris Pro). Morph targets break when topology changes, so store again after any Sculptris Pro or DynaMesh step. ## C3. Separating line from a planned path (form-clue line, then trim) Source: single lines separate cheek from muzzle, the marionette line, the lip-fiber line, the malar band; a line around the nose wing is drawn, then trimmed (Kingslien 00:43:31, 00:59:02-00:59:34, 01:17:28). No pressure token is proven in V02 strings, so taper comes from segments. Test: `live_c02` (stroke types), scenario-zbrush-expert `live_04` (camera mapping outside the front view). ```python def separation_line(pixels, brush="ClayBuildup", size=(14, 6), z=(22, 8), zsub=True, trim=True): """pixels: canvas points picked on the render of the view where the line is most frontal.""" t = zl.run("result = zbc.get_transform()") obj = zl.call("zb_ops", "export_obj", f"{OUT}/sep_probe.obj", overwrite=True)["path"] m = zb_audit.load_obj(obj) cam = zb_stroke.Camera(t, zb_audit.audit(m)["center"]) # front view proven; others [verify live_04] on = [zb_stroke.front_most_vertex(cam, m.verts, p, 4) is not None for p in pixels[::4]] if not all(on): raise RuntimeError("part of the line misses the mesh: re-pick the pixels") pts = zb_stroke.resample(pixels, 6) segs = zc.taper_segments(pts, 3, size=size, z=z) cut = zc.call("stamp", [s[0] for s in segs], brush=brush, stroke_type="FreeHand", zsub=zsub, per_stroke=[(s[1], s[2]) for s in segs]) out = {"cut": cut} if trim: out["trim"] = zc.call("stamp", [pts], brush="TrimDynamic", size=size[0] * 1.5, z_intensity=z[1]) return out # maxilla line, both sides with X symmetry: from beside the nose wing toward the jaw separation_line(zb_stroke.polyline([(0.46 * W, 0.50 * H), (0.44 * W, 0.56 * H), (0.43 * W, 0.62 * H)], 4)) ``` Pressure lead for the lead's live_02 [verify]: the SDK's own `examples/_tests/_stroke_fomat.py` carries V03 strings whose first four points have `p` tokens rising C0, F0, FC, FF, then none, which looks like a per-point pressure byte ramping to full. Until that is proven, taper by segments as above. `front_most_vertex` loops in pure Python; subsample the check on meshes above a few hundred thousand points. Judge the result on a before copy (`zl.call("zb_ops", ...)` Duplicate through `zb_ops.resolve("duplicate")`, then `Tool:Geometry:X Position` to park it beside [macro]). ## C4. Masked move without the Gizmo Source: Kingslien's masked Move pull on the nose, widen the base, lift the bottom, tuck the nostrils (6wiDxO-ZADg 00:50:21, 01:35:40). Gizmo drags cannot be scripted; masks constrain Deformation sliders (masking doc). Test: `live_c02`. ```python # A. region polygroup + auto-mask: a Move drag started on the nose only moves the nose group zc.call("automask_polygroups", 100) zc.call("stamp", [[(0.50 * W, 0.47 * H), (0.50 * W, 0.465 * H)]], brush="Move", size=110, z_intensity=35) zc.call("automask_polygroups", 0) # B. feature mask + one Deformation slider (value = percent of the unit radius for Offset) zc.call("masked_deform", "Inflate", 3, mask="cavity", blur=1) # lifts what is not in a cavity # C. synthesized MaskLasso polygon, if live_c02 shows it masks without Ctrl [verify] zl.run(""" zbc.press("Brush:MaskLasso") poly = zb_stroke.polyline(__POLY__, 10, closed=True) zbc.canvas_stroke(zbc.Stroke(zb_stroke.encode(poly))) result = zb_ops.current_brush_title() """.replace("__POLY__", repr([(0.45 * W, 0.44 * H), (0.55 * W, 0.44 * H), (0.55 * W, 0.56 * H), (0.45 * W, 0.56 * H)])), modules=("zb_ops", "zb_stroke")) zl.call("zb_ops", "mask", "invert"); zl.call("zb_ops", "mask", "blur") zl.call("zb_ops", "deform", "Size", 6, axes=1) # widen along X only (x=1, y=2, z=4) zl.call("zb_ops", "mask", "clear") ``` Gate: volume change in the expected direction, then the sheet with a before copy. ## C5. Subdivision stack: choose the route first, then the restore point Source: J Hill splits 3 to 4 regular plus 3 to 4 HD levels, never 6 or 7 regular then 1 or 2 HD, because "you are not able to go below where you started" and smoothing needs lower levels; for games he stops regular work at 20 to 40M, "40 million is high" (HlHoIGE2Ocs 00:10:37-00:13:54). Costa keeps the regular top at 1 to 2M so five 8K maps export in 7.5 minutes, then 3 DivideHD (Lfen-BSwWcE 00:17:53-00:18:58). HD ceiling 1 billion (HD doc). The SDK-only branch and its tiled micro map follow Costa's own last layer (Lfen 01:10:58) and J Hill's real-time advice (00:11:10) [added as the agent default]. Smt off on the first levels for joints (Eaton Ale6SXXbJMM 00:32:36). Test: offline `test_stack_check_two_branches`; scenario-zbrush-expert `live_06` (Divide x4), `live_c01` (StoreMT). The route is decided before the first Divide, because a 6-level regular stack cannot take HD afterwards without Del Higher (which throws the upper levels away): - **SDK only** (no computer-use agent): regular levels to about 20 to 25M, no HD. The finest pore and scale grain goes into a tiling micro-displacement map in the renderer, on the handoff to scenario-zbrush-paint-render or scenario-maya-lookdev. - **Computer use available:** 3 to 4 regular levels ending at 1 to 2M, then 3 to 4 DivideHD. The DivideHD button is a palette press [verify path `CHAR_PATHS["divide_hd"]`]; entering a region (A over the mesh) is the computer-use step. ```python st = zl.call("zb_ops", "stats") COMPUTER_USE = False # the orchestrator says whether one is attached plan = zc.subdiv_plan(st["faces"], route="hd" if COMPUTER_USE else "regular", smt_off_levels=1) gate = zc.stack_check(plan, computer_use=COMPUTER_USE) if not gate["ok"]: raise RuntimeError(gate["problems"]) # replan (lighter base, other route) first for lv in plan["levels"][1:]: if lv["kind"] == "regular": r = zl.call("zb_ops", "divide", 1, smt=lv["smt"], timeout=1800) if not r["ratio"] or abs(r["ratio"] - 4) > 0.05: raise RuntimeError(f"Divide did not quadruple faces: {r}") else: # HD: the button is scriptable, the A key is not zl.run("zbc.press(zb_ops.resolve(__P__)); result = 1".replace("__P__", repr(zc.CHAR_PATHS["divide_hd"])), modules=("zb_ops",), timeout=1800) zc.call("morph_store") # J Hill: store before any detail pass zl.call("zb_ops", "save_ztl", f"{OUT}/creature_stack.ztl") json.dump({"plan": plan, "gate": gate}, open(f"{OUT}/stack_plan.json", "w"), indent=1) ``` On the HD route, the computer-use agent presses A over the region: store the morph target again on every entry, and expect no layers across entries (J Hill 00:13:54). Aim off the front axis with perspective off (Costa 00:26:05) or front-on slightly forward for a "butterfly" region (J Hill 00:12:49); hide the polygroups you do not need first (Costa 00:26:40). A mesh that already carries 6 or 7 regular levels stays on the SDK-only branch. ## C6. A layer per frequency, with a ledger Source: layers only at the top level, intensity 1 as sculpted (3D Layers doc); one layer per detail frequency (character digest). Names cannot be set from Python. Test: `live_c01`. ```python LEDGER = [] def new_layer(purpose): r = zc.call("layer_new") LEDGER.append({"order": len(LEDGER) + 1, "purpose": purpose, "points": r["stats"]["points"]}) json.dump(LEDGER, open(f"{OUT}/layers.json", "w"), indent=1) return r def layer_seen(tag): """Render the current layer at 1 and 0 [verify path]; fallback: the morph target compare.""" shots = {} for v in (1.0, 0.0, 1.0): zc.call("layer_intensity", v) shots[v] = zl.call("zb_review", "snapshot", f"{OUT}/{tag}_layer_{v:g}.png") return shots new_layer("L1 wrinkles") ``` If `layer_intensity` raises (no path in this build), store a morph target before the pass and compare with `morph_switch` renders instead (Heavy then dial back, C10). ## C7. Pore or scale bed with Surface Noise Source: pore scale first and globally, negative strength, Mix Basic Noise 0, preview then Apply; in HD the noise projects from the camera, fix seams and soft zones with the Morph brush (J Hill 00:14:31-00:16:42). Surface Noise doc: Quick 3D Edit on or nothing shows; open NoisePlug with Strength at 0.5 or -0.5 to see the preview, then lower it; vary scale and strength per region with a mask and Magnify by Mask / Strength by Mask; Snake Skin needs Scale Variability and "especially some Amplitude"; UV projection can show seams; SNorm 100 at high scale and strength. NoiseMaker 2.0 (2026.0) rotates, scales and offsets the alpha, so a scale tile can follow each region's flow (deltas 3.2). Test: `live_c03`. ```python new_layer("L3 pore bed (Surface Noise)") zc.call("morph_store") # for the Morph brush clean-up below sn = zc.call("surface_noise", True, timeout=30) # sets Quick 3D Edit; opens NoiseMaker [verify blocking] # NoiseMaker lives in a floating plugin window: a computer-use agent sets it ONCE (Strength 0.5 # while choosing the generator, then low and negative for pores; Alpha mode with the 16-bit tile; # Alpha Angle per region; Magnify/Strength by Mask 1 over a region mask), saves a .ZNM, and later # sessions Open that file [verify path]. Shipped presets: Lightbox/Noises/*.ZNM. zl.call("zb_ops", "set_material", "MatCap Gray") zl.call("zb_review", "snapshot", f"{OUT}/noise_preview.png") # judge scale for age and sex first ap = zc.call("surface_noise_apply", snorm=100, timeout=900) # clear lids and lips with Morph-brush strokes confined to their polygroups (J Hill): the region # comes from the mesh, not from canvas fractions obj = zl.call("zb_ops", "export_obj", f"{OUT}/noise_probe.obj", overwrite=True)["path"] t = zl.run("result = zbc.get_transform()") for g in ("lid_L", "lid_R", "lips"): # polygroup names from the retopo handoff reg = zc.region_on_canvas(obj, t, g) if not reg["aim_ok"]: continue # re-aim (C9) and redo this region xs = [p[0] for p in reg["polygon"]]; ys = [p[1] for p in reg["polygon"]] zz = zb_stroke.zigzag((min(xs), (min(ys) + max(ys)) / 2), (max(xs), (min(ys) + max(ys)) / 2), (max(ys) - min(ys)) / 2, 12) run = [q for q in zz if zc.point_in_polygon(q, reg["polygon"])] zc.call("stamp", [run], brush="Morph", size=30, z_intensity=100, automask_pg=100) ``` Gate: close-up under a raking light; no seams; pores gone from lids and lips; the depth histogram is spread (J Hill: not an even depth). Whether the Morph brush accepts synthesized strokes is [verify live_c02-style check]. Mask direction: Mask By Cavity and Mask AO protect the crevices, so a masked Apply or fill lands on the tops; Inverse first to work in the crevices. ## C8. Alpha factory (Henning Sanden) Source: plane with Smt off at about 1M polys, layer, directional flow, domes then Dam_Standard borders, smooth inside each scale only, GrabDoc at 1024 with perspective off and Actual, MidValue 50 after +50 / -50 dots, test on the model at about 11, 26 and 41 minutes ("very much wasted work if you ... don't test your alpha") (TuRIf92oMCY 00:02:11-00:28:23). The dots must be taller and deeper than anything in the alpha, typed values, not dragged (00:25:09-00:26:15). `recenter_alpha` is the dot-free substitute [added]. Test: `live_c03`; offline `test_dots_check`. ```python CREATURE_ZTL = f"{OUT}/creature_v012.ztl" # the latest versioned save of the creature TEST_REGION = "forearm_scales" # a polygroup where the tile will live def load_tool(path): # Tool:Load Tool by path [verify dialog-free] return zl.run(f"zbc.set_next_filename({path!r}); zbc.press('Tool:Load Tool'); " "result = not zbc.has_next_filename()") def test_on_model(tag): """Grab the tile as it stands, stamp a patch on the creature on a throwaway layer, look.""" tile = zl.call("zb_ops", "save_ztl", f"{OUT}/tile.ztl")["path"] g = zc.call("grab_alpha", f"{OUT}/alphas/test_{tag}.tif", midvalue=None) a = zc.recenter_alpha(g["file"], f"{OUT}/alphas/test_{tag}_mid.tif") load_tool(CREATURE_ZTL) zc.call("layer_new") stamp_region(a, TEST_REGION, spacing=36) # C9 shot = zl.call("zb_review", "snapshot", f"{OUT}/tile_test_{tag}.png") zl.run("zbc.press('Tool:Layers:Delete'); result = 1") # [macro] path load_tool(tile) return shot # read it: too soft? variation inside each scale? visible tiling? a border? pl = zl.run(""" zbc.press("Tool:Plane3D"); zbc.press("Tool:Make PolyMesh3D") if zbc.get("Transform:Edit") < 0.5: zbc.press(zb_ops.resolve("layer_clear")) w, h = zbc.get("Document:Width"), zbc.get("Document:Height") zbc.canvas_click(w * 0.5, h * 0.5, w * 0.5, h * 0.85) zbc.set("Transform:Edit", 1) zb_ops.set_checked("smt", 0, tol=0.5) while int(zbc.query_mesh3d(1)[0]) < 1000000: zbc.press("Tool:Geometry:Divide"); zbc.update(redraw_ui=True) result = zb_ops.stats() """, modules=("zb_ops",), timeout=1800) new_layer("tile") snap = zl.call("zb_review", "snapshot", f"{OUT}/tile_canvas.png") x0, y0, x1, y1 = zb_review.silhouette_bbox(snap["path"])["bbox"] # the plane on the canvas m = 0.12 * (x1 - x0) # detail off the border and dot corners spacing = (x1 - x0) / 14 centers = zc.jittered_lattice(x1 - x0 - 2 * m, y1 - y0 - 2 * m, spacing, angle_deg=45, jitter=0.2, origin=(x0 + m, y0 + m)) zc.call("stamp", zc.dragdot_stamps(centers), brush="ClayBuildup", stroke_type="DragDot", size=spacing * 0.9, z_intensity=25, timeout=600) test_on_model("t1_domes") # first test (about 11 min) borders = zc.scale_borders(centers, spacing) rng = random.Random(7) weights = [(rng.uniform(4, 8), rng.uniform(25, 45)) for _ in borders] # varied line weight zc.call("stamp", borders, brush="Dam_Standard", stroke_type="FreeHand", per_stroke=weights, timeout=900) zc.call("masked_deform", "Polish", 10, mask="cavity") # smooth inside scales only test_on_model("t2_borders") # second test (about 26 min) # ... refine volumes (Alt strokes for variation, Standard for specificity), then: test_on_model("t3_refined") # third test (about 41 min) # canvas: 1024 x 1024 [verify Document:Resize], redraw the plane, Transform:Fit, perspective off, Actual ``` Final grab, two routes: ```python # A. dot-free [added]: grab, then rescale so the flat border sits at 0.5 g = zc.call("grab_alpha", f"{OUT}/alphas/scales_v001.tif", midvalue=50) final = zc.recenter_alpha(g["file"], f"{OUT}/alphas/scales_v002.tif") # B. Henning's dots: mask everything but a small corner circle (MaskLasso polygon, C4 C [verify]), # Inverse, typed Inflate +50; same at -50 in the opposite corner; Clear; grab; check; paint out for corner, value in (((x0 + 0.05 * (x1 - x0), y0 + 0.05 * (y1 - y0)), 50), ((x1 - 0.05 * (x1 - x0), y1 - 0.05 * (y1 - y0)), -50)): ring = zb_stroke.arc(corner, 0.025 * (x1 - x0), 0, 360, 4) zl.run("zbc.press('Brush:MaskLasso'); zbc.canvas_stroke(zbc.Stroke(zb_stroke.encode(__P__))); result = 1" .replace("__P__", repr(ring)), modules=("zb_stroke",)) zl.call("zb_ops", "mask", "invert") zl.call("zb_ops", "deform", "Inflate", value) # set() types the value: no slider drag zl.call("zb_ops", "mask", "clear") g = zc.call("grab_alpha", f"{OUT}/alphas/scales_dots_v001.tif", midvalue=50) chk = zc.dots_check(g["file"]) # dots at the extremes, border at mid if not chk["ok"]: raise RuntimeError(chk["problems"]) # raise the dots, grab again final = zc.clean_corners(g["file"], f"{OUT}/alphas/scales_dots_v002.tif") print(zc.alpha_check(final)) # must be ok before C9 ``` Library: copy each finished alpha (never overwrite) into `zc.alpha_library_dir()`, the Asset Directory's `LightBox/Alphas` since 2026.1 (deltas 3.1), so LightBox lists it next session. Offline alternative when strokes are too costly: `zc.save_alpha16(zc.scale_height_tile(1024, 64), path)` (a procedural stand-in; Henning's rules still apply, including the tests on the model). ## C9. Alpha stamps on the creature, aimed at the region, then unify Source: DragRect with Focal Shift -100 (full tile) or 0 (faded), MidValue 50, on a fresh layer, flow along the anatomy, hand unify pass mandatory (Henning 00:14:15-00:15:21, 00:27:51-00:29:29; 00:00:34 flow). Orient the view to the region before any stamp, so screen space maps to the surface (realism digest P3 step 3; Kingslien's agent translation); the stamp region comes from the region's polygroup, not from canvas fractions [added method: `aim_at`, `region_on_canvas`]. A close-up hero (Starkie's Nidhogg) gets one stamp per scale on a non-overlapping layout. Test: `live_c02` (DragRect size), `live_c03` (alpha import), scenario-zbrush-expert `live_04` (camera convention); offline `test_region_on_canvas`, `test_aim_at`. ```python def aim_region(group, probe=f"{OUT}/aim_probe.obj"): """Turn the region toward the camera, centre it, and return its canvas polygon.""" obj = zl.call("zb_ops", "export_obj", probe, overwrite=True)["path"] reg = zc.region_on_canvas(obj, zl.run("result = zbc.get_transform()"), group) rx, ry, rz = zc.aim_at(reg["mean_normal"])["rotation"] zl.run(f"zbc.set_transform(x_rotate={rx}, y_rotate={ry}, z_rotate={rz}); result = 1") t = zl.run("result = zbc.get_transform()") # read back what ZBrush applied reg = zc.region_on_canvas(obj, t, group, canvas=(W, H)) dx, dy = reg["center_shift"] zl.run(f"zbc.set_transform(x_position={t[0] + dx}, y_position={t[1] + dy}); result = 1") t = zl.run("result = zbc.get_transform()") reg = zc.region_on_canvas(obj, t, group, canvas=(W, H)) if not reg["aim_ok"] or reg["on_canvas"] < 0.95: # [added] thresholds raise RuntimeError(f"{group}: facing {reg['facing_frac']}, on canvas {reg['on_canvas']}: " "split the polygroup or zoom out") return reg def stamp_region(alpha, group, spacing, flow_deg=0.0, size_frac=0.95, z=20, hero=False, seed=3): reg = aim_region(group) poly = reg["polygon"] xs = [p[0] for p in poly]; ys = [p[1] for p in poly] o, w, h = (min(xs), min(ys)), max(xs) - min(xs), max(ys) - min(ys) if hero: # Starkie: one scale each, no overlap centers = zc.poisson_disk(w, h, spacing, origin=o, inside=poly, seed=seed) else: # coverage along the flow (Henning) centers = zc.jittered_lattice(w, h, spacing, angle_deg=flow_deg, origin=o, inside=poly, seed=seed) strokes = zc.dragrect_stamps(centers, spacing * size_frac, base_angle_deg=flow_deg, alternate=False, angle_jitter_deg=8) r = zc.call("stamp", strokes, brush="Standard", stroke_type="DragRect", alpha=alpha, focal=-100, z_intensity=z, automask_pg=100, timeout=900) zc.call("automask_polygroups", 0) zl.call("zb_review", "snapshot", f"{OUT}/stamp_{group}.png") # check one stamp landed where planned return r new_layer("L2 scales (alpha)") for group, flow in (("forearm_scales", 60), ("brow_scales", 10)): # flow angle read off the anatomy stamp_region(final, group, spacing=36, flow_deg=flow) new_layer("L1 hero scales") stamp_region(final, "orbit_rim", spacing=28, hero=True) ``` Flow angles are in canvas degrees after aiming: read them off the review render of the aimed view (the muscle underneath), and split a region whose flow turns by more than about 30 degrees into two polygroups [added]. Then unify on the hero areas (Henning reserves hand sculpting for them, 00:01:38) with short Standard strokes, because ClayBuildup erases the stamped detail beneath it (FlippedNormals TpS0QdlfHWU 00:07:44), and C11's mid-frequency lift. ## C10. Pores, clogged pores, wrinkles, spray breakup (Costa, J Hill) Source: pores DragRect Focal Shift -100, one size per zone, alternate drag direction (Costa Lfen-BSwWcE 00:38:27); clogged pores as bumps with DragDot (J Hill 00:22:50); wrinkles connect pores in short segments, never one long line, perpendicular to the circular muscles, Standard + Alpha 39 with a sharp curve (Costa 00:45:41-00:47:26); fine wrinkles follow the compression, circular around the eyes, Standard, Spray, Alpha 60, Z 3 (J Hill 00:17:38-00:18:10, frame 00:08:53); Spray loops then Inflat 1 to 2 (Costa 00:42:21-00:44:37); heavy then dial back (J Hill 00:19:16). Test: `live_c02`. Direction rule, general form [added reading of Costa and J Hill]: skin buckles into lines that lie across the direction it is compressed, which is the direction the muscle fibers pull. Forehead lines are horizontal because the frontalis pulls vertically; crow's feet radiate because the orbicularis closes in circles. On a creature the same rule sets the throat folds (across the neck's bend), the jaw-hinge folds (wrapping the hinge) and the elbow and knee rings. Big wrinkles are sketched by hand first, and alphas only break them into smaller ones (Starkie, "Drake"). ```python PORE_ALPHA = "/abs/alphas/pore_v001.tif" # a small round, sharp alpha picked by looking at it [added] ZONES = { # polygroup -> (pore spacing px, stamp px, direction field from the region) "cheek": (9, 7, lambda r: zc.radial_field(MOUTH_PX)), # perpendicular to the orbicularis oris "nose": (14, 5, lambda r: zc.constant_field(0.0)), # tip-to-bridge lines are horizontal "forehead": (10, 6, lambda r: zc.constant_field(0.0)), # frontalis pulls vertically "throat": (12, 7, lambda r: zc.constant_field(NECK_AXIS_DEG + 90.0)), # creature: across the bend } new_layer("L4 pores and wrinkles") zc.call("morph_store") # heavy then dial back (J Hill) for group, (spacing, px, field_of) in ZONES.items(): reg = aim_region(group) # C9: aimed, centered, polygon from the mesh poly = reg["polygon"] xs = [p[0] for p in poly]; ys = [p[1] for p in poly] pts = zc.poisson_disk(max(xs) - min(xs), max(ys) - min(ys), spacing, origin=(min(xs), min(ys)), inside=poly) zc.call("stamp", zc.dragrect_stamps(pts, px), brush="Standard", stroke_type="DragRect", alpha=PORE_ALPHA, focal=-100, z_intensity=18, zsub=True, timeout=900) wr = zc.connect_the_dots(pts, field_of(reg), max_link=spacing * 1.6, max_turn_deg=30) zc.call("stamp", wr["segments"], brush="Standard", stroke_type="FreeHand", alpha=39, size=5, z_intensity=18, zsub=True, timeout=900) if group == "nose": # clogged pores are bumps (J Hill) zc.call("stamp", zc.dragdot_stamps(pts[::5]), brush="Standard", stroke_type="DragDot", size=6, z_intensity=12) loops = [zc.spray_loop(c, 18, turns=2, seed=i) for i, c in enumerate(pts[::40])] zc.call("stamp", loops, brush="Standard", stroke_type="Spray", alpha=60, size=10, z_intensity=3) ``` `MOUTH_PX` is the mouth center on the aimed canvas (project the mouth landmark or the lips polygroup center with `region_on_canvas(...)["center_px"]` in the same view); `NECK_AXIS_DEG` is the neck's long axis on that canvas. Where the wrinkles came out too strong, paint them back with Morph-brush strokes (the morph target holds the pre-wrinkle state). Over this finished layer, further passes use Standard or Elastic (Costa's cloned Elastic on Spray with Alpha 16 for direction, j5XLtLMN0P8 00:52:22), never ClayBuildup, which erases what is under it. The alpha numbers are default alphas (`ZData/Alphas/Alpha 0NN.PSD`); Costa's and J Hill's custom brushes do not ship. ## C11. Breakup without strokes and the mid-frequency lift Source: Mask By Cavity then a light Inflate pulls bony detail (Lazov AQsmWcXLxk8 00:25:21); mask groups of scales, blur, invert, blur, Inflate some scales out (Henning 00:30:02); beat up the skin at several sizes (J Hill 00:18:10). Test: `live_c02`. ```python zc.call("masked_deform", "Inflate", 2, mask="cavity", blur=1) # Lazov patches = zc.poisson_disk(0.3 * W, 0.3 * H, 60, origin=(0.35 * W, 0.30 * H)) zc.call("stamp", zc.dragdot_stamps(patches[::2]), brush="Inflat", stroke_type="DragDot", size=70, z_intensity=6, automask_pg=100) # lift some scale groups beat = [] for s, zi in ((60, 4), (25, 6), (10, 8)): # three sizes, push and pull beat += [(zb_stroke.line(p, (p[0] + 6, p[1] + 3), 3), s, zi) for p in zc.poisson_disk(0.3 * W, 0.3 * H, s, origin=(0.35 * W, 0.30 * H), seed=s)] zc.call("stamp", [b[0] for b in beat], brush="Standard", per_stroke=[(b[1], b[2]) for b in beat]) ``` ## C12. Asymmetry pass, big forms first Source: FlippedNormals break symmetry already in the mid-frequency pass (G2o6fdoACIQ 00:06:19); J Hill's last 25 percent moves big forms: nose tip off the Z axis, one ear, the septum, a lip corner, one eye's scale (HlHoIGE2Ocs 00:29:42-00:30:47); asymmetry is "the only way" to likeness (Costa j5XLtLMN0P8 00:35:03); creatures: Store MT, push and pull freely, restore parts with the Morph brush, symmetry off (Lazov AQsmWcXLxk8 00:56:58), "asymmetry is the best thing for a creature" (00:58:04). Magnitudes are [added]. Run it twice: once on the big forms at the end of stage 4 (mid-frequency), once as the last-25-percent pass; scars, broken scales and chipped horns come after, on top. Test: `live_c04` (`mirror_deviation`), `live_c01` (StoreMT); offline `test_asymmetry_report_wants_big_forms`. Big forms by subject, as polygroups with a box in model units (from landmarks or the polygroup's bbox): - human head: nose tip and septum, one ear, one lip corner, one eye (scale), one brow; - creature head: skull mass on one side, one orbit rim, the jaw corner, one nostril, one horn's angle or length, one cheek plate; - figure: the weight-bearing side of the pelvis and ribcage, one shoulder (a posed figure is asymmetric from the start, Eaton). ```python obj0 = zl.call("zb_ops", "export_obj", f"{OUT}/asym_before.obj", overwrite=True)["path"] REGIONS = {"orbit_rim_L": {"box": ((0.15, 0.25, 0.2), (0.6, 0.6, 0.9)), "kind": "big"}, "jaw_corner_R": {"box": ((-0.9, -0.5, -0.4), (-0.4, 0.0, 0.3)), "kind": "big"}, "nostril_L": {"box": ((0.02, 0.0, 0.9), (0.15, 0.12, 1.2)), "kind": "big"}, "scar": {"box": ((0.3, 0.3, 0.6), (0.5, 0.45, 0.8)), "kind": "detail"}} zc.call("morph_store") # restore point; Morph brush restores parts zl.call("zb_ops", "set_symmetry", False) zc.call("automask_polygroups", 100) # a Move drag moves only the group it starts on for group, (dx, dy) in (("orbit_rim_L", (0.0, -6.0)), ("jaw_corner_R", (5.0, 3.0)), ("nostril_L", (3.0, 0.0))): # pixels on the aimed canvas [added] reg = aim_region(group) # C9 c = reg["center_px"] zc.call("stamp", [[tuple(c), (c[0] + dx, c[1] + dy)]], brush="Move", size=90, z_intensity=30) zc.call("automask_polygroups", 0) # one eye 1 to 2 percent larger: select the eye SubTool, Deformation Size (100 doubles the size) zl.run("zbc.select_subtool(__EYE__); result = zb_ops.deform('Size', 1.5)".replace("__EYE__", "2"), modules=("zb_ops",)) obj1 = zl.call("zb_ops", "export_obj", f"{OUT}/asym_after.obj", overwrite=True)["path"] rep = zc.asymmetry_report(obj0, obj1, REGIONS) # at least 2 big-form regions broken if not rep["ok"]: print(rep["problems"]) # move more big forms, not more scars ``` The review sheet is the judge: the face or creature must still read as the same design, one side subtly different, and a likeness closer to the reference when there is one. Restore anything that went too far with Morph-brush strokes against the stored target (Lazov). ## C13. Cloth folds planned from tension points (Grassetti) Source: tension points marked first; Standard (Z 25) pushes drop folds in, Clay (Z 80) builds ridges and compression rolls, a big Move sags the edge; folds end in folds, are heavier near the tension, and get split after a Divide (gNx4v0WVVHo 00:00:31-00:08:47). Paths from `drop_folds` and friends [added geometry]. Test: `live_c02` (strokes), offline fold tests. ```python snap = zl.call("zb_review", "snapshot", f"{OUT}/cloth_canvas.png") # plane at SDiv 2 to 4, front view x0, y0, x1, y1 = zb_review.silhouette_bbox(snap["path"])["bbox"] pin = ((x0 + x1) / 2, y0 + 10) folds = zc.drop_folds(pin, count=6, length=0.8 * (y1 - y0), spread_deg=50, seed=2) folds, free = zc.snap_free_ends(folds, border=(x0, y0, x1, y1), reach=0.2 * (x1 - x0)) assert not free, free ev = zc.evenness([zb_stroke.path_length(f["path"]) for f in folds]) assert ev and ev > 0.05, ev # never even (Grassetti); 0.05 is [added] for f in folds: segs = zc.taper_segments(f["path"], 3, size=(90, 40), z=(25, 12)) # heavier near the pin zc.call("stamp", [s[0] for s in segs], brush="Standard", stroke_type="FreeHand", zsub=True, per_stroke=[(s[1], s[2]) for s in segs]) x = zc.x_folds(((x0 + x1) / 2, (y0 + y1) * 0.6), 0.5 * (x1 - x0), 0.2 * (y1 - y0)) zc.call("stamp", [f["path"] for f in x], brush="Clay", stroke_type="FreeHand", size=65, z_intensity=80) zl.call("zb_ops", "divide", 1) # then smaller, split folds ``` Measure on an OBJ height field if needed; judge under a raking light, from every side. ## C14. Dynamics drape, then a sculpt pass Source: collision volume of visible SubTools, Recalc after changes, iterations versus gravity for stretch, Firmness for fabric weight, Collision Inflate for the gap (Pavlovich m5O_sBag_iA, F7bcjQAK0Wc, nqoCyOME8Jo; Dynamics doc). Test: `live_c05`. ```python subs = zl.call("zb_ops", "subtools") solid = {} for s in subs[1:]: # colliders: every visible SubTool but the cloth solid[s["index"]] = zl.run(f"zbc.select_subtool({s['index']}); result = zbc.is_polymesh3d_solid()") zl.run("zbc.select_subtool(0); result = 0") # the cloth zc.call("dynamics_config", gravity_strength=1.5, iterations=100, firmness=2, self_collision=1, resolution=1024, collision_inflate=0.3) zc.call("collision_volume") zc.call("morph_store") # surface-area reference (Dynamics doc tip) sim = zc.call("run_simulation", timeout=60) # stop method [verify live_c05] if abs(sim["area_change_pct"]) > 5: # [added] tolerance print("stretch: raise iterations or lower gravity, rerun from the morph target") ``` Then sculpt on a layer with C13 strokes: simulation "won't get you a hundred percent there" (Grassetti 00:12:06). Open colliders: close holes on a duplicate (`CHAR_PATHS["close_holes"]`). ## C15. Garment from the body: Mesh Extract Source: mask or hide the region, Extract, Accept, clear the mask on the source; Thick 0.01 cloth, 0.03 leather (Extract docs). Test: `live_c05`. ```python # mask the garment region: MaskLasso polygon (C4 C, [verify]) or a feature mask zc.call("extract_shell", 0.01) # new SubTool zl.call("zb_ops", "mask", "clear") # the doc: clear the mask on the body ``` ## C16. FiberMesh fur Source: groom or settings, not both; Max Fibers 11 and Segments 6 for creature fur; Fast Preview with PRE Vis 6 to 8; white fibers and a contrasting fill to see gaps (Pablo lgTA_ebLGDw 00:05:47-00:10:04); mask intensity drives density and length; Profile 1 (FiberMesh doc). Test: `live_c05`. ```python zc.call("mask_by", "cavity", blur=50) # or a polygroup region mask; blur softens edges fm = zc.call("fibermesh_grow", preset="/Applications/Maxon ZBrush 2026/Lightbox/FiberMeshes/Fibers5.ZFP", max_fibers=11, segments=6, accept=True, pre_vis=7, timeout=120) zl.call("zb_ops", "mask", "clear") ``` Grooming is stroke work with Groom brushes (GroomHairToss and others ship); the programmatic substitute is a simulation with the roots masked by fibers (`mask_by("fibers")`, then C14 with Self Collision 1). Keep topology unchanged until grooming is done: a Divide converts the FiberMesh to a polymesh. ## C17. Horns, spikes and tusks: curves, straight detail, staged cracks Source: curve IMM with Stretch, curve Resolution 10, Size falloff flipped, Imbed 0 (Pablo TN9ARiC_82w 00:13:24-00:16:43); tube route: detail a straight tube with radial symmetry, odd counts layered 8, 5, 3 ("odd numbers in this case work a lot better"), keep a straight master and duplicate it per variant, decimate to 20 %, then Bend Curve and Taper (00:20:46-00:28:42); cracks on tusks and spikes in stages, main cracks first, small ones last, Clay brushes for a bone and rock feel with hard broken edges (Starkie, "Drake"). Decimation runs on a duplicate, never the master (Decimation doc; `rx.decimate_copy`). Test: `live_c06`; offline `test_crack_tree_stages_large_to_small`. ```python # curve route horn = zc.horn_path((0.35, 0.85, 0.05), (0.3, 1.0, -0.2), 1.1, curl_deg=110, twist_deg=30, radius=(0.14, 0.02), samples=8) r = zc.call("push_curves", [horn["points"], zc.mirror_points(horn["points"])], brush="CurveTube", delete_after=True) # tube route, after radial detail on the STRAIGHT tube (Transform radial on Y, counts 8, 5, 3) reg = aim_region("horn_L") # C9: the tube's side toward the camera x0 = min(p[0] for p in reg["polygon"]); x1 = max(p[0] for p in reg["polygon"]) cy = reg["center_px"][1] cracks = zc.crack_tree((x0 + 0.1 * (x1 - x0), cy), (1, 0), 0.6 * (x1 - x0), levels=3, seed=4) for lev in range(3): # largest first, review between levels paths = [c["path"] for c in cracks if c["level"] == lev] size = 14 * next(c["size_frac"] for c in cracks if c["level"] == lev) # Draw Size shrinks per level zc.call("stamp", paths, brush="Dam_Standard", stroke_type="FreeHand", size=size, z_intensity=25, zsub=True, automask_pg=100) zl.call("zb_review", "snapshot", f"{OUT}/cracks_level{lev}.png") zc.call("automask_polygroups", 0) zl.call("zb_ops", "save_ztl", f"{OUT}/horn_straight_master.ztl") # the straight master d = rx.zcall("decimate_copy", percent=20, timeout=900) # a copy, never the master zl.call("zb_ops", "deform", "Twist", 20, axes=2) # then bend, taper, twist the copy ``` Taper along a curve tube comes from the brush's Curve Modifiers Size curve, which has no SDK setter: save a tapered curve brush once (Brush > Save As) and select it by name. Decimation Master is a ZScript plugin: a scripted Pre-process ended the calling ZScript in a forum report, so run the copy's decimation as its own bridge call with a timeout [verify]. Orb Cracks do not ship; Dam_Standard, Slash3 and TrimDynamic stand in, selected through `select_brush` (`brush_check` lists what resolves). ## C18. Landmark markers, eyes and world scale (Costa) Source: markers for measurement (A4S note [added]); head 229 mm, eyeball 24 mm, IPD 63 mm, eyes 3 to 10 degrees outward (Costa j5XLtLMN0P8 00:38:12). The insertion sequence is the shipped Append Eyes macro. Test: `live_c04`. ```python H_units = 2.0 # measured head height in model units mm = H_units / zc.COSTA_MM["head_height"] idx = [] for sx in (-1, 1): r = zc.call("insert_sphere", sx * 31.5 * mm, 0.10, 0.62, 24 * mm) # XYZ Size semantics [verify live_c04] idx.append(r["active"]) eyes = [] for i in idx: zl.run(f"zbc.select_subtool({i}); result = zbc.get_active_subtool_index()") obj = zl.call("zb_ops", "export_obj", f"{OUT}/eye_{i}.obj", overwrite=True)["path"] eyes.append(zc.marker_centroids(obj)[0]) print(zc.eye_report(eyes[0]["center"], eyes[1]["center"], 24 * mm, H_units)) ``` Rotating a plain sphere outward shows nothing; the divergence check matters once the eye has a modeled cornea or iris (or a painted one, scenario-zbrush-paint-render). ## C19. Reprojection with danger zones protected, then the level walk Source: FlippedNormals (Zp07GW3rND0): at the highest level, Store MT, then a new Layer, then Project All, "both safety nets" before projecting (00:02:13, 00:02:47); PA Blur lowered; parts that sit close together grab the wrong surface (00:01:41), so keep a polygroup on the inside of the eye, grow it just past the lid, mask by it, then project (00:05:31-00:06:03); step through every level and toggle the layer (00:03:49); armpits, between the fingers, butt and crotch are the usual failures, and their artifacts become hard lines in displacement maps (00:07:10-00:07:43); repair with the Morph brush or a masked local reprojection, never by smoothing (00:06:36, 00:09:27); stop the concept sculpt before micro detail when retopology is coming (00:08:22). Generalized [added]: the danger zones are wherever two surfaces sit close, so a creature adds the gum line, lid folds over the eye, nostril interiors, horn bases, frills and toe webbing. The toolkit owns the projection: `zb_ops.project_all(checkpoint=..., store_mt=True, layer=True, exclusive=...)` saves a versioned ZTL first, stores the morph target and a new layer at the top level, sets Dist 0.1, and gates the result (point count, volume, a target reaching outside the visible bbox = spikes), with `zb_ops.morph_repair(points)` to paint busted areas back; `rx.project_stack` rebuilds a stack level by level. This procedure adds what is character-specific: the interior masks before, and a per-polygroup spike walk after, because a lid folded into the socket or a lip fused to a tooth stays inside the bbox and passes the lead's gate. Test: offline `test_spike_report`; the projection itself is tested by scenario-zbrush-expert (`live_06`) and scenario-zbrush-retopology-export. ```python DANGER = ("eye_socket_L", "eye_socket_R", "mouth_bag", "nostril_L", "nostril_R") # polygroups zc.call("to_top_level") before = zl.call("zb_ops", "export_obj", f"{OUT}/proj_before.obj", overwrite=True)["path"] # 1. danger zones masked. SDK route [verify live_c02 MaskLasso]: each interior polygroup's canvas # polygon, grown past the lid; computer-use route: Ctrl+Shift click the group, Tool > Visibility # > Grow once or twice, mask the visible part, ShowPt (FlippedNormals agent translation) for g in DANGER: reg = aim_region(g) # C9 cx, cy = reg["center_px"] grown = [(cx + 1.25 * (x - cx), cy + 1.25 * (y - cy)) for x, y in reg["polygon"]] # [added] margin zl.run("zbc.press('Brush:MaskLasso'); zbc.canvas_stroke(zbc.Stroke(zb_stroke.encode(__P__))); result = 1" .replace("__P__", repr(zb_stroke.polyline(grown, 6, closed=True))), modules=("zb_stroke",)) # 2. project: checkpoint, morph target and top-level layer inside; only source and target visible pr = zl.call("zb_ops", "project_all", checkpoint=f"{OUT}/before_projection.ztl", pa_blur=0, exclusive=True, timeout=1800) zl.call("zb_ops", "mask", "clear") if not pr["gate"]["ok"] or pr["gate"]["warnings"]: print(pr["gate"], pr.get("repair")) # 3. walk every level: spikes per polygroup, danger-zone close-ups top = zl.call("zb_ops", "stats")["sdiv_max"] walk = {} for lv in range(1, int(top) + 1): zl.run(f"zbc.set(zb_ops.resolve('sdiv'), {lv}); result = 1", modules=("zb_ops",)) obj = zl.call("zb_ops", "export_obj", f"{OUT}/proj_L{lv}.obj", overwrite=True)["path"] walk[lv] = zc.spike_report(obj, before=before if lv == top else None) for g in DANGER: aim_region(g) zl.call("zb_review", "snapshot", f"{OUT}/proj_L{lv}_{g}.png") json.dump(walk, open(f"{OUT}/projection_walk.json", "w"), indent=1) # 4. repair each flagged group at the top level: Morph brush back to the stored target zc.call("to_top_level") for g in {gr for w in walk.values() for gr in w.get("per_group", {})}: reg = aim_region(g) xs = [p[0] for p in reg["polygon"]]; ys = [p[1] for p in reg["polygon"]] zz = zb_stroke.zigzag((min(xs), (min(ys) + max(ys)) / 2), (max(xs), (min(ys) + max(ys)) / 2), (max(ys) - min(ys)) / 2, 10) run = [q for q in zz if zc.point_in_polygon(q, reg["polygon"])] zc.call("automask_polygroups", 100) zl.call("zb_ops", "morph_repair", run, size=30, z_intensity=100) zc.call("automask_polygroups", 0) ``` Above a few million points per level, OBJ exports get heavy: walk the lower levels, compare the top only against `before`, and read the top level's danger zones on the close-ups. The stroke-free alternative to `morph_repair` is a local reprojection: mask the rest, invert, and Project All again on that region only (FlippedNormals 00:09:27). Never smooth a busted region. Then a sculpting pass to recover what projection lost (00:08:22). Gate: `pr["gate"]` ok with no "safety net missing" warning; `spike_report` ok at every level in every danger-zone group; the close-ups clean; `rx.projection_verdict` within its bbox and volume tolerances. ## C20. Handoff checks: SSS survival, map request, UDIM round trip Source: carve deeper than feels right, SSS eats sharpness (FlippedNormals 0PaYUUvgwYM 00:19:49-00:20:22); only the sharp detail survives in the SSS highlights (J Hill 00:34:53); Adaptive identical across maps meant to combine (HD doc); maps compare the chosen level with the top (AskZBrush 2zDAtaQqwh8 00:01:44); MME film values (Costa Lfen-BSwWcE frames 00:20:31-00:23:31: 8192, Border 4, FlipV, SubDiv 2, Adaptive, DPSubPix 4, SmoothUV, Mid 0, 32-bit EXR, 3 Channels off, Scale 1) or the FlippedNormals Arnold preset (Mid 0.5 with Scalar Zero Value 0.5, Adaptive off, DpSubPix 0, UDIM file names with a dot, Merge Maps off with EXR, OBJ Grp off, -ThBTEc8L_M 00:04:43-00:21:09); the external-UV round trip (Costa 00:22:17-00:23:59). Test: offline `test_detail_survival`, `test_map_request_check`. ```python # 1. SSS survival: the same camera and crop, MatCap Gray against the Redshift SSS render # (Redshift runs inside ZBrush since 2023; scenario-zbrush-paint-render owns the render itself) sv = zc.detail_survival(f"{OUT}/final_matcap.png", f"{OUT}/final_sss.png", box=(600, 300, 1000, 700)) if not sv["ok"]: print("deepen the tertiary pass with Standard or Dam_Standard, then render again", sv) # 2. the map request for scenario-zbrush-retopology-export (it runs rx.mme_set / rx.mme_create_all) req = {"top_level": 6, "renderer_zero": 0.5, "merge_maps": False, "udim": True, "tile_format": "UDIM", "tile_dot": True, "displacement": {"subdiv": 1, "adaptive": False, "bits": 32, "mid": 0.5, "exr": True}, "normal": {"subdiv": 5, "adaptive": False}} # micro-only normal from the level below the top chk = zc.map_request_check(req) assert chk["ok"], chk["problems"] ``` UVs relaid outside ZBrush after sculpting (a UDIM layout from Maya): delete old morph targets, go to the export level, Store MT, Switch, `Tool:Import` the relaid mesh, no Divide ("otherwise you'll ruin everything"), export with Switch MT on (Costa). FlippedNormals found Switch MT did nothing in their build (00:07:09): check the displacement for leaked big forms on a test tile [verify on 2026]. The receiving side (Maya File node UV Tiling Mode UDIM (Mari), Filter off, Catmull-Clark about 3 iterations, Scalar Zero Value = Mid) is in `rx.renderer_plan("arnold")`. For Painter, Substance Bridge (2026.2, Texture palette) sends Low and High directly. -
sources.md 16.9 KB
# Sources of scenario-zbrush-character-creature Each entry: expert, credential, where, what it is best for, best timestamps. Per-source notes live in the project's `notes/anatomy/`, `notes/character/`, `notes/creature/` and `notes/hair-fibers/`. Stylized hair notes in `hair-fibers/` (Dan Eder) belong to scenario-zbrush-stylized and were not used here. ## Evidence produced on this install (highest weight) - **scenario-zbrush-expert bridge tests** v01 to v03 (2026-09-24, ZBrush 2026.2.1 build 53205). Best for: synthesized V02 strokes sculpt, dialog-free `Document:Export` and `Tool:Export`, silent `set()` on missing paths. - **Offline tests of this skill** (`tests/code/zbrush-character-creature/test_zb_character.py`, 54 tests, `offline_results.json`). Best for: the stroke plans, fold planner, canon checks, stack and map-request gates, crack planner, view aiming and region mapping, projection spikes, big-form asymmetry, calibration dots, SSS detail survival, OBJ and alpha tools, and the wrappers against a fake `zbrush.commands` through the real bridge server. Nothing has run in ZBrush yet. - **Local install listing** (`/Applications/Maxon ZBrush 2026`, read-only inspection 2026-09-24). - Present: - `ZData/BrushPresets` (Move, Standard, ClayBuildup, DamStandard, Inflat, Elastic, Morph, TrimAdaptive, TrimDynamic, Slash3, SnakeHook, Clay, MaskLasso, MaskPen, the cloth and groom brushes, CurveTube, CurveFlat, CurveFlatSnap, IMM Curve); - `Lightbox/Brushes/Smooth/SmoothDirectional.ZBP` and `Smooth Stronger.ZBP`; - `Lightbox/Brushes/Scales`; - `ZData/Alphas/Alpha 0NN.PSD` (including 006, 016, 039, 060); - `Lightbox/Alphas` (Scaly, Leathery and Bumpy Skin); - `Lightbox/Noises` (40 ZNM presets); - `Lightbox/FiberMeshes` (ZFP presets). - Absent: Orb brushes, Kingslien's RK_ brushes, Costa's Antro_ brushes. - **Shipped 2026.2.1 macros** (`ZData/Macros`, text in `sources/docs/automation__zscript-shipped-macros-2026.md`): - Enhance Details: `Tool:Layers:New`, `Delete`, `Duplicate`, `Bake All`, `Rename` through ZFileUtils `RenameSetNext`, `Tool:Morph Target:StoreMT` and `Switch`, Polish with `IModSet`. - Append Eyes: `Tool:SubTool:Insert`, `PopUp:Sphere3D`, `Tool:Geometry:X/Y/Z Position`, `XYZ Size`, `Material:ToyPlastic`, `Color:FillObject`. - Toggle Mask By Polygroups: `Brush:Auto Masking:Mask By Polygroups`. - Create Stencil From Subtool: `Alpha:Transfer:GrabDoc`, `Alpha:Modify:Intensity`. - **ZBrush Activity log** of the 05:20 session (`~/Library/Preferences/Maxon/ZBrush_03C27D49/Logs/Activity/`). Best for: startup values such as `Preferences:Mem:MaxPolyPerMesh` 100 and `Preferences:Draw:Dynamic Brush Scale` 1. - **Python SDK stub and examples** (`/Applications/Maxon ZBrush 2026/Documentation/python-api/`): - curves API (`new_curves`, `add_new_curve`, `add_curve_point`, `curves_to_ui`, `delete_curves`, example `add_curve_point.0.py` with `Brush:CurveStandard`); - `create_displacement_map` and `create_normal_map`; - `get_stroke_info` (pressure readable per point); - `press_key` marked "does not work"; - `pixol_pick`; - `examples/_tests/_stroke_fomat.py`, whose V03 strings carry `p` tokens (a pressure candidate [verify]). - **OBJ fixture** `tests/code/zbrush-expert/fixtures/v03_sphere.obj`. Best for: ZBrush writes one `g Group<id>` line per polygroup. - **`sources/zbrush-version-deltas.md`** (project). Best for: every 2026 fact used here (Asset Directory, NoiseMaker 2.0, FiberMesh Width Profile, Dynamics unchanged, Cmd+W). ## Faces and heads - **Ryan Kingslien**, first ZBrush product manager at Pixologic (2004 to 2008), founder of ZBrushWorkshops and Vertex School. "Common Mistakes When Sculpting The Face" (live talk, 2021-07-28, ZBrush 2021.6.2), https://www.youtube.com/watch?v=6wiDxO-ZADg. Best for: the face order of operations and the critique checklist. Timestamps: - move counting 00:20:29; - cranial versus facial mass 00:31:15; - Bridgeman masses 00:38:12; - maxilla 00:43:31; - nose 00:49:14; - nostril 00:54:07; - form-clue line 00:59:02; - eye 01:03:55; - brow 01:09:55; - mouth 01:12:20; - mouth corner 01:19:56; - lid 01:28:37; - nasolabial area 01:33:42; - "right or wrong back in the beginning" 01:37:20. - **Kris Costa** ("Antropus"), 14 years at ILM, creature modeling lead (Warcraft, Star Wars TFA, Kong), portrait instructor. - "Realistic Portraiture" (ZBrush Summit 2018), https://www.youtube.com/watch?v=Lfen-BSwWcE. Best for: the HD stack, Multi Map Exporter values, Switch MT, pore and wrinkle method. Timestamps: stack 00:17:53; MME 00:19:30-00:23:59; HD regions 00:25:04; from afar and upside down 00:28:50; pores 00:31:18-00:39:01; spray texture 00:42:21; wrinkles 00:45:09; albedo 00:50:16; micro-displacement map 01:10:58. - "Realism Idealized: Learning How to Observe" (Summit 2021), https://www.youtube.com/watch?v=j5XLtLMN0P8. Best for: the evaluation checklist, subtle ClayBuildup, age and ripple, eyes and measurements, skin by region, Elastic pass. Timestamps: form hierarchy 00:04:44; blur read 00:09:18; ClayBuildup 00:12:58; lights 00:18:46; eyes 00:28:36; asymmetry 00:35:03; measurements 00:38:12; skin by region 00:40:24; Elastic 00:51:50. - **Anatomy For Sculptors** (Uldis Zarins and team, authors of the Anatomy for Sculptors books), free blog articles on the head, skull, forehead, pelvis, shoulder, arm and hand, https://anatomy4sculptors.com/blog/ (local copy `sources/docs/character-creature__anatomy-for-sculptors-articles.md`). Best for: numeric canon (head thirds, hand ratios, landmarks, sex differences). ## Skin - **J Hill** (Jason Hill), lead character artist at Turtle Rock Studios, Bloodhound (Apex Legends) at Respawn, former student of Costa. "Sculpting SKIN DETAILS with ZBrush" (2021-10-17), https://www.youtube.com/watch?v=HlHoIGE2Ocs. Best for: regular plus HD split, the Surface Noise pore bed, heavy then dial back, region rules, lips, asymmetry, MME export. Timestamps: - stack 00:10:37-00:13:54; - noise bed 00:14:31; - fine wrinkles 00:17:38; - plastic 00:18:10; - Morph dial-back 00:19:16; - Elastic 00:20:21; - nose 00:22:16; - lips 00:24:10; - lower-level smoothing 00:27:57; - asymmetry 00:29:42; - export 00:32:43; - SSS highlights 00:34:53. ## Bodies - **Scott Eaton**, classically trained sculptor, character supervisor and lead modeler on features, anatomy instructor to ILM, Framestore, Valve, Sony, Ubisoft, DICE. "Dynamic Figure Sculpture" (ZBrush Summit 2015), https://www.youtube.com/watch?v=Ale6SXXbJMM. Best for: force chain, landmarks, scapula rotation, subdivision with Smt off, layers as proportion dials. Timestamps: active and relaxed 00:13:39; base and stack 00:32:01; morph and layer 00:33:33; landmarks 00:35:59; scapula 00:39:55; active muscles straight 00:54:07; Smooth Directional 01:09:27. ## Creatures - **Zachary Berger**, lead creature designer on Avatar: The Way of Water (Lightstorm), 2023 Concept Art Award. "The Creature Design Philosophy of Avatar: The Way of Water" (ZBrush Summit 2023), https://www.youtube.com/watch?v=PcuC8K-bz44. Best for: the design pyramid, reference blending, layers for variants and articulation, grayscale approvals, readability. Timestamps: pyramid 00:04:24; blend 00:12:33; metaphor 00:14:13; three versions 00:16:59; evolution layers 00:18:05; open and closed mouth 00:20:53; grayscale 00:37:47; underwater colors 00:41:07. - **Henning Sanden**, FlippedNormals co-founder, former creature modeler at Framestore, MPC and DNEG. "Create Killer Alphas From Scratch" (2025-06-02), https://www.youtube.com/watch?v=TuRIf92oMCY. Best for: the alpha factory, mid value 50 calibration, flow along anatomy, unify pass. Timestamps: flow 00:00:34; plane setup 00:02:11; generic tile 00:03:52; first test 00:14:15; photos lie 00:17:33; smoothing is blurring 00:20:47; calibration 00:25:09; stamping 00:27:51; unify 00:28:55; mid-frequency 00:30:02. - **Luke Starkie**, creature designer and sculptor (God of War: Ragnarok Nidhogg and Drake; ex-Rockstar; ex-MPC senior creature artist). Maxon interview "Sculpting Creatures for God of War: Ragnarok and More" (2023-11-09), https://www.maxon.net/en/article/sculpting-creatures-for-god-of-war-ragnarok-and-more (local copy `sources/docs/character-creature__maxon-starkie-creature-interview.md`). Best for: hand scales on a hero close-up, staged cracks, wrinkles from elephant and rhino. It gives no brush values: the low ClayBuildup intensity in numbers (Z 2 to 3) is Costa's. - **Marko Lazov**, creature artist at Mundfish. ZBrush Summit 2025 creature concept speed sculpt, https://www.youtube.com/watch?v=AQsmWcXLxk8. Best for: skull first, Mask by Cavity plus Inflate, detail zoning, asymmetry by Morph Target. Timestamps: skull 00:15:58; mask tricks 00:22:02-00:25:56; silhouette 00:28:36; zones 00:31:12; growths 00:41:03; asymmetry 00:56:58. - **Pablo Munoz Gomez**, ZBrushGuides and Pablander Academy, ZBrushLIVE host. "4 Ways to Create Horns in ZBrush" (2022-11-14), https://www.youtube.com/watch?v=TN9ARiC_82w. Best for: horn workflows with numbers (Array Mesh, curve IMM, Gizmo deformers, Spiral3D). Timestamps: decimation 00:05:14; curve IMM 00:13:24; radial tube 00:21:19; Bend Curve 00:26:13; Spiral3D 00:29:23. ## Cloth - **Rafael Grassetti**, art director on God of War (Santa Monica Studio). "How to sculpt cloth in 3D?" (2020-11-23), https://www.youtube.com/watch?v=gNx4v0WVVHo. Best for: fold grammar (tension, termination, unevenness, four types) and the reference breakdown. Timestamps: drop 00:00:31; termination 00:02:35; unevenness 00:03:08; X 00:04:26; sleeve 00:05:30; subdivide and split 00:08:14; reference 00:08:50; simulation limits 00:12:06. - **Michael Pavlovich**, Director of Character, Weapon and Vehicle Art at Certain Affinity, ZBrushLIVE host. ZBrush 2021 lessons, each best for the part named: - 001 Dynamic Cloth Overview, https://www.youtube.com/watch?v=m5O_sBag_iA: the solver model, iterations versus speed, Firmness. - 005 Gravity Strength and Simulation Iterations, https://www.youtube.com/watch?v=F7bcjQAK0Wc. - 016 Inflate, Deflate, Expand, Contract, https://www.youtube.com/watch?v=s2qVeTnx89M: masked Expand for hems. - 018 Cloth Brushes, https://www.youtube.com/watch?v=gyaMwGSsrb8: Trails, dimples. - 021 Collision Volumes, https://www.youtube.com/watch?v=nqoCyOME8Jo: cached colliders, resolution, gap. - 025 Hair FiberMesh Dynamic Simulation, https://www.youtube.com/watch?v=QCcDTuDT2HM: roots, collision, cards, strands. - 082 Curve Flat Brushes, https://www.youtube.com/watch?v=goZavCi515k: hair cards. ## Fur and hair - **Pablo Munoz Gomez**, "Working with FiberMesh for Hair and Fur: ZBrush Top Tips" (Maxon, 2020-11-14), https://www.youtube.com/watch?v=lgTA_ebLGDw. Best for: groom versus settings, visibility tricks, guides, clump polygroups. Timestamps: routes 00:01:24; white fibers 00:06:53; fur settings 00:08:28; guides 00:10:04; clumps 00:12:43. ## Maxon official documentation (2026 help site; local copies in `sources/docs/`) - **3D Layers and Morph Targets**, https://help.maxon.net/zbr/en-us/Content/html/user-guide/3d-modeling/sculpting/3d-layers/3d-layers.html (`character-creature__layers-morph-targets.md`). Best for: top-level rule, Record, intensity, baking, erasing. - **Surface Noise, NoiseMaker, Brush Noise, Alphas, Spotlight**, https://help.maxon.net/zbr/en-us/Content/html/user-guide/3d-modeling/sculpting/surface-noise/surface-noise.html (`character-creature__surface-noise-alphas-spotlight.md`). Best for: Quick 3D Edit, SNorm, mask mixing, 16-bit alpha rules, Tool > Surface buttons. - **HD Geometry, XTractor, Displacement Map**, https://help.maxon.net/zbr/en-us/Content/html/user-guide/3d-modeling/modeling-basics/subdivision-levels/hd-geometry/hd-geometry.html (`character-creature__hd-geometry-xtractor-displacement.md`). Best for: HD capacity and regions, XTractor, displacement settings. - **Cloth Simulation (Dynamics), cloth brushes, Mesh Extract**, https://help.maxon.net/zbr/en-us/Content/html/user-guide/3d-modeling/cloth-simulation/cloth-simulation.html (`paint-pose-render-print__dynamics-cloth.md`, `character-creature__cloth-dynamics-extract.md`). Best for: every Dynamics control, stop and resume, Max Simulation Points, Extract Thick. - **FiberMesh**, https://help.maxon.net/zbr/en-us/Content/html/user-guide/3d-modeling/fibermesh/fibermesh.html (`paint-pose-render-print__fibermesh.md`). Best for: Preview and Accept rules, conversion triggers, profiles, export. - **Masking and Polygroups** (`fundamentals-sculpting__masking-polygroups.md`). Best for: Mask By Cavity, Smoothness, PeaksAndValleys, AO and Fibers. - **Tool palette reference: Geometry, Deformation, SubTool** (`hard-surface__zbrush-ref-tool-palette-geometry-deformation-subtool.md`). Best for: Deformation slider semantics (Offset as percent of the unit radius, Size 100 doubles, axis letters), Extract, SmartReSym. - **Multi Map Exporter** (`topology-export__multi-map-exporter-maps.md`, owned by scenario-zbrush-retopology-export). Best for: Mid 0 for 32-bit maps. ## Projection, sharpness, brush order and map plumbing - **Henning Sanden and Morten Jaeger** (FlippedNormals founders; ex-MPC and ex-Framestore senior film character artists): - "Reprojecting Details in ZBrush: Top Production Tip" (2018-09-10), https://www.youtube.com/watch?v=Zp07GW3rND0. Best for: Store MT plus a top-level layer before Project All, danger zones, the eye-interior mask, the level walk, Morph-brush repair. Timestamps: close parts 00:01:41; order at the top 00:02:13; staged projection 00:03:17; walk the levels 00:03:49; eye mask 00:05:31; no smoothing 00:06:36; danger zones 00:07:10; hard lines in displacement 00:07:43; local reprojection 00:09:27. - "Top Tips for Sculpting in ZBrush" (2020-02-27), https://www.youtube.com/watch?v=G2o6fdoACIQ. Best for: frequency stages, symmetry broken at mid-frequency 00:06:19, silhouette stability 00:17:27. - "Learn to Sculpt Like a Pro in ZBrush" (2018-06-11), https://www.youtube.com/watch?v=0PaYUUvgwYM. Best for: carve deeper than feels right because SSS eats sharpness 00:19:49-00:20:22. - "The Only 6 Brushes You Ever Need in ZBrush" (Henning alone, 2020-12-17), https://www.youtube.com/watch?v=TpS0QdlfHWU. Best for: ClayBuildup erases what is under it, Standard enhances 00:03:55, 00:07:44. - "ZBrush to Arnold for Maya: 32-bit displacement UDIM" (2018-04-02), https://www.youtube.com/watch?v=-ThBTEc8L_M (owned by scenario-zbrush-retopology-export). Best for: Merge Maps off with EXR 00:04:43, UDIM names with a dot 00:06:36, Switch MT found broken 00:07:09, Adaptive off 00:08:12, OBJ Grp off 00:15:08, Maya UDIM (Mari) tiling 00:17:29. - **Joseph Drust** (Pixologic/Maxon), #AskZBrush "Is there a way to bake out the small details when creating a Normal/Displacement Map?" (2020-01-11), https://www.youtube.com/watch?v=2zDAtaQqwh8. Best for: maps compare the chosen level with the top 00:01:44; level max-1 gives micro detail only 00:02:53. - **Maxon docs, Decimation Master** (`sources/docs/topology-export__decimation-master.md`). Best for: decimate a duplicate; pre-process state; the scripted pre-process that ended a ZScript (forum report, [verify]). ## Game production - **Rakan Khamash**, senior character artist on Overwatch 2 at Blizzard (hero Illari), now at Epic Games. "Overwatch Character Creation Pipeline" (ZBrush Summit 2023), https://www.youtube.com/watch?v=FRZtVXpAokc. Best for: readability rules, 70/30, rest areas, rig-first topology, floaters, fold intent, wear physics, 16k budget. Timestamps: readability 00:02:16; shape language 00:06:07; topology 00:11:04; floaters 00:13:16; folds 00:19:55; blockout share 00:21:37; wear 00:25:34; face projection 00:41:27. ## Sister skills used for shape - scenario-blender-sculpting (Blender Expert Skills project): the layout of a domain skill with a design table and gated stages. ## Revision log - 2026-09-24, v1: first build (SKILL.md, five references, `zb_character.py`, 42 offline tests). Snapshot: `skills/_versions/zbrush-character-creature v1 2026-09-24/`. - 2026-09-24, v2 (refactor after the Z2 blind grade, `tests/grading/Z2_grade.md`): stack route chosen before the first Divide with `stack_check` (SDK only: regular to about 20 to 25M plus a tiled micro map; computer use: 3 to 4 regular plus 3 to 4 HD; never HD over 6 or 7 regular levels); reprojection safety and danger zones (C19, `spike_report`, FlippedNormals Zp07GW3rND0); alpha tests on the model and calibration dots (C8, `dots_check`); big-form asymmetry (C12, `asymmetry_report`); Surface Noise guards (C7); SSS survival and brush order (`detail_survival`, critique); map consistency and UDIM plumbing (C20, `map_request_check`); decimate a copy; wrinkle direction across the compression and staged cracks (C10, C17, `crack_tree`); the 2026 Dam_Standard name (`brush_check`, live_c01); Redshift, Asset Directory (`alpha_library_dir`) and Substance Bridge; aimed, mesh-derived stamp regions instead of canvas fractions (C9, `aim_at`, `region_on_canvas`). Fixed: the Starkie attribution (he gives no numbers; Z 2 to 3 is Costa's) and the HD numbers (HD total = regular top x 4 per HD level; 3 DivideHD over 16M hits the 1 billion ceiling). 54 offline tests pass.
-
-
scripts
-
zb_character.py 109.9 KB
""" zb_character: toolkit of the scenario-zbrush-character-creature skill (realistic heads, figures, creatures, skin and scale detail, cloth folds, FiberMesh fur, horns). It builds on the scenario-zbrush-expert toolkit and never re-implements it: strokes are encoded with zb_stroke.encode, palette items are resolved and read back with zb_ops.resolve / set_checked / press / stats, meshes are read with zb_audit.load_obj, views come from zb_stroke.Camera. Three layers in one file: 1. Pure Python, both sides (no numpy): stroke plans that feed zb_stroke.encode. jittered_lattice, poisson_disk, scale_borders, dragrect_stamps, dragdot_stamps, spray_loop, constant_field / radial_field / tangential_field, connect_the_dots, taper_segments, fold planner (fold_type_for, drop_folds, diaper_folds, x_folds, y_fold, snap_free_ends, free_endpoints, evenness), horn_path, mirror_points, crack_tree, MoveBudget, subdiv_plan, stack_check, map_request_check, head_canon, head_ratio_check, eye_report, split_ratio, scapula_check, view_facing, aim_at. 2. Inside ZBrush (run through `call()`): checked wrappers for layers, morph targets, masks by cavity / smoothness / peaks / AO / fibers, masked deformation, stroke types, alphas, auto-masking by polygroups, stamping, Surface Noise, GrabDoc alphas, Dynamics, Mesh Extract, FiberMesh, curves (horns, cards), eye spheres, brush_check. Every item path comes from a candidate list checked with exists(), because set() on a missing path is silent. 3. Agent side (numpy, PIL): OBJ measurements (load_groups, marker_centroids, front_profile, front_section, profile_landmarks, fits, face_report, mirror_deviation, asymmetry_report, region_on_canvas, spike_report), alpha files (alpha_check, dots_check, recenter_alpha, clean_corners, scale_height_tile, save_alpha16, alpha_library_dir), review images (thumbnail, upside_down, blurred, detail_survival). Evidence tags in comments: [macro] used by a shipped 2026.2.1 macro, [doc] Maxon docs, [bridge] proven through the bridge (tests/code/zbrush-expert v01-v03), [verify] not confirmed. NOTHING in layer 2 has run in ZBrush yet: see tests/code/zbrush-character-creature/live_c*. Numbers attributed to experts carry their source; every threshold marked [added] is this toolkit's own default and must be calibrated (see the skill's references/critique.md). Calling a ZBrush-side function from the agent: import sys; sys.path.insert(0, "<skills>/scenario-zbrush-character-creature/scripts") import zb_character as zc zc.call("layer_new") # through zb_launch.run, main thread """ __version__ = "0.1" # ZBrush Expert Skills v0.1 (2026-09-24) import glob import importlib import json import math import os import random import sys HERE = os.path.dirname(os.path.abspath(__file__)) EXPERT_SCRIPTS = os.path.abspath(os.path.join(HERE, "..", "..", "scenario-zbrush-expert", "scripts")) INSTALL = "/Applications/Maxon ZBrush 2026" def _import_expert(name): """Import a scenario-zbrush-expert toolkit module without leaving its folder on sys.path (the shared-interpreter rule of the Maxon SDK style guide, as in zb_launch's prelude).""" try: return importlib.import_module(name) except ImportError: sys.path.insert(0, EXPERT_SCRIPTS) try: return importlib.import_module(name) finally: sys.path.remove(EXPERT_SCRIPTS) zb_stroke = _import_expert("zb_stroke") zb_ops = _import_expert("zb_ops") class ZBCharError(RuntimeError): pass # ============================================================================================ # 1. Pure Python plans (both sides) # ============================================================================================ def _rot2(v, deg): a = math.radians(deg) c, s = math.cos(a), math.sin(a) return (v[0] * c - v[1] * s, v[0] * s + v[1] * c) def _unit2(v): n = math.hypot(v[0], v[1]) return (v[0] / n, v[1] / n) if n > 1e-12 else (1.0, 0.0) def point_in_polygon(p, poly): """Even-odd rule; poly is a list of (x, y).""" x, y = p inside = False n = len(poly) for i in range(n): x1, y1 = poly[i] x2, y2 = poly[(i + 1) % n] if (y1 > y) != (y2 > y): xc = x1 + (y - y1) * (x2 - x1) / (y2 - y1) if xc > x: inside = not inside return inside def _inside_fn(inside): if inside is None: return lambda p: True if callable(inside): return inside return lambda p: point_in_polygon(p, inside) def jittered_lattice(width, height, spacing, angle_deg=45.0, jitter=0.2, seed=0, origin=(0.0, 0.0), stagger=True, inside=None): """Scale or pore centres on a rotated, staggered lattice, jittered by jitter x spacing. Henning Sanden (TuRIf92oMCY 00:03:20-00:04:25): a scale tile has a clear directionality (diagonal, crisscrossing flow) and fairly uniform sizes so it stays generic and does not tile visibly. angle_deg is the flow direction in canvas or tile space; `inside` is an optional polygon or predicate. Returns (x, y) points. """ rng = random.Random(seed) row = spacing * (math.sqrt(3.0) / 2.0 if stagger else 1.0) reach = math.hypot(width, height) cx, cy = origin[0] + width / 2.0, origin[1] + height / 2.0 keep = _inside_fn(inside) out = [] nr = int(reach / row) + 2 nc = int(reach / spacing) + 2 for r in range(-nr, nr + 1): off = spacing / 2.0 if (stagger and r % 2) else 0.0 for c in range(-nc, nc + 1): u, v = c * spacing + off, r * row x, y = _rot2((u, v), angle_deg) x += cx + rng.uniform(-jitter, jitter) * spacing y += cy + rng.uniform(-jitter, jitter) * spacing if origin[0] <= x <= origin[0] + width and origin[1] <= y <= origin[1] + height \ and keep((x, y)): out.append((x, y)) return out def poisson_disk(width, height, radius, seed=0, k=30, origin=(0.0, 0.0), inside=None, limit=200000): """Bridson Poisson-disk samples at least `radius` apart inside the rectangle (and the optional polygon or predicate). Used for pore and blemish layouts with one radius band per zone (Costa Lfen-BSwWcE 00:39:01: keep one stamp size per zone) [added method].""" rng = random.Random(seed) keep = _inside_fn(inside) cell = radius / math.sqrt(2.0) gw, gh = int(width / cell) + 1, int(height / cell) + 1 grid = {} pts, active = [], [] def fits(p): gx, gy = int((p[0] - origin[0]) / cell), int((p[1] - origin[1]) / cell) for i in range(max(0, gx - 2), min(gw, gx + 3)): for j in range(max(0, gy - 2), min(gh, gy + 3)): q = grid.get((i, j)) if q is not None and math.dist(p, q) < radius: return False return True def add(p): grid[(int((p[0] - origin[0]) / cell), int((p[1] - origin[1]) / cell))] = p pts.append(p) active.append(p) for _ in range(200): p = (origin[0] + rng.uniform(0, width), origin[1] + rng.uniform(0, height)) if keep(p): add(p) break while active and len(pts) < limit: i = rng.randrange(len(active)) base = active[i] for _ in range(k): a = rng.uniform(0, 2 * math.pi) d = rng.uniform(radius, 2 * radius) p = (base[0] + d * math.cos(a), base[1] + d * math.sin(a)) if origin[0] <= p[0] < origin[0] + width and origin[1] <= p[1] < origin[1] + height \ and keep(p) and fits(p): add(p) break else: active.pop(i) return pts def dragrect_stamps(centers, size_px, base_angle_deg=0.0, alternate=True, angle_jitter_deg=35.0, size_jitter=0.0, seed=0): """Two-point strokes for a DragRect stamp per centre: start at the centre, end at size_px along a direction. Costa (Lfen-BSwWcE 00:38:27-00:39:01): alternate the drag direction (top to bottom, then left to right) and drag on a diagonal so each stamp is rotated; keep one size per zone (size_jitter 0 by default). That DragRect maps the drag length to the stamp size and the drag direction to its rotation is [verify live_c02].""" rng = random.Random(seed) out = [] for i, c in enumerate(centers): a = base_angle_deg + (90.0 if (alternate and i % 2) else 0.0) a += rng.uniform(-angle_jitter_deg, angle_jitter_deg) s = size_px * (1.0 + rng.uniform(-size_jitter, size_jitter)) d = _rot2((s, 0.0), a) out.append([(float(c[0]), float(c[1])), (float(c[0]) + d[0], float(c[1]) + d[1])]) return out def scale_borders(centers, spacing, length_frac=0.55, reach=1.35): """Short separating strokes between neighbouring scale centres: each crosses the midpoint of a neighbour link, perpendicular to it (a Voronoi-like border set), for Dam_Standard on the tile. Henning (TuRIf92oMCY 00:06:36-00:08:16): each scale individual but part of a whole, broken lines of varying weight; vary Draw Size per stroke for line weight [added geometry]. Returns two-point strokes.""" cell = spacing * reach grid = {} for i, p in enumerate(centers): grid.setdefault((int(p[0] // cell), int(p[1] // cell)), []).append(i) out = [] half = spacing * length_frac / 2.0 for i, p in enumerate(centers): gx, gy = int(p[0] // cell), int(p[1] // cell) for a in (gx - 1, gx, gx + 1): for b in (gy - 1, gy, gy + 1): for j in grid.get((a, b), ()): if j <= i: continue q = centers[j] d = math.dist(p, q) if d == 0 or d > cell: continue m = ((p[0] + q[0]) / 2.0, (p[1] + q[1]) / 2.0) n = (-(q[1] - p[1]) / d, (q[0] - p[0]) / d) out.append([(m[0] - n[0] * half, m[1] - n[1] * half), (m[0] + n[0] * half, m[1] + n[1] * half)]) return out def dragdot_stamps(centers, repeats=2): """One repeated-point stroke per centre (DragDot places the alpha where it lands: J Hill places clogged pores one by one this way, HlHoIGE2Ocs 00:22:50-00:23:54). Uses zb_stroke.dab; how many repeats sculpt best is [verify live_c02].""" return [zb_stroke.dab(c, repeats) for c in centers] def spray_loop(center, radius, turns=3.0, spacing=3.0, wobble=0.15, seed=0): """A small outward spiral for a Spray stroke. Costa (Lfen-BSwWcE 00:42:21-00:42:54): low intensity Spray in small circles, "try not to go over the same area too often"; an Archimedean spiral never revisits a point [added shape].""" rng = random.Random(seed) pts = [] total = 2 * math.pi * turns t = 0.0 while t <= total: r = radius * t / total rr = r * (1.0 + rng.uniform(-wobble, wobble)) pts.append((center[0] + rr * math.cos(t), center[1] + rr * math.sin(t))) step = spacing / max(r, spacing) # about `spacing` px of arc per sample t += min(step, 0.8) return pts def constant_field(angle_deg): """Direction field: the same direction everywhere (forehead lines are horizontal: Costa j5XLtLMN0P8 00:42:33).""" d = _rot2((1.0, 0.0), angle_deg) return lambda p: d def radial_field(center): """Direction field pointing away from a centre (lines perpendicular to a circular muscle, such as the orbicularis oris: Costa Lfen-BSwWcE 00:46:18; crow's feet from the outer eye corner) [added reading of the rule].""" return lambda p: _unit2((p[0] - center[0], p[1] - center[1])) def tangential_field(center): """Direction field around a centre (fine lid wrinkles "circular around the eyes": J Hill HlHoIGE2Ocs 00:17:38-00:18:10).""" return lambda p: _unit2((-(p[1] - center[1]), p[0] - center[0])) def connect_the_dots(points, field, max_link, max_turn_deg=30.0, chain=(2, 4), seed=0): """Wrinkles built by connecting neighbouring pores along a direction field, as short chains of short segments, never one long line (Costa Lfen-BSwWcE 00:46:18-00:47:26). Returns {"chains": [[p, ...]], "segments": [[p, q]]}: play each segment as its own stroke. A point is used by one chain at most.""" rng = random.Random(seed) cell = float(max_link) grid = {} for i, p in enumerate(points): grid.setdefault((int(p[0] // cell), int(p[1] // cell)), []).append(i) used = set() cos_max = math.cos(math.radians(max_turn_deg)) order = list(range(len(points))) rng.shuffle(order) chains = [] def neighbours(i): p = points[i] gx, gy = int(p[0] // cell), int(p[1] // cell) for a in (gx - 1, gx, gx + 1): for b in (gy - 1, gy, gy + 1): for j in grid.get((a, b), ()): if j != i: yield j for start in order: if start in used: continue want = rng.randint(chain[0], chain[1]) cur, path = start, [start] sign = None while len(path) - 1 < want: d = field(points[cur]) best, best_d = None, None for j in neighbours(cur): if j in used or j in path: continue v = (points[j][0] - points[cur][0], points[j][1] - points[cur][1]) dist = math.hypot(*v) if dist == 0 or dist > max_link: continue c = (v[0] * d[0] + v[1] * d[1]) / dist s = 1 if c >= 0 else -1 if abs(c) < cos_max or (sign is not None and s != sign): continue if best_d is None or dist < best_d: best, best_d, best_s = j, dist, s if best is None: break sign = best_s path.append(best) cur = best if len(path) >= 2: used.update(path) chains.append([tuple(points[k]) for k in path]) segments = [[c[i], c[i + 1]] for c in chains for i in range(len(c) - 1)] return {"chains": chains, "segments": segments} def taper_segments(points, n=3, size=(24.0, 8.0), z=(30.0, 10.0), overlap=1): """Split a path into n consecutive pieces with decreasing Draw Size and Z Intensity: the substitute for pen-pressure taper, which the proven V02 stroke format cannot carry (scenario-zbrush-expert, zb_stroke PRESSURE_TOKEN). Returns [(points, draw_size, z_intensity)].""" pts = list(points) if len(pts) < 2 or n <= 1: return [(pts, size[0], z[0])] out = [] step = (len(pts) - 1) / float(n) for k in range(n): a = int(round(k * step)) b = int(round((k + 1) * step)) + overlap piece = pts[a:min(b, len(pts) - 1) + 1] f = k / float(n - 1) if len(piece) >= 2: out.append((piece, size[0] + (size[1] - size[0]) * f, z[0] + (z[1] - z[0]) * f)) return out # -------------------------------------------------------------------------------------------- # Cloth fold planner (Rafael Grassetti, gNx4v0WVVHo) # -------------------------------------------------------------------------------------------- # Tightness picks the fold type (Grassetti 00:04:26, 00:05:30-00:06:36, 00:10:31). FOLD_TYPES = {"loose": "drop", "two_point": "diaper", "tight": "x", "split": "y", "joint": "rolls"} def fold_type_for(tightness): if tightness not in FOLD_TYPES: raise ValueError(f"tightness must be one of {sorted(FOLD_TYPES)}") return FOLD_TYPES[tightness] def _polyline_pts(p0, p1, n): return [(p0[0] + (p1[0] - p0[0]) * i / (n - 1), p0[1] + (p1[1] - p0[1]) * i / (n - 1)) for i in range(n)] def drop_folds(pin, count=5, length=300.0, spread_deg=40.0, gravity=(0.0, 1.0), weight=(1.0, 0.35), start_offset=0.08, samples=12, seed=0): """Drop (curtain) folds: cones fanning down from one tension point, heavier near the pin and relaxing away from it (Grassetti 00:00:31-00:03:41). Each fold carries per-point weights from weight[0] at the pin end to weight[1] at the far end, for taper_segments. Lengths and angles are jittered so the family is never even [added numbers].""" rng = random.Random(seed) g = _unit2(gravity) out = [] for i in range(count): f = 0.0 if count == 1 else i / float(count - 1) ang = -spread_deg / 2.0 + spread_deg * f + rng.uniform(-0.15, 0.15) * spread_deg / max(count, 1) d = _rot2(g, ang) ln = length * rng.uniform(0.75, 1.0) s0 = (pin[0] + d[0] * length * start_offset, pin[1] + d[1] * length * start_offset) p1 = (pin[0] + d[0] * ln, pin[1] + d[1] * ln) path = _polyline_pts(s0, p1, samples) w = [weight[0] + (weight[1] - weight[0]) * k / (samples - 1) for k in range(samples)] out.append({"type": "drop", "path": path, "weight": w, "anchors": [tuple(pin)], "anchor_tol": length * start_offset * 1.5}) return out def diaper_folds(pin_a, pin_b, count=4, sag=60.0, spacing=35.0, growth=1.3, gravity=(0.0, 1.0), samples=16, weight=(1.0, 0.5)): """Diaper (zigzag) folds between two tension points: nested sags whose ends terminate on the previous sag, spacing growing downward (Grassetti 00:02:04-00:03:41). The first sag hangs from the pins; a parabola stands in for the catenary [added].""" g = _unit2(gravity) out = [] prev = None depth = 0.0 for k in range(count): depth += sag if k == 0 else spacing * (growth ** (k - 1)) if prev is None: a, b = tuple(pin_a), tuple(pin_b) else: a, b = prev[1], prev[-2] # ends sit on the sag above (termination) path = [] for i in range(samples): t = i / float(samples - 1) x = a[0] + (b[0] - a[0]) * t y = a[1] + (b[1] - a[1]) * t s = depth * 4.0 * t * (1.0 - t) path.append((x + g[0] * s, y + g[1] * s)) w = [weight[0] + (weight[1] - weight[0]) * k / max(count - 1, 1)] * samples out.append({"type": "diaper", "path": path, "weight": w, "anchors": [tuple(pin_a), tuple(pin_b)] if prev is None else []}) prev = path return out def x_folds(center, width, height, pairs=2, spread=0.6, samples=10): """X folds on a tight, compressed band: pairs of wide crossing diagonals whose ends run to the band edges (Grassetti 00:04:26-00:05:30). Build them as raised ridges first (Clay Z 80), then push the valleys in (Standard), per his frames.""" out = [] for k in range(pairs): dx = width / 2.0 * (1.0 - 0.25 * k) dy = height / 2.0 cx = center[0] + (k - (pairs - 1) / 2.0) * width * spread / max(pairs, 1) for sgn in (1, -1): p0 = (cx - dx, center[1] - sgn * dy) p1 = (cx + dx, center[1] + sgn * dy) out.append({"type": "x", "path": _polyline_pts(p0, p1, samples), "weight": [1.0] * samples, "anchors": []}) return out def y_fold(start, direction, length=200.0, fork_at=0.5, split_deg=28.0, samples=8): """A Y fold: one stem splitting off a tension line (Grassetti 00:06:36, 00:09:16).""" d = _unit2(direction) fork = (start[0] + d[0] * length * fork_at, start[1] + d[1] * length * fork_at) stem = _polyline_pts(start, fork, samples) out = [{"type": "y", "path": stem, "weight": [1.0] * samples, "anchors": [tuple(start)]}] rest = length * (1.0 - fork_at) for sgn in (1, -1): b = _rot2(d, sgn * split_deg) end = (fork[0] + b[0] * rest, fork[1] + b[1] * rest) out.append({"type": "y", "path": _polyline_pts(fork, end, samples), "weight": [0.8] * samples, "anchors": []}) return out def _near_path(p, path, tol): for q in path: if math.dist(p, q) <= tol: return True for a, b in zip(path, path[1:]): ab = (b[0] - a[0], b[1] - a[1]) L2 = ab[0] ** 2 + ab[1] ** 2 if L2 == 0: continue t = max(0.0, min(1.0, ((p[0] - a[0]) * ab[0] + (p[1] - a[1]) * ab[1]) / L2)) c = (a[0] + ab[0] * t, a[1] + ab[1] * t) if math.dist(p, c) <= tol: return True return False def free_endpoints(folds, border=None, tol=6.0, pins=()): """Fold ends that terminate by themselves: not on a pin, another fold, or the border rectangle (x0, y0, x1, y1). Grassetti's rule (00:02:35): a fold never ends alone. Target zero. Returns [(fold_index, 'start'|'end', point)].""" shared = [tuple(p) for p in pins] for f in folds: shared += [tuple(a) for a in f.get("anchors", [])] out = [] for i, f in enumerate(folds): own = [tuple(a) for a in f.get("anchors", [])] own_tol = max(tol, float(f.get("anchor_tol", 0.0))) for tag, p in (("start", f["path"][0]), ("end", f["path"][-1])): if any(math.dist(p, a) <= own_tol for a in own): continue if any(math.dist(p, a) <= tol for a in shared): continue if border is not None: x0, y0, x1, y1 = border if min(abs(p[0] - x0), abs(p[0] - x1), abs(p[1] - y0), abs(p[1] - y1)) <= tol: continue if any(j != i and _near_path(p, g["path"], tol) for j, g in enumerate(folds)): continue out.append((i, tag, p)) return out def snap_free_ends(folds, border=None, tol=6.0, reach=80.0, pins=()): """Extend each free end to the nearest point of another fold (or the border) within `reach`, so every fold terminates into a neighbour or an edge (Grassetti 00:02:35). Returns (folds, still_free).""" for i, tag, p in free_endpoints(folds, border, tol, pins): best, best_d = None, None for j, g in enumerate(folds): if j == i: continue for q in g["path"]: d = math.dist(p, q) if best_d is None or d < best_d: best, best_d = q, d if border is not None: x0, y0, x1, y1 = border for q in ((x0, p[1]), (x1, p[1]), (p[0], y0), (p[0], y1)): d = math.dist(p, q) if best_d is None or d < best_d: best, best_d = q, d if best is not None and best_d <= reach: path = folds[i]["path"] w = folds[i]["weight"] if tag == "end": path.append(tuple(best)) w.append(w[-1]) else: path.insert(0, tuple(best)) w.insert(0, w[0]) return folds, free_endpoints(folds, border, tol, pins) def evenness(values): """Coefficient of variation (std / mean) of fold spacings, lengths or depths. Grassetti (00:03:08): folds are never even; a value near 0 means a mechanical family [added].""" vals = [float(v) for v in values] if len(vals) < 2: return None m = sum(vals) / len(vals) if m == 0: return None var = sum((v - m) ** 2 for v in vals) / len(vals) return math.sqrt(var) / abs(m) # -------------------------------------------------------------------------------------------- # Horns and curves (Pablo Munoz Gomez, TN9ARiC_82w) # -------------------------------------------------------------------------------------------- def _v3(a): return [float(a[0]), float(a[1]), float(a[2])] def _norm3(v): n = math.sqrt(v[0] ** 2 + v[1] ** 2 + v[2] ** 2) return [v[0] / n, v[1] / n, v[2] / n] if n > 1e-12 else [0.0, 0.0, 1.0] def _cross(a, b): return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]] def _rodrigues(v, k, deg): a = math.radians(deg) c, s = math.cos(a), math.sin(a) kv = _cross(k, v) kd = k[0] * v[0] + k[1] * v[1] + k[2] * v[2] return [v[i] * c + kv[i] * s + k[i] * kd * (1 - c) for i in range(3)] def horn_path(base, direction, length, curl_deg=90.0, twist_deg=0.0, bend_axis=None, radius=(0.15, 0.02), samples=8): """Centreline and radii for a horn in tool space, to push into a curve brush with the SDK curves API (new_curves, add_new_curve, add_curve_point, curves_to_ui: Maxon example add_curve_point.0.py). Pablo curves a straight, detailed horn with Bend Curve, where 5 or 6 points are enough (00:26:48); the curve route uses curve Resolution 10 and a Size falloff that starts large (00:15:04-00:16:43). curl_deg bends the tangent progressively around bend_axis; twist_deg turns the bend axis along the length (a spiral horn) [added geometry]. Returns {"points", "radii", "length"}.""" d = _norm3(_v3(direction)) if bend_axis is None: up = [0.0, 1.0, 0.0] if abs(d[1]) < 0.9 else [1.0, 0.0, 0.0] bend_axis = _cross(d, up) k = _norm3(_v3(bend_axis)) step = length / float(samples - 1) pts = [_v3(base)] t = d[:] for i in range(1, samples): kk = _rodrigues(k, t, twist_deg / (samples - 1) * i) if twist_deg else k t = _norm3(_rodrigues(t, _norm3(kk), curl_deg / (samples - 1))) p = pts[-1] pts.append([p[0] + t[0] * step, p[1] + t[1] * step, p[2] + t[2] * step]) radii = [radius[0] + (radius[1] - radius[0]) * i / (samples - 1) for i in range(samples)] return {"points": pts, "radii": radii, "length": step * (samples - 1)} def mirror_points(points, axis=0, center=0.0): """Mirror a path across a plane (the second horn of a pair).""" out = [] for p in points: q = list(p) q[axis] = 2 * center - q[axis] out.append(q) return out def crack_tree(start, direction, length, levels=3, branches=2, shrink=0.5, spread_deg=35.0, wobble_deg=12.0, samples=8, seed=0): """Staged cracks for tusks, spikes, horns, bone plates and dry skin, largest first. Starkie ("Drake"): main cracks first with Orb Cracks and Dam_Standard, main shapes split into secondary ones as you go, small Orb Cracks last. Orb brushes do not ship in 2026.2.1: play level 0 with Dam_Standard at full size, each later level smaller (Draw Size x size_frac), and review a raking render between levels. Geometry [added]: a jittered polyline per crack, `branches` children per crack starting ON the parent, each `shrink` times as long, turned up to spread_deg off the parent's tangent. On horns, detail the straight tube first and bend last (Pablo TN9ARiC_82w 00:23:04). Canvas coordinates. Returns [{"level", "path", "size_frac", "parent"}] in play order (level by level).""" rng = random.Random(seed) out = [] def walk(p0, d, L): pts = [tuple(p0)] a = math.degrees(math.atan2(d[1], d[0])) step = L / float(samples - 1) for _ in range(1, samples): a += rng.uniform(-wobble_deg, wobble_deg) r = math.radians(a) p = pts[-1] pts.append((p[0] + step * math.cos(r), p[1] + step * math.sin(r))) return pts frontier = [(None, tuple(start), _unit2(direction), float(length))] for lev in range(levels): nxt = [] for parent, p0, d, L in frontier: path = walk(p0, d, L) idx = len(out) out.append({"level": lev, "path": path, "size_frac": shrink ** lev, "parent": parent}) if lev + 1 >= levels: continue for b in range(branches): k = min(max(int(round(rng.uniform(0.25, 0.8) * (samples - 1))), 1), samples - 2) tang = _unit2((path[k + 1][0] - path[k - 1][0], path[k + 1][1] - path[k - 1][1])) sgn = 1 if b % 2 == 0 else -1 bd = _rot2(tang, sgn * rng.uniform(0.6, 1.0) * spread_deg) nxt.append((idx, path[k], bd, L * shrink)) frontier = nxt return out # -------------------------------------------------------------------------------------------- # Process discipline, budgets, canon numbers # -------------------------------------------------------------------------------------------- class MoveBudget: """Stroke ledger per stage and region (Ryan Kingslien, 6wiDxO-ZADg 00:20:29-00:24:47): count moves, cut them (48 down to 13 for the head wedge; a 16-move target 00:28:04), and treat a second touch of the same region as a signal to go back and replan, not to add strokes ("I've touched the back of the head twice, that's not good", 00:21:01).""" def __init__(self, budgets=None, max_touches=1): self.budgets = dict(budgets or {}) self.max_touches = int(max_touches) self.rows = [] def log(self, stage, region, n=1, note=""): self.rows.append({"stage": stage, "region": region, "n": int(n), "note": note}) return self.touches(stage, region) def touches(self, stage, region): return sum(1 for r in self.rows if r["stage"] == stage and r["region"] == region) def report(self, stage): rows = [r for r in self.rows if r["stage"] == stage] regions = {} for r in rows: regions[r["region"]] = regions.get(r["region"], 0) + 1 strokes = sum(r["n"] for r in rows) budget = self.budgets.get(stage) return {"stage": stage, "strokes": strokes, "budget": budget, "over_budget": budget is not None and strokes > budget, "retouched": sorted(k for k, v in regions.items() if v > self.max_touches), "regions": regions} def to_json(self): return json.dumps({"budgets": self.budgets, "max_touches": self.max_touches, "rows": self.rows}) @classmethod def from_json(cls, text): d = json.loads(text) mb = cls(d.get("budgets"), d.get("max_touches", 1)) mb.rows = d.get("rows", []) return mb def subdiv_plan(base_faces, route="regular", max_faces=25.6e6, regular_top=(1.0e6, 2.0e6), hd_levels=3, smt_off_levels=0, max_regular_levels=4): """Subdivision stack for detail work. Faces multiply by 4 per level (Tool > Geometry > Divide doc; Pablo Logic Part 7). Choose the route BEFORE the first Divide and gate the plan with stack_check: a deep regular stack cannot take HD afterwards. route "hd" (computer-use agent only; Costa Lfen-BSwWcE 00:17:53-00:18:25; J Hill HlHoIGE2Ocs 00:13:22): 3 to 4 regular levels (at most max_regular_levels, top at most 1 to 2 million), then 3 (Costa) or 3 to 4 (J Hill) DivideHD levels, because "you are not able to go below where you started" in HD and smoothing needs lower levels. Entering an HD region needs the A key over the cursor, which the SDK cannot send (press_key "does not work"). route "regular" (SDK-only agent default [added]): Divide while faces stay under max_faces; J Hill stops game work at 20 to 40 million (SDiv 7 = 25.5M from a 6,240-face base); the finest micro detail then goes into a tiled map at render (Costa Lfen 01:10:58). smt_off_levels: first levels divided with Smt off to keep joints and knuckles crisp (Eaton Ale6SXXbJMM 00:32:36, about 6 levels and 1.7M for a full figure).""" if base_faces <= 0: raise ValueError("base_faces must be positive") levels = [{"level": 1, "faces": int(base_faces), "kind": "base", "smt": None}] n = float(base_faces) notes = [] if route == "hd": while n < regular_top[0] and len(levels) < max_regular_levels: n *= 4 levels.append({"level": len(levels) + 1, "faces": int(n), "kind": "regular", "smt": len(levels) > smt_off_levels}) if n < regular_top[0]: notes.append(f"regular top {int(n)} after {len(levels)} levels, under Costa's 1 to 2 " "million: J Hill's split still holds (3 to 4 regular, 3 to 4 HD); " "consider hd_levels=4") if n > regular_top[1]: notes.append(f"regular top {int(n)} is above {int(regular_top[1])}: Costa keeps the " "regular stack at 1 to 2 million so 8K exports stay fast (00:18:58); " "use a lighter base") for i in range(hd_levels): n *= 4 levels.append({"level": len(levels) + 1, "faces": int(n), "kind": "hd", "smt": True}) notes.append("HD levels: sculpt HD needs the A key over the mesh (computer use); store " "the morph target again on every HD entry; layers do not survive leaving HD " "(J Hill 00:13:54)") elif route == "regular": while n * 4 <= max_faces: n *= 4 levels.append({"level": len(levels) + 1, "faces": int(n), "kind": "regular", "smt": len(levels) > smt_off_levels}) else: raise ValueError("route must be 'regular' or 'hd'") regular = [lv for lv in levels if lv["kind"] != "hd"] if len(regular) < 3: notes.append("fewer than 2 levels below the detail level: J Hill smooths at lower levels " "(00:27:57); start from a denser base or a lower top") return {"route": route, "levels": levels, "top_faces": levels[-1]["faces"], "regular_top_faces": regular[-1]["faces"], "notes": notes} HD_CEILING = 1.0e9 # HD Geometry doc: "divide your model to 1 billion polygons" def stack_check(plan, computer_use=False): """Gate a subdivision plan (subdiv_plan output, or {"levels": [{"level", "faces", "kind"}]} with kind "base", "regular" or "hd") before the first Divide. The two branches: SDK only: regular levels to about 20 to 25M, no HD, micro detail in a tiled map. computer use: split from the start, 3 to 4 regular levels (top 1 to 2M) + 3 to 4 HD. Problems (J Hill HlHoIGE2Ocs 00:11:10, 00:13:22; Costa Lfen-BSwWcE 00:17:53, 00:18:58; HD doc): HD without a computer-use agent; 6 or more regular levels then HD; a regular top above 2M under HD; more than 1 billion faces; a regular-only top above 40M ("40 million is high"). Warnings: 5 regular levels before HD [added: between J Hill's 3 to 4 and his 6 to 7], fewer than 3 HD levels, a regular top above 25.6M, fewer than 2 levels of smoothing room below the top (below the HD entry, levels do not count).""" levels = plan["levels"] reg = [lv for lv in levels if lv["kind"] != "hd"] hd = [lv for lv in levels if lv["kind"] == "hd"] top = int(levels[-1]["faces"]) problems, warnings, notes = [], [], [] if hd: route = "hd" if not computer_use: problems.append("HD levels without a computer-use agent: entering an HD region needs " "the A key over the mesh and press_key does not work in the 2026 SDK; " "plan the regular route and put micro detail in a tiled map") if len(reg) >= 6: problems.append(f"{len(reg)} regular levels then HD: J Hill splits 3 to 4 regular plus 3 " "to 4 HD, never 6 or 7 regular then HD, because you cannot go below the " "HD entry level and need lower levels to smooth (00:13:22)") elif len(reg) == 5: warnings.append("5 regular levels before HD [added]: lighter top preferred (J Hill 3 to 4)") if reg[-1]["faces"] > 2.0e6: problems.append(f"regular top {int(reg[-1]['faces'])} above 2M under HD: Costa keeps it " "at 1 to 2 million so 8K map exports stay fast (00:17:53, 00:18:58)") if len(hd) < 3: warnings.append(f"{len(hd)} HD level(s): Costa uses 3, J Hill 3 to 4") room = len(hd) - 1 notes.append("store the morph target on every HD entry; layers do not survive leaving HD " "(J Hill 00:13:54)") else: route = "regular" if top > 40.0e6: problems.append(f"regular top {top} above 40M: J Hill calls 40 million high (00:11:10); " "stop at about 20 to 25M and move micro detail into a tiled map") elif top > 25.6e6: warnings.append(f"regular top {top} above the 25.6M agent default [added]") room = len(reg) - 1 notes.append("micro detail (finest pores, scale grain) as a tiled micro-displacement map at " "render (Costa Lfen 01:10:58; J Hill 00:11:10)") if top > HD_CEILING: problems.append(f"{top} faces above the 1 billion HD Geometry ceiling (HD doc): each level " "multiplies by 4, so 3 DivideHD over 16M is already 1 billion") if room < 2: warnings.append(f"{room} level(s) of smoothing room below the top: J Hill smooths at a lower " "level (00:27:57)") return {"ok": not problems, "route": route, "computer_use": bool(computer_use), "regular_levels": len(reg), "hd_levels": len(hd), "top_faces": top, "problems": problems, "warnings": warnings, "notes": notes} def map_request_check(req): """Gate the map request this skill hands to scenario-zbrush-retopology-export, which owns the Multi Map Exporter (rx.mme_set, rx.renderer_plan). req: {"top_level": int, "displacement": {"subdiv", "adaptive", "bits", "mid", "exr"}, "normal": {"subdiv", "adaptive"} or None, "renderer_zero": float, "merge_maps": bool, "udim": bool, "tile_format": "UDIM", "tile_dot": bool}. Rules: - Adaptive identical in normal and displacement maps meant to combine (HD doc "Adaptive"; MME doc); - every map's SubDiv level below the top: a map compares the chosen level with the highest one (AskZBrush 2zDAtaQqwh8 00:01:44; MME uses level 1 at or above the top); a normal from the level just below the top holds micro detail only (00:02:53); - 32-bit displacement with its Mid equal to the renderer's zero (Costa Mid 0 for Arnold; FlippedNormals Mid 0.5 with Scalar Zero Value 0.5); 16-bit: Mid 0.5 (MME doc); - EXR and Merge Maps do not combine: Merge Maps off, one object per UDIM tile (FlippedNormals -ThBTEc8L_M 00:04:43-00:05:41, 00:11:08; [verify on 2026]); - UDIMs: tile ID format UDIM with a dot before the tile number, so the files form an image sequence (00:06:36-00:07:09).""" problems, warnings = [], [] top = req.get("top_level") disp, nrm = req.get("displacement") or {}, req.get("normal") or {} if disp and nrm and bool(disp.get("adaptive")) != bool(nrm.get("adaptive")): problems.append("Adaptive differs between the displacement and normal maps: set it the " "same when they combine (HD doc)") for name, m in (("displacement", disp), ("normal", nrm)): if m and top is not None and m.get("subdiv") is not None and m["subdiv"] >= top: problems.append(f"{name} SubDiv level {m['subdiv']} at or above the top ({top}): the " "map compares that level with the top, so it would hold nothing") if disp: bits = disp.get("bits", 32) mid = disp.get("mid") if mid is None: problems.append("displacement Mid not recorded: the renderer needs it as its zero") elif bits == 16 and abs(mid - 0.5) > 1e-6: problems.append(f"16-bit displacement with Mid {mid}: the MME doc gives 0.5 for 16-bit") zero = req.get("renderer_zero") if mid is not None and zero is not None and abs(mid - zero) > 1e-6: problems.append(f"Mid {mid} but the renderer zero is {zero}: set them equal") if disp.get("exr") and req.get("merge_maps"): problems.append("EXR with Merge Maps: Merge Maps writes TIFF; turn it off and give each " "object its own UDIM tile (FlippedNormals 00:04:43)") if req.get("udim"): if str(req.get("tile_format", "")).upper() != "UDIM": problems.append("UDIM layout but the tile ID format is not UDIM (Mari numbering, 1001)") if not req.get("tile_dot"): warnings.append("no dot before the tile number: the files do not read as an image " "sequence in Maya (FlippedNormals 00:06:36)") return {"ok": not problems, "problems": problems, "warnings": warnings} # Anatomy For Sculptors head canon ("Adding detail"): crown to hairline 1/6 of the head height; # the rest in three equal thirds (hairline-brow, brow-nose base, nose base-chin); the lower # third split in three (nose-mouth, mouth-upper chin, chin); the eye line at 1/2. def head_canon(H, chin_y=0.0): t = 5.0 * H / 18.0 return {"chin": chin_y, "chin_crease": chin_y + t / 3.0, "mouth": chin_y + 2.0 * t / 3.0, "nose_base": chin_y + t, "brow": chin_y + 2.0 * t, "hairline": chin_y + 3.0 * t, "crown": chin_y + H, "eye": chin_y + H / 2.0} def head_ratio_check(landmarks, tol=0.05, deliberate=()): """Compare measured landmark heights (front view, y up) with head_canon. Needs 'chin' and 'crown'. tol is a fraction of head height (plus or minus 5 percent [added], per the character digest). `deliberate` names ratios broken on purpose (stylized: know which ratio you break, A4S note).""" if "chin" not in landmarks or "crown" not in landmarks: raise ValueError("landmarks need 'chin' and 'crown' heights") H = landmarks["crown"] - landmarks["chin"] if H <= 0: raise ValueError("crown must be above chin") canon = head_canon(H, landmarks["chin"]) rows = {} for k, v in landmarks.items(): if k in ("chin", "crown") or k not in canon: continue err = (v - canon[k]) / H rows[k] = {"measured": v, "canon": round(canon[k], 6), "error_H": round(err, 4), "ok": abs(err) <= tol or k in deliberate, "deliberate": k in deliberate} return {"H": H, "tol": tol, "rows": rows, "ok": all(r["ok"] for r in rows.values())} # Kris Costa, "Useful Measurements" slide (j5XLtLMN0P8 00:38:12): head 229 mm, eyeball 24 mm, # pupillary distance 63 mm, eyes rotated 3 to 10 degrees outward (about 5, kappa angle). COSTA_MM = {"head_height": 229.0, "eyeball": 24.0, "ipd": 63.0, "kappa_deg": (3.0, 10.0)} def eye_report(center_l, center_r, eye_diameter, head_height, forward_l=None, forward_r=None, head_forward=(0.0, 0.0, 1.0), tol_mm=(2.0, 5.0)): """Eye placement against Costa's measurements, in the units of the mesh. head_height (chin to crown) sets the millimetre scale (229 mm). forward_* are each eye's gaze vectors (+Z faces the camera in the front view of an OBJ export, zb_stroke notes); divergence is measured in the horizontal plane, positive outward. tol_mm = (eyeball, ipd) [added].""" mm = head_height / COSTA_MM["head_height"] cl, cr = _v3(center_l), _v3(center_r) ipd = math.dist(cl, cr) / mm eye = eye_diameter / mm out = {"units_per_mm": mm, "eyeball_mm": round(eye, 2), "ipd_mm": round(ipd, 2), "eyeball_ok": abs(eye - COSTA_MM["eyeball"]) <= tol_mm[0], "ipd_ok": abs(ipd - COSTA_MM["ipd"]) <= tol_mm[1]} if forward_l is not None and forward_r is not None: hf = _norm3(_v3(head_forward)) div = {} for name, c, f, other in (("left", cl, forward_l, cr), ("right", cr, forward_r, cl)): f = _norm3(_v3(f)) out_dir = _norm3([c[0] - other[0], 0.0, c[2] - other[2]]) # away from the other eye fh = [f[0], 0.0, f[2]] n = math.hypot(fh[0], fh[2]) ang = math.degrees(math.atan2(fh[0] * out_dir[0] + fh[2] * out_dir[2], fh[0] * hf[0] + fh[2] * hf[2])) if n > 1e-9 else 0.0 div[name] = round(ang, 2) lo, hi = COSTA_MM["kappa_deg"] out["divergence_deg"] = div out["divergence_ok"] = all(lo <= v <= hi for v in div.values()) out["cross_eyed_risk"] = any(v < lo for v in div.values()) return out def split_ratio(position, start, end, band=(0.45, 0.55)): """Where a main break sits along a limb or piece, as a fraction. Rakan Khamash (Blizzard, FRZtVXpAokc 00:16:37): 70/30, never 50/50; the 0.45 to 0.55 flag band is [added].""" L = float(end) - float(start) if L == 0: raise ValueError("zero length") r = (float(position) - float(start)) / L return {"ratio": round(r, 4), "fifty_fifty": band[0] <= r <= band[1]} def scapula_check(humerus_elevation_deg, scapula_rotation_deg, tol_deg=8.0): """Scott Eaton (Ale6SXXbJMM 00:39:55): with the humerus raised 90 degrees from vertical the scapula should be rotated about 30 degrees. Only that data point is given, so the check runs for elevations of 80 to 100 degrees; A4S: rotation only shows above shoulder level. tol_deg is [added].""" if not 80.0 <= humerus_elevation_deg <= 100.0: return {"applicable": False, "note": "Eaton gives one number: about 30 degrees at 90"} return {"applicable": True, "expected": 30.0, "ok": abs(scapula_rotation_deg - 30.0) <= tol_deg} def view_facing(axis, convention=None, step=90.0): """A (x_rotate, y_rotate, z_rotate) triple that turns the model axis ('+X', '-Y', ...) toward the camera under the zb_stroke convention (hypothesis H0 until the lead's live_04 fits it). '-Y' gives Kingslien's view from below (6wiDxO-ZADg 01:07:39) [verify on the canvas: the chin must face the camera].""" vec = {"+X": (1, 0, 0), "-X": (-1, 0, 0), "+Y": (0, 1, 0), "-Y": (0, -1, 0), "+Z": (0, 0, 1), "-Z": (0, 0, -1)}[axis] vals = [0.0, 90.0, 180.0, -90.0] if step == 90.0 else [i * step for i in range(int(360 / step))] for rx in vals: for ry in vals: for rz in vals: cam = zb_stroke.Camera([0, 0, 0, 1, 1, 1, rx, ry, rz], (0, 0, 0), convention) if cam.facing(vec) > 0.999: return (rx, ry, rz) return None def aim_at(normal, step=15.0, convention=None): """Rotation (x_rotate, y_rotate, 0) that turns a model-space normal toward the camera: aim the view at a region (its mean normal from region_on_canvas) before stamping, as the realism digest's stamp procedure and Kingslien's view discipline require. Grid search, smallest rotations first [added]; same [verify] as view_facing (live_04 fits the convention). Returns {"rotation", "facing"} (facing 1 = straight at the camera).""" n = _norm3(_v3(normal)) vals = [0.0] k = 1 while k * step < 180.0 + 1e-9: vals += [k * step, -k * step] k += 1 best = None for rx in vals: for ry in vals: f = zb_stroke.Camera([0, 0, 0, 1, 1, 1, rx, ry, 0.0], (0, 0, 0), convention).facing(n) if best is None or f > best[0] + 1e-9: best = (f, rx, ry) return {"rotation": (best[1], best[2], 0.0), "facing": round(best[0], 4)} # ============================================================================================ # 2. Inside ZBrush (through call()). NOT YET RUN IN ZBRUSH. # ============================================================================================ CHAR_PATHS = { # layers (Enhance Details macro uses New, Delete, Duplicate, Bake All, Rename, Scrollbar) "layer_new": ["Tool:Layers:New"], # [macro] "layer_delete": ["Tool:Layers:Delete"], # [macro] "layer_duplicate": ["Tool:Layers:Duplicate"], # [macro] "layer_bake_all": ["Tool:Layers:Bake All"], # [macro] "layer_scrollbar": ["Tool:Layers:Layers Scrollbar"], # [macro] enabled = has layers "layer_intensity": ["Tool:Layers:Intensity", "Tool:Layers:Layer Intensity"], # [verify] "layer_record_mdd": ["Tool:Layers:Record Deformation Animation", "Tool:Layers:Record"], # [verify] "mt_delete": ["Tool:Morph Target:DelMT"], # [verify] # masks by feature (Tool > Masking reference) "mask_cavity": ["Tool:Masking:Mask By Cavity"], # [doc] "mask_cavity_blur": ["Tool:Masking:Mask By Cavity:Blur", "Tool:Masking:Blur"], # [verify] "mask_cavity_intensity": ["Tool:Masking:Mask By Cavity:Intensity", "Tool:Masking:Intensity"], # [verify] "mask_smoothness": ["Tool:Masking:Mask By Smoothness"], # [doc] "mask_peaks": ["Tool:Masking:Mask PeaksAndValleys", "Tool:Masking:MaskPeaksAndValleys"], # [doc] "mask_ao": ["Tool:Masking:Mask Ambient Occlusion"], # [doc] "mask_fibers": ["Tool:Masking:FiberMask", "Tool:Masking:Mask By Fibers:FiberMask"], # [doc] "automask_pg": ["Brush:Auto Masking:Mask By Polygroups"], # [macro] Toggle Mask By Polygroups # strokes and alphas (picker items are addressable by name: SDK overview) [verify names] "stroke_dragrect": ["Stroke:DragRect"], "stroke_dragdot": ["Stroke:DragDot"], "stroke_spray": ["Stroke:Spray"], "stroke_dots": ["Stroke:Dots"], "stroke_freehand": ["Stroke:FreeHand", "Stroke:Freehand"], "grabdoc": ["Alpha:Transfer:GrabDoc"], # [macro] Create Stencil "alpha_intensity": ["Alpha:Modify:Intensity"], # [macro] "alpha_mid": ["Alpha:Modify:MidValue"], # [verify] "alpha_export": ["Alpha:Export"], # [verify] "alpha_import": ["Alpha:Import"], # [verify] "doc_resize": ["Document:Resize"], # [verify] "actual": ["Transform:Actual", "Document:Actual"], # [verify] # Surface Noise (Tool > Surface reference) "surf_noise": ["Tool:Surface:Noise"], # [doc] opens NoiseMaker if none "surf_edit": ["Tool:Surface:Edit"], # [doc] "surf_del": ["Tool:Surface:Del"], # [doc] "surf_apply": ["Tool:Surface:Apply To Mesh"], # [doc] "surf_snorm": ["Tool:Surface:SNorm"], # [doc] "quick3d": ["Transform:Quick 3D Edit", "Transform:Quick"], # [verify] noise shows only with it on # HD and polygroups for UDIM regions "divide_hd": ["Tool:Geometry HD:Divide HD", "Tool:Geometry HD:DivideHD"], # [verify] "pg_uv": ["Tool:Polygroups:Auto Groups With UV"], # [verify] "pg_uv_islands": ["Tool:Polygroups:Uv Groups", "Tool:Polygroups:UV Groups"], # [verify] # Mesh Extract (Tool > SubTool > Extract reference) "extract": ["Tool:SubTool:Extract", "Tool:SubTool:Extract:Extract"], # [doc] [verify path] "extract_thick": ["Tool:SubTool:Thick", "Tool:SubTool:Extract:Thick"], # [verify] "extract_ssmt": ["Tool:SubTool:S Smt", "Tool:SubTool:Extract:S Smt"], # [verify] "extract_accept": ["Tool:SubTool:Accept", "Tool:SubTool:Extract:Accept"], # [verify] # Dynamics palette (DYNA doc labels; duplicate labels need the group level) [verify all] "dyn_gravity": ["Dynamics:Gravity"], "dyn_gravity_strength": ["Dynamics:Gravity Strength"], "dyn_iterations": ["Dynamics:Simulation Iterations"], "dyn_strength": ["Dynamics:Strength"], "dyn_firmness": ["Dynamics:Firmness"], "dyn_self_collision": ["Dynamics:Self Collision"], "dyn_floor": ["Dynamics:Floor Collision"], "dyn_allow_shrink": ["Dynamics:Allow Shrink"], "dyn_allow_expand": ["Dynamics:Allow Expand"], "dyn_liquify": ["Dynamics:Liquify"], "dyn_set_direction": ["Dynamics:Set Direction"], "dyn_inflate": ["Dynamics:Inflate"], "dyn_expand": ["Dynamics:Expand"], "dyn_contract": ["Dynamics:Contract"], "dyn_deflate": ["Dynamics:Deflate"], "dyn_collision": ["Dynamics:CollisionVolume", "Dynamics:Collision Volume:CollisionVolume"], "dyn_recalc": ["Dynamics:Recalc", "Dynamics:Collision Volume:Recalc"], "dyn_resolution": ["Dynamics:Resolution", "Dynamics:Collision Volume:Resolution"], "dyn_collision_inflate": ["Dynamics:Collision Volume:Inflate"], "dyn_run": ["Dynamics:Run Simulation"], "dyn_max_points": ["Dynamics:Max Simulation Points"], "dsub_dynamic": ["Tool:Geometry:Dynamic Subdiv:Dynamic", "Tool:Geometry:Dynamic"], "dsub_smooth": ["Tool:Geometry:Dynamic Subdiv:SmoothSubdiv", "Tool:Geometry:SmoothSubdiv"], "dsub_thickness": ["Tool:Geometry:Dynamic Subdiv:Thickness", "Tool:Geometry:Thickness"], "dsub_offset": ["Tool:Geometry:Dynamic Subdiv:Offset"], "dsub_apply": ["Tool:Geometry:Dynamic Subdiv:Apply", "Tool:Geometry:Apply"], "close_holes": ["Tool:Geometry:Modify Topology:Close Holes", "Tool:Geometry:Close Holes"], # FiberMesh (FIB doc labels) [verify all] "fm_preview": ["Tool:FiberMesh:Preview"], "fm_accept": ["Tool:FiberMesh:Accept"], "fm_open": ["Tool:FiberMesh:Open"], "fm_save": ["Tool:FiberMesh:Save"], "fm_max_fibers": ["Tool:FiberMesh:Modifiers:Max Fibers", "Tool:FiberMesh:Max Fibers"], "fm_segments": ["Tool:FiberMesh:Modifiers:Segments", "Tool:FiberMesh:Segments"], "fm_fast_preview": ["Tool:FiberMesh:Preview Settings:Fast Preview", "Tool:FiberMesh:Fast Preview"], "fm_pre_vis": ["Tool:FiberMesh:Preview Settings:PRE Vis", "Tool:FiberMesh:PRE Vis"], "fiber_uv": ["Tool:UV Map:FiberUV"], # eyes, as the shipped Append Eyes macro builds them "popup_sphere": ["PopUp:Sphere3D"], # [macro] "toyplastic": ["Material:ToyPlastic"], # [macro] } # Friendly names for dynamics_config (keys of CHAR_PATHS without the prefix). DYN_KEYS = ("gravity", "gravity_strength", "iterations", "strength", "firmness", "self_collision", "floor", "allow_shrink", "allow_expand", "liquify", "inflate", "expand", "contract", "deflate", "resolution", "collision_inflate", "max_points") def _z(): return zb_ops._z() def _r(key, required=True): cands = CHAR_PATHS.get(key) return zb_ops.resolve(cands if cands is not None else key, required) def _set(key, value, tol=0.51): cands = CHAR_PATHS.get(key) return zb_ops.set_checked(cands if cands is not None else key, value, tol) def _press(key, check_enabled=True): p = _r(key) z = _z() if check_enabled and not z.is_enabled(p): raise ZBCharError(f"{p} is greyed out in the current state") z.press(p) z.update(redraw_ui=True) return p def path_inventory(keys=None): """Which candidate paths exist in this build, per CHAR_PATHS key (a path oracle to run once per session: live_c01 logs it).""" z = _z() out = {} for k in (keys or sorted(CHAR_PATHS)): out[k] = [p for p in CHAR_PATHS[k] if z.exists(p)] return out # Brushes the procedures name, for one brush_check per session. PLAN_BRUSHES = ("Move", "Standard", "ClayBuildup", "Clay", "Dam_Standard", "Inflat", "Elastic", "Morph", "TrimAdaptive", "TrimDynamic", "Slash3", "CurveTube", "MaskLasso") def brush_check(names=PLAN_BRUSHES): """Which palette item each brush name resolves to in this build, without selecting it. `Brush:Dam_Standard` does not resolve on 2026.2.1 (README exists() list): zb_ops.select_brush tries the DamStandard spellings and loads the .ZBP otherwise. Orb_Cracks and the RK_ and Antro_ brushes do not ship. match: "palette" (an exists() hit), "file" (exact or alias file to load), "fuzzy" (a close name only: pick the stand-in yourself), None (absent).""" z = _z() files = zb_ops.list_brush_files() out = {} for name in names: key = zb_ops.normalize_name(name) aliases = list(zb_ops.BRUSH_ALIASES.get(key, [])) hits = zb_ops.find_brush(name, files=files) labels = aliases + [f["name"] for f in hits] + [name] path = next(("Brush:" + lab for lab in dict.fromkeys(labels) if z.exists("Brush:" + lab)), None) exact = {key} | {zb_ops.normalize_name(a) for a in aliases} f0 = next((f for f in hits if zb_ops.normalize_name(f["name"]) in exact), None) match = "palette" if path else ("file" if f0 else ("fuzzy" if hits else None)) out[name] = {"path": path, "file": f0["path"] if f0 else None, "match": match, "ok": match in ("palette", "file"), "close": [f["name"] for f in hits] if match == "fuzzy" else []} return out def to_top_level(): """Set SDiv to its maximum. New layers must be created at the top subdivision level, and Record can only be left there (3D Layers doc).""" z = _z() p = zb_ops.resolve("sdiv", required=False) if not p: return {"sdiv": None, "sdiv_max": None, "moved": False} cur, mx = z.get(p), z.get_max(p) if cur < mx: z.set(p, mx) z.update(redraw_ui=True) return {"sdiv": z.get(p), "sdiv_max": mx, "moved": cur < mx} def has_layers(): p = _r("layer_scrollbar", required=False) return bool(p and _z().is_enabled(p)) def layer_new(): """New 3D layer at the top level (Record turns on by itself). A layer cannot be named from Python: the shipped macros rename through ZFileUtils RenameSetNext, which is a ZScript FileExecute call with no Python port, so keep an agent-side ledger (layer order -> purpose) [added].""" top = to_top_level() if top["sdiv"] is not None and top["sdiv"] < top["sdiv_max"]: raise ZBCharError(f"could not reach the top level: {top}") before = has_layers() p = _press("layer_new") return {"pressed": p, "top": top, "had_layers": before, "has_layers": has_layers(), "stats": zb_ops.stats()} def layer_intensity(value): """Selected layer intensity: 1 as sculpted, above 1 exaggerates, negative inverts; always 1 while recording (3D Layers doc). Path [verify live_c01].""" return {"intensity": _set("layer_intensity", float(value), tol=0.01), "volume": round(float(_z().get_polymesh3d_volume()), 6)} def layer_bake_all(): return {"pressed": _press("layer_bake_all"), "has_layers": has_layers()} def morph_store(): """Tool > Morph Target > StoreMT (one morph target per SubTool; it breaks when topology changes: Morph Targets doc).""" return {"pressed": _press("store_mt", check_enabled=False)} def morph_switch(): return {"pressed": _press("switch_mt"), "volume": round(float(_z().get_polymesh3d_volume()), 6)} def mask_by(kind, blur=None, intensity=None): """Feature masks without strokes: cavity, smoothness, peaks, ao, fibers. Mask data cannot be read back (no API, Maxon forum 2026): judge the effect by volume changes or renders. A mask protects what it finds: Mask By Cavity and Mask Ambient Occlusion mask the crevices, so what follows (Deformation, fill, Surface Noise) works on the tops and plates; Inverse first to work in the crevices (Pablo inverts the AO mask to tint occluded skin, d03QU-eUaPo 01:37:25) [added reading].""" key = {"cavity": "mask_cavity", "smoothness": "mask_smoothness", "peaks": "mask_peaks", "ao": "mask_ao", "fibers": "mask_fibers"}.get(kind) if key is None: raise ValueError("kind must be cavity, smoothness, peaks, ao or fibers") out = {} if kind == "cavity": if blur is not None: out["blur"] = _set("mask_cavity_blur", blur) if intensity is not None: out["intensity"] = _set("mask_cavity_intensity", intensity) out["pressed
-
-
SKILL.md 17.9 KB
--- name: scenario-zbrush-character-creature description: 'Use when sculpting a realistic head, face, portrait, body or creature in ZBrush (anatomy, landmarks, proportions, "the face looks off", eyes, lips, jaw), adding skin, pores, wrinkles or reptile and dragon scales with alphas, Surface Noise, layers or morph targets, planning subdivision or HD Geometry for a film close-up, reprojecting detail, sculpting cloth folds or running Dynamics cloth, growing FiberMesh fur or hair, making horns, or judging a character sculpt the way a senior character artist would.' license: MIT --- # Realistic characters and creatures (ZBrush 2026) Expert level here means every form has a cause (skull, mass, tension, compression) before any pore exists, and every detail pass is reversible, regional and judged in renders from several views and lights. The agent works the way Kingslien and Costa do: find the upstream cause, fix it at the lowest level that holds it, and keep detail on layers or behind a stored morph target. If a sibling skill named here is missing from your available skills, ask the user to install it (`npx skills add scenario-labs/skills --skill <name>`); unattended, proceed from tool schemas and flag the gap. **REQUIRED BACKGROUND:** scenario-zbrush-expert (bridge, stroke engine, review loop, 2026 traps). Toolkit: [`scripts/zb_character.py`](scripts/zb_character.py) (stroke plans, ZBrush-side wrappers via `zb_character.call(...)`, OBJ, alpha and render checks) on the lead's toolkit. ## Stance (the expert delta) - **Cause before form, in planes.** Kingslien: cranial versus facial mass, then the Bridgeman masses (forehead box, flat cheek, maxilla cylinder, triangular jaw), then features sculpted through the forms around them (nostril through the alar facial juncture, eye through the nasojugal groove). "Almost everything right or wrong in this face is right or wrong back in the beginning." A region touched twice means replan (`MoveBudget`). His single separating lines suit synthesized strokes. - **Detail is worthless on a weak base.** Costa: judge primaries blurred, from afar, upside down and under three lights from the first sketch; keep level 1 alive for likeness edits until export. - **Asymmetry lives in the big forms.** FlippedNormals break symmetry in the mid-frequency pass; J Hill again in the last 25 percent (nose tip off the axis, one ear, a lip corner, one eye's scale); Lazov pushes the whole creature from a stored morph target. Creature big forms: skull mass, one orbit rim, the jaw corner, a nostril, a horn's angle. Scars and broken scales come on top, never instead. - **Skin: scale first, regions second, breakup third.** J Hill sets pore scale globally for age and sex first; lids and lips stay clean; even skin between details reads as plastic. Lines lie across the compression, in short connected segments (Costa: wrinkles connect pores, perpendicular to the circular muscles). Cracks on horns and tusks go large to small in stages (Starkie). - **Scales follow the anatomy; alphas never finish a surface.** Henning Sanden: generic tile, mid value 50, tested on the model at least three times while you build it, then a hand unify pass; smooth inside a scale, never across its border. Starkie: a close-up hero gets individual scales (he gives no numbers). Berger: story, then biology, then cool; reference animals blended with no seams. - **Sharp enough to survive the render.** FlippedNormals: carve deeper than feels right, because SSS and soft light eat sharpness; J Hill: only the sharp detail survives in the highlights. Over finished detail ClayBuildup erases what is under it: enhance with Standard, make directional passes with Elastic. - **Folds and poses have causes.** Grassetti: every fold starts at a tension point, ends in another fold or an edge, is heavier near the tension; tightness picks drop, zigzag, X or Y. Eaton: only the force chain is tense; active muscles run straight between landmarks; scapula about 30 degrees at 90 degrees of arm elevation. - **Agent limits [added].** Transfer well: palette operations, feature masks plus Deformation sliders, layers and morph targets, two-point alpha stamps, short lines, spray loops, curves-API horns. Stay weak: Costa's thin ClayBuildup layering, groom and Morph-brush feel, anything needing Ctrl or Shift held, and HD Geometry (the A key cannot be sent). Say so in the report. ## Establish first | Input | Changes | Default when silent | | --------------------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------------- | | Purpose and shot | stack, map path | film close-up still, 32-bit displacement into Maya/Arnold [added] | | Driver | stack route | SDK only: regular levels, no HD; computer use: HD split | | Subject and reference | canon, blend | human: A4S canon, Costa's millimeters; creature: two or three animals blended (Berger) | | Age, sex, weight | pore scale, fat pads, gonial angle | adult, written down first | | Pose | symmetry strategy | neutral: symmetric until the mid-frequency pass; posed: symmetry off (Eaton) | | Base mesh | where detail starts | ZRemeshed quads with UVs and interior polygroups (eyes, mouth, nostrils) from scenario-zbrush-retopology-export | ## Workflow 0. **Brief and maps.** Design pyramid (Berger); rest versus rich zones (Lazov); scale flow over the anatomy (Henning). GATE: all three written down. 1. **Masses** (from scenario-zbrush-sculpting, or skull first here, Lazov). Head: cranial versus facial mass, then Bridgeman masses on a stroke budget (Kingslien: 16 Move strokes; C2). Creature: skull, jaw hinge, horns (C17), open and closed mouth on layers (Berger). Figure: landmarks first (Eaton). GATE: sheet with front, profile, three-quarter and below (`view_facing("-Y")`); `head_ratio_check` or `scapula_check` passes or the deviation is deliberate; no retouched region. 2. **Face critique pass** in Kingslien's order ([`references/critique.md`](references/critique.md)). The first failing step is the fix: a separating line (C3), a masked move (C4) or plane strokes. GATE: `face_report` moves the right way against the previous stage. 3. **Topology, stack, projection.** Detail only on a clean, UV'd base. Choose the route before the first Divide (C5): SDK only, `subdiv_plan(route="regular")` to about 20 to 25M, micro detail in a tiled map at render; computer use, 3 to 4 regular levels (top 1 to 2M) then 3 to 4 DivideHD. Never add HD over a 6 or 7-level stack: you cannot go below the HD entry (J Hill). Reproject with `zb_ops.project_all` (checkpoint, morph target and top-level layer built in), eye, mouth and nostril interiors masked first; then walk every level (C19). GATE: `stack_check` ok; `spike_report` ok in every danger-zone group; points per level logged. 4. **Secondary forms:** folds, wrinkles, fat pads, lids, lips; Costa's thin ClayBuildup (Z 2 to 3, many short strokes), Kingslien's lines; symmetry off for the big-form mid-frequency pass. Cloth: plan folds (C13) or simulate, then sculpt (C14, C15). GATE: blurred and thumbnail renders; no form "screams". 5. **Tertiary detail, one layer per frequency** (`layer_new`, agent-side ledger). Aim first: `aim_at` the region, stamps inside `region_on_canvas` (C9). Pore or scale bed: Surface Noise (C7) or alpha stamps (C8, C9), each alpha tested on the model while built. Wrinkles across the compression (C10); cracks with `crack_tree`, largest first; breakup (C11); hand unify. GATE: raking close-ups; `alpha_check` (and `dots_check` if dots were used); no tiling; lids and lips clean; each layer seen at intensity 0 and 1. 6. **Asymmetry pass (C12)**, big forms first. GATE: `asymmetry_report` finds at least two big-form regions broken; posed figures skip it. 7. **Fur and hair (C16).** Mask, preset from a file, Preview, Accept, Fast Preview PRE Vis 6 to 8. GATE: coverage render with a contrasting fill. 8. **Final review and handoff (C20).** Costa's eight steps under three lights, upside down, blurred; `eye_report`; `detail_survival` on the SSS render; `map_request_check`; versioned ZTL. GATE: scorecard passes, or open items listed. ## Numbers | Item | Value | Source | | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | | Stack, SDK only | regular to about 20 to 25M (6,240 faces reach 25.5M at SDiv 7); "40 million is high"; micro detail tiled at render | J Hill; Costa | | Stack, computer use | 3 to 4 regular levels, top 1 to 2M, then 3 to 4 DivideHD (Costa: 1.1M top, 3 DivideHD, 70M); HD ceiling 1 billion, so 3 DivideHD over 16M is already at it | Costa; J Hill; HD doc | | Reprojection | target at about 3M; Store MT and a layer at the top level; PA Blur lowered | FlippedNormals | | Face block-in | under about 360k points with Sculptris Pro | Kingslien | | Head canon | crown to hairline 1/6 H, then thirds of 5/6 H; eyes at 1/2 H | Anatomy For Sculptors | | Portrait scale | head 229 mm, eyeball 24 mm, IPD 63 mm, eyes 3 to 10 degrees outward | Costa | | Wrinkles | Standard + Alpha 39, sharpened curve; fine: Standard, Spray, Alpha 60, Z 3 | Costa; J Hill | | Pores | DragRect, Focal Shift -100, one size per zone; Surface Noise strength negative | Costa; J Hill | | Alpha tile | plane, Smt off, about 1M polys (a 1K map), document 1024, perspective off, +50/-50 dots taller than any detail, MidValue 50; tested at 11, 26, 41 minutes | Henning | | Folds | Standard Z 25 valleys, Clay Z 80 ridges, big Move for sag, SDiv 2 to 4 first | Grassetti (frames) | | Cloth | Gravity 1 to 2.5; iterations 100 to 1000; Firmness 1 to 2 silky, 5 to 6 leather; collision about 1024; Extract Thick 0.01 cloth, 0.03 leather | Pavlovich; Maxon docs | | Fur | Max Fibers 11 (thousands), Segments 6, PRE Vis 6 to 8, Profile 1 | Pablo; FiberMesh docs | | Horns | detail straight, radial counts 8, 5, 3, then bend; decimate a copy to 20 % (arrays 5 % then 1 %); curve Res 10, Imbed 0 | Pablo | ## Quality gates - **Measured:** `stack_check`; points per level and layer count; `spike_report` per polygroup at every level; `head_ratio_check` within 5 percent of H [added]; `face_report` deltas; `mirror_deviation` and `asymmetry_report`; `alpha_check`, `dots_check`; fold `free_endpoints` = 0, `evenness` > 0; area drift under 5 percent after a sim [added]; `eye_report`; `detail_survival` ratio at least 0.5 [added]; `map_request_check`; volume before and after every stroke batch. - **Seen:** `zb_review.review` sheets in MatCap Gray (front, profile, three-quarter, below, back), raking close-ups per region and per danger zone after projection, `blurred`, `thumbnail`, `upside_down`, and the three-light SSS render (highlights keep the sharp detail). `references/critique.md` says what to look for. ## Common mistakes | Mistake | Looks like | Fix | | ------------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | | Features on an undivided egg | no facial plane in profile | cranial versus facial mass, Bridgeman masses (C2) | | Eye centered in the orbit | straight rim, lumps under the eye | eye high, margin hooked into the orbit, check from below (C3) | | HD on a deep regular stack | 6 or 7 regular levels then HD; no smoothing room; 1 billion faces | route chosen first, `stack_check` (C5) | | Projecting without nets | spikes in lids, lips, nostrils, fingers; hard lines in the displacement | `project_all` nets, interiors masked, level walk; `morph_repair`, never smooth (C19) | | Symmetry broken only in details | scars on a mirrored skull | move the big forms (C12) | | Uniform detail | plastic, noisy, no rest | region map; lids and lips clean; beat-up at three sizes | | Alpha built blind | soft, tiled, bordered on the model | test on the model while building; dots taller than any detail; hand unify (C8) | | ClayBuildup over finished pores | detail erased | Standard to enhance, Elastic for direction | | Micro detail too soft | gone in the SSS render | carve deeper; `detail_survival` | | One long wrinkle line | drawn, not skin | short connected segments across the compression (C10) | | Lone or even folds | dangling ends, ladder of copies | termination and unevenness rules (C13) | | Sim stretch or ghost collider | long polygons at contacts; cloth hugging nothing | more iterations or less gravity; Recalc after visibility changes | | Fibers lost | groom vanished, FiberUV grayed | neutral settings before a groom; groom first, Divide last | ## Handoffs - **Receives** from scenario-zbrush-sculpting: a versioned ZTL with the primary forms, polygroups per region, `stats()` and the last sheet. From scenario-zbrush-retopology-export: the clean quad base with UVs (UDIMs), interior polygroups for eyes, mouth and nostrils, and the projected stack (`rx.project_stack`). - **Delivers** to scenario-zbrush-retopology-export: the detailed ZTL with level 1 intact, eyes as separate SubTools, layers baked or the ledger, a morph target at the export level, and a map request that passes `map_request_check` (Adaptive identical across maps, SubDiv below the top, Mid equal to the renderer's zero, UDIM tile names with a dot, Merge Maps off with EXR; Costa's film values in [`references/procedures.md`](references/procedures.md) C20). UVs relaid outside ZBrush come back by Store MT, Switch, Import at the export level, no Divide, Switch MT on (Costa). To scenario-zbrush-paint-render: the same ZTL, region polygroups, grayscale approval renders and the three-light SSS request. - **Related:** scenario-zbrush-stylized, scenario-zbrush-pose-print, scenario-maya-groom (XGen). ## ZBrush 2026 notes - Not scriptable: HD entry (A key; `press_key` "does not work"), layer names (the macros rename through ZFileUtils), mask painting, Shift-smoothing, the NoiseMaker window (set once by computer use, save a .ZNM). - `Brush:Dam_Standard` does not resolve on 2026.2.1: select brushes through `zb_ops.select_brush`, run `brush_check` once per session. Orb_Cracks, RK_ and Antro_ brushes do not ship: Dam_Standard, Slash3, ClayBuildup, TrimAdaptive stand in. - Since 2026.1 custom alphas and brushes live in the Asset Directory (`alpha_library_dir()`). NoiseMaker 2.0 (2026.0) rotates a tiling alpha to follow each region's flow. Redshift inside ZBrush runs the SSS check; Substance Bridge (2026.2) is the Painter route. - Decimate a duplicate, never the master (`rx.decimate_copy`); a scripted pre-process may end the calling script [verify]. - Unconfirmed: layer intensity path, NoiseMaker blocking, scripted Run Simulation stop (live_c01, c03, c05). ## References - [`references/expert-notes.md`](references/expert-notes.md): principles by expert with timestamps, conflicts with their deciding condition. Load when planning a stage. - `references/procedures.md`: bridge procedures C1 to C20, full code, live tests. Load before writing bridge code. - `references/critique.md`: Kingslien's face checklist, Costa's evaluation, skin, creature, projection, cloth and fur rubrics. Load at every gate. - [`references/gui-paths.md`](references/gui-paths.md): palettes, hotkeys, brushes, settings for a computer-use agent. - [`references/sources.md`](references/sources.md): sources, credentials, URLs, best timestamps, revision log.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.