Files
qlcplus/scripts/make_simple_vc.py
jimmyandCursor 5eb623d6af 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>
2026-09-08 00:59:36 +12:00

306 lines
8.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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()