#!/usr/bin/env python3 """Rewrite xchc pattern scenes so the U of cans + centre movers act as one. Building 7 m wide. Coordinates in metres: X = 0 at centre line, +X to the right Y = 0 at the row of 4, +Y toward audience (in front of the 4) Audience 1 8 ← wall-mounted sides @ 2.4 m in front of the 4 2 M1 M2 7 ← wall-mounted sides @ 1.2 m; movers @ 1.1 m, ±0.3 m 3 — 4 — 5 — 6 ← row of 4 at ±0.6 m and ±1.8 m from centre Building 7 m → side walls at ±3.5 m. #1,#2 left wall; #7,#8 right wall. #1 front left. Movers 1.1 m in front of the row of 4, 0.3 m off centre. """ from __future__ import annotations import re from pathlib import Path CANS = list(range(8)) MOVERS = (8, 9) WALL = 3.5 # building half-width # Fixture ID → (x_m, y_m) — Y=0 = row of 4; +Y toward audience LAYOUT_M = { 0: (-WALL, 2.4), # #1 front left — left wall 1: (-WALL, 1.2), # #2 — left wall 2: (-1.8, 0.0), # #3 row of 4 3: (-0.6, 0.0), # #4 row of 4 4: (0.6, 0.0), # #5 row of 4 5: (1.8, 0.0), # #6 row of 4 6: (WALL, 1.2), # #7 — right wall 7: (WALL, 2.4), # #8 front right — right wall 8: (-0.3, 1.1), # Small Eye #1 — 0.3 m off centre, 1.1 m in front of row of 4 9: (0.3, 1.1), # Small Eye #2 — 0.3 m off centre, 1.1 m in front of row of 4 } # Walk U: #1 → #2 → along row of 4 L→R → #7 → #8 U_WALK = [0, 1, 2, 3, 4, 5, 6, 7] LEFT_SIDE = {0, 1} RIGHT_SIDE = {6, 7} ROW4 = {2, 3, 4, 5} RINGS = [ {8, 9}, {8, 9, 3, 4}, # movers + centre of row of 4 {8, 9, *ROW4}, {8, 9, *CANS}, ] LEFT = LEFT_SIDE | {2, 3, 8} RIGHT = RIGHT_SIDE | {4, 5, 9} GRID_W = 7.0 GRID_H = 3.2 X_ORIGIN = -3.5 def m_to_grid(x_m: float, y_m: float) -> tuple[float, float]: gx = round(x_m - X_ORIGIN, 2) # Row of 4 (y=0) toward top; audience / front sides toward bottom gy = round(y_m, 2) return gx, gy COLORS = { "Red": (255, 0, 0), "Orange": (255, 127, 0), "Yellow": (255, 255, 0), "Green": (0, 255, 0), "Cyan": (0, 255, 255), "Blue": (0, 0, 255), "Violet": (127, 0, 255), "Magenta": (255, 0, 255), "White": (255, 255, 255), } RAINBOW = [ (255, 0, 0), (255, 127, 0), (255, 255, 0), (0, 255, 0), (0, 255, 255), (0, 0, 255), (127, 0, 255), (255, 0, 255), ] def can_channels(r: int, g: int, b: int, *, white: int = 0, on: bool = True) -> str: if not on: return "0,0,1,0,2,0,3,0,4,0,5,0,6,0,7,0" if white: return f"1,0,2,0,3,0,4,0,5,{white},6,0,7,0" return f"1,0,2,{r},3,{g},4,{b},5,0,6,0,7,0" def mover_channels(r: int, g: int, b: int, *, on: bool = True, dim: int = 255) -> str: if not on or (r == g == b == 0): dim = 0 r = g = b = 0 parts = [ 0, 128, 2, 80, 4, 0, 5, 0, 6, 191, 7, 70, 8, dim, 9, 0, 10, r, 11, g, 12, b, 13, 0, ] for pixel in range(1, 7): base = 10 + pixel * 4 parts.extend([base, 0, base + 1, 0, base + 2, 0, base + 3, 0]) return ",".join(str(x) for x in parts) def fixture_vals( lit: dict[int, tuple[int, int, int] | None], *, white_ids: set[int] | None = None ) -> str: white_ids = white_ids or set() lines = [] for fid in list(CANS) + list(MOVERS): rgb = lit.get(fid) if rgb is None: ch = ( mover_channels(0, 0, 0, on=False) if fid in MOVERS else can_channels(0, 0, 0, on=False) ) lines.append(f' {ch}') continue r, g, b = rgb if fid in MOVERS: ch = mover_channels(r, g, b) elif fid in white_ids: ch = can_channels(0, 0, 0, white=255) else: ch = can_channels(r, g, b) lines.append(f' {ch}') return "\n".join(lines) def scene( fid: int, name: str, lit: dict, *, white_ids: set[int] | None = None, fade: int = 0, ) -> str: return f""" {fixture_vals(lit, white_ids=white_ids)} """ def replace_function(text: str, function_id: int, new_xml: str) -> str: pattern = re.compile( rf' \n', re.MULTILINE, ) if not pattern.search(text): raise SystemExit(f"Function ID {function_id} not found") return pattern.sub(new_xml.rstrip() + "\n", text, count=1) def all_color(rgb: tuple[int, int, int]) -> dict: lit = {i: rgb for i in CANS} lit[8] = rgb lit[9] = rgb return lit def monitor_xml() -> str: items = [] for fid, (xm, ym) in LAYOUT_M.items(): gx, gy = m_to_grid(xm, ym) items.append(f' ') return f""" Arial,12,-1,5,50,0,0,0,0,0 0 0 {chr(10).join(items)} """ def main(): path = Path(__file__).resolve().parents[1] / "shows" / "xchc.qxw" text = path.read_text() for i, (name, rgb) in enumerate(COLORS.items()): white_ids = set(CANS) if name == "White" else None text = replace_function(text, i, scene(i, name, all_color(rgb), white_ids=white_ids)) for step in range(8): lit = {} for ci, can in enumerate(U_WALK): lit[can] = RAINBOW[(ci + step) % 8] lit[8] = RAINBOW[(3 + step) % 8] lit[9] = RAINBOW[(4 + step) % 8] text = replace_function(text, 10 + step, scene(10 + step, f"Rainbow {step + 1}", lit)) blue, red = (0, 0, 255), (255, 0, 0) odd_blue, odd_red = {}, {} for i in CANS: if i % 2 == 0: odd_blue[i], odd_red[i] = blue, red else: odd_blue[i], odd_red[i] = red, blue odd_blue[8], odd_blue[9] = blue, red odd_red[8], odd_red[9] = red, blue text = replace_function(text, 20, scene(20, "Odd Blue Even Red", odd_blue)) text = replace_function(text, 21, scene(21, "Odd Red Even Blue", odd_red)) off = {i: None for i in list(CANS) + list(MOVERS)} text = replace_function(text, 23, scene(23, "Test Off", off)) text = replace_function(text, 72, scene(72, "Soft Blackout", off, fade=4000)) for i, can in enumerate(U_WALK): lit = {fid: None for fid in list(CANS) + list(MOVERS)} lit[can] = (255, 255, 255) text = replace_function( text, 33 + i, scene(33 + i, f"Scanner {i + 1}", lit, white_ids={can}) ) for mid, name, ids in ((99, "Scanner Movers", {8, 9}), (100, "Scanner Off", set())): lit = {fid: None for fid in list(CANS) + list(MOVERS)} for m in ids: lit[m] = (255, 255, 255) block = scene(mid, name, lit) if f'Function ID="{mid}"' in text: text = replace_function(text, mid, block) else: text = text.replace( ' ', block + '\n ', 1, ) scanner_steps = "\n".join( f' {sid}' for i, sid in enumerate([*range(33, 41), 99, 100]) ) text = replace_function( text, 41, f""" Forward Loop {scanner_steps} """, ) for n in range(1, 9): lit = {fid: None for fid in list(CANS) + list(MOVERS)} for can in U_WALK[:n]: lit[can] = (255, 255, 255) if n >= 3: lit[8] = lit[9] = (255, 255, 255) white = {c for c in U_WALK[:n]} text = replace_function( text, 41 + n, scene(41 + n, f"Fill {n}", lit, white_ids=white) ) for i, ring in enumerate(RINGS): lit = {fid: None for fid in list(CANS) + list(MOVERS)} white = {f for f in ring if f in CANS} for fid in ring: lit[fid] = (255, 255, 255) text = replace_function( text, 51 + i, scene(51 + i, f"Center {i + 1}", lit, white_ids=white) ) for i, (ids, name) in enumerate( [ ({0, 7}, "Pairs 1"), ({1, 6}, "Pairs 2"), ({2, 5}, "Pairs 3"), ({3, 4, 8, 9}, "Pairs 4"), ] ): lit = {fid: None for fid in list(CANS) + list(MOVERS)} white = {f for f in ids if f in CANS} for fid in ids: lit[fid] = (255, 255, 255) text = replace_function(text, 56 + i, scene(56 + i, name, lit, white_ids=white)) lbrr = {fid: None for fid in list(CANS) + list(MOVERS)} lrbr = {fid: None for fid in list(CANS) + list(MOVERS)} for fid in LEFT: lbrr[fid], lrbr[fid] = (0, 0, 255), (255, 0, 0) for fid in RIGHT: lbrr[fid], lrbr[fid] = (255, 0, 0), (0, 0, 255) text = replace_function(text, 61, scene(61, "Left Blue Right Red", lbrr)) text = replace_function(text, 62, scene(62, "Left Red Right Blue", lrbr)) for step in range(3): lit = {fid: None for fid in list(CANS) + list(MOVERS)} white = set() for can in CANS: if can % 3 == step: lit[can] = (255, 255, 255) white.add(can) if step == 0: lit[8] = (255, 255, 255) elif step == 1: lit[9] = (255, 255, 255) else: lit[8] = lit[9] = (255, 200, 140) text = replace_function( text, 64 + step, scene(64 + step, f"Theater {step + 1}", lit, white_ids=white) ) collapse_steps = "\n".join( f' {sid}' for i, sid in enumerate([54, 53, 52, 51, 23]) ) text = replace_function( text, 68, f""" Forward SingleShot {collapse_steps} """, ) chase_steps = "\n".join( f' {sid}' for i, sid in enumerate([*range(33, 41), 99, 23]) ) text = replace_function( text, 69, f""" Forward SingleShot {chase_steps} """, ) text = replace_function( text, 70, scene(70, "Home Soft", all_color((255, 180, 80)), fade=2500) ) text = re.sub( r' [\s\S]*?', lambda _m: monitor_xml(), text, count=1, ) text = text.replace("BCC145 Line", "BCC145 U", 1) path.write_text(text) print("Updated", path) print("Y=0 row of 4; +Y toward audience (in front of the 4)") for fid in sorted(LAYOUT_M): x, y = LAYOUT_M[fid] label = f"Can #{fid + 1}" if fid < 8 else f"Mover #{fid - 7}" print(f" {label:10} x={x:+.1f} y={y:.1f}") if __name__ == "__main__": main()