Organize the lighting project and ship the combined XCHC show.
Group workspaces, generators, stage map, and tests into clear folders, and add the unified cans+movers show plus a simple shared/individual RGBW control board. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,366 @@
|
||||
#!/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' <FixtureVal ID="{fid}">{ch}</FixtureVal>')
|
||||
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' <FixtureVal ID="{fid}">{ch}</FixtureVal>')
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def scene(
|
||||
fid: int,
|
||||
name: str,
|
||||
lit: dict,
|
||||
*,
|
||||
white_ids: set[int] | None = None,
|
||||
fade: int = 0,
|
||||
) -> str:
|
||||
return f""" <Function ID="{fid}" Type="Scene" Name="{name}">
|
||||
<Speed FadeIn="{fade}" FadeOut="0" Duration="{fade}" />
|
||||
{fixture_vals(lit, white_ids=white_ids)}
|
||||
</Function>"""
|
||||
|
||||
|
||||
def replace_function(text: str, function_id: int, new_xml: str) -> str:
|
||||
pattern = re.compile(
|
||||
rf' <Function ID="{function_id}"[\s\S]*?</Function>\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' <FxItem ID="{fid}" XPos="{gx}" YPos="{gy}" />')
|
||||
return f""" <Monitor DisplayMode="1" ShowLabels="1">
|
||||
<Font>Arial,12,-1,5,50,0,0,0,0,0</Font>
|
||||
<ChannelStyle>0</ChannelStyle>
|
||||
<ValueStyle>0</ValueStyle>
|
||||
<Grid Width="{GRID_W}" Height="{GRID_H}" Depth="5" Units="0" />
|
||||
{chr(10).join(items)}
|
||||
</Monitor>"""
|
||||
|
||||
|
||||
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(
|
||||
' <Function ID="41" Type="Chaser" Name="Scanner">',
|
||||
block + '\n <Function ID="41" Type="Chaser" Name="Scanner">',
|
||||
1,
|
||||
)
|
||||
|
||||
scanner_steps = "\n".join(
|
||||
f' <Step Number="{i}" FadeIn="0" Hold="400" FadeOut="0">{sid}</Step>'
|
||||
for i, sid in enumerate([*range(33, 41), 99, 100])
|
||||
)
|
||||
text = replace_function(
|
||||
text,
|
||||
41,
|
||||
f""" <Function ID="41" Type="Chaser" Name="Scanner">
|
||||
<Speed FadeIn="0" FadeOut="0" Duration="400" />
|
||||
<Direction>Forward</Direction>
|
||||
<RunOrder>Loop</RunOrder>
|
||||
<SpeedModes FadeIn="Common" FadeOut="Common" Duration="Common" />
|
||||
{scanner_steps}
|
||||
</Function>""",
|
||||
)
|
||||
|
||||
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' <Step Number="{i}" FadeIn="200" Hold="300" FadeOut="0">{sid}</Step>'
|
||||
for i, sid in enumerate([54, 53, 52, 51, 23])
|
||||
)
|
||||
text = replace_function(
|
||||
text,
|
||||
68,
|
||||
f""" <Function ID="68" Type="Chaser" Name="Collapse In">
|
||||
<Speed FadeIn="200" FadeOut="0" Duration="500" />
|
||||
<Direction>Forward</Direction>
|
||||
<RunOrder>SingleShot</RunOrder>
|
||||
<SpeedModes FadeIn="Common" FadeOut="Common" Duration="Common" />
|
||||
{collapse_steps}
|
||||
</Function>""",
|
||||
)
|
||||
|
||||
chase_steps = "\n".join(
|
||||
f' <Step Number="{i}" FadeIn="0" Hold="300" FadeOut="0">{sid}</Step>'
|
||||
for i, sid in enumerate([*range(33, 41), 99, 23])
|
||||
)
|
||||
text = replace_function(
|
||||
text,
|
||||
69,
|
||||
f""" <Function ID="69" Type="Chaser" Name="Chase Out">
|
||||
<Speed FadeIn="0" FadeOut="0" Duration="400" />
|
||||
<Direction>Forward</Direction>
|
||||
<RunOrder>SingleShot</RunOrder>
|
||||
<SpeedModes FadeIn="Common" FadeOut="Common" Duration="Common" />
|
||||
{chase_steps}
|
||||
</Function>""",
|
||||
)
|
||||
|
||||
text = replace_function(
|
||||
text, 70, scene(70, "Home Soft", all_color((255, 180, 80)), fade=2500)
|
||||
)
|
||||
|
||||
text = re.sub(
|
||||
r' <Monitor DisplayMode="1" ShowLabels="1">[\s\S]*?</Monitor>',
|
||||
lambda _m: monitor_xml(),
|
||||
text,
|
||||
count=1,
|
||||
)
|
||||
text = text.replace("<Name>BCC145 Line</Name>", "<Name>BCC145 U</Name>", 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()
|
||||
Reference in New Issue
Block a user