Basketball Hoop

showcase/basketball-hoop/

A procedural in-ground basketball goal on a patch of driveway court — a square gooseneck pole swept in one piece with welded bend webs, its plate sunk and bolted into a two-pour concrete pad with a sealed expansion joint, a braced board in an aluminium frame, a breakaway regulation rim, a knotted twelve-loop net hung on the rim hooks, faded court paint set into the slabs and two pebbled basketballs resting on the concrete — through UVs, bake, LOD, convex collider, and Unity glTF, asserting recomputed budgets rather than an API contract.

Rendered headless by the showcase piece itself. Select it to enlarge.

witnesses Recomputed: 19564 tris, twelve materials with 2546 steel, 54 board, 32 board-paint, 970 rim, 2784 net, 382 pad, 144 aluminium, 108 concrete, 504 court-line, 12 key, 1024 ball and 2310 seam faces, UVs in 0..1 with zero AABB overlap, outer AABB 5.200×6.600×4.050 m, zmin 0, hygiene 0 including zero coplanar cross-shell pairs, both braces 9.5 mm into the collar and 21.0 mm into the lower rail, all 12 net loops threaded through their hook eyes (worst 7.0 mm from the eye centre against a 9 mm clear radius), rim top 3.050 m over the court, inside diameter 0.457 m, inner edge 0.151 m off the board, tilt 0.000°, both balls radius 0.1194 m bearing 2.0 mm into their slabs, all four anchor bolts 70 mm into the slab and 62 mm over the plate, all 18 paint shells 1.0–2.2 mm proud and 1.4–3.0 mm sunk into their slabs, one connected assembly of 209 shells, LOD ratios in band, convex collider 44 tris, non-empty glTF. --skip-decimate exits 9; --stray-vert 15; --lift-z 16; --short-brace 17; --drop-net 18; --tilt-rim 19; --loose-pad 20; --float-ball 21; --short-bolts 22; --float-paint 23.

category Sports

tags mesh export showcase

blender --background --python showcase/basketball-hoop/basketball_hoop.py --

A showcase piece, not an example, and the first in the sports category. It builds a procedural in-ground basketball goal standing on a patch of driveway court:

The pole, gooseneck and mast are one sweep of a rounded-square section, because a bent bar is one bar. The straight run of the gooseneck is solved from the mast's offset (gooseneck_path()), so the mast always lands behind the standoffs. Each bend web is outlined from the bend's own inner arc (bend_webs()), a bite inside the tube. Each brace is a tube between two named stations. Its foot sits in the middle of the collar wall and its head sits inside the lower rail. Pad, collar, clamps and cap are the pole section grown by an offset, so every sleeve is a true parallel of it.

The rim follows regulation: the top of the ring is 3.05 m above the court surface, its inside diameter is 0.457 m, and its inner edge sits 0.151 m off the board face. The bottom line of the shooter's square has its top edge level with the rim. Each net loop is a torus threaded across the hook eye. Its cord passes through the eye and bears 1.5 mm into the hook's lower inside, so the loop hangs on the hook rather than floating near it.

The court markings are a driveway layout, not regulation: a 2.7 m lane, the free-throw line 2.98 m off the board face, a 4.2 m arc about the basket centre. They are scaled to the pad, so the key, circle and arc all fit on it.

Every paint line is a strip sunk into its own slab and standing proud of it. Groups that overlap never share a plane: each has its own top and sink (baseline 2.2 / 2.6 mm, lane lines 1.8 / 2.0, free-throw line 2.2 / 2.6, key fill 1.0 / 1.4, lane marks and circle 1.4 / 3.0, arc 1.8 / 2.0). Lines stop 8 mm short of the joint gap, and the free-throw circle is 6 mm wider than the lane so its ends are clear of the lane lines' side faces. The base plate is sunk 4 mm into the slab rather than laid on it, so its underside never lands on the slab top. Each ball is a UV sphere bearing 2 mm into the slab; its four seams are tori just under its surface, turned so none runs through the contact point.

Paint on the board is thin annuli sunk 1 mm into the board. They stand proud by different amounts (border 0.8 mm, square 1.2 mm), and the border is 44 mm wide so its inner edge clears the square's bottom line. The two bolt heads on each clamp face stand 1 mm apart so their caps never share a plane.

Bevels run once per material with material= set. On the first run, without it, the board's chamfer faces took slot 0 and the board shell classified as steel.

Shading follows what each part is. Round stock (ring, cord, balls, braces) is smooth-shaded. Chamfers, material boundaries and bends over 35° stay sharp, and so does a flat face meeting a much narrower facet, so the pole's flats read flat and its corner fillets read round. The steel is near-black satin powder coat and the frame satin aluminium, each with a studio reflection term in the material (after espresso-machine), so neither reads as grey plastic on the dark stage.

It asserts budget conformance of the generated result. It does not witness an API contract. "It rendered without error" is not a check.

Composes skills mesh-editing-and-bmesh, bake-high-to-low, depsgraph-and-evaluated-data, engine-export-presets, and snippets bake_normal_high_to_low.py, setup_bake_target_image.py, lod_chain.py / decimate_to_budget.py, convex_hull_collider.py, export_preset_unity.py (helpers copied inline, not imported).

Intended size: 5.2 × 6.6 m court pad; 1.83 × 1.05 m board with its bottom 2.90 m above the court; pole axis 1.22 m behind the board face. The outer AABB is 5.200 × 6.600 × 4.050 m; the pad sets the footprint, and the pole cap on the 100 mm slab sets the height. The convex collider wraps the pad and the hoop together (44 tris).

Budgets#

Every budget is declared as a named constant. Every gate recomputes its value from the mesh, materials, UVs, evaluated LOD, collider, or export file.

AxisDeclaredMeasured (5.2.1)
Base triangles18500–2050019564
LOD1 ratio0.32–0.62 of base0.4984
LOD2 ratio0.10–0.35 of base0.2183
Materialsexactly 12 distinct; face floors ≥2200 steel, ≥45 board, ≥28 board paint, ≥850 rim, ≥2400 net, ≥330 pad, ≥120 aluminium, ≥90 concrete, ≥440 court line, ≥12 key, ≥900 ball, ≥2000 seam12 slots; 2546 / 54 / 32 / 970 / 2784 / 382 / 144 / 108 / 504 / 12 / 1024 / 2310
UVsin 0..1, AABB overlap ≤ 1e-5in range, overlap 0
Outer AABB(5.200, 6.600, 4.050) m ± 0.01(5.2000, 6.6000, 4.0500), zmin 0
Collider tris≤ 22044
Exportwritten, size > 0, removed after measuring1454192 / 1454192 / 1454180 bytes (4.5.11 / 5.1.2 / 5.2.1)

DECIMATE COLLAPSE triangle counts are not identical across Blender series, so the LOD gate is a ratio band, not an exact count. Bake pixels are stochastic, so the bake gate is has_data plus operator FINISHED, not byte-identity. Construction uses no RNG; two default runs print identical measurements.

Hygiene#

Recomputed from the generated mesh, not asserted about the script.

AxisDeclaredMeasured
Non-manifold edges00
Loose verts / edges0 / 00 / 0
Doubles merged at 1e-500
Zero-area faces00
N-gons00
Coplanar cross-shell face pairs (KD range 0.05 m, plane ε 1e-4)00
Grounded: zminwithin 1e-4 of 00.0000

Joint fit, net seat, rim, court and assembly#

AxisDeclaredMeasured
Brace bite: deepest vertex of each of the 2 braces inside the collar / inside the lower rail (signed distance to the host shell)≥ 0.006 m each, exactly 2 braces0.00950 / 0.02098
Net seat: for each of the 12 hooks, distance from the eye centre to the nearest net loop's cord circle (both fitted from the mesh by PCA)≤ 0.009 m (the eye's clear radius), 12 hooks, 12 loopsworst 0.00700 (24 strands, 72 knots)
Rim top over the court (ring shell max z minus the top of the slab under it)3.05 m ± 0.0103.0500
Rim inside diameter (min in-plane radius × 2)0.457 m ± 0.0040.4570
Rim inner edge to board face0.151 m ± 0.0050.1510
Rim tilt (fitted ring axis vs vertical)≤ 0.5°0.000°
Ball seat: each of the 2 balls' fitted radius, and its lowest vertex below the top of the slab under its centreradius 0.1194 m ± 0.002; sink 0.0005–0.004 m0.1194 / 0.00200, 0.1194 / 0.00200
Anchor bolts: each of the 4 bolts' foot below the top of the slab it stands in, and its head above the plate≥ 0.050 m embedded, ≥ 0.030 m proudworst 0.0700 / 0.0620
Court paint: each of the 18 paint shells against the slab under its centre — top above the slab, bottom below ittop 0.0005–0.003 m, sink 0.001–0.004 mtop 0.00100–0.00220, sink 0.00140–0.00300
One connected assembly (union of shells whose BVH trees overlap)1 component1 (209 shells)

Falsifiers#

Each falsifier violates one named budget. Every one was run on 5.2.1 and exited its declared code.

FlagBudget violatedExit
--skip-decimateLOD1 ratio band (measured 1.0000)9
--stray-vertloose vertex count is 0 (measured 1)15
--lift-zbounding box zmin is 0 (measured 0.05000)16
--short-bracebrace bite (braces stop 60 mm short of the rail: −0.02150 m)17
--drop-netnet seat (net lowered 25 mm: worst eye-to-loop 0.03200 m)18
--tilt-rimrim level (rim assembly tilted 3° about the flange: 3.000°)19
--float-ballball seat (both balls lifted 20 mm: sink −0.01800 m, 3 components)21
--short-boltsanchor bolts through the plate into the slab (feet stop in the plate: embed −0.0125 m)22
--float-paintcourt paint set into the slab (paint lifted 6 mm: sink −0.00460 to −0.00300 m, top 0.00700–0.00820 m)23
--loose-padone connected assembly (pad bore 6 mm clear of the pole: 2 components)20

--drop-net and --tilt-rim move whole sub-assemblies. The net drops without its hooks, and the rim tilts with its hooks and net about the flange, so only the budget each one targets can see the change. --drop-net, --float-ball and --float-paint also split the assembly, so the net-seat, ball-seat and paint checks all run before the component check (20). --short-bolts keeps each bolt in the plate, so it stays connected and only the embedment fails. --loose-pad needs a part held by one joint only: the pad (with its flap) touches nothing but the pole.

Run#

blender --background --python basketball_hoop.py --
blender --background --python basketball_hoop.py -- --skip-decimate
blender --background --python basketball_hoop.py -- --stray-vert
blender --background --python basketball_hoop.py -- --lift-z
blender --background --python basketball_hoop.py -- --short-brace
blender --background --python basketball_hoop.py -- --drop-net
blender --background --python basketball_hoop.py -- --tilt-rim
blender --background --python basketball_hoop.py -- --float-ball
blender --background --python basketball_hoop.py -- --short-bolts
blender --background --python basketball_hoop.py -- --float-paint
blender --background --python basketball_hoop.py -- --loose-pad
blender --background --python basketball_hoop.py -- --output hoop.png

Smoke passes no flags.

The hero turns the piece HERO_YAW_DEG (−172°) and shoots it with a 70 mm lens from 20 m, a little above the board, so the court runs toward the camera and the key, circle and arc read at thumbnail size while the board and net read face-on. The long lens keeps the near corner of the pad from swelling. The key light's spread keeps it on the hoop and court instead of the near floor, and a warm wedge washes the back wall on the right.

Exit codes#

File-local. 9 is a valid check code. 10 is reserved for gallery_framing.check_framing on the --output path. 15–19 are the hygiene and joint-fit family. 20–23 are file-local. 24 is the asset-quality floor on the render path: check_asset_quality returns 11, which this piece already spends on the collider ceiling, so the call site remaps it.

CodeMeaning
0Success
1Uncaught exception (FATAL wrapper)
2argparse / usage
3Mesh did not build / no UV layer
4Base triangle count outside range
5Material count ≠ 12 distinct slots, or a face-count floor missed
6UVs outside 0..1
7UV AABB overlap above tolerance
8World AABB off declared outer size
9LOD ratio band (--skip-decimate lands here)
10Framing gate (render path only)
11Collider triangle count above ceiling
12Bake did not finish or image has no data
13Export file missing or empty
14--output produced no file
15Mesh hygiene: loose, non-manifold, zero-area, doubles, n-gons, coplanar cross-shell pairs
16Not grounded: bounding box zmin off 0
17Brace bite: a brace end not inside the collar or the lower rail, or not exactly two braces (--short-brace)
18Net seat: a hook with no net loop threaded through its eye, or not 12 hooks and 12 loops (--drop-net)
19Rim off regulation: height over the court, inside diameter, board gap or level (--tilt-rim)
20Assembly splits into more than one connected component (--loose-pad)
21Ball seat: not 2 balls, a radius off size 7, or a ball not bearing 0.5–4 mm into its slab (--float-ball)
22Anchor bolts: not 4, a foot less than 50 mm into the slab, or a head less than 30 mm over the plate (--short-bolts)
23Court paint: not 18 shells, or a shell's top or sink outside its band against its slab (--float-paint)
24Asset-quality floor (render path only; remapped from 11)

Source

showcase/basketball-hoop/basketball_hoop.py View the script on GitHub →
A basketball goal on a concrete court pad: padded square gooseneck pole, framed backboard, orange rim and white net over a faded green key and arc, two basketballs on the concrete.