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>
306 lines
8.5 KiB
Python
306 lines
8.5 KiB
Python
#!/usr/bin/env python3
|
||
"""Generate shows/simple.qxw — per-mover position + shared and individual RGBW."""
|
||
|
||
from __future__ import annotations
|
||
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parents[1]
|
||
OUT = ROOT / "shows" / "simple.qxw"
|
||
|
||
# Fixture IDs match shows/xchc.qxw
|
||
CANS = list(range(8)) # BCC145 #1–#8
|
||
M1, M2 = 8, 9
|
||
MOVERS = [M1, M2]
|
||
|
||
# Can 8ch: Dim=0, R=2, G=3, B=4, W=5
|
||
# Mover 43ch: Center Bright=8; RGBW heads 1–7
|
||
MOVER_R = [10, 14, 18, 22, 26, 30, 34]
|
||
MOVER_G = [11, 15, 19, 23, 27, 31, 35]
|
||
MOVER_B = [12, 16, 20, 24, 28, 32, 36]
|
||
MOVER_W = [13, 17, 21, 25, 29, 33, 37]
|
||
|
||
COLORS = [
|
||
("R", 2, MOVER_R, "4294901760", "Red"),
|
||
("G", 3, MOVER_G, "4278255360", "Green"),
|
||
("B", 4, MOVER_B, "4278190335", "Blue"),
|
||
("W", 5, MOVER_W, "4294967295", "None"),
|
||
]
|
||
|
||
|
||
def appearance(
|
||
fg: str = "Default",
|
||
bg: str = "Default",
|
||
frame: str = "Sunken",
|
||
) -> str:
|
||
return f""" <Appearance>
|
||
<FrameStyle>{frame}</FrameStyle>
|
||
<ForegroundColor>{fg}</ForegroundColor>
|
||
<BackgroundColor>{bg}</BackgroundColor>
|
||
<BackgroundImage>None</BackgroundImage>
|
||
<Font>Default</Font>
|
||
</Appearance>"""
|
||
|
||
|
||
def slider(
|
||
wid: int,
|
||
caption: str,
|
||
x: int,
|
||
y: int,
|
||
w: int,
|
||
h: int,
|
||
channels: list[tuple[int, int]],
|
||
*,
|
||
value: int = 0,
|
||
fg: str = "Default",
|
||
cng: str = "None",
|
||
) -> str:
|
||
ch_xml = "\n".join(
|
||
f' <Channel Fixture="{fid}">{ch}</Channel>' for fid, ch in channels
|
||
)
|
||
return f""" <Slider Caption="{caption}" ID="{wid}" WidgetStyle="Slider" InvertedAppearance="false">
|
||
<WindowState Visible="True" X="{x}" Y="{y}" Width="{w}" Height="{h}"/>
|
||
{appearance(fg=fg)}
|
||
<SliderMode ValueDisplayStyle="Exact" ClickAndGoType="{cng}" Monitor="true">Level</SliderMode>
|
||
<Reset/>
|
||
<Level LowLimit="0" HighLimit="255" Value="{value}">
|
||
{ch_xml}
|
||
</Level>
|
||
<Playback>
|
||
<Function>4294967295</Function>
|
||
</Playback>
|
||
</Slider>"""
|
||
|
||
|
||
def xypad(wid: int, caption: str, fid: int, x: int, y: int, w: int, h: int) -> str:
|
||
return f""" <XYPad Caption="{caption}" ID="{wid}" InvertedAppearance="0">
|
||
<WindowState Visible="True" X="{x}" Y="{y}" Width="{w}" Height="{h}"/>
|
||
{appearance()}
|
||
<Fixture ID="{fid}" Head="0">
|
||
<Axis ID="X" LowLimit="0" HighLimit="1" Reverse="False"/>
|
||
<Axis ID="Y" LowLimit="0" HighLimit="1" Reverse="False"/>
|
||
</Fixture>
|
||
<Window hMin="0" hMax="255" vMin="0" vMax="255"/>
|
||
<Pan Position="128"/>
|
||
<Tilt Position="128"/>
|
||
</XYPad>"""
|
||
|
||
|
||
def label_frame(caption: str, x: int, y: int, w: int, h: int) -> str:
|
||
return f""" <Frame Caption="{caption}">
|
||
<Appearance>
|
||
<FrameStyle>Sunken</FrameStyle>
|
||
<ForegroundColor>Default</ForegroundColor>
|
||
<BackgroundColor>Default</BackgroundColor>
|
||
<BackgroundImage>None</BackgroundImage>
|
||
<Font>Default</Font>
|
||
</Appearance>
|
||
<WindowState Visible="True" X="{x}" Y="{y}" Width="{w}" Height="{h}"/>
|
||
<AllowChildren>True</AllowChildren>
|
||
<AllowResize>True</AllowResize>
|
||
<ShowHeader>True</ShowHeader>
|
||
<ShowEnableButton>False</ShowEnableButton>
|
||
<ShowIntensityButton>False</ShowIntensityButton>
|
||
<Mixing>False</Mixing>
|
||
</Frame>"""
|
||
|
||
|
||
def fixture_xml() -> str:
|
||
parts = []
|
||
for i in range(8):
|
||
parts.append(
|
||
f""" <Fixture>
|
||
<Manufacturer>beamZ</Manufacturer>
|
||
<Model>BCC145</Model>
|
||
<Mode>8 Channel</Mode>
|
||
<ID>{i}</ID>
|
||
<Name>BCC145 #{i + 1}</Name>
|
||
<Universe>0</Universe>
|
||
<Address>{87 + i * 8}</Address>
|
||
<Channels>8</Channels>
|
||
</Fixture>"""
|
||
)
|
||
for i, addr in enumerate((0, 44)):
|
||
parts.append(
|
||
f""" <Fixture>
|
||
<Manufacturer>Small Eye 7</Manufacturer>
|
||
<Model>Mover</Model>
|
||
<Mode>43 Channel</Mode>
|
||
<ID>{8 + i}</ID>
|
||
<Name>Small Eye 7 #{i + 1}</Name>
|
||
<Universe>0</Universe>
|
||
<Address>{addr}</Address>
|
||
<Channels>43</Channels>
|
||
</Fixture>"""
|
||
)
|
||
return "\n".join(parts)
|
||
|
||
|
||
def main() -> None:
|
||
pad_w, pad_h = 240, 240
|
||
sl_w, sl_h = 44, 240
|
||
ind_w, ind_h = 36, 160
|
||
gap = 8
|
||
|
||
widgets: list[str] = []
|
||
wid = 0
|
||
max_x = 0
|
||
max_y = 0
|
||
|
||
def place_slider(caption, x, y, w, h, channels, value=0, fg="Default", cng="None"):
|
||
nonlocal wid, max_x, max_y
|
||
widgets.append(
|
||
slider(wid, caption, x, y, w, h, channels, value=value, fg=fg, cng=cng)
|
||
)
|
||
wid += 1
|
||
max_x = max(max_x, x + w)
|
||
max_y = max(max_y, y + h)
|
||
|
||
y0 = 28
|
||
x = 10
|
||
widgets.append(xypad(wid, "M1 Position", M1, x, y0, pad_w, pad_h))
|
||
wid += 1
|
||
x += pad_w + gap
|
||
widgets.append(xypad(wid, "M2 Position", M2, x, y0, pad_w, pad_h))
|
||
wid += 1
|
||
x += pad_w + gap + 12
|
||
max_x = max(max_x, x)
|
||
max_y = max(max_y, y0 + pad_h)
|
||
|
||
place_slider("C Dim", x, y0, sl_w, sl_h, [(f, 0) for f in CANS], 255, "4294967295")
|
||
x += sl_w + 2
|
||
for name, can_ch, _mover_chs, fg, cng in COLORS:
|
||
place_slider(
|
||
f"C {name}",
|
||
x,
|
||
y0,
|
||
sl_w,
|
||
sl_h,
|
||
[(f, can_ch) for f in CANS],
|
||
fg=fg,
|
||
cng=cng,
|
||
)
|
||
x += sl_w + 2
|
||
|
||
x += 14
|
||
|
||
place_slider("M Dim", x, y0, sl_w, sl_h, [(f, 8) for f in MOVERS], 255, "4294967295")
|
||
x += sl_w + 2
|
||
for name, _can_ch, mover_chs, fg, cng in COLORS:
|
||
place_slider(
|
||
f"M {name}",
|
||
x,
|
||
y0,
|
||
sl_w,
|
||
sl_h,
|
||
[(f, c) for f in MOVERS for c in mover_chs],
|
||
fg=fg,
|
||
cng=cng,
|
||
)
|
||
x += sl_w + 2
|
||
|
||
y1 = y0 + pad_h + 36
|
||
x = 10
|
||
widgets.append(label_frame("Cans individual RGBW", x, y1 - 28, 200, 24))
|
||
|
||
for i, fid in enumerate(CANS):
|
||
col_x = x + i * (4 * (ind_w + 2) + 10)
|
||
for j, (name, can_ch, _m, fg, cng) in enumerate(COLORS):
|
||
place_slider(
|
||
f"{i + 1}{name}",
|
||
col_x + j * (ind_w + 2),
|
||
y1,
|
||
ind_w,
|
||
ind_h,
|
||
[(fid, can_ch)],
|
||
fg=fg,
|
||
cng=cng,
|
||
)
|
||
|
||
mx = x + 8 * (4 * (ind_w + 2) + 10) + 16
|
||
widgets.append(label_frame("Movers individual RGBW", mx, y1 - 28, 220, 24))
|
||
for mi, (fid, label) in enumerate(((M1, "M1"), (M2, "M2"))):
|
||
col_x = mx + mi * (4 * (ind_w + 2) + 14)
|
||
for j, (name, _can_ch, mover_chs, fg, cng) in enumerate(COLORS):
|
||
place_slider(
|
||
f"{label}{name}",
|
||
col_x + j * (ind_w + 2),
|
||
y1,
|
||
ind_w,
|
||
ind_h,
|
||
[(fid, c) for c in mover_chs],
|
||
fg=fg,
|
||
cng=cng,
|
||
)
|
||
|
||
total_w = max_x + 16
|
||
total_h = max_y + 16
|
||
|
||
qxw = f"""<?xml version="1.0" encoding="UTF-8"?>
|
||
<!DOCTYPE Workspace>
|
||
<Workspace xmlns="http://www.qlcplus.org/Workspace" CurrentWindow="VirtualConsole">
|
||
<Creator>
|
||
<Name>Q Light Controller Plus</Name>
|
||
<Version>4.12.7</Version>
|
||
<Author>Jimmy</Author>
|
||
</Creator>
|
||
<Engine>
|
||
<InputOutputMap>
|
||
<Universe Name="Universe 1" ID="0">
|
||
<Output Plugin="ArtNet" UID="192.168.1.92" Line="7">
|
||
<PluginParameters outputIP="192.168.1.228" outputUni="0"/>
|
||
</Output>
|
||
</Universe>
|
||
<Universe Name="Universe 2" ID="1"/>
|
||
<Universe Name="Universe 3" ID="2"/>
|
||
<Universe Name="Universe 4" ID="3"/>
|
||
</InputOutputMap>
|
||
{fixture_xml()}
|
||
<FixtureGroup ID="0">
|
||
<Name>BCC145 U</Name>
|
||
<Size X="8" Y="1"/>
|
||
<Head X="0" Y="0" Fixture="0">0</Head>
|
||
<Head X="1" Y="0" Fixture="1">0</Head>
|
||
<Head X="2" Y="0" Fixture="2">0</Head>
|
||
<Head X="3" Y="0" Fixture="3">0</Head>
|
||
<Head X="4" Y="0" Fixture="4">0</Head>
|
||
<Head X="5" Y="0" Fixture="5">0</Head>
|
||
<Head X="6" Y="0" Fixture="6">0</Head>
|
||
<Head X="7" Y="0" Fixture="7">0</Head>
|
||
</FixtureGroup>
|
||
<FixtureGroup ID="1">
|
||
<Name>Small Eye Movers</Name>
|
||
<Size X="2" Y="1"/>
|
||
<Head X="0" Y="0" Fixture="8">0</Head>
|
||
<Head X="1" Y="0" Fixture="9">0</Head>
|
||
</FixtureGroup>
|
||
</Engine>
|
||
<VirtualConsole>
|
||
<Frame Caption="">
|
||
<Appearance>
|
||
<FrameStyle>None</FrameStyle>
|
||
<ForegroundColor>Default</ForegroundColor>
|
||
<BackgroundColor>Default</BackgroundColor>
|
||
<BackgroundImage>None</BackgroundImage>
|
||
<Font>Default</Font>
|
||
</Appearance>
|
||
{"\n".join(widgets)}
|
||
</Frame>
|
||
<Properties>
|
||
<Size Width="{total_w}" Height="{total_h}"/>
|
||
<GrandMaster ChannelMode="Intensity" ValueMode="Reduce" SliderMode="Normal"/>
|
||
</Properties>
|
||
</VirtualConsole>
|
||
<SimpleDesk>
|
||
<Engine/>
|
||
</SimpleDesk>
|
||
</Workspace>
|
||
"""
|
||
OUT.parent.mkdir(parents=True, exist_ok=True)
|
||
OUT.write_text(qxw)
|
||
print(f"Wrote {OUT} ({total_w}x{total_h}, {wid} widgets)")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|