Add ruff static analysis and a Gitea Actions release workflow
Release / release (push) Successful in 52s
Release / release (push) Successful in 52s
Adds ruff (lint + format check) as a dev dependency and gate, applies its fixes across the codebase (modernized typing, safer zip usage, line length), and adds a Gitea Actions workflow that lints, tests, builds, and publishes to the Gitea PyPI registry on v*.*.* tag pushes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,39 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*.*.*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: https://github.com/actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: https://github.com/actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.12"
|
||||||
|
|
||||||
|
- name: Install build dependencies
|
||||||
|
run: pip install ".[dev]" build twine
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: ruff check .
|
||||||
|
|
||||||
|
- name: Format check
|
||||||
|
run: ruff format --check .
|
||||||
|
|
||||||
|
- run: pytest
|
||||||
|
|
||||||
|
- name: Set package version from the tag
|
||||||
|
run: sed -i "s/^__version__ = .*/__version__ = \"${GITHUB_REF_NAME#v}\"/" src/openrgb_hue/__init__.py
|
||||||
|
|
||||||
|
- name: Build sdist and wheel
|
||||||
|
run: python -m build
|
||||||
|
|
||||||
|
- name: Publish to Gitea PyPI registry
|
||||||
|
run: twine upload --repository-url https://dev.pivoine.art/api/packages/valknar/pypi dist/*
|
||||||
|
env:
|
||||||
|
TWINE_USERNAME: valknar
|
||||||
|
TWINE_PASSWORD: ${{ secrets.PACKAGE_TOKEN }}
|
||||||
+11
-1
@@ -17,7 +17,7 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
dev = ["pytest>=7", "pytest-cov"]
|
dev = ["pytest>=7", "pytest-cov", "ruff>=0.6"]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
openrgb-hue = "openrgb_hue.cli:app"
|
openrgb-hue = "openrgb_hue.cli:app"
|
||||||
@@ -30,3 +30,13 @@ packages = ["src/openrgb_hue"]
|
|||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
testpaths = ["tests"]
|
testpaths = ["tests"]
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 120
|
||||||
|
target-version = "py310"
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
select = ["E", "F", "I", "UP", "B"]
|
||||||
|
ignore = [
|
||||||
|
"B008", # typer.Option(...)/typer.Argument(...) as a parameter default is the idiomatic Typer pattern
|
||||||
|
]
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import math
|
import math
|
||||||
import time
|
import time
|
||||||
from typing import TYPE_CHECKING, Callable, ClassVar, Protocol
|
from collections.abc import Callable
|
||||||
|
from typing import TYPE_CHECKING, ClassVar, Protocol
|
||||||
|
|
||||||
from openrgb_hue import client as client_mod
|
from openrgb_hue import client as client_mod
|
||||||
from openrgb_hue.client import LedRef
|
from openrgb_hue.client import LedRef
|
||||||
@@ -124,7 +125,7 @@ def build_animation(
|
|||||||
|
|
||||||
|
|
||||||
def run_animation(
|
def run_animation(
|
||||||
client: "OpenRGBClient",
|
client: OpenRGBClient,
|
||||||
base: dict[LedRef, float],
|
base: dict[LedRef, float],
|
||||||
gradient: Gradient,
|
gradient: Gradient,
|
||||||
animation: AnimationMode,
|
animation: AnimationMode,
|
||||||
|
|||||||
+29
-24
@@ -4,8 +4,6 @@ mapping/animation logic lives here -- this module only parses options,
|
|||||||
delegates to the other modules, and renders output.
|
delegates to the other modules, and renders output.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
import typer
|
import typer
|
||||||
from rich import box
|
from rich import box
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
@@ -49,7 +47,7 @@ def main(
|
|||||||
ctx: typer.Context,
|
ctx: typer.Context,
|
||||||
host: str = typer.Option("127.0.0.1", envvar="OPENRGB_HOST", help="OpenRGB SDK server host."),
|
host: str = typer.Option("127.0.0.1", envvar="OPENRGB_HOST", help="OpenRGB SDK server host."),
|
||||||
port: int = typer.Option(6742, envvar="OPENRGB_PORT", help="OpenRGB SDK server port."),
|
port: int = typer.Option(6742, envvar="OPENRGB_PORT", help="OpenRGB SDK server port."),
|
||||||
version: Optional[bool] = typer.Option(
|
version: bool | None = typer.Option(
|
||||||
None, "--version", callback=_version_callback, is_eager=True, help="Show the version and exit."
|
None, "--version", callback=_version_callback, is_eager=True, help="Show the version and exit."
|
||||||
),
|
),
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -61,17 +59,15 @@ def _error_exit(message: str) -> None:
|
|||||||
raise typer.Exit(code=1)
|
raise typer.Exit(code=1)
|
||||||
|
|
||||||
|
|
||||||
def _swatch(colors: List[Color], width: int = 2) -> Text:
|
def _swatch(colors: list[Color], width: int = 2) -> Text:
|
||||||
text = Text()
|
text = Text()
|
||||||
for color in colors:
|
for color in colors:
|
||||||
text.append(" " * width, style=f"on #{color.to_hex()}")
|
text.append(" " * width, style=f"on #{color.to_hex()}")
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
def _build_target_filter(device: List[str], device_type: List[str], zone: List[str]) -> targets_mod.TargetFilter:
|
def _build_target_filter(device: list[str], device_type: list[str], zone: list[str]) -> targets_mod.TargetFilter:
|
||||||
return targets_mod.TargetFilter(
|
return targets_mod.TargetFilter(device_names=tuple(device), device_types=tuple(device_type), zone_names=tuple(zone))
|
||||||
device_names=tuple(device), device_types=tuple(device_type), zone_names=tuple(zone)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _load_gradient(scene_name: str, dedupe: bool, interpolation: str, brightness: bool) -> Gradient:
|
def _load_gradient(scene_name: str, dedupe: bool, interpolation: str, brightness: bool) -> Gradient:
|
||||||
@@ -85,7 +81,14 @@ def _load_gradient(scene_name: str, dedupe: bool, interpolation: str, brightness
|
|||||||
_error_exit(str(exc))
|
_error_exit(str(exc))
|
||||||
|
|
||||||
|
|
||||||
def _resolve_targets(ctx: typer.Context, device: List[str], device_type: List[str], zone: List[str], dry_run: bool, dry_run_leds: Optional[int]):
|
def _resolve_targets(
|
||||||
|
ctx: typer.Context,
|
||||||
|
device: list[str],
|
||||||
|
device_type: list[str],
|
||||||
|
zone: list[str],
|
||||||
|
dry_run: bool,
|
||||||
|
dry_run_leds: int | None,
|
||||||
|
):
|
||||||
if dry_run:
|
if dry_run:
|
||||||
return None, targets_mod.synthetic_targets(dry_run_leds)
|
return None, targets_mod.synthetic_targets(dry_run_leds)
|
||||||
try:
|
try:
|
||||||
@@ -111,7 +114,7 @@ def _build_mapping(mapping: str, direction: str, seed: int) -> mapping_mod.Mappi
|
|||||||
_error_exit(str(exc))
|
_error_exit(str(exc))
|
||||||
|
|
||||||
|
|
||||||
def _print_led_preview(targets: List[LedRef], colors: dict) -> None:
|
def _print_led_preview(targets: list[LedRef], colors: dict) -> None:
|
||||||
by_device: dict = {}
|
by_device: dict = {}
|
||||||
for led in targets:
|
for led in targets:
|
||||||
by_device.setdefault(led.device_name, []).append(led)
|
by_device.setdefault(led.device_name, []).append(led)
|
||||||
@@ -155,7 +158,7 @@ def devices(
|
|||||||
|
|
||||||
@scenes_app.command("list")
|
@scenes_app.command("list")
|
||||||
def scenes_list(
|
def scenes_list(
|
||||||
name_filter: Optional[str] = typer.Option(None, "--filter", help="Case-insensitive substring filter on scene name."),
|
name_filter: str | None = typer.Option(None, "--filter", help="Case-insensitive substring filter on scene name."),
|
||||||
) -> None:
|
) -> None:
|
||||||
"""List all bundled Hue scenes with a gradient preview."""
|
"""List all bundled Hue scenes with a gradient preview."""
|
||||||
scenes = scenes_mod.load_scenes()
|
scenes = scenes_mod.load_scenes()
|
||||||
@@ -194,7 +197,9 @@ def scenes_show(
|
|||||||
table.add_column("Hex")
|
table.add_column("Hex")
|
||||||
table.add_column("Brightness", justify="right")
|
table.add_column("Brightness", justify="right")
|
||||||
for light in scene.lights:
|
for light in scene.lights:
|
||||||
table.add_row(light.light_id, _swatch([light.color], width=4), f"#{light.color.to_hex()}", str(light.brightness))
|
table.add_row(
|
||||||
|
light.light_id, _swatch([light.color], width=4), f"#{light.color.to_hex()}", str(light.brightness)
|
||||||
|
)
|
||||||
console.print(table)
|
console.print(table)
|
||||||
|
|
||||||
gradient = Gradient.from_scene(scene, dedupe=dedupe, interpolation=interpolation, use_brightness=brightness)
|
gradient = Gradient.from_scene(scene, dedupe=dedupe, interpolation=interpolation, use_brightness=brightness)
|
||||||
@@ -222,11 +227,11 @@ def scenes_update(
|
|||||||
def apply(
|
def apply(
|
||||||
ctx: typer.Context,
|
ctx: typer.Context,
|
||||||
scene: str = typer.Argument(..., help="Hue scene name (see `scenes list`)."),
|
scene: str = typer.Argument(..., help="Hue scene name (see `scenes list`)."),
|
||||||
device: List[str] = typer.Option([], "--device", help="Target only devices with this name (repeatable)."),
|
device: list[str] = typer.Option([], "--device", help="Target only devices with this name (repeatable)."),
|
||||||
device_type: List[str] = typer.Option(
|
device_type: list[str] = typer.Option(
|
||||||
[], "--device-type", help="Target only devices of this type, e.g. gpu, motherboard, ledstrip (repeatable)."
|
[], "--device-type", help="Target only devices of this type, e.g. gpu, motherboard, ledstrip (repeatable)."
|
||||||
),
|
),
|
||||||
zone: List[str] = typer.Option([], "--zone", help="Target only zones with this name (repeatable)."),
|
zone: list[str] = typer.Option([], "--zone", help="Target only zones with this name (repeatable)."),
|
||||||
mapping: str = typer.Option("sequence", help=f"Mapping mode: {', '.join(mapping_mod.MAPPING_NAMES)}."),
|
mapping: str = typer.Option("sequence", help=f"Mapping mode: {', '.join(mapping_mod.MAPPING_NAMES)}."),
|
||||||
direction: str = typer.Option("left-right", help=f"Matrix direction: {', '.join(mapping_mod.DIRECTIONS)}."),
|
direction: str = typer.Option("left-right", help=f"Matrix direction: {', '.join(mapping_mod.DIRECTIONS)}."),
|
||||||
seed: int = typer.Option(0, help="Shuffle mapping seed."),
|
seed: int = typer.Option(0, help="Shuffle mapping seed."),
|
||||||
@@ -236,7 +241,7 @@ def apply(
|
|||||||
dim: float = typer.Option(1.0, help="Global brightness multiplier (0-1), independent of scene brightness."),
|
dim: float = typer.Option(1.0, help="Global brightness multiplier (0-1), independent of scene brightness."),
|
||||||
fast: bool = typer.Option(False, help="Use OpenRGB's fast (no-readback) update mode."),
|
fast: bool = typer.Option(False, help="Use OpenRGB's fast (no-readback) update mode."),
|
||||||
dry_run: bool = typer.Option(False, help="Compute and preview without connecting to OpenRGB."),
|
dry_run: bool = typer.Option(False, help="Compute and preview without connecting to OpenRGB."),
|
||||||
dry_run_leds: Optional[int] = typer.Option(None, help="Number of synthetic flat LEDs for --dry-run."),
|
dry_run_leds: int | None = typer.Option(None, help="Number of synthetic flat LEDs for --dry-run."),
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Apply a Hue scene as a static gradient."""
|
"""Apply a Hue scene as a static gradient."""
|
||||||
gradient = _load_gradient(scene, dedupe, interpolation, brightness)
|
gradient = _load_gradient(scene, dedupe, interpolation, brightness)
|
||||||
@@ -259,11 +264,11 @@ def apply(
|
|||||||
def animate(
|
def animate(
|
||||||
ctx: typer.Context,
|
ctx: typer.Context,
|
||||||
scene: str = typer.Argument(..., help="Hue scene name (see `scenes list`)."),
|
scene: str = typer.Argument(..., help="Hue scene name (see `scenes list`)."),
|
||||||
device: List[str] = typer.Option([], "--device", help="Target only devices with this name (repeatable)."),
|
device: list[str] = typer.Option([], "--device", help="Target only devices with this name (repeatable)."),
|
||||||
device_type: List[str] = typer.Option(
|
device_type: list[str] = typer.Option(
|
||||||
[], "--device-type", help="Target only devices of this type, e.g. gpu, motherboard, ledstrip (repeatable)."
|
[], "--device-type", help="Target only devices of this type, e.g. gpu, motherboard, ledstrip (repeatable)."
|
||||||
),
|
),
|
||||||
zone: List[str] = typer.Option([], "--zone", help="Target only zones with this name (repeatable)."),
|
zone: list[str] = typer.Option([], "--zone", help="Target only zones with this name (repeatable)."),
|
||||||
mapping: str = typer.Option("sequence", help=f"Mapping mode: {', '.join(mapping_mod.MAPPING_NAMES)}."),
|
mapping: str = typer.Option("sequence", help=f"Mapping mode: {', '.join(mapping_mod.MAPPING_NAMES)}."),
|
||||||
direction: str = typer.Option("left-right", help=f"Matrix direction: {', '.join(mapping_mod.DIRECTIONS)}."),
|
direction: str = typer.Option("left-right", help=f"Matrix direction: {', '.join(mapping_mod.DIRECTIONS)}."),
|
||||||
seed: int = typer.Option(0, help="Shuffle mapping seed."),
|
seed: int = typer.Option(0, help="Shuffle mapping seed."),
|
||||||
@@ -274,12 +279,12 @@ def animate(
|
|||||||
mode: str = typer.Option("scroll", help=f"Animation mode: {', '.join(animation_mod.ANIMATION_NAMES)}."),
|
mode: str = typer.Option("scroll", help=f"Animation mode: {', '.join(animation_mod.ANIMATION_NAMES)}."),
|
||||||
speed: float = typer.Option(0.1, help="Cycles/sec (scroll, pingpong) or Hz (pulse, wave)."),
|
speed: float = typer.Option(0.1, help="Cycles/sec (scroll, pingpong) or Hz (pulse, wave)."),
|
||||||
fps: float = typer.Option(30.0, help="Target frames per second."),
|
fps: float = typer.Option(30.0, help="Target frames per second."),
|
||||||
duration: Optional[float] = typer.Option(None, help="Seconds to run; omit to run until Ctrl+C."),
|
duration: float | None = typer.Option(None, help="Seconds to run; omit to run until Ctrl+C."),
|
||||||
min_brightness: float = typer.Option(0.05, help="Brightness floor for pulse/wave (0-1)."),
|
min_brightness: float = typer.Option(0.05, help="Brightness floor for pulse/wave (0-1)."),
|
||||||
wavelength: float = typer.Option(2.0, help="Brightness peaks across the target set, for wave."),
|
wavelength: float = typer.Option(2.0, help="Brightness peaks across the target set, for wave."),
|
||||||
restore: bool = typer.Option(True, help="Restore original colors when the animation stops."),
|
restore: bool = typer.Option(True, help="Restore original colors when the animation stops."),
|
||||||
dry_run: bool = typer.Option(False, help="Preview sample frames without connecting to OpenRGB."),
|
dry_run: bool = typer.Option(False, help="Preview sample frames without connecting to OpenRGB."),
|
||||||
dry_run_leds: Optional[int] = typer.Option(None, help="Number of synthetic flat LEDs for --dry-run."),
|
dry_run_leds: int | None = typer.Option(None, help="Number of synthetic flat LEDs for --dry-run."),
|
||||||
dry_run_frames: int = typer.Option(5, help="Number of sample frames to preview with --dry-run."),
|
dry_run_frames: int = typer.Option(5, help="Number of sample frames to preview with --dry-run."),
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Apply a Hue scene as an animated gradient."""
|
"""Apply a Hue scene as an animated gradient."""
|
||||||
@@ -329,9 +334,9 @@ def animate(
|
|||||||
@app.command()
|
@app.command()
|
||||||
def off(
|
def off(
|
||||||
ctx: typer.Context,
|
ctx: typer.Context,
|
||||||
device: List[str] = typer.Option([], "--device", help="Target only devices with this name (repeatable)."),
|
device: list[str] = typer.Option([], "--device", help="Target only devices with this name (repeatable)."),
|
||||||
device_type: List[str] = typer.Option([], "--device-type", help="Target only devices of this type (repeatable)."),
|
device_type: list[str] = typer.Option([], "--device-type", help="Target only devices of this type (repeatable)."),
|
||||||
zone: List[str] = typer.Option([], "--zone", help="Target only zones with this name (repeatable)."),
|
zone: list[str] = typer.Option([], "--zone", help="Target only zones with this name (repeatable)."),
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Turn off targeted LEDs (or all LEDs, if no filters are given)."""
|
"""Turn off targeted LEDs (or all LEDs, if no filters are given)."""
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -94,17 +94,12 @@ def apply_colors(client: OpenRGBClient, colors: dict[LedRef, Color], fast: bool
|
|||||||
for device_index, led_colors in by_device.items():
|
for device_index, led_colors in by_device.items():
|
||||||
device: Device = client.ee_devices[device_index]
|
device: Device = client.ee_devices[device_index]
|
||||||
current = device.colors
|
current = device.colors
|
||||||
new_colors = [
|
new_colors = [led_colors[i].to_openrgb() if i in led_colors else current[i] for i in range(len(current))]
|
||||||
led_colors[i].to_openrgb() if i in led_colors else current[i] for i in range(len(current))
|
|
||||||
]
|
|
||||||
device.set_colors(new_colors, fast=fast)
|
device.set_colors(new_colors, fast=fast)
|
||||||
|
|
||||||
|
|
||||||
def snapshot_colors(client: OpenRGBClient) -> dict[int, list[Color]]:
|
def snapshot_colors(client: OpenRGBClient) -> dict[int, list[Color]]:
|
||||||
return {
|
return {i: [Color(c.red, c.green, c.blue) for c in device.colors] for i, device in enumerate(client.ee_devices)}
|
||||||
i: [Color(c.red, c.green, c.blue) for c in device.colors]
|
|
||||||
for i, device in enumerate(client.ee_devices)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def restore_colors(client: OpenRGBClient, snapshot: dict[int, list[Color]], fast: bool = True) -> None:
|
def restore_colors(client: OpenRGBClient, snapshot: dict[int, list[Color]], fast: bool = True) -> None:
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class Color:
|
|||||||
object.__setattr__(self, "b", _clamp255(self.b))
|
object.__setattr__(self, "b", _clamp255(self.b))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_hex(cls, value: str) -> "Color":
|
def from_hex(cls, value: str) -> Color:
|
||||||
value = value.lstrip("#")
|
value = value.lstrip("#")
|
||||||
if len(value) != 6:
|
if len(value) != 6:
|
||||||
raise ValueError(f"Expected a 6-digit hex color, got {value!r}")
|
raise ValueError(f"Expected a 6-digit hex color, got {value!r}")
|
||||||
@@ -39,12 +39,12 @@ class Color:
|
|||||||
def to_hex(self) -> str:
|
def to_hex(self) -> str:
|
||||||
return f"{self.r:02x}{self.g:02x}{self.b:02x}"
|
return f"{self.r:02x}{self.g:02x}{self.b:02x}"
|
||||||
|
|
||||||
def to_openrgb(self) -> "openrgb.utils.RGBColor": # noqa: F821
|
def to_openrgb(self) -> openrgb.utils.RGBColor: # noqa: F821
|
||||||
from openrgb.utils import RGBColor
|
from openrgb.utils import RGBColor
|
||||||
|
|
||||||
return RGBColor(self.r, self.g, self.b)
|
return RGBColor(self.r, self.g, self.b)
|
||||||
|
|
||||||
def scale_brightness(self, factor: float) -> "Color":
|
def scale_brightness(self, factor: float) -> Color:
|
||||||
factor = max(0.0, factor)
|
factor = max(0.0, factor)
|
||||||
return Color(self.r * factor, self.g * factor, self.b * factor)
|
return Color(self.r * factor, self.g * factor, self.b * factor)
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ logic in the animation layer.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
from collections.abc import Sequence
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import TYPE_CHECKING, Literal, Sequence
|
from typing import TYPE_CHECKING, Literal
|
||||||
|
|
||||||
from openrgb_hue.color import Color, hsv_lerp, rgb_lerp
|
from openrgb_hue.color import Color, hsv_lerp, rgb_lerp
|
||||||
|
|
||||||
@@ -43,23 +45,23 @@ class Gradient:
|
|||||||
return list(self._stops)
|
return list(self._stops)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_colors(cls, colors: Sequence[Color], interpolation: Interpolation = "hsv") -> "Gradient":
|
def from_colors(cls, colors: Sequence[Color], interpolation: Interpolation = "hsv") -> Gradient:
|
||||||
colors = list(colors)
|
colors = list(colors)
|
||||||
if not colors:
|
if not colors:
|
||||||
raise ValueError("Gradient needs at least one color")
|
raise ValueError("Gradient needs at least one color")
|
||||||
n = len(colors)
|
n = len(colors)
|
||||||
positions = [0.0] if n == 1 else [i / (n - 1) for i in range(n)]
|
positions = [0.0] if n == 1 else [i / (n - 1) for i in range(n)]
|
||||||
return cls([Stop(p, c) for p, c in zip(positions, colors)], interpolation=interpolation)
|
return cls([Stop(p, c) for p, c in zip(positions, colors, strict=True)], interpolation=interpolation)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_scene(
|
def from_scene(
|
||||||
cls,
|
cls,
|
||||||
scene: "Scene",
|
scene: Scene,
|
||||||
*,
|
*,
|
||||||
dedupe: bool = True,
|
dedupe: bool = True,
|
||||||
interpolation: Interpolation = "hsv",
|
interpolation: Interpolation = "hsv",
|
||||||
use_brightness: bool = True,
|
use_brightness: bool = True,
|
||||||
) -> "Gradient":
|
) -> Gradient:
|
||||||
colors: list[Color] = []
|
colors: list[Color] = []
|
||||||
for light in scene.lights:
|
for light in scene.lights:
|
||||||
color = light.color.scale_brightness(light.brightness / 255) if use_brightness else light.color
|
color = light.color.scale_brightness(light.brightness / 255) if use_brightness else light.color
|
||||||
@@ -81,7 +83,7 @@ class Gradient:
|
|||||||
t_eff = t + 1.0 if t < stops[0].position else t
|
t_eff = t + 1.0 if t < stops[0].position else t
|
||||||
extended = stops + [Stop(stops[0].position + 1.0, stops[0].color)]
|
extended = stops + [Stop(stops[0].position + 1.0, stops[0].color)]
|
||||||
lerp = _LERP[self.interpolation]
|
lerp = _LERP[self.interpolation]
|
||||||
for a, b in zip(extended, extended[1:]):
|
for a, b in itertools.pairwise(extended):
|
||||||
if a.position <= t_eff <= b.position:
|
if a.position <= t_eff <= b.position:
|
||||||
span = b.position - a.position
|
span = b.position - a.position
|
||||||
local_t = 0.0 if span == 0 else (t_eff - a.position) / span
|
local_t = 0.0 if span == 0 else (t_eff - a.position) / span
|
||||||
|
|||||||
@@ -12,8 +12,9 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import math
|
import math
|
||||||
import random
|
import random
|
||||||
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Callable, ClassVar, Protocol
|
from typing import ClassVar, Protocol
|
||||||
|
|
||||||
from openrgb_hue.client import LedRef
|
from openrgb_hue.client import LedRef
|
||||||
from openrgb_hue.color import Color
|
from openrgb_hue.color import Color
|
||||||
@@ -113,8 +114,7 @@ class MatrixMapping:
|
|||||||
non_matrix_leds.append(led)
|
non_matrix_leds.append(led)
|
||||||
if non_matrix_leds and not warned:
|
if non_matrix_leds and not warned:
|
||||||
self._warn(
|
self._warn(
|
||||||
"Some targeted LEDs are not part of a matrix zone; they will be held at a "
|
"Some targeted LEDs are not part of a matrix zone; they will be held at a fixed gradient position."
|
||||||
"fixed gradient position."
|
|
||||||
)
|
)
|
||||||
warned = True
|
warned = True
|
||||||
for led in non_matrix_leds:
|
for led in non_matrix_leds:
|
||||||
@@ -171,7 +171,7 @@ class ShuffleMapping:
|
|||||||
positions = [0.0] if n <= 1 else [i / (n - 1) for i in range(n)]
|
positions = [0.0] if n <= 1 else [i / (n - 1) for i in range(n)]
|
||||||
shuffled = list(targets)
|
shuffled = list(targets)
|
||||||
random.Random(self.seed).shuffle(shuffled)
|
random.Random(self.seed).shuffle(shuffled)
|
||||||
return dict(zip(shuffled, positions))
|
return dict(zip(shuffled, positions, strict=True))
|
||||||
|
|
||||||
|
|
||||||
def build_mapping(
|
def build_mapping(
|
||||||
|
|||||||
@@ -73,9 +73,7 @@ def get_scene(name: str, path: Path | None = None) -> Scene:
|
|||||||
return scene
|
return scene
|
||||||
suggestions = get_close_matches(name, scenes.keys(), n=3)
|
suggestions = get_close_matches(name, scenes.keys(), n=3)
|
||||||
hint = f" Did you mean: {', '.join(suggestions)}?" if suggestions else ""
|
hint = f" Did you mean: {', '.join(suggestions)}?" if suggestions else ""
|
||||||
raise SceneNotFoundError(
|
raise SceneNotFoundError(f"No scene named {name!r}.{hint} Run `openrgb-hue scenes list` to see all scenes.")
|
||||||
f"No scene named {name!r}.{hint} Run `openrgb-hue scenes list` to see all scenes."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def refresh_bundled_csv(dest: Path | None = None, url: str = GIST_RAW_URL, timeout: float = 10.0) -> Path:
|
def refresh_bundled_csv(dest: Path | None = None, url: str = GIST_RAW_URL, timeout: float = 10.0) -> Path:
|
||||||
|
|||||||
@@ -32,8 +32,7 @@ def select_targets(all_leds: list[LedRef], filt: TargetFilter) -> list[LedRef]:
|
|||||||
targets = [led for led in all_leds if _matches(led, filt)]
|
targets = [led for led in all_leds if _matches(led, filt)]
|
||||||
if not targets:
|
if not targets:
|
||||||
raise NoTargetsError(
|
raise NoTargetsError(
|
||||||
"No LEDs matched the given filters. Run `openrgb-hue devices --leds` to see available "
|
"No LEDs matched the given filters. Run `openrgb-hue devices --leds` to see available device/zone names."
|
||||||
"device/zone names."
|
|
||||||
)
|
)
|
||||||
return targets
|
return targets
|
||||||
|
|
||||||
@@ -59,9 +58,7 @@ def synthetic_targets(n: int | None = None) -> list[LedRef]:
|
|||||||
|
|
||||||
leds: list[LedRef] = []
|
leds: list[LedRef] = []
|
||||||
# Two linear strips of different lengths...
|
# Two linear strips of different lengths...
|
||||||
for device_index, (device_name, count) in enumerate(
|
for device_index, (device_name, count) in enumerate([("Dry-Run LED Strip", 12), ("Dry-Run Motherboard", 8)]):
|
||||||
[("Dry-Run LED Strip", 12), ("Dry-Run Motherboard", 8)]
|
|
||||||
):
|
|
||||||
for i in range(count):
|
for i in range(count):
|
||||||
leds.append(
|
leds.append(
|
||||||
LedRef(
|
LedRef(
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ from openrgb_hue.animation import (
|
|||||||
build_animation,
|
build_animation,
|
||||||
run_animation,
|
run_animation,
|
||||||
)
|
)
|
||||||
from openrgb_hue.gradient import Gradient
|
|
||||||
from openrgb_hue.color import Color
|
from openrgb_hue.color import Color
|
||||||
|
from openrgb_hue.gradient import Gradient
|
||||||
from openrgb_hue.mapping import SequenceMapping, base_positions
|
from openrgb_hue.mapping import SequenceMapping, base_positions
|
||||||
from openrgb_hue.targets import synthetic_targets
|
from openrgb_hue.targets import synthetic_targets
|
||||||
|
|
||||||
|
|||||||
+1
-3
@@ -47,9 +47,7 @@ def test_apply_dry_run_matrix_radial():
|
|||||||
|
|
||||||
@pytest.mark.parametrize("mode", ["static", "scroll", "pingpong", "pulse", "wave"])
|
@pytest.mark.parametrize("mode", ["static", "scroll", "pingpong", "pulse", "wave"])
|
||||||
def test_animate_dry_run_across_animation_modes(mode):
|
def test_animate_dry_run_across_animation_modes(mode):
|
||||||
result = runner.invoke(
|
result = runner.invoke(app, ["animate", "Tropical twilight", "--dry-run", "--mode", mode, "--dry-run-frames", "3"])
|
||||||
app, ["animate", "Tropical twilight", "--dry-run", "--mode", mode, "--dry-run-frames", "3"]
|
|
||||||
)
|
|
||||||
assert result.exit_code == 0, result.output
|
assert result.exit_code == 0, result.output
|
||||||
assert "t=0.00s" in result.output
|
assert "t=0.00s" in result.output
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user