Initial minecraft-builder-mcp prototype and Gothic hall build
This commit is contained in:
@@ -0,0 +1,291 @@
|
||||
"""Main Gothic hall geometry in local coordinates; pure voxel composition, no IO."""
|
||||
|
||||
|
||||
def build(v):
|
||||
"""Compose the north-facing hall, hollow interior, roof, and attached masonry."""
|
||||
stone = "minecraft:stone_bricks"
|
||||
ashlar = "minecraft:polished_andesite"
|
||||
plinth = "minecraft:andesite"
|
||||
pale = "minecraft:polished_diorite"
|
||||
dark = "minecraft:deepslate_tiles"
|
||||
glass = "minecraft:tinted_glass"
|
||||
wood = "minecraft:dark_oak_planks"
|
||||
oak = "minecraft:spruce_planks"
|
||||
|
||||
def slab(material="stone_brick", half="bottom"):
|
||||
return f"minecraft:{material}_slab[type={half},waterlogged=false]"
|
||||
|
||||
def stair(material, facing, half="bottom"):
|
||||
return (f"minecraft:{material}_stairs[facing={facing},half={half},"
|
||||
"shape=straight,waterlogged=false]")
|
||||
|
||||
def log(axis="y"):
|
||||
return f"minecraft:dark_oak_log[axis={axis}]"
|
||||
|
||||
def roof_height(x):
|
||||
return 43 - (abs(x - 19) * 16 + 13) // 14
|
||||
|
||||
def pinnacle(x, z, base, height=8):
|
||||
"""Square masonry shaft with a cap, taper, and slender stone finial."""
|
||||
v.box(x - 1, base, z - 1, x + 1, base, z + 1, ashlar)
|
||||
v.box(x, base + 1, z, x, base + height - 3, z, stone)
|
||||
v.box(x - 1, base + height - 4, z - 1,
|
||||
x + 1, base + height - 4, z + 1, slab())
|
||||
v.set(x, base + height - 2, z, ashlar)
|
||||
v.set(x, base + height - 1, z, slab())
|
||||
|
||||
def side_window(face, inner, center, bottom, tops, trim_face):
|
||||
"""Carved pointed aperture with glass one block behind the wall face."""
|
||||
half = max(tops)
|
||||
lo, hi = sorted((face, inner))
|
||||
for dz in range(-half, half + 1):
|
||||
top = tops[abs(dz)]
|
||||
v.clear(lo, bottom, center + dz, hi, top, center + dz)
|
||||
v.box(inner, bottom, center + dz, inner, top, center + dz, glass)
|
||||
v.set(trim_face, top + 1, center + dz, ashlar)
|
||||
for dz in (-half - 1, half + 1):
|
||||
v.box(trim_face, bottom - 1, center + dz,
|
||||
trim_face, tops[half] + 1, center + dz, ashlar)
|
||||
v.box(trim_face, bottom - 1, center - half - 1,
|
||||
trim_face, bottom - 1, center + half + 1, pale)
|
||||
# Alternating vertical/horizontal pieces describe the arch, rather than
|
||||
# colouring an arch outline on an otherwise flat facade.
|
||||
for dz in range(-half, half + 1):
|
||||
top = tops[abs(dz)]
|
||||
if dz:
|
||||
facing = "south" if dz < 0 else "north"
|
||||
v.set(trim_face, top + 2, center + dz, stair("stone_brick", facing))
|
||||
v.set(trim_face, tops[0] + 2, center, slab())
|
||||
|
||||
def front_aperture(z_face, z_glass, center, bottom, tops):
|
||||
for dx in range(-max(tops), max(tops) + 1):
|
||||
top = tops[abs(dx)]
|
||||
v.clear(center + dx, bottom, z_face, center + dx, top, z_glass)
|
||||
v.box(center + dx, bottom, z_glass, center + dx, top, z_glass, glass)
|
||||
|
||||
# Dense foundations; two genuinely hollow occupied storeys above them.
|
||||
v.box(7, 0, 13, 31, 0, 56, plinth)
|
||||
v.box(7, 1, 13, 31, 1, 56, ashlar)
|
||||
v.box(7, 2, 13, 31, 27, 56, stone)
|
||||
v.clear(9, 2, 15, 29, 5, 54)
|
||||
v.clear(9, 7, 15, 29, 43, 54)
|
||||
v.box(9, 6, 15, 29, 6, 54, wood)
|
||||
v.box(17, 6, 15, 21, 6, 53, ashlar)
|
||||
for x in (9, 29):
|
||||
v.box(x, 6, 15, x, 6, 54, stone)
|
||||
for z in (15, 54):
|
||||
v.box(9, 6, z, 29, 6, z, stone)
|
||||
|
||||
# Continuous ledges make the raised main floor legible from the courtyard.
|
||||
for x in (6, 32):
|
||||
v.box(x, 0, 13, x, 1, 56, ashlar)
|
||||
v.box(x, 5, 13, x, 5, 56, stone)
|
||||
v.box(x, 6, 13, x, 6, 56, slab("smooth_stone"))
|
||||
v.box(x, 25, 12, x, 25, 57, ashlar)
|
||||
v.box(x, 26, 12, x, 26, 57, pale)
|
||||
v.box(x, 27, 12, x, 27, 57, slab())
|
||||
for z in (12, 57):
|
||||
v.box(6, 5, z, 32, 5, z, stone)
|
||||
v.box(6, 6, z, 32, 6, z, slab("smooth_stone"))
|
||||
v.box(6, 25, z, 32, 25, z, ashlar)
|
||||
v.box(6, 26, z, 32, 26, z, pale)
|
||||
# Timber is visible in the short shadow immediately below the roof edge.
|
||||
for x in (7, 31):
|
||||
v.box(x, 26, 15, x, 26, 54, log("z"))
|
||||
for z in range(15, 55, 2):
|
||||
v.set(x, 25, z, stair("spruce", "east" if x == 7 else "west", "top"))
|
||||
|
||||
bays = (18, 26, 34, 42, 50)
|
||||
piers = (14, 22, 30, 38, 46, 54)
|
||||
for center in bays:
|
||||
for face, inner, trim in ((7, 8, 6), (31, 30, 32)):
|
||||
side_window(face, inner, center, 10, {0: 22, 1: 21, 2: 19}, trim)
|
||||
# The recessed central mullion and transom divide four dark lancets.
|
||||
v.box(inner, 10, center, inner, 20, center, ashlar)
|
||||
v.box(inner, 15, center - 2, inner, 15, center + 2, ashlar)
|
||||
v.set(inner, 21, center, wood)
|
||||
# A modest three-wide basement window below each high bay.
|
||||
side_window(face, inner, center, 2, {0: 4, 1: 3}, trim)
|
||||
v.set(inner, 2, center, wood)
|
||||
# Deep, bevelled main-window sill; its ends connect to the piers.
|
||||
outside = trim - 1 if trim < 19 else trim + 1
|
||||
v.box(outside, 8, center - 3, outside, 8, center + 3, slab())
|
||||
v.box(trim, 9, center - 3, trim, 9, center + 3, stone)
|
||||
|
||||
# Each buttress has an outward foot and two real setbacks.
|
||||
for z in piers:
|
||||
for left in (True, False):
|
||||
if left:
|
||||
foot, middle, shaft, outside = (3, 7), (4, 7), (5, 7), 5
|
||||
else:
|
||||
foot, middle, shaft, outside = (31, 35), (31, 34), (31, 33), 33
|
||||
v.box(foot[0], 0, z - 1, foot[1], 1, z + 1, plinth)
|
||||
v.box(middle[0], 2, z - 1, middle[1], 5, z + 1, stone)
|
||||
v.box(middle[0], 6, z - 1, middle[1], 6, z + 1, slab("smooth_stone"))
|
||||
v.box(shaft[0], 7, z - 1, shaft[1], 11, z + 1, stone)
|
||||
v.box(shaft[0], 12, z - 1, shaft[1], 12, z + 1, ashlar)
|
||||
v.box(min(outside, 7 if left else 31), 13, z,
|
||||
max(outside, 7 if left else 31), 24, z, stone)
|
||||
v.set(outside, 19, z, ashlar)
|
||||
v.box(shaft[0], 25, z - 1, shaft[1], 25, z + 1, ashlar)
|
||||
v.set(outside, 26, z, slab("smooth_stone"))
|
||||
pinnacle(outside + (1 if left else -1), z, 27, 7)
|
||||
|
||||
# Recessed rear lancets, kept separate from the side gallery connection.
|
||||
for center in (12, 19, 26):
|
||||
front_aperture(55, 56, center, 10, {0: 22, 1: 20})
|
||||
for dx in (-2, 2):
|
||||
v.box(center + dx, 9, 57, center + dx, 21, 57, ashlar)
|
||||
v.set(center, 24, 57, ashlar)
|
||||
v.box(center - 2, 9, 57, center + 2, 9, 57, pale)
|
||||
|
||||
# Steep, two-cell-thick stepped roof. Both sides slope to the central ridge.
|
||||
for x in range(5, 34):
|
||||
high = roof_height(x)
|
||||
if x == 19:
|
||||
v.box(x, 42, 12, x, 43, 57, dark)
|
||||
else:
|
||||
v.box(x, high - 1, 12, x, high, 57, dark)
|
||||
v.box(x, high, 12, x, high, 57,
|
||||
stair("deepslate_tile", "east" if x < 19 else "west"))
|
||||
# A restrained ridge crest uses slabs and piers instead of unsupported fences.
|
||||
v.box(19, 43, 14, 19, 43, 55, slab("stone_brick"))
|
||||
for z in range(15, 56, 4):
|
||||
v.set(19, 43, z, ashlar)
|
||||
|
||||
# Front and rear stepped gables: substantial masonry underneath the coping.
|
||||
for z in (12, 57):
|
||||
for x in range(6, 33):
|
||||
high = roof_height(x)
|
||||
v.box(x, 27, z, x, high, z, stone)
|
||||
v.set(x, high, z, ashlar)
|
||||
if x != 19:
|
||||
v.set(x, high + 1, z, stair("stone_brick", "east" if x < 19 else "west"))
|
||||
v.box(19, 44, z, 19, 46, z, ashlar)
|
||||
v.box(18, 47, z, 20, 47, z, stone)
|
||||
v.set(19, 48, z, slab())
|
||||
|
||||
# Tall north facade, projecting three-layer porch, and its open pointed door.
|
||||
v.box(13, 6, 9, 25, 6, 14, ashlar)
|
||||
v.box(14, 6, 8, 24, 6, 8, ashlar)
|
||||
portal_top = {0: 19, 1: 18, 2: 17, 3: 15, 4: 13}
|
||||
for dx, top in portal_top.items():
|
||||
for sign in (-1, 1) if dx else (1,):
|
||||
x = 19 + sign * dx
|
||||
v.clear(x, 7, 9, x, top, 15)
|
||||
for z, material, expand in ((9, ashlar, 1), (10, stone, 0), (11, pale, 0)):
|
||||
for dx in (-5, 5):
|
||||
v.box(19 + dx, 7, z, 19 + dx, 14, z, material)
|
||||
for dx, top in portal_top.items():
|
||||
for sign in (-1, 1) if dx else (1,):
|
||||
x = 19 + sign * dx
|
||||
v.box(x, top + 1, z, x, top + 1 + expand, z, material)
|
||||
for x in (13, 25):
|
||||
v.box(x, 7, 10, x, 14, 12, stone)
|
||||
v.box(x, 15, 10, x, 15, 12, slab())
|
||||
# Open wooden leaves are flush with the inner jambs, not across the entrance.
|
||||
v.box(15, 7, 14, 15, 12, 15, log())
|
||||
v.box(23, 7, 14, 23, 12, 15, log())
|
||||
v.clear(16, 7, 9, 22, 12, 15)
|
||||
|
||||
# Two slim flanking lancets and a large upper traceried front window.
|
||||
for center in (10, 28):
|
||||
front_aperture(12, 14, center, 10, {0: 23, 1: 21})
|
||||
for x in (center - 2, center + 2):
|
||||
v.box(x, 9, 11, x, 22, 11, ashlar)
|
||||
v.set(center, 25, 11, slab())
|
||||
v.box(center - 2, 9, 11, center + 2, 9, 11, pale)
|
||||
front_aperture(12, 14, 19, 29, {0: 39, 1: 38, 2: 36, 3: 33})
|
||||
for dx, top in {0: 39, 1: 38, 2: 36, 3: 33}.items():
|
||||
for sign in (-1, 1) if dx else (1,):
|
||||
v.set(19 + dx * sign, top + 1, 11, ashlar)
|
||||
for x in (15, 23):
|
||||
v.box(x, 28, 11, x, 34, 11, ashlar)
|
||||
for x in (18, 20):
|
||||
v.box(x, 29, 14, x, 36, 14, ashlar)
|
||||
v.box(16, 33, 14, 22, 33, 14, ashlar)
|
||||
v.box(15, 28, 11, 23, 28, 11, pale)
|
||||
# Small heraldic mosaic echoes the reference's central hanging cross.
|
||||
v.box(17, 22, 11, 21, 27, 11, dark)
|
||||
v.box(19, 23, 10, 19, 26, 10, pale)
|
||||
v.box(18, 25, 10, 20, 25, 10, pale)
|
||||
v.set(19, 21, 11, dark)
|
||||
for x in (7, 31):
|
||||
v.box(x - 1, 2, 11, x + 1, 9, 13, stone)
|
||||
v.box(x, 10, 11, x, 28, 12, ashlar)
|
||||
for y in (6, 13, 25):
|
||||
v.box(x - 1, y, 10, x + 1, y, 13, slab("smooth_stone"))
|
||||
pinnacle(x, 12, 29, 9)
|
||||
|
||||
# Five small pointed dormers on each long roof slope.
|
||||
for z in bays:
|
||||
for left in (True, False):
|
||||
lo, hi, face, inner = (10, 14, 10, 11) if left else (24, 28, 28, 27)
|
||||
v.clear(lo, 33, z - 1, hi, 36, z + 1)
|
||||
for dz in (-2, 2):
|
||||
v.box(lo, 32, z + dz, hi, 37, z + dz, dark)
|
||||
v.box(face, 32, z - 1, face, 37, z + 1, ashlar)
|
||||
v.clear(min(face, inner), 34, z, max(face, inner), 36, z)
|
||||
v.box(inner, 34, z, inner, 36, z, glass)
|
||||
for dz in range(-2, 3):
|
||||
top = 39 - abs(dz)
|
||||
v.box(lo, top, z + dz, hi, top, z + dz, dark)
|
||||
v.set(face, top, z + dz, ashlar)
|
||||
v.set(face, 40, z, slab())
|
||||
|
||||
# Galleries retain a broad, open central nave and three-block walkways.
|
||||
for x1, x2 in ((9, 12), (26, 29)):
|
||||
v.box(x1, 16, 16, x2, 16, 52, wood)
|
||||
rail = x2 if x1 < 19 else x1
|
||||
v.box(rail, 17, 16, rail, 17, 52, slab("stone_brick"))
|
||||
for z in range(16, 53, 4):
|
||||
v.set(rail, 17, z, ashlar)
|
||||
v.set(rail, 18, z, slab())
|
||||
for z in piers[1:]:
|
||||
if z > 52:
|
||||
continue
|
||||
v.box(rail, 7, z, rail, 15, z, stone)
|
||||
v.box(rail - 1, 7, z - 1, rail + 1, 7, z + 1, ashlar)
|
||||
v.box(rail - 1, 15, z, rail + 1, 15, z, ashlar)
|
||||
# A rear bridge connects both galleries to the single staircase.
|
||||
v.box(9, 16, 51, 29, 16, 53, wood)
|
||||
v.box(12, 17, 50, 25, 17, 50, slab())
|
||||
for x in (12, 19, 25):
|
||||
v.set(x, 17, 50, ashlar)
|
||||
v.set(x, 18, 50, slab())
|
||||
for x in (12, 26):
|
||||
v.box(x, 7, 52, x, 15, 52, stone)
|
||||
# Transverse oak roof frames are visible from the nave between the dormers.
|
||||
for z in (22, 30, 38, 46, 54):
|
||||
v.box(9, 28, z, 29, 28, z, log("x"))
|
||||
for x in range(9, 30):
|
||||
y = 40 - abs(x - 19)
|
||||
v.set(x, y, z, wood)
|
||||
for x in (9, 29):
|
||||
v.box(x, 19, z, x, 28, z, log())
|
||||
|
||||
# Rear right gallery stair: ten rises, with its complete headroom shaft.
|
||||
v.clear(26, 7, 39, 28, 19, 49)
|
||||
for step in range(10):
|
||||
y, z = 7 + step, 40 + step
|
||||
if y > 7:
|
||||
v.box(26, 7, z, 28, y - 1, z, wood)
|
||||
v.box(26, y, z, 28, y, z, stair("spruce", "south"))
|
||||
# Separate basement access under the west gallery; the main floor is pierced.
|
||||
v.clear(10, 2, 43, 11, 9, 48)
|
||||
for step in range(5):
|
||||
y, z = 2 + step, 44 + step
|
||||
if y > 2:
|
||||
v.box(10, 2, z, 11, y - 1, z, stone)
|
||||
v.box(10, y, z, 11, y, z, stair("stone_brick", "south"))
|
||||
|
||||
# Modest furnishings preserve a five-block processional route to the dais.
|
||||
v.box(14, 7, 50, 24, 7, 53, wood)
|
||||
v.box(16, 7, 49, 22, 7, 49, stair("spruce", "south"))
|
||||
for z in (24, 30, 36, 42):
|
||||
for x1, x2 in ((14, 16), (22, 24)):
|
||||
v.box(x1, 7, z, x2, 7, z, stair("spruce", "north"))
|
||||
v.set(x1, 8, z, slab("spruce"))
|
||||
v.set(x2, 8, z, slab("spruce"))
|
||||
# Agreed lower-level passage to the east gallery/wing, cut after decoration.
|
||||
v.clear(30, 7, 36, 35, 11, 39)
|
||||
@@ -0,0 +1,157 @@
|
||||
"""Deterministic voxel blueprint for the generated Gothic hall reference."""
|
||||
from collections import Counter, defaultdict
|
||||
import importlib
|
||||
import re
|
||||
|
||||
ORIGIN = (-50, -60, -40)
|
||||
|
||||
class Voxels:
|
||||
def __init__(self):
|
||||
self.blocks = {}
|
||||
self.stage = 'site'
|
||||
self.owners = {}
|
||||
|
||||
def set(self, x, y, z, block):
|
||||
assert all(isinstance(v, int) for v in (x, y, z)), (x, y, z)
|
||||
assert -2 <= x <= 68 and -1 <= y <= 64 and -2 <= z <= 68, (x, y, z)
|
||||
if not block.startswith('minecraft:'):
|
||||
block = 'minecraft:' + block
|
||||
assert re.fullmatch(r'minecraft:[a-z0-9_]+(?:\[[a-z0-9_=,]+\])?', block), block
|
||||
if block == 'minecraft:air':
|
||||
self.blocks.pop((x, y, z), None)
|
||||
self.owners.pop((x, y, z), None)
|
||||
else:
|
||||
self.blocks[(x, y, z)] = block
|
||||
self.owners[(x, y, z)] = self.stage
|
||||
|
||||
def box(self, x1, y1, z1, x2, y2, z2, block):
|
||||
x1, x2 = sorted((x1, x2))
|
||||
y1, y2 = sorted((y1, y2))
|
||||
z1, z2 = sorted((z1, z2))
|
||||
for y in range(y1, y2 + 1):
|
||||
for z in range(z1, z2 + 1):
|
||||
for x in range(x1, x2 + 1):
|
||||
self.set(x, y, z, block)
|
||||
|
||||
def clear(self, x1, y1, z1, x2, y2, z2):
|
||||
self.box(x1, y1, z1, x2, y2, z2, 'air')
|
||||
|
||||
def site(v):
|
||||
v.box(1, 0, 1, 63, 0, 62, 'smooth_stone')
|
||||
for z in (1, 62):
|
||||
v.box(1, 0, z, 63, 0, z, 'polished_andesite')
|
||||
for x in (1, 63):
|
||||
v.box(x, 0, 1, x, 0, 62, 'polished_andesite')
|
||||
# A few continuous paving bands give the terrace scale without noisy texture.
|
||||
for x in (5, 33, 60):
|
||||
v.box(x, 0, 2, x, 0, 61, 'stone_bricks')
|
||||
for z in (4, 59):
|
||||
v.box(2, 0, z, 62, 0, z, 'stone_bricks')
|
||||
for step in range(1, 7):
|
||||
z = step + 1
|
||||
v.box(13, 1, z, 25, step, z, 'stone_bricks')
|
||||
v.box(14, step, z, 24, step, z,
|
||||
'stone_brick_stairs[facing=south,half=bottom,shape=straight,waterlogged=false]')
|
||||
for x in (12, 26):
|
||||
v.box(x, 1, z, x, step + 1, z, 'polished_andesite')
|
||||
v.set(x, step + 2, z, 'stone_brick_slab[type=bottom,waterlogged=false]')
|
||||
v.box(13, 1, 8, 25, 6, 9, 'stone_bricks')
|
||||
for x in (8, 30, 35, 61):
|
||||
for z in (6, 59):
|
||||
v.box(x, 1, z, x + 1, 1, z + 1, 'polished_andesite')
|
||||
v.box(x, 2, z, x + 1, 3, z + 1, 'stone_bricks')
|
||||
v.box(x, 4, z, x + 1, 4, z + 1,
|
||||
'stone_brick_slab[type=bottom,waterlogged=false]')
|
||||
|
||||
def connection(v):
|
||||
# Two-level enclosed passage between the great hall and companion wing.
|
||||
v.stage = 'connecting gallery'
|
||||
v.box(31, 0, 34, 40, 6, 42, 'stone_bricks')
|
||||
v.box(31, 7, 34, 40, 13, 34, 'stone_bricks')
|
||||
v.box(31, 7, 42, 40, 13, 42, 'stone_bricks')
|
||||
v.box(31, 13, 33, 40, 13, 43, 'polished_andesite')
|
||||
v.box(31, 14, 34, 40, 14, 42, 'deepslate_tiles')
|
||||
v.box(31, 15, 36, 40, 15, 40, 'deepslate_tiles')
|
||||
v.box(31, 16, 38, 40, 16, 38, 'deepslate_tiles')
|
||||
for z, facing, y in [(34, 'south', 14), (35, 'south', 14),
|
||||
(36, 'south', 15), (37, 'south', 15),
|
||||
(39, 'north', 15), (40, 'north', 15),
|
||||
(41, 'north', 14), (42, 'north', 14)]:
|
||||
v.box(31, y, z, 40, y, z,
|
||||
f'deepslate_tile_stairs[facing={facing},half=bottom,shape=straight,waterlogged=false]')
|
||||
v.clear(30, 7, 36, 32, 11, 39)
|
||||
v.clear(33, 8, 36, 34, 11, 39)
|
||||
v.clear(35, 9, 36, 40, 12, 39)
|
||||
v.box(31, 6, 36, 32, 6, 39, 'spruce_planks')
|
||||
for x, y in [(33, 6), (34, 7)]:
|
||||
v.box(x, y, 36, x, y, 39,
|
||||
'spruce_stairs[facing=east,half=bottom,shape=straight,waterlogged=false]')
|
||||
v.box(35, 8, 36, 40, 8, 39, 'spruce_planks')
|
||||
|
||||
def build_scene():
|
||||
v = Voxels()
|
||||
site(v)
|
||||
for name, stage in [('main_hall', 'great hall'), ('side_wing', 'side wing'), ('tower', 'bell tower')]:
|
||||
v.stage = stage
|
||||
importlib.import_module('builds.gothic_hall.' + name).build(v)
|
||||
connection(v)
|
||||
return v
|
||||
|
||||
def boxes(blocks):
|
||||
"""Merge runs on X, then Z and Y, preserving the exact sparse block mask."""
|
||||
rows = defaultdict(list)
|
||||
for (x, y, z), block in blocks.items():
|
||||
rows[(y, z, block)].append(x)
|
||||
runs = []
|
||||
for (y, z, block), xs in sorted(rows.items()):
|
||||
xs.sort(); start = end = xs[0]
|
||||
for x in xs[1:]:
|
||||
if x == end + 1:
|
||||
end = x
|
||||
else:
|
||||
runs.append((start, y, z, end, y, z, block)); start = end = x
|
||||
runs.append((start, y, z, end, y, z, block))
|
||||
merged = []
|
||||
groups = defaultdict(list)
|
||||
for x1, y1, z1, x2, y2, z2, block in runs:
|
||||
groups[(x1, x2, y1, block)].append(z1)
|
||||
for (x1, x2, y, block), zs in sorted(groups.items()):
|
||||
zs.sort(); start = end = zs[0]
|
||||
for z in zs[1:]:
|
||||
if z == end + 1: end = z
|
||||
else:
|
||||
merged.append((x1, y, start, x2, y, end, block)); start = end = z
|
||||
merged.append((x1, y, start, x2, y, end, block))
|
||||
result = []; groups = defaultdict(list)
|
||||
for x1, y1, z1, x2, y2, z2, block in merged:
|
||||
groups[(x1, x2, z1, z2, block)].append(y1)
|
||||
for (x1, x2, z1, z2, block), ys in sorted(groups.items()):
|
||||
ys.sort(); start = end = ys[0]
|
||||
for y in ys[1:]:
|
||||
if y == end + 1: end = y
|
||||
else:
|
||||
result.append((x1, start, z1, x2, end, z2, block)); start = end = y
|
||||
result.append((x1, start, z1, x2, end, z2, block))
|
||||
return result
|
||||
|
||||
def manifest(v):
|
||||
tiles = defaultdict(dict)
|
||||
for p, block in v.blocks.items():
|
||||
tiles[(p[1] // 8, p[2] // 16, p[0] // 16)][p] = block
|
||||
batches = []
|
||||
for key, data in sorted(tiles.items()):
|
||||
recipe = []
|
||||
for x1, y1, z1, x2, y2, z2, block in boxes(data):
|
||||
lo = dict(zip(('x', 'y', 'z'), (x1 + ORIGIN[0], y1 + ORIGIN[1], z1 + ORIGIN[2])))
|
||||
hi = dict(zip(('x', 'y', 'z'), (x2 + ORIGIN[0], y2 + ORIGIN[1], z2 + ORIGIN[2])))
|
||||
recipe.append({'type': 'box', 'min': lo, 'max': hi, 'block': block})
|
||||
positions = list(data)
|
||||
minimum = [min(p[i] for p in positions) + ORIGIN[i] for i in range(3)]
|
||||
maximum = [max(p[i] for p in positions) + ORIGIN[i] for i in range(3)]
|
||||
batches.append({'key': '-'.join(map(str, key)), 'blocks': len(data),
|
||||
'min': dict(zip(('x', 'y', 'z'), minimum)), 'max': dict(zip(('x', 'y', 'z'), maximum)),
|
||||
'recipe': {'version': 1, 'operations': recipe},
|
||||
'stages': dict(Counter(v.owners[p] for p in data))})
|
||||
return {'name': 'Gothic hall reference reconstruction', 'origin': ORIGIN,
|
||||
'blocks': len(v.blocks), 'stages': dict(Counter(v.owners.values())),
|
||||
'palette': dict(Counter(v.blocks.values())), 'batches': batches}
|
||||
@@ -0,0 +1,176 @@
|
||||
"""The smaller, two-storey gabled companion to the main Gothic hall.
|
||||
|
||||
Coordinates are inclusive and local to the complete composition. ``build``
|
||||
only writes through the supplied voxel API; it does not contact Minecraft.
|
||||
"""
|
||||
|
||||
STONE = "minecraft:stone_bricks"
|
||||
TRIM = "minecraft:polished_andesite"
|
||||
LIGHT = "minecraft:polished_diorite"
|
||||
GLASS = "minecraft:tinted_glass"
|
||||
WOOD = "minecraft:spruce_planks"
|
||||
BEAM = "minecraft:stripped_spruce_log[axis=y]"
|
||||
ROOF = "minecraft:deepslate_tiles"
|
||||
AIR = "minecraft:air"
|
||||
|
||||
|
||||
def _stair(material, facing, half="bottom"):
|
||||
return (
|
||||
f"minecraft:{material}[facing={facing},half={half},"
|
||||
"shape=straight,waterlogged=false]"
|
||||
)
|
||||
|
||||
|
||||
def _wall_block(x, y, z):
|
||||
"""Sparse, deterministic masonry variation, without a checkerboard."""
|
||||
n = (x * 31 + y * 17 + z * 47 + x * z * 3) % 53
|
||||
if n < 3:
|
||||
return "minecraft:cracked_stone_bricks"
|
||||
if n < 8:
|
||||
return "minecraft:andesite"
|
||||
return STONE
|
||||
|
||||
|
||||
def _lancet(v, left, right, bottom, top, wall, outside, north=False):
|
||||
"""A stone pointed surround, open reveal, and recessed dark glass."""
|
||||
center = (left + right) / 2
|
||||
|
||||
def point(u, y, depth, block):
|
||||
if north:
|
||||
v.set(u, y, depth, block)
|
||||
else:
|
||||
v.set(depth, y, u, block)
|
||||
|
||||
for u in range(left - 1, right + 2):
|
||||
cap = top + 1 - int(abs(u - center))
|
||||
for y in range(bottom - 1, cap + 1):
|
||||
point(u, y, outside, LIGHT if y == cap else TRIM)
|
||||
for u in range(left, right + 1):
|
||||
cap = top - int(abs(u - center))
|
||||
for y in range(bottom, cap + 1):
|
||||
point(u, y, outside, AIR)
|
||||
point(u, y, wall, GLASS)
|
||||
# The sill projects one block; the glass stays in the wall behind it.
|
||||
for u in range(left - 1, right + 2):
|
||||
point(u, bottom - 1, outside, LIGHT)
|
||||
|
||||
|
||||
def _buttress(v, west, z):
|
||||
"""Three diminishing stages, with projecting pale weathering caps."""
|
||||
outer, inner = (36, 38) if west else (57, 59)
|
||||
v.box(outer, 0, z - 1, inner, 1, z + 1, TRIM)
|
||||
a, b = (37, 38) if west else (57, 58)
|
||||
v.box(a, 2, z - 1, b, 6, z + 1, STONE)
|
||||
v.box(outer, 7, z - 1, inner, 7, z + 1, LIGHT)
|
||||
v.box(a, 8, z, b, 14, z, STONE)
|
||||
v.box(a, 15, z - 1, b, 15, z + 1, TRIM)
|
||||
v.box(a, 16, z, b, 18, z, STONE)
|
||||
v.box(a, 19, z, b, 19, z, LIGHT)
|
||||
|
||||
|
||||
def _entrance(v):
|
||||
# Two nested pointed archivolts give the entrance visible depth.
|
||||
for z, left, right, peak, block in (
|
||||
(14, 44, 51, 8, TRIM),
|
||||
(15, 45, 50, 7, LIGHT),
|
||||
):
|
||||
for x in range(left, right + 1):
|
||||
cap = peak - int(abs(x - 47.5))
|
||||
v.box(x, 1, z, x, cap, z, block)
|
||||
for x in range(46, 50):
|
||||
cap = 6 - int(abs(x - 47.5))
|
||||
v.clear(x, 1, 14, x, cap, 17)
|
||||
# Timber inner jambs suggest the tall wooden doorway, leaving it usable.
|
||||
v.box(45, 1, 16, 45, 4, 16, BEAM)
|
||||
v.box(50, 1, 16, 50, 4, 16, BEAM)
|
||||
v.box(44, 0, 13, 51, 0, 16, TRIM)
|
||||
v.box(46, 0, 14, 49, 0, 17, "minecraft:smooth_stone")
|
||||
|
||||
|
||||
def build(v):
|
||||
"""Build within x=36..59, y=0..28, z=13..43."""
|
||||
# Hollow masonry shell, an accessible ground floor and an upper floor.
|
||||
v.box(38, 0, 16, 57, 0, 41, TRIM)
|
||||
v.clear(39, 1, 17, 56, 27, 40)
|
||||
for y in range(1, 18):
|
||||
for x in range(38, 58):
|
||||
for z in (16, 41):
|
||||
v.set(x, y, z, _wall_block(x, y, z))
|
||||
for z in range(17, 41):
|
||||
for x in (38, 57):
|
||||
v.set(x, y, z, _wall_block(x, y, z))
|
||||
v.box(39, 0, 17, 56, 0, 40, WOOD)
|
||||
v.box(39, 8, 17, 56, 8, 40, WOOD)
|
||||
|
||||
# Floor bands and the projecting eaves visually join the two storeys.
|
||||
for y, block in ((0, TRIM), (7, TRIM), (8, LIGHT), (16, TRIM)):
|
||||
v.box(37, y, 15, 58, y, 15, block)
|
||||
v.box(37, y, 42, 58, y, 42, block)
|
||||
v.box(58, y, 16, 58, y, 41, block)
|
||||
for z in range(16, 42):
|
||||
# The main composition will connect the gallery through here.
|
||||
if not (7 <= y <= 11 and 36 <= z <= 39):
|
||||
v.set(37, y, z, block)
|
||||
|
||||
for z in (17, 25, 33, 41):
|
||||
_buttress(v, True, z)
|
||||
_buttress(v, False, z)
|
||||
for x in (38, 57):
|
||||
v.box(x - 1, 0, 14, x + 1, 1, 16, TRIM)
|
||||
v.box(x, 2, 14, x, 16, 16, STONE)
|
||||
v.box(x - 1, 17, 14, x + 1, 17, 16, LIGHT)
|
||||
v.box(x, 18, 15, x, 20, 15, STONE)
|
||||
v.set(x, 21, 15, LIGHT)
|
||||
|
||||
# A paired rhythm of small lower and tall upper side windows.
|
||||
for z in (21, 29, 37):
|
||||
for wall, outside in ((38, 37), (57, 58)):
|
||||
if wall == 38 and z == 37:
|
||||
continue # Keep the gallery doorway and its approach clear.
|
||||
_lancet(v, z - 1, z + 1, 2, 6, wall, outside)
|
||||
_lancet(v, z - 1, z + 1, 10, 15, wall, outside)
|
||||
for left, right in ((41, 42), (53, 54)):
|
||||
_lancet(v, left, right, 2, 6, 16, 15, north=True)
|
||||
for left, right in ((42, 45), (50, 53)):
|
||||
_lancet(v, left, right, 10, 15, 16, 15, north=True)
|
||||
_entrance(v)
|
||||
|
||||
# Timber supports remain against the edges, leaving the rooms traversable.
|
||||
for x in (40, 55):
|
||||
for z in (18, 39):
|
||||
v.box(x, 1, z, x, 7, z, BEAM)
|
||||
for z in (23, 31, 39):
|
||||
v.box(39, 7, z, 56, 7, z, "minecraft:stripped_spruce_log[axis=x]")
|
||||
v.clear(53, 8, 28, 55, 11, 35)
|
||||
for step in range(8):
|
||||
y, z = step + 1, 28 + step
|
||||
if y > 1:
|
||||
v.box(53, 1, z, 55, y - 1, z, WOOD)
|
||||
v.box(53, y, z, 55, y, z, _stair("spruce_stairs", "south"))
|
||||
v.clear(53, y + 1, z, 55, y + 2, z)
|
||||
v.box(52, 9, 28, 52, 9, 35, "minecraft:spruce_slab[type=bottom,waterlogged=false]")
|
||||
|
||||
# A steep roof, dark in the middle and bounded by pale stepped gables.
|
||||
for x in range(37, 59):
|
||||
rise = min(x - 37, 58 - x)
|
||||
y = 17 + rise
|
||||
facing = "east" if x <= 47 else "west"
|
||||
if 38 <= x <= 57 and y >= 18:
|
||||
for z in (16, 41):
|
||||
v.box(x, 18, z, x, y, z, STONE)
|
||||
v.box(x, y, 15, x, y, 42, _stair("deepslate_tile_stairs", facing))
|
||||
for z in (14, 43):
|
||||
v.set(x, y, z, _stair("stone_brick_stairs", facing))
|
||||
if y > 17:
|
||||
v.set(x, y - 1, z, TRIM)
|
||||
v.box(47, 28, 15, 48, 28, 42, ROOF)
|
||||
for z in (14, 43):
|
||||
v.box(47, 27, z, 48, 28, z, LIGHT)
|
||||
_lancet(v, 46, 49, 19, 24, 16, 15, north=True)
|
||||
for x in (47, 48):
|
||||
v.box(x, 26, 15, x, 28, 15, TRIM)
|
||||
|
||||
# Wooden cornice brackets are visible under the long roof slopes.
|
||||
for z in (19, 23, 27, 31, 35, 39):
|
||||
for x, facing in ((37, "east"), (58, "west")):
|
||||
v.set(x, 16, z, _stair("spruce_stairs", facing, half="top"))
|
||||
@@ -0,0 +1,190 @@
|
||||
"""Reference bell tower, in the shared scene's inclusive local coordinates.
|
||||
|
||||
The scene supplies set(x, y, z, block), box(x1, y1, z1, x2, y2, z2,
|
||||
block), and clear(x1, y1, z1, x2, y2, z2). This module has no I/O.
|
||||
All geometry stays inside x=34..46, y=0..60, z=42..56.
|
||||
"""
|
||||
|
||||
|
||||
STONE = "minecraft:stone_bricks"
|
||||
TRIM = "minecraft:polished_andesite"
|
||||
LIGHT = "minecraft:polished_diorite"
|
||||
DARK = "minecraft:deepslate_tiles"
|
||||
GLASS = "minecraft:tinted_glass"
|
||||
AIR = "minecraft:air"
|
||||
|
||||
|
||||
def _face(face, u, y, depth=0):
|
||||
"""Positive depth projects outward; -1 is the recessed glazing plane."""
|
||||
if face == "north":
|
||||
return u, y, 43 - depth
|
||||
if face == "south":
|
||||
return u, y, 55 + depth
|
||||
if face == "west":
|
||||
return 35 - depth, y, u
|
||||
return 45 + depth, y, u
|
||||
|
||||
|
||||
def _put(v, face, u, y, block, depth=0):
|
||||
v.set(*_face(face, u, y, depth), block)
|
||||
|
||||
|
||||
def _ring(v, x1, z1, x2, z2, y, block):
|
||||
v.box(x1, y, z1, x2, y, z1, block)
|
||||
v.box(x1, y, z2, x2, y, z2, block)
|
||||
v.box(x1, y, z1, x1, y, z2, block)
|
||||
v.box(x2, y, z1, x2, y, z2, block)
|
||||
|
||||
|
||||
def _lancet(v, face, center, bottom, top, half_width):
|
||||
"""A pointed opening through a two-block wall, glass one block behind it."""
|
||||
for y in range(bottom, top + 1):
|
||||
radius = 0 if y >= top - 1 else half_width
|
||||
for u in range(center - radius, center + radius + 1):
|
||||
_put(v, face, u, y, AIR)
|
||||
_put(v, face, u, y, GLASS, -1)
|
||||
# The projecting dressings are stone; the glazing never projects.
|
||||
for u in {center - radius - 1, center + radius + 1}:
|
||||
_put(v, face, u, y, TRIM, 1)
|
||||
for u in range(center - half_width - 1, center + half_width + 2):
|
||||
_put(v, face, u, bottom - 1, "minecraft:stone_brick_slab[type=top]", 1)
|
||||
_put(v, face, center, top + 1, LIGHT, 1)
|
||||
|
||||
|
||||
def _belfry_arch(v, face, center):
|
||||
# Seven blocks wide below, narrowing to a high, single-block apex.
|
||||
for y in range(32, 41):
|
||||
radius = 3 if y <= 37 else 40 - y
|
||||
for u in range(center - radius, center + radius + 1):
|
||||
for depth in (0, -1):
|
||||
_put(v, face, u, y, AIR, depth)
|
||||
for u in (center - radius - 1, center + radius + 1):
|
||||
_put(v, face, u, y, TRIM, 1)
|
||||
_put(v, face, center, 41, LIGHT, 1)
|
||||
for u in range(center - 4, center + 5):
|
||||
_put(v, face, u, 31, "minecraft:stone_brick_slab[type=top]", 1)
|
||||
|
||||
|
||||
def _stairs(v):
|
||||
# Three narrow alternating flights join the interior floors. A landing at
|
||||
# each end leaves a three-block-wide central well open through the tower.
|
||||
for y_floor, xs, reverse in ((1, (37, 38), False),
|
||||
(10, (42, 43), True),
|
||||
(19, (37, 38), False)):
|
||||
facing = "north" if reverse else "south"
|
||||
state = f"minecraft:stone_brick_stairs[facing={facing},half=bottom,shape=straight]"
|
||||
for i in range(9):
|
||||
z = 53 - i if reverse else 45 + i
|
||||
y = y_floor + i + 1
|
||||
for x in xs:
|
||||
# Clearance through the destination floor, including headroom.
|
||||
v.clear(x, y + 1, z, x, y + 3, z)
|
||||
v.set(x, y, z, state)
|
||||
# The flight exits sideways onto the destination floor.
|
||||
v.clear(39, 29, 53, 41, 32, 53)
|
||||
v.set(41, 29, 52, "minecraft:stone_brick_stairs[facing=north]")
|
||||
v.set(41, 30, 51, "minecraft:stone_brick_stairs[facing=north]")
|
||||
v.clear(41, 30, 52, 41, 33, 52)
|
||||
v.clear(41, 31, 51, 41, 33, 51)
|
||||
|
||||
|
||||
def _spire(v):
|
||||
# A hollow, steep rectangular hip roof; alternate contractions avoid a
|
||||
# stack of disconnected floating rings while giving the spire a fine tip.
|
||||
for y in range(45, 57):
|
||||
step = y - 45
|
||||
rx = 5 - step // 2
|
||||
rz = 6 - (step + 1) // 2
|
||||
if rx < 0:
|
||||
rx = 0
|
||||
if rz < 0:
|
||||
rz = 0
|
||||
x1, x2, z1, z2 = 40 - rx, 40 + rx, 49 - rz, 49 + rz
|
||||
v.box(x1, y, z1, x2, y, z2, DARK)
|
||||
if rx > 0 and rz > 0:
|
||||
v.clear(x1 + 1, y, z1 + 1, x2 - 1, y, z2 - 1)
|
||||
# Stair edges soften the one-block roof steps without adding width.
|
||||
if rx > 0 and rz > 0:
|
||||
for x in range(x1 + 1, x2):
|
||||
v.set(x, y, z1, "minecraft:deepslate_tile_stairs[facing=south]")
|
||||
v.set(x, y, z2, "minecraft:deepslate_tile_stairs[facing=north]")
|
||||
for z in range(z1 + 1, z2):
|
||||
v.set(x1, y, z, "minecraft:deepslate_tile_stairs[facing=east]")
|
||||
v.set(x2, y, z, "minecraft:deepslate_tile_stairs[facing=west]")
|
||||
# The cross is plain masonry, with no unsupported entities or thin blocks.
|
||||
v.box(40, 57, 49, 40, 60, 49, TRIM)
|
||||
v.box(39, 59, 49, 41, 59, 49, TRIM)
|
||||
|
||||
|
||||
def build(v):
|
||||
"""Add a hollow Gothic bell tower and its pointed roof to a scene."""
|
||||
# Foundation and shell: two-block walls give the apertures real reveals.
|
||||
v.box(35, 0, 43, 45, 43, 55, STONE)
|
||||
v.clear(37, 2, 45, 43, 42, 53)
|
||||
_ring(v, 34, 42, 46, 56, 0, "minecraft:deepslate_bricks")
|
||||
_ring(v, 34, 42, 46, 56, 1, TRIM)
|
||||
_ring(v, 34, 42, 46, 56, 3, "minecraft:stone_brick_slab[type=bottom]")
|
||||
|
||||
# Dressed corners and stepped buttress feet, all within the tower envelope.
|
||||
corners = ((34, 42, 35, 43), (45, 42, 46, 43),
|
||||
(34, 55, 35, 56), (45, 55, 46, 56))
|
||||
for x1, z1, x2, z2 in corners:
|
||||
v.box(x1, 1, z1, x2, 5, z2, TRIM)
|
||||
v.box(x1, 6, z1, x2, 29, z2, STONE)
|
||||
for y in (6, 15, 28):
|
||||
v.box(x1, y, z1, x2, y, z2, TRIM)
|
||||
for x, z in ((35, 43), (45, 43), (35, 55), (45, 55)):
|
||||
v.box(x, 4, z, x, 43, z, TRIM)
|
||||
|
||||
# Strong horizontal courses separate the shaft from the open belfry.
|
||||
for y in (15, 16, 29, 30, 42, 43):
|
||||
_ring(v, 34, 42, 46, 56, y, TRIM if y % 2 else STONE)
|
||||
for y in (10, 19, 28, 30):
|
||||
v.box(37, y, 45, 43, y, 53, "minecraft:oak_planks")
|
||||
if y != 30:
|
||||
v.clear(39, y, 47, 41, y, 51)
|
||||
v.box(35, 44, 43, 45, 44, 55, STONE)
|
||||
_ring(v, 34, 42, 46, 56, 44, "minecraft:stone_brick_slab[type=bottom]")
|
||||
|
||||
# Paired narrow lower lights and broader, pointed upper lancets.
|
||||
for face, centers in (("north", (38, 42)), ("south", (38, 42)),
|
||||
("west", (47, 51)), ("east", (47, 51))):
|
||||
for center in centers:
|
||||
_lancet(v, face, center, 6, 12, 0)
|
||||
_lancet(v, face, center, 19, 26, 1)
|
||||
_belfry_arch(v, face, 40 if face in ("north", "south") else 49)
|
||||
|
||||
# An actual walk-in doorway toward the wing; no decorative solid door.
|
||||
v.clear(39, 2, 42, 41, 5, 44)
|
||||
v.clear(40, 6, 42, 40, 6, 44)
|
||||
for x in (38, 42):
|
||||
v.box(x, 2, 42, x, 5, 42, TRIM)
|
||||
v.set(39, 6, 42, TRIM)
|
||||
v.set(41, 6, 42, TRIM)
|
||||
v.set(40, 7, 42, LIGHT)
|
||||
_stairs(v)
|
||||
|
||||
# A block-built ochre bell echoes the reference using allowlisted sandstone.
|
||||
# Its open mouth and central clapper remain visible through all four arches.
|
||||
v.box(35, 41, 49, 45, 41, 49, "minecraft:spruce_log[axis=x]")
|
||||
v.box(40, 38, 49, 40, 40, 49, "minecraft:dark_oak_log[axis=y]")
|
||||
v.box(40, 36, 49, 40, 37, 49, "minecraft:cut_sandstone")
|
||||
for x, z in ((39, 49), (40, 48), (40, 49), (40, 50), (41, 49)):
|
||||
v.set(x, 35, z, "minecraft:cut_sandstone")
|
||||
_ring(v, 39, 48, 41, 50, 34, "minecraft:cut_sandstone")
|
||||
v.set(40, 34, 49, "minecraft:dark_oak_log[axis=y]")
|
||||
v.set(40, 33, 49, TRIM)
|
||||
|
||||
# A row of small corbels under the cornice is readable from a distance.
|
||||
for face, us in (("north", (37, 39, 41, 43)), ("south", (37, 39, 41, 43)),
|
||||
("west", (45, 47, 49, 51, 53)), ("east", (45, 47, 49, 51, 53))):
|
||||
for u in us:
|
||||
_put(v, face, u, 41, f"minecraft:stone_brick_stairs[facing={face},half=top]", 1)
|
||||
_spire(v)
|
||||
|
||||
# Four light corner pinnacles frame the darker central spire.
|
||||
for x, z in ((35, 43), (45, 43), (35, 55), (45, 55)):
|
||||
v.box(x, 44, z, x, 49, z, TRIM)
|
||||
for dx, dz in ((-1, 0), (1, 0), (0, -1), (0, 1)):
|
||||
v.set(x + dx, 46, z + dz, "minecraft:stone_brick_slab[type=top]")
|
||||
v.set(x, 50, z, "minecraft:stone_brick_slab[type=bottom]")
|
||||
Reference in New Issue
Block a user