{"slug":"scenario-blender-hard-surface","title":"scenario-blender-hard-surface","summary":"Use when modeling hard-surface objects in Blender (sci-fi panels, props, weapons, vehicles, robots, machined or product parts) with booleans and cutters, bevel modifiers, weighted normals, or subdivision with support loops or creases. Also when shading is broken (smears on flat f","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-10-17T09:11:00.515591Z","repo":{"url":"https://github.com/scenario-labs/skills","stars":1035,"forks":129,"license":"MIT","updatedAt":"2026-10-17T05:01:44Z"},"bodyHtml":"<hr>\n<h2>name: scenario-blender-hard-surface\ndescription: \"Use when modeling hard-surface objects in Blender (sci-fi panels, props, weapons, vehicles, robots, machined or product parts) with booleans and cutters, bevel modifiers, weighted normals, or subdivision with support loops or creases. Also when shading is broken (smears on flat faces, stretching near a cut, pinching), a bevel shrinks or vanishes after a boolean, a boolean empties the mesh or leaves holes, choosing n-gons vs quads, or making a game-ready mid-poly.\"\nlicense: MIT</h2>\n<h1>Blender hard surface</h1>\n<p>Expert hard surface is controlled light on edges: every edge catches a highlight from a bevel or a support loop, flat faces read perfectly flat, and cuts stay editable until the design is settled. The experts disagree on method (n-gons and booleans vs quad cages and subdivision), not on the checks: order of operations, cutter hygiene, planar faces, a shiny matcap orbit. The agent builds the stack with the toolkit and lets the measurable gates find what a human finds by orbiting. If a sibling skill named here is missing from your available skills, ask the user to install it (<code>npx skills add scenario-labs/skills --skill &lt;name&gt;</code>); unattended, proceed from tool schemas and flag the gap.</p>\n<p><strong>REQUIRED BACKGROUND:</strong> scenario-blender-expert (execution channel, review loop, 5.2 API changes). Toolkit: <a href=\"scripts/bx_hardsurface.py\"><code>scripts/bx_hardsurface.py</code></a> (<code>import sys; sys.path.append(\"&lt;skills&gt;/scenario-blender-hard-surface/scripts\"); import bx_hardsurface as HS</code>), world-space meters, stock Blender only.</p>\n<h2>Stance (the expert delta)</h2>\n<ul>\n<li><strong>Cut first, bevel after, normals last.</strong> Booleans above the angle-limited micro bevel, Weighted Normal at the very bottom, a late Mirror above it (Ryuu [00:26:26], [00:28:37]; du Mont [00:19:46]). In 5.2 a pinned modifier keeps its relative order inside the pinned group and new modifiers land above it, so pin bottom-up: <code>HS.order_stack</code> does it and <code>HS.add_boolean</code> always lands above the tail (verified).</li>\n<li><strong>Two bevels, two jobs</strong> (du Mont [00:12:25] to [00:15:15]): a weight-limited design bevel (8 to 12 segments) on chosen silhouette edges before the cuts, and an angle-limited micro bevel (30 degrees, Harden Normals, Miter Outer Arc) after every cut, because no real edge is perfectly sharp (Gambrell, du Mont, Ryuu).</li>\n<li><strong>A bevel that shrinks after a boolean is geometry, not a setting</strong> (du Mont [00:20:18], [00:30:20]). Move the cutter well away from target edges or make it exactly coincident; keep Clamp Overlap on. Clamp Overlap limits the bevel over the whole object: a slot running 0.7 degrees off a facet edge (clamp ratio 3.8) wiped out the 3 mm bevel on a port elsewhere on the part [verified in 5.2.1]. <code>HS.clamp_culprits</code> names the cut.</li>\n<li><strong>Every wall, step, gap and rounding facet must be wider than 2x the micro bevel width</strong> [added, verified]: a 1 mm pocket floor, a 1.5 mm wall, a 2 mm groove with 1.5 mm bevels (a 3.1 mm one was clean) and a slot rounded in 30-degree facets all clamped the whole object's bevel.</li>\n<li><strong>If Harden Normals or Weighted Normal changes nothing, the face is not flat</strong> (Gambrell [00:01:06]). Flatten it; more normal modifiers will not help.</li>\n<li><strong>Pick the school by the part, not by taste.</strong> Planar machined parts with many cuts: n-gons + booleans + bevel + custom normals, about a quarter of the time and stays editable (Gambrell [00:18:01]). Continuous curvature, non-uniform roundness, a sculpt or bake high: quad cage + subdivision (rileyb3d, PzThree). Sharpen subD edges with support loops, not creases: 36,800 vs 3,600 triangles at equal quality (Lampel [00:05:28]).</li>\n<li><strong>EXACT is the solver.</strong> Gambrell's 2021 \"switch to Fast\" predates 5.x: Float left 16 non-manifold edges on a coplanar cutter where Exact and Manifold were clean [verified]. When Exact misbehaves the operand is broken (Ryuu [00:15:13]): an open cutter gave Exact 19 non-manifold edges and Manifold silently skipped the cut [verified].</li>\n<li><strong>Judge in a shiny matcap and close up on every cut</strong> (Gambrell [00:00:33], rileyb3d [00:06:32]). Whole-object sheets hide smears and shrunken bevels.</li>\n</ul>\n<h2>Establish first</h2>\n<table>\n<thead>\n<tr>\n<th>Input</th>\n<th>Why it changes the plan</th>\n<th>Default when silent</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Purpose</td>\n<td>render: 3+ micro segments (Gambrell); game mid-poly: 1 to 2 segments, triangulated, custom normals; bake: subD or boolean high + separate low</td>\n<td>render still</td>\n</tr>\n<tr>\n<td>Real size</td>\n<td>bevel widths are absolute: du Mont uses about 3 mm on a small robot body, 1 mm on small parts, 0.1 mm on a collar</td>\n<td>model at real size; if unknown, micro width 0.5% of the largest dimension [added]</td>\n</tr>\n<tr>\n<td>Surface</td>\n<td>mostly planar vs curved vs organic-mechanical</td>\n<td>School A for planar parts, B for curved shells, hybrid when mixed</td>\n</tr>\n<tr>\n<td>Budget and engine</td>\n<td>Augusto: 15k tris, one 2K map; a quad-only pipeline forces School B (Gambrell [00:24:35])</td>\n<td>no budget: report tris</td>\n</tr>\n<tr>\n<td>Later stages</td>\n<td>sculpted damage or multires detail needs a quad cage (PzThree); rigging needs collapsed copies (du Mont)</td>\n<td>none</td>\n</tr>\n<tr>\n<td>Symmetry</td>\n<td>mirror the target when the part is symmetric; mirror the cutter around the target when only the cut is (Gambrell [00:14:53])</td>\n<td>ask the reference</td>\n</tr>\n</tbody>\n</table>\n<h2>Workflow</h2>\n<ol>\n<li><strong>Blockout at real size.</strong> Primitives or bmesh, then <code>HS.apply_scale(ob)</code> (keeps children in place, flips winding on negative scale). Check proportions against a human-scale box through a long lens (du Mont [00:10:01]). GATE: scale is 1 on every object with a bevel, solidify or array; silhouette sheet reads (<code>bx_review.review(..., modes=(\"silhouette\",))</code>).</li>\n<li><strong>Pick the school and build the base stack.</strong>\n<ul>\n<li>A: design edges with <code>HS.find_edges(ob, min_angle=80, inside=...)</code> + <code>HS.set_edge_attr(ob, edges, 1.0)</code>, then <code>HS.hard_surface_stack(ob, micro_width=..., design_width=...)</code>.</li>\n<li>B: quad cage in bmesh (few control points, square quads, rileyb3d / PzThree), then <code>HS.subd_stack(ob, support_width=...)</code> (Bevel Weight, even segments, profile 1.0, Subdivision with UV Smooth Keep Corners) or explicit <code>HS.support_loops</code>.\nGATE: <code>HS.stack_problems(ob) == []</code>; A has custom normals on the evaluated mesh.</li>\n</ul>\n</li>\n<li><strong>Cut (School A).</strong> <code>HS.make_cutter(target, \"BOX\"|\"CYLINDER\"|\"SLOT\", size, location, ...)</code> builds a cutter that meets the checklist (scale 1, smooth, Cutters collection only, wire, hidden from render, parented, EXACT, wired above the tail). <code>HS.as_cutter</code> for custom shapes, <code>HS.slice_boolean</code> for Slice. Rules: extend past every surface entered; keep cutter edges far from target edges or exactly on them; offset a round cutter by half a segment against a target with the same count (same phase gave 10 non-manifold edges and streaks [verified]; <code>make_cutter</code> default); round cutter corners in facets well under the bevel angle (4+ segments per 90 degrees). GATE after each cut: <code>HS.cutter_report(c, target)</code> clean, <code>HS.clamp_probe(target)[\"ratio\"] &lt; 0.1</code>, a <code>HS.closeup</code> of the cut looked at.</li>\n<li><strong>Design pass.</strong> Cluster details, leave calm areas, align with neighboring features (Ryuu [00:28:37], Gambrell [00:34:15]); hide perfect intersections behind a collar (du Mont [00:39:36]); union parts that must share a bevel (Gambrell [00:18:57]). GATE: a review sheet shows intent, not noise.</li>\n<li><strong>Shading pass.</strong> <code>r = HS.shading_audit(ob); HS.verdict(r)</code>, then <code>HS.review([ob], out, modes=(\"matcap\",\"reflect\",\"hsgrey\",\"wire\"))</code> and closeups. Triage with <a href=\"references/critique.md\"><code>references/critique.md</code></a>: smear on flat faces, non-planar faces, cuts on curvature (render first: Harden + WN already shaded 24/48-segment cylinder cuts cleanly; on a sphere <code>HS.transfer_normals</code> cut the normal error p95 from 2.79 to 0.34 degrees [verified]), mirror seam (<code>HS.mirror_seam_fix</code>). GATE: verdict empty or every remaining line explained, and the closeups agree.</li>\n<li><strong>School B detail and pinch check.</strong> Even bevel segments, profile 1.0, Keep Corners (Lampel [00:11:15]); brace both sides of a ridge equally (rileyb3d [00:11:08]); outset a border ring around inset circles (PzThree [00:42:25]); turn subdivision off to debug a cage artifact (Gambrell [00:14:44]). GATE: <code>r[\"subd_cage\"]</code> quads near 100%, no long rectangles on a cage that will be sculpted, shiny-matcap closeups free of pinches.</li>\n<li><strong>Game or export.</strong> Mid-poly: <code>g, tris = HS.game_version(ob, micro_segments=1)</code> (Triangulate with keep custom normals after WN; stays in place because it shares the cutters); glTF kept the custom normals 100% [verified]. Baked asset: the low poly and bake go to scenario-blender-retopology and scenario-blender-uv-baking. Rig or export: <code>HS.collapse(ob)</code> on a copy (du Mont [01:07:29]). GATE: tri count within budget, triangles only, matcap closeup of the game copy.</li>\n<li><strong>Deliver</strong> renders, the verdict, tris per object, and what was not verified.</li>\n</ol>\n<h2>Numbers</h2>\n<table>\n<thead>\n<tr>\n<th>Value</th>\n<th>Setting</th>\n<th>Relative to / source</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Micro bevel</td>\n<td>angle 30 degrees, 3 segments render, 1 to 2 game, Harden Normals, Miter Outer Arc</td>\n<td>Gambrell [00:17:20], du Mont [00:14:40]</td>\n</tr>\n<tr>\n<td>Micro width</td>\n<td>about 3 mm body, 1 mm small parts, 0.1 mm collar</td>\n<td>real size, du Mont</td>\n</tr>\n<tr>\n<td>Design bevel</td>\n<td>Limit Method Weight, 8 to 12 segments</td>\n<td>du Mont [00:13:32]</td>\n</tr>\n<tr>\n<td>Big rolling curve</td>\n<td>46 to 50 segments; cutter edge bevels about 8 to 10</td>\n<td>Ryuu [00:12:53], [00:05:42]</td>\n</tr>\n<tr>\n<td>Min feature</td>\n<td>wall, step, groove, facet width &gt; 2 x micro width</td>\n<td>[added, verified]</td>\n</tr>\n<tr>\n<td>Clamp probe</td>\n<td>displacement over 10% of width = clamping</td>\n<td>[added]</td>\n</tr>\n<tr>\n<td>Solver</td>\n<td>EXACT; MANIFOLD for speed with manifold operands; FLOAT only to diagnose</td>\n<td>verified in 5.2.1</td>\n</tr>\n<tr>\n<td>Round cutters</td>\n<td>vertex count set at creation (40 for screw holes); half-segment phase vs target</td>\n<td>Ryuu [00:29:43]; [verified]</td>\n</tr>\n<tr>\n<td>Bevel before Subdivision</td>\n<td>2 or 4 segments, profile 1.0, Weight, UV Smooth Keep Corners</td>\n<td>Lampel [00:08:06], [00:11:15]</td>\n</tr>\n<tr>\n<td>Creases</td>\n<td>fine for small details or rounding a coarse cylinder at level 1; 10x tris for primary edges</td>\n<td>Lampel, du Mont [00:52:18]</td>\n</tr>\n<tr>\n<td>SubD cage</td>\n<td>circle 18 verts body, 36 on a denser part; square quads</td>\n<td>rileyb3d [00:01:34], PzThree [00:03:15]</td>\n</tr>\n<tr>\n<td>Mirror seam fix</td>\n<td>bevel Face Strength Affected + WN Face Influence, weight 100</td>\n<td>Gambrell [00:06:57]</td>\n</tr>\n<tr>\n<td>Game budget</td>\n<td>15k tris, one 2K map, animated parts separate</td>\n<td>Augusto [00:11:48]</td>\n</tr>\n<tr>\n<td>Cost measured</td>\n<td>case 1,984 tris (A) vs 4,608 (B, level 2); barrel 7,308 (A), 25,728 (B), 3,716 (A game)</td>\n<td>test_03 [added]</td>\n</tr>\n</tbody>\n</table>\n<h2>Quality gates</h2>\n<p><strong>In code</strong> (all in <code>HS.shading_audit</code>, readable through <code>HS.verdict</code>): scale applied; <code>stack_problems</code> empty; custom normals present when expected; 0 non-manifold edges; 0 flat-shaded faces; 0 non-planar base faces (tolerance 1e-4 x size); no smear (corner normal leaning over 3 degrees on a flat face [added]); no hard edge (over 45 degrees) smoothed across; clamp ratio under 0.1; every boolean changes the mesh; cutter reports clean (coplanar, near-parallel, thin wall, narrow gap, near-miss, rounding facets, pokes out, hygiene). School B: quads %, quad aspect, crease length. Game: <code>HS.tri_count</code>, triangles only.</p>\n<p><strong>Visual:</strong> <code>HS.review</code> rows matcap (forms), reflect (<code>check_reflection_horizontal</code>: stripes run continuous over bevels and rounds), hsgrey (bevel highlights), wire (real boolean topology); views front, threequarter, top, low. Then one <code>HS.closeup(ob, point, radius, ...)</code> per cut in <code>shiny</code> (metal_carpaint). Look for gradient smears on flat faces, a bevel highlight broken or narrower at a cut, stretch at cut corners on curves, a butterfly on a mirror seam, subD pinches, facets on cut walls. A straight-down shiny view saturates white: use matcap or hsgrey from the top.</p>\n<h2>Common mistakes</h2>\n<table>\n<thead>\n<tr>\n<th>Mistake</th>\n<th>What it looks like</th>\n<th>Fix</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Boolean below the micro bevel</td>\n<td>cut edges razor sharp, rest beveled</td>\n<td><code>HS.order_stack(ob)</code></td>\n</tr>\n<tr>\n<td>Weighted Normal added after pinning the bevel</td>\n<td>WN lands above the bevel, smears return</td>\n<td>pin bottom-up (<code>order_stack</code>)</td>\n</tr>\n<tr>\n<td>Cutter near but not on a target face or edge</td>\n<td>whole-object bevel narrower, thin steps</td>\n<td>move clearly away or snap exactly; <code>cutter_report</code>, <code>clamp_culprits</code></td>\n</tr>\n<tr>\n<td>Groove or wall thinner than 2x bevel</td>\n<td>clamped bevels everywhere</td>\n<td>widen the feature or thin the bevel</td>\n</tr>\n<tr>\n<td>Round cutter in phase with the target facets</td>\n<td>streaks, black patches, non-manifold edges</td>\n<td>half-segment phase (default in <code>make_cutter</code>)</td>\n</tr>\n<tr>\n<td>Rounded cutter corners at 30-degree facets</td>\n<td>micro bevel catches facet edges, clamps</td>\n<td>more segments (<code>beveled_narrow_facet_edges</code>)</td>\n</tr>\n<tr>\n<td>Flat-shaded cutter</td>\n<td>facets on the cut walls</td>\n<td>cutters smooth (<code>make_cutter</code> does it)</td>\n</tr>\n<tr>\n<td>\"Switch to Fast\"</td>\n<td>non-manifold result on coplanar faces</td>\n<td>EXACT; repair the operand instead</td>\n</tr>\n<tr>\n<td>Unapplied scale</td>\n<td>uneven bevel widths</td>\n<td><code>HS.apply_scale</code></td>\n</tr>\n<tr>\n<td>Stacking normal fixes on a non-planar face</td>\n<td>stretch that nothing fixes</td>\n<td><code>HS.flatten_faces</code></td>\n</tr>\n<tr>\n<td>Data Transfer placed before WN, or scoped by a vertex group</td>\n<td>no effect / cut walls shaded like the skin</td>\n<td><code>HS.transfer_normals</code> (after WN, distance-scoped)</td>\n</tr>\n<tr>\n<td>du Mont's support cut placed through a round cut</td>\n<td>rim edges halved, clamp 0 to 1.2 [verified]</td>\n<td>only where <code>sliver_report</code> shows boolean slivers; measure before and after</td>\n</tr>\n<tr>\n<td>Creases on primary subD edges</td>\n<td>soft, pinched, heavy</td>\n<td>support loops or bevel before subdivision</td>\n</tr>\n<tr>\n<td>Moving a game copy or slice</td>\n<td>cuts vanish (cutters stay with the original)</td>\n<td>keep it in place, or <code>HS.collapse</code> then move</td>\n</tr>\n</tbody>\n</table>\n<h2>Blender 5.2 notes</h2>\n<ul>\n<li>Auto Smooth is gone (4.1): <code>bpy.ops.object.shade_auto_smooth</code> adds a pinned Smooth by Angle node modifier (works headless with <code>temp_override</code>); WN, Harden Normals and Data Transfer need no Auto Smooth.</li>\n<li>Boolean solvers: <code>'EXACT'</code> (default), <code>'MANIFOLD'</code> (4.5), <code>'FLOAT'</code> (the old Fast, renamed 5.0).</li>\n<li>Bevel weights and creases are attributes <code>bevel_weight_edge</code> / <code>crease_edge</code> (4.0); the Bevel modifier reads any attribute through <code>edge_weight</code>.</li>\n<li><code>ob.modifiers.move(i, j)</code> silently refuses moves that involve pinned modifiers; <code>modifier_move_to_index</code> returns CANCELED. Unpin, move, re-pin bottom-up.</li>\n<li><code>bmesh.ops.bevel</code> defaults are <code>segments=0, profile=0.0</code>: always pass both.</li>\n<li>EXACT gives cutter-made vertices the target's vertex-group weights (verified), so vertex groups cannot keep a Data Transfer off cut walls.</li>\n<li>Bool Tool, LoopTools, Extra Objects are extensions (4.2); LoopTools Circle/Flatten/Space are built in (<code>mesh.circularize</code>, <code>mesh.flatten</code>, <code>mesh.space_edge_loops_evenly</code>); Relax and Round Cube are not.</li>\n</ul>\n<h2>References</h2>\n<ul>\n<li><a href=\"references/procedures.md\"><code>references/procedures.md</code></a>: full tested code for the three parts (panel, case both schools, barrel both schools), every audit case, game routes. Load before scripting.</li>\n<li><code>references/critique.md</code>: the rubric and triage table to judge a render and a verdict. Load at every shading pass.</li>\n<li><a href=\"references/expert-notes.md\"><code>references/expert-notes.md</code></a>: the experts' principles, disagreements and deciding conditions, with timestamps. Load when a choice is not covered here.</li>\n<li><a href=\"references/sources.md\"><code>references/sources.md</code></a>: the nine videos, who, what each is best for, best timestamps.</li>\n</ul>\n","files":[{"path":"references/critique.md","sizeBytes":14411,"isText":true},{"path":"references/expert-notes.md","sizeBytes":17875,"isText":true},{"path":"references/procedures.md","sizeBytes":25309,"isText":true},{"path":"references/sources.md","sizeBytes":7412,"isText":true},{"path":"scripts/bx_hardsurface.py","sizeBytes":69529,"isText":true},{"path":"SKILL.md","sizeBytes":18295,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-10-17T09:13:04.317723Z","sha256":"3D8D6E1F9E499CA179B078315C62E4B22B65233AAD8B7FF4E0668E4FE07C487C","sizeBytes":51709},"review":null,"source":{"repositoryUrl":"https://github.com/scenario-labs/skills","path":"skills/dcc/blender/scenario-blender-hard-surface","license":"MIT","commit":"dc592af7139ffacfe8fdae7dc7efa80f4dd09be3","subtreeSha":"3E48C9D3E1C627F2B5289D4E508870D4EB6D98E3DB7A4E622D7A876575066583","lastSyncedAt":"2026-10-17T09:10:46.286688Z"},"reviewedAt":"2026-10-17T09:16:43.686057Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/scenario-labs/skills/tree/main/skills/dcc/blender/scenario-blender-hard-surface"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install scenario-labs-skills@llmmart"},{"target":"git","command":"git clone https://github.com/scenario-labs/skills.git"}]}