Split cabaret figure-8s onto the floor and the wall, and make Speed a fader.

Floor 8 stays on the live ground aim; Wall 8 uses the Here–Out box. Pan/tilt speed is a slider so it matches Bright and Focus.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-10 00:47:18 +12:00
co-authored by Cursor
parent f069a8091b
commit 8f08d44188
2 changed files with 86 additions and 77 deletions
+66 -61
View File
@@ -16,7 +16,8 @@ GROUP_C = [2, 3, 4, 5]
GROUP_R = [6, 7] GROUP_R = [6, 7]
# Can 8ch: Dim=0, R=2, G=3, B=4 # Can 8ch: Dim=0, R=2, G=3, B=4
# Mover 43ch: Focus=7, Center Bright=8; RGB heads 1–7 # Mover 43ch: Pan/Tilt Speed=4, Focus=7, Center Bright=8; RGB heads 1–7
MOVER_SPEED = 4
MOVER_FOCUS = 7 MOVER_FOCUS = 7
MOVER_DIM = 8 MOVER_DIM = 8
MOVER_R = [10, 14, 18, 22, 26, 30, 34] MOVER_R = [10, 14, 18, 22, 26, 30, 34]
@@ -69,11 +70,11 @@ POSITIONS: list[tuple[str, tuple[int, int], tuple[int, int], str]] = [
POSITION_FUNC0 = 100 POSITION_FUNC0 = 100
PADS_FUNC = POSITION_FUNC0 + len(POSITIONS) PADS_FUNC = POSITION_FUNC0 + len(POSITIONS)
PADS_BG = "4282800750" # #465A6E pad grey-blue PADS_BG = "4282800750" # #465A6E pad grey-blue
FIGURE8_FUNC = 110 FIGURE8_FLOOR_FUNC = 110
FIGURE8_SIDE_FUNC = 111 FIGURE8_WALL_FUNC = 111
FIGURE8_MS = 12000 FIGURE8_MS = 12000
FIGURE8_BG = "4282664004" FIGURE8_FLOOR_BG = "4282664004"
FIGURE8_SIDE_BG = "4286611584" FIGURE8_WALL_BG = "4286611584"
LOOK_PAIRS: list[tuple[str, str | None]] = [ LOOK_PAIRS: list[tuple[str, str | None]] = [
("Amber", None), ("Amber", None),
("Pink", "Amber"), ("Pink", "Amber"),
@@ -132,11 +133,13 @@ def slider(
value: int = 0, value: int = 0,
fg: str = "Default", fg: str = "Default",
cng: str = "None", cng: str = "None",
inverted: bool = False,
) -> str: ) -> str:
ch_xml = "\n".join( ch_xml = "\n".join(
f' <Channel Fixture="{fid}">{ch}</Channel>' for fid, ch in channels f' <Channel Fixture="{fid}">{ch}</Channel>' for fid, ch in channels
) )
return f""" <Slider Caption="{caption}" ID="{wid}" WidgetStyle="Slider" InvertedAppearance="false"> inv = "true" if inverted else "false"
return f""" <Slider Caption="{caption}" ID="{wid}" WidgetStyle="Slider" InvertedAppearance="{inv}">
<WindowState Visible="True" X="{x}" Y="{y}" Width="{w}" Height="{h}"/> <WindowState Visible="True" X="{x}" Y="{y}" Width="{w}" Height="{h}"/>
{appearance(fg=fg)} {appearance(fg=fg)}
<SliderMode ValueDisplayStyle="Exact" ClickAndGoType="{cng}" Monitor="true">Level</SliderMode> <SliderMode ValueDisplayStyle="Exact" ClickAndGoType="{cng}" Monitor="true">Level</SliderMode>
@@ -150,33 +153,6 @@ def slider(
</Slider>""" </Slider>"""
def speed_dial(
wid: int,
caption: str,
x: int,
y: int,
w: int,
h: int,
func_ids: list[int],
*,
time_ms: int = FIGURE8_MS,
min_ms: int = 500,
max_ms: int = 30000,
) -> str:
fn_xml = "\n".join(
f' <Function FadeIn="0" FadeOut="0" Duration="6">{fid}</Function>'
for fid in func_ids
)
return f""" <SpeedDial Caption="{caption}" ID="{wid}">
<WindowState Visible="True" X="{x}" Y="{y}" Width="{w}" Height="{h}"/>
{appearance()}
<Visibility>2</Visibility>
<AbsoluteValue Minimum="{min_ms}" Maximum="{max_ms}"/>
<Time>{time_ms}</Time>
{fn_xml}
</SpeedDial>"""
def xypad( def xypad(
wid: int, wid: int,
caption: str, caption: str,
@@ -401,12 +377,23 @@ def position_button_specs() -> list[tuple[str, int, str]]:
(name, POSITION_FUNC0 + i, bg) (name, POSITION_FUNC0 + i, bg)
for i, (name, _m1, _m2, bg) in enumerate(POSITIONS) for i, (name, _m1, _m2, bg) in enumerate(POSITIONS)
] ]
+ [("Fig 8", FIGURE8_FUNC, FIGURE8_BG), ("Side 8", FIGURE8_SIDE_FUNC, FIGURE8_SIDE_BG)] + [
("Floor 8", FIGURE8_FLOOR_FUNC, FIGURE8_FLOOR_BG),
("Wall 8", FIGURE8_WALL_FUNC, FIGURE8_WALL_BG),
]
) )
def figure8_bounds() -> tuple[int, int, int, int]: def figure8_floor_center() -> tuple[int, int]:
"""Pan/tilt centre and amplitude covering Here–Out (not the live Now aim).""" """Now is the live floor aim; average both heads so the 8 stays on the ground."""
now = next((m1, m2) for name, m1, m2, _bg in POSITIONS if name == "Now")
pans = [now[0][0], now[1][0]]
tilts = [now[0][1], now[1][1]]
return (pans[0] + pans[1]) // 2, (tilts[0] + tilts[1]) // 2
def figure8_wall_bounds() -> tuple[int, int, int, int]:
"""Pan/tilt centre and amplitude covering Here–Out (the wall looks)."""
pans: list[int] = [] pans: list[int] = []
tilts: list[int] = [] tilts: list[int] = []
for name, m1, m2, _bg in POSITIONS: for name, m1, m2, _bg in POSITIONS:
@@ -461,22 +448,29 @@ def figure8_efx_block(
def figure8_efx_xml() -> str: def figure8_efx_xml() -> str:
pan_c, tilt_c, pan_amp, tilt_amp = figure8_bounds() floor_pan, floor_tilt = figure8_floor_center()
# Sideways ∞ is rotation 0. 90° is a vertical 8 (tilt 0 → 36 down); wall_pan, wall_tilt, wall_pan_amp, wall_tilt_amp = figure8_wall_bounds()
# width/height are swapped so both still fill the Here–Out box. # Floor 8: around Now, tilt stays on the ground. Rotation 90 = upright 8.
# Wall 8: Here–Out box (tilt 0–36). Rotation 0 = 8 across the wall.
return "\n".join( return "\n".join(
[ [
figure8_efx_block( figure8_efx_block(
FIGURE8_FUNC, "Figure of 8", tilt_amp, pan_amp, 90, pan_c, tilt_c FIGURE8_FLOOR_FUNC,
"Figure of 8 Floor",
16,
18,
90,
floor_pan,
floor_tilt,
), ),
figure8_efx_block( figure8_efx_block(
FIGURE8_SIDE_FUNC, FIGURE8_WALL_FUNC,
"Figure of 8 Side", "Figure of 8 Walls",
pan_amp, wall_pan_amp,
tilt_amp, wall_tilt_amp,
0, 0,
pan_c, wall_pan,
tilt_c, wall_tilt,
), ),
] ]
) )
@@ -554,10 +548,24 @@ def main() -> None:
max_x = 0 max_x = 0
max_y = 0 max_y = 0
def place_slider(caption, x, y, w, h, channels, value=0, fg="Default", cng="None"): def place_slider(
caption, x, y, w, h, channels, value=0, fg="Default", cng="None", inverted=False
):
nonlocal wid, max_x, max_y nonlocal wid, max_x, max_y
widgets.append( widgets.append(
slider(wid, caption, x, y, w, h, channels, value=value, fg=fg, cng=cng) slider(
wid,
caption,
x,
y,
w,
h,
channels,
value=value,
fg=fg,
cng=cng,
inverted=inverted,
)
) )
wid += 1 wid += 1
max_x = max(max_x, x + w) max_x = max(max_x, x + w)
@@ -688,20 +696,17 @@ def main() -> None:
"4294902015", "4294902015",
) )
x += sl_w + 2 x += sl_w + 2
widgets.append( place_slider(
speed_dial( "Speed",
wid, x,
"Speed", y0,
x, sl_w,
y0, sl_h,
sl_w, [(M1, MOVER_SPEED), (M2, MOVER_SPEED)],
sl_h, 0,
[FIGURE8_FUNC, FIGURE8_SIDE_FUNC], "4294967295",
) inverted=True,
) )
wid += 1
max_x = max(max_x, x + sl_w)
max_y = max(max_y, y0 + sl_h)
x += sl_w + 2 x += sl_w + 2
for name, _can_ch, mover_chs, fg, cng in RGB: for name, _can_ch, mover_chs, fg, cng in RGB:
place_slider( place_slider(
+20 -16
View File
@@ -381,7 +381,7 @@
<FixtureVal ID="8">0,200,1,0,2,22,3,0</FixtureVal> <FixtureVal ID="8">0,200,1,0,2,22,3,0</FixtureVal>
<FixtureVal ID="9">0,200,1,0,2,22,3,0</FixtureVal> <FixtureVal ID="9">0,200,1,0,2,22,3,0</FixtureVal>
</Function> </Function>
<Function ID="110" Type="EFX" Name="Figure of 8"> <Function ID="110" Type="EFX" Name="Figure of 8 Floor">
<Fixture> <Fixture>
<ID>8</ID> <ID>8</ID>
<Direction>Forward</Direction> <Direction>Forward</Direction>
@@ -397,21 +397,21 @@
<Direction>Forward</Direction> <Direction>Forward</Direction>
<RunOrder>Loop</RunOrder> <RunOrder>Loop</RunOrder>
<Algorithm>Eight</Algorithm> <Algorithm>Eight</Algorithm>
<Width>18</Width> <Width>16</Width>
<Height>30</Height> <Height>18</Height>
<Rotation>90</Rotation> <Rotation>90</Rotation>
<Axis Name="X"> <Axis Name="X">
<Offset>170</Offset> <Offset>77</Offset>
<Frequency>1</Frequency> <Frequency>1</Frequency>
<Phase>0</Phase> <Phase>0</Phase>
</Axis> </Axis>
<Axis Name="Y"> <Axis Name="Y">
<Offset>18</Offset> <Offset>153</Offset>
<Frequency>1</Frequency> <Frequency>1</Frequency>
<Phase>0</Phase> <Phase>0</Phase>
</Axis> </Axis>
</Function> </Function>
<Function ID="111" Type="EFX" Name="Figure of 8 Side"> <Function ID="111" Type="EFX" Name="Figure of 8 Walls">
<Fixture> <Fixture>
<ID>8</ID> <ID>8</ID>
<Direction>Forward</Direction> <Direction>Forward</Direction>
@@ -523,21 +523,25 @@
<Function>4294967295</Function> <Function>4294967295</Function>
</Playback> </Playback>
</Slider> </Slider>
<SpeedDial Caption="Speed" ID="4"> <Slider Caption="Speed" ID="4" WidgetStyle="Slider" InvertedAppearance="true">
<WindowState Visible="True" X="654" Y="28" Width="44" Height="240"/> <WindowState Visible="True" X="654" Y="28" Width="44" Height="240"/>
<Appearance> <Appearance>
<FrameStyle>Sunken</FrameStyle> <FrameStyle>Sunken</FrameStyle>
<ForegroundColor>Default</ForegroundColor> <ForegroundColor>4294967295</ForegroundColor>
<BackgroundColor>Default</BackgroundColor> <BackgroundColor>Default</BackgroundColor>
<BackgroundImage>None</BackgroundImage> <BackgroundImage>None</BackgroundImage>
<Font>Default</Font> <Font>Default</Font>
</Appearance> </Appearance>
<Visibility>2</Visibility> <SliderMode ValueDisplayStyle="Exact" ClickAndGoType="None" Monitor="true">Level</SliderMode>
<AbsoluteValue Minimum="500" Maximum="30000"/> <Reset/>
<Time>12000</Time> <Level LowLimit="0" HighLimit="255" Value="0">
<Function FadeIn="0" FadeOut="0" Duration="6">110</Function> <Channel Fixture="8">4</Channel>
<Function FadeIn="0" FadeOut="0" Duration="6">111</Function> <Channel Fixture="9">4</Channel>
</SpeedDial> </Level>
<Playback>
<Function>4294967295</Function>
</Playback>
</Slider>
<Slider Caption="M R" ID="5" WidgetStyle="Slider" InvertedAppearance="false"> <Slider Caption="M R" ID="5" WidgetStyle="Slider" InvertedAppearance="false">
<WindowState Visible="True" X="700" Y="28" Width="44" Height="240"/> <WindowState Visible="True" X="700" Y="28" Width="44" Height="240"/>
<Appearance> <Appearance>
@@ -736,7 +740,7 @@
<Action>Toggle</Action> <Action>Toggle</Action>
<Intensity Adjust="False">100</Intensity> <Intensity Adjust="False">100</Intensity>
</Button> </Button>
<Button Caption="Fig 8" ID="16" Icon=""> <Button Caption="Floor 8" ID="16" Icon="">
<WindowState Visible="True" X="82" Y="176" Width="72" Height="42"/> <WindowState Visible="True" X="82" Y="176" Width="72" Height="42"/>
<Appearance> <Appearance>
<FrameStyle>None</FrameStyle> <FrameStyle>None</FrameStyle>
@@ -749,7 +753,7 @@
<Action>Toggle</Action> <Action>Toggle</Action>
<Intensity Adjust="False">100</Intensity> <Intensity Adjust="False">100</Intensity>
</Button> </Button>
<Button Caption="Side 8" ID="17" Icon=""> <Button Caption="Wall 8" ID="17" Icon="">
<WindowState Visible="True" X="4" Y="224" Width="72" Height="42"/> <WindowState Visible="True" X="4" Y="224" Width="72" Height="42"/>
<Appearance> <Appearance>
<FrameStyle>None</FrameStyle> <FrameStyle>None</FrameStyle>