Files
sow-module-tilesets/twu01/generator/build.py
T
2026-07-05 01:46:27 +02:00

167 lines
5.3 KiB
Python

"""Build the Westgate_Urban_Foundation_MVP Blender scene (run in Blender 4.0.2).
blender-4.0.2 --background --python build.py -- out.blend
Realises tiles.py: one Collection per tile (VIS_/WOK_/BLOCK_ sub-objects + edge
custom props), a TILE_EDGE_METADATA text block, and the two preview assemblies.
Geometry is intentionally simple (KICKOFF.md: a geometry/grammar prototype).
"""
import json
import os
import sys
import bpy
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import tiles # noqa: E402
def get_mat(name):
m = bpy.data.materials.get(name)
if m:
return m
m = bpy.data.materials.new(name)
r, g, b = tiles.COLORS.get(name, (0.5, 0.5, 0.5))
m.diffuse_color = (r, g, b, 1.0) # viewport (solid) colour
m.use_nodes = True
bsdf = m.node_tree.nodes.get("Principled BSDF")
if bsdf:
bsdf.inputs["Base Color"].default_value = (r, g, b, 1.0)
return m
def build_vis(spec):
"""Subdivided 10x10 plane (100 quads): per-face material via paint(), per-vertex Z."""
n, step = 10, 1.0
paint = tiles.make_paint(spec)
verts, vidx = [], {}
def vi(i, j):
if (i, j) not in vidx:
x, y = -5 + i * step, -5 + j * step
vidx[(i, j)] = len(verts)
verts.append((x, y, tiles.height(spec, x, y)))
return vidx[(i, j)]
faces, face_mat, mats, midx = [], [], [], {}
for i in range(n):
for j in range(n):
faces.append((vi(i, j), vi(i + 1, j), vi(i + 1, j + 1), vi(i, j + 1)))
cx, cy = -5 + (i + 0.5) * step, -5 + (j + 0.5) * step
mn = paint(cx, cy)
if mn not in midx:
midx[mn] = len(mats)
mats.append(mn)
face_mat.append(midx[mn])
me = bpy.data.meshes.new("VIS_" + spec["code"])
me.from_pydata(verts, [], faces)
for mn in mats:
me.materials.append(get_mat(mn))
for poly, mi in zip(me.polygons, face_mat):
poly.material_index = mi
me.update()
ob = bpy.data.objects.new("VIS_" + spec["code"], me)
n_, e_, s_, w_ = spec["edges"]
ob["edge_N"], ob["edge_E"], ob["edge_S"], ob["edge_W"] = n_, e_, s_, w_
return ob
def build_quads(name, quads, matname):
if not quads:
return None
verts, faces = [], []
for q in quads:
b = len(verts)
verts += q
faces.append((b, b + 1, b + 2, b + 3))
me = bpy.data.meshes.new(name)
me.from_pydata(verts, [], faces)
me.materials.append(get_mat(matname))
me.update()
return bpy.data.objects.new(name, me)
def build_tile(spec):
col = bpy.data.collections.new(f"{spec['code']}_{spec['name']}")
bpy.context.scene.collection.children.link(col)
col.objects.link(build_vis(spec))
quads = tiles.wok_quads(spec)
wok = build_quads("WOK_" + spec["code"], [q for q, w in quads if w], "MAT_debug_walkmesh")
blk = build_quads("BLOCK_" + spec["code"], [q for q, w in quads if not w], "MAT_debug_blocker")
if wok:
col.objects.link(wok)
if blk:
col.objects.link(blk)
return col
def instance(col, loc, parent, name):
ob = bpy.data.objects.new(name, None)
ob.instance_type = "COLLECTION"
ob.instance_collection = col
ob.location = loc
parent.objects.link(ob)
def label(text, loc, parent):
cu = bpy.data.curves.new(text, "FONT")
cu.body = text
cu.size = 1.4
ob = bpy.data.objects.new("LBL_" + text, cu)
ob.location = loc
parent.objects.link(ob)
def main():
argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
out = os.path.abspath(argv[0]) if argv else os.path.abspath("westgate_urban_foundation_mvp.blend")
bpy.ops.wm.read_factory_settings(use_empty=True)
bpy.context.scene.unit_settings.system = "METRIC"
tilecol = {s["code"]: build_tile(s) for s in tiles.TILES}
# edge metadata text block (KICKOFF.md)
txt = bpy.data.texts.new("TILE_EDGE_METADATA")
for s in tiles.TILES:
n_, e_, s_, w_ = s["edges"]
txt.write(f"{s['code']}_{s['name']}\nN: {n_}\nE: {e_}\nS: {s_}\nW: {w_}\n\n")
# library grid preview (8 wide, 12 m pitch, labelled)
grid = bpy.data.collections.new("PREVIEW_tile_library_grid")
bpy.context.scene.collection.children.link(grid)
cols = 8
for i, s in enumerate(tiles.TILES):
gx, gy = (i % cols) * 12, -(i // cols) * 12
instance(tilecol[s["code"]], (gx, gy, 0), grid, "GRID_" + s["code"])
label(s["code"], (gx - 4.5, gy + 5.5, 0), grid)
# 12x12 test district (10 m pitch), row south-ward, col east-ward
dist = bpy.data.collections.new("PREVIEW_12x12_test_district")
bpy.context.scene.collection.children.link(dist)
skipped = []
for r, row in enumerate(tiles.MAP):
for c, code in enumerate(row):
tc = tilecol.get(code)
if tc is None:
skipped.append([r, c, code])
continue
instance(tc, (c * 10, -r * 10, 0), dist, f"D_{r:02d}_{c:02d}_{code}")
bpy.ops.wm.save_as_mainfile(filepath=out)
report = {
"blend": out,
"tile_collections": len(tilecol),
"district_cells": sum(len(r) for r in tiles.MAP),
"district_skipped": skipped,
"materials": len(tiles.COLORS),
}
print("<<<WGT_REPORT_BEGIN>>>" + json.dumps(report, indent=2) + "<<<WGT_REPORT_END>>>")
if __name__ == "__main__":
main()