Add Pattern Templates for Key Colors targets with visual canvas editor
Introduce Pattern Template entity as a reusable rectangle layout that Key Colors targets reference via pattern_template_id. This replaces inline rectangle storage with a shared template system. Backend: - New PatternTemplate data model, store (JSON persistence), CRUD API - KC targets now reference pattern_template_id instead of inline rectangles - ProcessorManager resolves pattern template at KC processing start - Picture source test endpoint supports capture_duration=0 for single frame - Delete protection: 409 when template is referenced by a KC target Frontend: - Pattern Templates section in Key Colors sub-tab with card UI - Visual canvas editor with drag-to-move, 8-point resize handles - Background capture from any picture source for visual alignment - Precise coordinate list synced bidirectionally with canvas - Resizable editor container, viewport-constrained modal - KC target editor uses pattern template dropdown instead of inline rects - Localization (en/ru) for all new UI elements Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -155,7 +155,7 @@ class ProcessorManager:
|
||||
Targets are registered for processing (streaming sources to devices).
|
||||
"""
|
||||
|
||||
def __init__(self, picture_source_store=None, capture_template_store=None, pp_template_store=None):
|
||||
def __init__(self, picture_source_store=None, capture_template_store=None, pp_template_store=None, pattern_template_store=None):
|
||||
"""Initialize processor manager."""
|
||||
self._devices: Dict[str, DeviceState] = {}
|
||||
self._targets: Dict[str, TargetState] = {}
|
||||
@@ -165,6 +165,7 @@ class ProcessorManager:
|
||||
self._picture_source_store = picture_source_store
|
||||
self._capture_template_store = capture_template_store
|
||||
self._pp_template_store = pp_template_store
|
||||
self._pattern_template_store = pattern_template_store
|
||||
self._live_stream_manager = LiveStreamManager(
|
||||
picture_source_store, capture_template_store, pp_template_store
|
||||
)
|
||||
@@ -1054,8 +1055,19 @@ class ProcessorManager:
|
||||
if not state.picture_source_id:
|
||||
raise ValueError(f"KC target {target_id} has no picture source assigned")
|
||||
|
||||
if not state.settings.rectangles:
|
||||
raise ValueError(f"KC target {target_id} has no rectangles defined")
|
||||
if not state.settings.pattern_template_id:
|
||||
raise ValueError(f"KC target {target_id} has no pattern template assigned")
|
||||
|
||||
# Resolve pattern template to get rectangles
|
||||
try:
|
||||
pattern_template = self._pattern_template_store.get_template(state.settings.pattern_template_id)
|
||||
except (ValueError, AttributeError):
|
||||
raise ValueError(f"Pattern template {state.settings.pattern_template_id} not found")
|
||||
|
||||
if not pattern_template.rectangles:
|
||||
raise ValueError(f"Pattern template {state.settings.pattern_template_id} has no rectangles")
|
||||
|
||||
state._resolved_rectangles = pattern_template.rectangles
|
||||
|
||||
# Acquire live stream
|
||||
try:
|
||||
@@ -1133,9 +1145,11 @@ class ProcessorManager:
|
||||
frame_time = 1.0 / target_fps
|
||||
fps_samples: List[float] = []
|
||||
|
||||
rectangles = state._resolved_rectangles
|
||||
|
||||
logger.info(
|
||||
f"KC processing loop started for target {target_id} "
|
||||
f"(fps={target_fps}, rects={len(settings.rectangles)})"
|
||||
f"(fps={target_fps}, rects={len(rectangles)})"
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -1152,7 +1166,7 @@ class ProcessorManager:
|
||||
h, w = img.shape[:2]
|
||||
|
||||
colors: Dict[str, Tuple[int, int, int]] = {}
|
||||
for rect in settings.rectangles:
|
||||
for rect in rectangles:
|
||||
# Convert relative coords to pixel coords
|
||||
px_x = max(0, int(rect.x * w))
|
||||
px_y = max(0, int(rect.y * h))
|
||||
|
||||
Reference in New Issue
Block a user