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>
269 lines
8.8 KiB
Python
269 lines
8.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Merge Patterns + Movers into one SoloFrame and one Speed dial in xchc.qxw."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
|
|
def btn(
|
|
widget_id: int,
|
|
caption: str,
|
|
func_id: int,
|
|
x: int,
|
|
y: int,
|
|
w: int,
|
|
h: int,
|
|
bg: str,
|
|
fg: str = "4294967295",
|
|
) -> str:
|
|
return f""" <Button Caption="{caption}" ID="{widget_id}" Icon="">
|
|
<WindowState Visible="True" X="{x}" Y="{y}" Width="{w}" Height="{h}"/>
|
|
<Appearance>
|
|
<FrameStyle>None</FrameStyle>
|
|
<ForegroundColor>{fg}</ForegroundColor>
|
|
<BackgroundColor>{bg}</BackgroundColor>
|
|
<BackgroundImage>None</BackgroundImage>
|
|
<Font>Default</Font>
|
|
</Appearance>
|
|
<Function ID="{func_id}"/>
|
|
<Action>Toggle</Action>
|
|
<Intensity Adjust="False">100</Intensity>
|
|
</Button>"""
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def main():
|
|
path = ROOT / "shows" / "xchc.qxw"
|
|
text = path.read_text()
|
|
|
|
# Combined looks: can colour chase + figure-of-8 movement
|
|
combos = [
|
|
(101, "Colour + F8", 9, 77),
|
|
(102, "Rainbow + F8", 18, 77),
|
|
(103, "OddEven + F8", 22, 77),
|
|
(104, "Center + F8", 55, 77),
|
|
(105, "Theater + F8", 67, 77),
|
|
]
|
|
combo_xml = []
|
|
for fid, name, a, b in combos:
|
|
block = f""" <Function ID="{fid}" Type="Collection" Name="{name}">
|
|
<Step Number="0">{a}</Step>
|
|
<Step Number="1">{b}</Step>
|
|
</Function>
|
|
"""
|
|
if f'Function ID="{fid}"' in text:
|
|
text = re.sub(
|
|
rf' <Function ID="{fid}"[\s\S]*?</Function>\n',
|
|
block,
|
|
text,
|
|
count=1,
|
|
)
|
|
else:
|
|
combo_xml.append(block.rstrip())
|
|
if combo_xml:
|
|
text = text.replace(
|
|
' <Monitor DisplayMode="1"',
|
|
"\n".join(combo_xml) + "\n <Monitor DisplayMode=\"1\"",
|
|
1,
|
|
)
|
|
|
|
# Build unified Show SoloFrame
|
|
# Row 0: colour / spatial patterns (cans+movers already in scenes)
|
|
# Row 1: combined flight looks
|
|
# Row 2: mover-only + exits
|
|
w, h = 150, 56
|
|
gap_x, gap_y = 8, 8
|
|
x0, y0 = 10, 28
|
|
|
|
def cell(col, row):
|
|
return x0 + col * (w + gap_x), y0 + row * (h + gap_y)
|
|
|
|
buttons = []
|
|
# Row 0 — parcans + movers colour/spatial
|
|
row0 = [
|
|
(0, "Colour", 9, "4282664004"),
|
|
(28, "Rainbow", 18, "4280427042"),
|
|
(37, "Odd Even", 22, "4284752486"),
|
|
(47, "Scanner", 41, "4292033564"),
|
|
(48, "Fill", 50, "4278218076"),
|
|
(49, "Center Out", 55, "4280817043"),
|
|
]
|
|
for i, (wid, cap, fid, bg) in enumerate(row0):
|
|
x, y = cell(i, 0)
|
|
buttons.append(btn(wid, cap, fid, x, y, w, h, bg))
|
|
|
|
row1 = [
|
|
(50, "Pairs", 60, "4294946560", "4278190080"),
|
|
(51, "Left Right", 63, "4279648704", "4294967295"),
|
|
(52, "Theater", 67, "4285143962", "4294967295"),
|
|
(200, "Colour+F8", 101, "4282664004", "4294967295"),
|
|
(201, "Rainbow+F8", 102, "4280427042", "4294967295"),
|
|
(202, "Center+F8", 104, "4280817043", "4294967295"),
|
|
]
|
|
for i, (wid, cap, fid, bg, fg) in enumerate(row1):
|
|
x, y = cell(i, 1)
|
|
buttons.append(btn(wid, cap, fid, x, y, w, h, bg, fg))
|
|
|
|
row2 = [
|
|
(100, "Figure of 8", 77, "4282664004"),
|
|
(203, "OddEven+F8", 103, "4284752486"),
|
|
(204, "Theater+F8", 105, "4285143962"),
|
|
(53, "Collapse In", 68, "4281545523"),
|
|
(54, "Chase Out", 69, "4284900966"),
|
|
(55, "Home Fade", 71, "4286611584"),
|
|
]
|
|
for i, (wid, cap, fid, bg) in enumerate(row2):
|
|
x, y = cell(i, 2)
|
|
buttons.append(btn(wid, cap, fid, x, y, w, h, bg))
|
|
|
|
# Home Fade for whole stage uses can Home Fade 71; keep mover Home Fade via MIB/Soft BO
|
|
row3 = [
|
|
(101, "Ring Collapse", 85, "4281545523"),
|
|
(114, "Ring Chase", 94, "4284900966"),
|
|
(116, "Move in Black", 98, "4279900698"),
|
|
(56, "Soft Blackout", 72, "4278190080"),
|
|
(38, "Red Test", 32, "4286578688"),
|
|
]
|
|
for i, (wid, cap, fid, bg) in enumerate(row3):
|
|
x, y = cell(i, 3)
|
|
buttons.append(btn(wid, cap, fid, x, y, w, h, bg))
|
|
|
|
frame_w = x0 + 6 * (w + gap_x) + 10
|
|
frame_h = y0 + 4 * (h + gap_y) + 16
|
|
show_frame = f""" <SoloFrame Caption="Show">
|
|
<Appearance>
|
|
<FrameStyle>Sunken</FrameStyle>
|
|
<ForegroundColor>Default</ForegroundColor>
|
|
<BackgroundColor>Default</BackgroundColor>
|
|
<BackgroundImage>None</BackgroundImage>
|
|
<Font>Default</Font>
|
|
</Appearance>
|
|
<WindowState Visible="True" X="10" Y="10" Width="{frame_w}" Height="{frame_h}"/>
|
|
<AllowChildren>True</AllowChildren>
|
|
<AllowResize>True</AllowResize>
|
|
<ShowHeader>True</ShowHeader>
|
|
<Mixing>False</Mixing>
|
|
{chr(10).join(buttons)}
|
|
</SoloFrame>"""
|
|
|
|
# Replace Patterns SoloFrame
|
|
text = re.sub(
|
|
r' <SoloFrame Caption="Patterns">[\s\S]*?</SoloFrame>',
|
|
show_frame,
|
|
text,
|
|
count=1,
|
|
)
|
|
|
|
# Remove Movers SoloFrame (buttons now in Show)
|
|
text = re.sub(
|
|
r' <SoloFrame Caption="Movers">[\s\S]*?</SoloFrame>\n',
|
|
"",
|
|
text,
|
|
count=1,
|
|
)
|
|
|
|
# Merge mover speed targets into main Speed dial; drop Mover Speed dial
|
|
text = re.sub(
|
|
r' <SpeedDial Caption="Mover Speed" ID="102">[\s\S]*?</SpeedDial>\n',
|
|
"",
|
|
text,
|
|
count=1,
|
|
)
|
|
speed_extra = """ <Function FadeIn="6" FadeOut="0" Duration="6">74</Function>
|
|
<Function FadeIn="0" FadeOut="0" Duration="6">76</Function>
|
|
<Function FadeIn="6" FadeOut="0" Duration="6">101</Function>
|
|
<Function FadeIn="6" FadeOut="0" Duration="6">102</Function>
|
|
<Function FadeIn="6" FadeOut="0" Duration="6">103</Function>
|
|
<Function FadeIn="6" FadeOut="0" Duration="6">104</Function>
|
|
<Function FadeIn="0" FadeOut="0" Duration="6">105</Function>
|
|
"""
|
|
if 'Function FadeIn="6" FadeOut="0" Duration="6">74</Function>' not in text:
|
|
text = text.replace(
|
|
' <Function FadeIn="0" FadeOut="0" Duration="6">69</Function>\n </SpeedDial>',
|
|
' <Function FadeIn="0" FadeOut="0" Duration="6">69</Function>\n'
|
|
+ speed_extra
|
|
+ " </SpeedDial>",
|
|
1,
|
|
)
|
|
|
|
# Make Speed dial visible next to Show
|
|
text = text.replace(
|
|
'<SpeedDial Caption="Speed" ID="3">\n <WindowState Visible="False" X="720" Y="20" Width="90" Height="250"/>',
|
|
f'<SpeedDial Caption="Speed" ID="3">\n <WindowState Visible="True" X="{frame_w + 20}" Y="10" Width="100" Height="{frame_h}"/>',
|
|
1,
|
|
)
|
|
|
|
# All Dim also drives mover dimmers
|
|
if "<Channel Fixture=\"8\">8</Channel>" not in text.split('Caption="All Dim"', 1)[1][:800]:
|
|
text = text.replace(
|
|
""" <Level LowLimit="0" HighLimit="255" Value="174">
|
|
<Channel Fixture="0">0</Channel>
|
|
<Channel Fixture="1">0</Channel>
|
|
<Channel Fixture="2">0</Channel>
|
|
<Channel Fixture="3">0</Channel>
|
|
<Channel Fixture="4">0</Channel>
|
|
<Channel Fixture="5">0</Channel>
|
|
<Channel Fixture="6">0</Channel>
|
|
<Channel Fixture="7">0</Channel>
|
|
</Level>
|
|
<Playback>
|
|
<Function>4294967295</Function>
|
|
</Playback>
|
|
</Slider>
|
|
<Slider Caption="1 R\"""",
|
|
""" <Level LowLimit="0" HighLimit="255" Value="174">
|
|
<Channel Fixture="0">0</Channel>
|
|
<Channel Fixture="1">0</Channel>
|
|
<Channel Fixture="2">0</Channel>
|
|
<Channel Fixture="3">0</Channel>
|
|
<Channel Fixture="4">0</Channel>
|
|
<Channel Fixture="5">0</Channel>
|
|
<Channel Fixture="6">0</Channel>
|
|
<Channel Fixture="7">0</Channel>
|
|
<Channel Fixture="8">8</Channel>
|
|
<Channel Fixture="9">8</Channel>
|
|
</Level>
|
|
<Playback>
|
|
<Function>4294967295</Function>
|
|
</Playback>
|
|
</Slider>
|
|
<Slider Caption="1 R\"""",
|
|
1,
|
|
)
|
|
|
|
# Relabel / place XY pad under Show
|
|
text = text.replace(
|
|
'Caption="Mover Position"',
|
|
'Caption="Position"',
|
|
1,
|
|
)
|
|
text = text.replace(
|
|
'<XYPad Caption="Position" ID="103" InvertedAppearance="0">\n <WindowState Visible="True" X="720" Y="180" Width="320" Height="320"/>',
|
|
f'<XYPad Caption="Position" ID="103" InvertedAppearance="0">\n <WindowState Visible="True" X="10" Y="{frame_h + 20}" Width="320" Height="300"/>',
|
|
1,
|
|
)
|
|
|
|
# Shift mover sliders under Show, right of XY pad
|
|
slider_y = frame_h + 20
|
|
old_xs = [1050 + i * 46 for i in range(10)]
|
|
new_xs = [340 + i * 46 for i in range(10)]
|
|
for ox, nx in zip(old_xs, new_xs):
|
|
text = text.replace(
|
|
f'Visible="True" X="{ox}" Y="180" Width="44" Height="320"',
|
|
f'Visible="True" X="{nx}" Y="{slider_y}" Width="44" Height="300"',
|
|
1,
|
|
)
|
|
|
|
path.write_text(text)
|
|
print(f"Unified Show controls in {path}")
|
|
print(f" SoloFrame {frame_w}x{frame_h} with pattern + F8 combos + exits")
|
|
print(" One Speed dial; Position pad + mover sliders below")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|