Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
07ce689
add: basic template generation for C2CW vFilter
Doralitze Jan 11, 2026
964e849
fix: import formatting
Doralitze Jan 12, 2026
9172d61
add: ColorToColorWheel support to vfilter_factory
Doralitze Mar 17, 2026
37fd93a
add: method to automatically generate color mapping
Doralitze Mar 25, 2026
e00353f
add: automatic mode support to color wheel vfilter
Doralitze Mar 26, 2026
9401423
add: filter node integration for color2colorwheel adapter
Doralitze Mar 26, 2026
21f93e3
add: towards configuration widget for colorwheel adapter
Doralitze Mar 26, 2026
4a354f3
add: color mapping list implementation
Doralitze Mar 27, 2026
20e0d8c
add: towards color wheel parsing from OFL
Doralitze Apr 2, 2026
413654f
add: fixture selection in config widget
Doralitze Apr 2, 2026
2aa0ca4
add: combobox options in config widget
Doralitze Apr 2, 2026
91cc647
add: color name parsing
Doralitze Apr 3, 2026
f91c989
fix: fixture color wheel decoding
Doralitze Apr 4, 2026
0b582f1
fix: disappearing FSI after removing dimmer channels
Doralitze Apr 5, 2026
c28c383
fix: first half of ruff issues
Doralitze Apr 5, 2026
7c86141
fix: remaining ruff issues
Doralitze Apr 5, 2026
1f5bff2
fix: new ruff rule
Doralitze Apr 7, 2026
f686f81
chg: more elaborated requests by ruff
Doralitze Apr 7, 2026
910d64d
add: color from temperature
Doralitze Apr 9, 2026
85deb1e
fix: first documentation instances
Doralitze Apr 9, 2026
56125ea
fix: docs in filter node classes
Doralitze Apr 9, 2026
89acc9f
fix: docs in color selection UI widget
Doralitze Apr 9, 2026
c5fe98b
fix: requested autotracker docs
Doralitze Apr 9, 2026
bfffe55
fix: doc string in node_editor_widget.py
Doralitze Apr 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/controller/cli/cli_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def __init__(self, show: BoardConfiguration, network_manager: NetworkManager, ex
help="subcommands help", dest="subparser_name"
)
for c in self._commands:
c.configure_parser(subparsers.add_parser(c.name, help=c.help, exit_on_error=False))
c.configure_parser(subparsers.add_parser(c.name, help=c.help_text, exit_on_error=False))
if exit_available:
subparsers.add_parser("exit", exit_on_error=False, help="Close this remote connection")
self._return_text = ""
Expand Down
6 changes: 3 additions & 3 deletions src/controller/cli/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ def name(self) -> str:
return "Unnamed Command"

@property
def help(self) -> str:
def help_text(self) -> str:
"""Help text."""
return self._help_text

@help.setter
def help(self, new_help: str) -> None:
@help_text.setter
def help_text(self, new_help: str) -> None:
self._help_text = str(new_help)
3 changes: 2 additions & 1 deletion src/controller/file/read.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
from controller.file.deserialization.migrations import replace_old_filter_configurations
from controller.file.deserialization.post_load_operations import link_patched_fixtures
from controller.utils.process_notifications import get_process_notifier
from model import BoardConfiguration, ColorHSI, Filter, Scene, UIPage, Universe
from model import BoardConfiguration, Filter, Scene, UIPage, Universe
from model.color_hsi import ColorHSI
from model.control_desk import BankSet, ColorDeskColumn, FaderBank, RawDeskColumn
from model.events import EventSender, mark_sender_persistent
from model.filter import VirtualFilter
Expand Down
2 changes: 1 addition & 1 deletion src/controller/joystick/joystick_handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,5 @@ def reformat(key: Key) -> None:

def __new__(cls) -> Self:
"""Connect a joystick and setup the key bindings."""
mngr = pyjoystick.ThreadEventManager(event_loop=run_event_loop, handle_key_event=lambda key: cls.reformat(key))
mngr = pyjoystick.ThreadEventManager(event_loop=run_event_loop, handle_key_event=cls.reformat)
mngr.start()
3 changes: 1 addition & 2 deletions src/model/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
"""Public classes and methods from the model package"""
"""Public classes and methods from the model package."""

from .board_configuration import BoardConfiguration
from .broadcaster import Broadcaster
from .color_hsi import ColorHSI
from .device import Device
from .filter import DataType, Filter
from .scene import Scene
Expand Down
36 changes: 32 additions & 4 deletions src/model/board_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from collections.abc import Callable, Sequence
from logging import getLogger
from uuid import UUID

import numpy as np
from PySide6 import QtCore, QtGui
Expand Down Expand Up @@ -30,7 +31,7 @@ def __init__(self, show_name: str = "", default_active_scene: int = 0, notes: st
self._scenes_index: dict[int, int] = {}
self._devices: list[Device] = []
self._universes: dict[int, Universe] = {}
self._fixtures: list[UsedFixture] = []
self._fixtures: dict[str, UsedFixture] = {}
self._ui_hints: dict[str, str] = {}
self._macros: list[Macro] = []

Expand Down Expand Up @@ -100,7 +101,7 @@ def _add_universe(self, universe: Universe) -> None:
self._universes.update({universe.id: universe})

def _add_fixture(self, used_fixture: UsedFixture) -> None:
self._fixtures.append(used_fixture)
self._fixtures[str(used_fixture.uuid)] = used_fixture

def _delete_universe(self, universe: Universe) -> None:
"""Remove the passed universe from the list of universes.
Expand All @@ -110,7 +111,10 @@ def _delete_universe(self, universe: Universe) -> None:

"""
try:
del self._universes[universe.id]
uid = universe.id if isinstance(universe, Universe) else universe if isinstance(universe, int) else None
if uid is None:
raise ValueError("Expected a universe.")
del self._universes[uid]
except ValueError:
logger.exception("Unable to remove universe %s", universe.name)

Expand Down Expand Up @@ -146,7 +150,7 @@ def universe(self, universe_id: int) -> Universe | None:
@property
def fixtures(self) -> Sequence[UsedFixture]:
"""Fixtures associated with this Show."""
return self._fixtures
return self._fixtures.values()

@property
def show_name(self) -> str:
Expand Down Expand Up @@ -312,3 +316,27 @@ def get_occupied_channels(self, universe_id: int) -> np.typing.NDArray[int]:
]

return np.concatenate(ranges) if ranges else np.array([], dtype=int)

def get_fixture(self, fixture_id: str | UUID) -> UsedFixture | None:
"""Get the fixture specified by its id."""
if fixture_id == "" or fixture_id is None:
return None
if isinstance(fixture_id, UUID):
fixture_id = str(fixture_id)
return self._fixtures.get(fixture_id)

def get_fixture_by_address(self, fixture_univ: int, fixture_chan: int) -> UsedFixture | None:
"""Search for a fixture matching the provided address.

Args:
fixture_univ: The universe of the fixture.
fixture_chan: The first channel of the fixture.

Returns:
The fixture or None if no fixture was found.

"""
for fixture in self._fixtures.values():
if fixture.universe_id == fixture_univ and fixture.start_index == fixture_chan:
return fixture
return None
57 changes: 57 additions & 0 deletions src/model/color_hsi.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import colorsys
from typing import TYPE_CHECKING

import numpy as np
from PySide6.QtGui import QColor

if TYPE_CHECKING:
Expand Down Expand Up @@ -55,6 +56,62 @@ def from_filter_str(cls, filter_format: str) -> ColorHSI:
intensity = 1.0
return ColorHSI(hue, saturation, intensity)

@classmethod
def from_rgb(cls, red: int, green: int, blue: int) -> ColorHSI:
"""Initialize an HSI color from the given RGB color.

Args:
red: Red component of the color. It must be in the range [0, 255]
green: Green component of the color. It must be in the range [0, 255]
blue: Blue component of the color. It must be in the range [0, 255]

Returns:
The HSI color object.

"""
hue, luminescence, saturation = colorsys.rgb_to_hls(red / 255, green / 255, blue / 255)
return ColorHSI(hue, luminescence, saturation)

@classmethod
def from_color_temperature(cls, temperature: float | str) -> ColorHSI:
"""Initialize an HSI color from the given color temperature.

If a string is provided it will be converted automatically.

Args:
temperature: Color temperature in degrees Kelvin.

Returns:
A ColorHSI object representing the given color temperature.

"""

def cutoff(value: float, min_val: int, max_val: int) -> int:
return max(min(int(value), max_val), min_val)

if isinstance(temperature, str):
temperature = float(temperature.lower().replace(" ", "").replace("k", ""))
temperature = temperature / 100
if temperature <= 66:
red = 255
green = temperature
green = 99.4708025861 * np.log(green) - 161.1195681661
if temperature <= 19:
blue = 0
else:
blue = temperature - 10
blue = 138.5177312231 * np.log(blue) - 305.0447927307
else:
red = temperature - 60
red = 329.698727446 * (red ** -0.1332047592)
green = temperature - 60
green = 288.1221695283 * (green ** -0.0755148492)
blue = 255
red = cutoff(red, 0, 255)
green = cutoff(green, 0, 255)
blue = cutoff(blue, 0, 255)
return ColorHSI.from_rgb(red, green, blue)

@property
def hue(self) -> confloat(ge=0, le=360):
"""Color itself in the form of an angle between [0,360] degrees."""
Expand Down
2 changes: 1 addition & 1 deletion src/model/control_desk.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def __init__(self, uid: str | None = None) -> None:
uid: Unique identifier for the column.

"""
self.id = uid if uid else _generate_unique_id()
self.id = uid or _generate_unique_id()
self.bank_set: BankSet | None = None
self._bottom_display_line_inverted = False
self._top_display_line_inverted = False
Expand Down
9 changes: 5 additions & 4 deletions src/model/filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ class FilterTypeEnumeration(IntFlag):
Negative values indicate virtual filters.
"""

VFILTER_COLOR_TO_COLORWHEEL = -13
VFILTER_SEQUENCER = -12
VFILTER_COLOR_MIXER = -11
VFILTER_IMPORT = -10
Expand Down Expand Up @@ -303,16 +304,16 @@ def copy(self, new_scene: Scene = None, new_id: str | None = None) -> Filter:

if self.is_virtual_filter:
f = construct_virtual_filter_instance(
new_scene if new_scene else self.scene,
new_scene or self.scene,
self._filter_type,
new_id if new_id else self._filter_id,
new_id or self._filter_id,
pos=self._pos,
)
f.filter_configurations.update(self.filter_configurations.copy())
else:
f = Filter(
new_scene if new_scene else self.scene,
new_id if new_id else self._filter_id,
new_scene or self.scene,
new_id or self._filter_id,
self._filter_type,
self._pos,
self.filter_configurations.copy(),
Expand Down
3 changes: 2 additions & 1 deletion src/model/filter_data/cues/cue.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
from logging import getLogger
from typing import TYPE_CHECKING, Never, Union, override

from model import ColorHSI, DataType
from model import DataType
from model.color_hsi import ColorHSI
from model.filter_data.transfer_function import TransferFunction
from model.filter_data.utility import format_seconds

Expand Down
3 changes: 2 additions & 1 deletion src/model/filter_data/sequencer/sequencer_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
from enum import Enum
from logging import getLogger

from model import ColorHSI, DataType
from model import DataType
from model.color_hsi import ColorHSI
from model.filter_data.sequencer._utils import _rf

logger = getLogger(__name__)
Expand Down
3 changes: 2 additions & 1 deletion src/model/filter_data/sequencer/transition.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
from types import MappingProxyType
from typing import TYPE_CHECKING

from model import ColorHSI, DataType
from model import DataType
from model.color_hsi import ColorHSI
from model.filter_data.cues.cue import Cue, KeyFrame, State, StateColor, StateDouble, StateEightBit, StateSixteenBit
from model.filter_data.sequencer._utils import _rf
from model.filter_data.transfer_function import TransferFunction
Expand Down
41 changes: 41 additions & 0 deletions src/model/ofl/color_name_dict.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Contains method to query color by name."""

import csv
import os
from logging import getLogger
from typing import TYPE_CHECKING

from model.color_hsi import ColorHSI
from utility import resource_path

logger = getLogger(__name__)
_COLOR_DICT: dict[str, tuple[float, float, float]] = {}

with open(resource_path(os.path.join("resources", "data", "colornames.csv")), "r") as f:
for row in csv.reader(f, delimiter=";"):
name, hue, saturation, value = row
_COLOR_DICT[name.lower()] = (float(hue), float(saturation), float(value))

def get_color_by_name(name: str) -> ColorHSI:
"""Method queries the color name database and returns black if none was found.

HTML color codes are supported.

Args:
name: The name or color code of the color.

Returns:
The inferred color object.

"""
if name.startswith("#"):
name = name.replace("#", "")
r = int(name[0:2], 16)
g = int(name[2:4], 16)
b = int(name[4:6], 16)
return ColorHSI.from_rgb(r, g, b)
color_tuple = _COLOR_DICT.get(name.lower())
if color_tuple is not None:
return ColorHSI(color_tuple[0], color_tuple[1], color_tuple[2])
logger.warning("No color name found for %s", name)
return ColorHSI(0, 0, 0)
Loading
Loading