From 9fb0ca833bd3e64f0ad0cb9bbc7276f9dd61e71a Mon Sep 17 00:00:00 2001 From: type-two Date: Mon, 3 Aug 2026 21:32:26 +1000 Subject: [PATCH] =?UTF-8?q?trolley=20=E2=80=94=20abandoned=20shopping=20tr?= =?UTF-8?q?olley=20(53=20props)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wire basket via translucent panels, galv frame, casters. Grinds: handle bar + front rim. As found in every Brisbane park. Co-Authored-By: Claude Opus 5 --- manifest.json | 46 +++++++++++++++++++++++++++++++++++++++++++ props/trolley.glb | Bin 0 -> 21424 bytes tools/prop_author.py | 44 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 props/trolley.glb diff --git a/manifest.json b/manifest.json index d92e594..eee6729 100644 --- a/manifest.json +++ b/manifest.json @@ -1865,6 +1865,52 @@ "collider": { "type": "none" } + }, + { + "id": "trolley", + "src": "props/trolley.glb", + "category": "furniture", + "desc": "abandoned shopping trolley \u2014 gap bait", + "size": [ + 0.936, + 1.022, + 0.552 + ], + "tris": 384, + "grinds": [ + { + "a": [ + -0.52, + -0.25 + ], + "b": [ + -0.52, + 0.25 + ], + "ya": 1.0, + "yb": 1.0, + "kind": "rail" + }, + { + "a": [ + 0.38, + -0.26 + ], + "b": [ + 0.38, + 0.26 + ], + "ya": 0.95, + "yb": 0.95, + "kind": "rail" + } + ], + "collider": { + "type": "box", + "hw": 0.52, + "hd": 0.3, + "h": 1.0 + } } ] } \ No newline at end of file diff --git a/props/trolley.glb b/props/trolley.glb new file mode 100644 index 0000000000000000000000000000000000000000..9fae1317cb4ed34534f7ccef893e8cb6ee17a638 GIT binary patch literal 21424 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 literal 0 HcmV?d00001 diff --git a/tools/prop_author.py b/tools/prop_author.py index c4f4df5..702545c 100644 --- a/tools/prop_author.py +++ b/tools/prop_author.py @@ -750,6 +750,48 @@ def bleachers(): p.collider = {'type': 'box', 'hw': L / 2, 'hd': 0.75, 'h': 0.92} return p +def trolley(): + """Shopping trolley. Lives in every Brisbane park whether the park likes it + or not. Wire basket faked with translucent panels; handle bar grinds.""" + p = Prop('trolley', 'furniture', 'abandoned shopping trolley — gap bait') + WIRE = Mat('trolley_wire', tint=(0.72, 0.75, 0.78, 0.45), rough=0.4, + metal=0.8, double=True) + # basket: tapered shell, small base -> wide mouth + b = {'x0': -0.28, 'x1': 0.30, 'z': 0.20, 'y': 0.50} + t = {'x0': -0.44, 'x1': 0.38, 'z': 0.26, 'y': 0.95} + q = lambda a, bb, c, d: quad(a, bb, c, d) + p.add(WIRE, q((b['x0'], b['y'], -b['z']), (b['x1'], b['y'], -b['z']), + (t['x1'], t['y'], -t['z']), (t['x0'], t['y'], -t['z']))) + p.add(WIRE, q((b['x1'], b['y'], b['z']), (b['x0'], b['y'], b['z']), + (t['x0'], t['y'], t['z']), (t['x1'], t['y'], t['z']))) + p.add(WIRE, q((b['x1'], b['y'], -b['z']), (b['x1'], b['y'], b['z']), + (t['x1'], t['y'], t['z']), (t['x1'], t['y'], -t['z']))) + p.add(WIRE, q((b['x0'], b['y'], b['z']), (b['x0'], b['y'], -b['z']), + (t['x0'], t['y'], -t['z']), (t['x0'], t['y'], t['z']))) + p.add(WIRE, box(( b['x0'] + b['x1']) / 2, b['y'], 0, + b['x1'] - b['x0'], 0.015, 2 * b['z'])) + # top rim + handle + frame + rim = [(t['x0'], t['y'], -t['z']), (t['x1'], t['y'], -t['z']), + (t['x1'], t['y'], t['z']), (t['x0'], t['y'], t['z']), + (t['x0'], t['y'], -t['z'])] + p.add(GALV, tube(rim, 0.016, seg=6)) + p.add(GALV, tube([(-0.52, 1.0, -0.25), (-0.52, 1.0, 0.25)], 0.022, seg=8)) + for sz in (-1, 1): + p.add(GALV, tube([(-0.42, 0.05, sz * 0.22), (-0.52, 0.98, sz * 0.25)], + 0.016, seg=6)) + p.add(GALV, tube([(0.34, 0.05, sz * 0.22), (t['x1'], t['y'] - 0.02, sz * t['z'])], + 0.016, seg=6)) + p.add(GALV, tube([(-0.42, 0.08, sz * 0.22), (0.34, 0.08, sz * 0.22)], + 0.014, seg=6)) + for wx in (-0.42, 0.34): # casters + for sz in (-1, 1): + p.add(STEEL, tube([(wx, 0.045, sz * 0.22 - 0.015), (wx, 0.045, sz * 0.22 + 0.015)], + 0.045, seg=8)) + p.grind(-0.52, -0.25, -0.52, 0.25, 1.0, 1.0, 'rail') # handle bar + p.grind(t['x1'], -t['z'], t['x1'], t['z'], t['y'], t['y'], 'rail') # front rim + p.collider = {'type': 'box', 'hw': 0.52, 'hd': 0.3, 'h': 1.0} + return p + def drain_grate(): p = Prop('drain_grate', 'dressing', 'flush drainage grate') p.add(Mat('grate', tex='steel_grate', uv=0.9, rough=0.5, metal=0.6), @@ -793,7 +835,7 @@ BUILDERS = [ 'inclined manual pad', graf=True), pyramid, euro_gap, volcano, roller, jersey_barrier, parking_block, hydrant, pool_edge, - fence_chain, floodlight, bleachers, drain_grate, + fence_chain, floodlight, bleachers, drain_grate, trolley, ] def main():