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,268 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user