Added backend filament search by similar color
This commit is contained in:
@@ -14,12 +14,14 @@ from spoolman.api.v1.models import EventType, Filament, FilamentEvent
|
||||
from spoolman.database import models, vendor
|
||||
from spoolman.database.utils import (
|
||||
SortOrder,
|
||||
add_where_clause_int_in,
|
||||
add_where_clause_int_opt,
|
||||
add_where_clause_str,
|
||||
add_where_clause_str_opt,
|
||||
parse_nested_field,
|
||||
)
|
||||
from spoolman.exceptions import ItemDeleteError, ItemNotFoundError
|
||||
from spoolman.math import delta_e, hex_to_rgb, rgb_to_lab
|
||||
from spoolman.ws import websocket_manager
|
||||
|
||||
|
||||
@@ -82,6 +84,7 @@ async def get_by_id(db: AsyncSession, filament_id: int) -> models.Filament:
|
||||
async def find(
|
||||
*,
|
||||
db: AsyncSession,
|
||||
ids: Optional[list[int]] = None,
|
||||
vendor_name: Optional[str] = None,
|
||||
vendor_id: Optional[Union[int, Sequence[int]]] = None,
|
||||
name: Optional[str] = None,
|
||||
@@ -104,6 +107,7 @@ async def find(
|
||||
.join(models.Filament.vendor, isouter=True)
|
||||
)
|
||||
|
||||
stmt = add_where_clause_int_in(stmt, models.Filament.id, ids)
|
||||
stmt = add_where_clause_int_opt(stmt, models.Filament.vendor_id, vendor_id)
|
||||
stmt = add_where_clause_str(stmt, models.Vendor.name, vendor_name)
|
||||
stmt = add_where_clause_str_opt(stmt, models.Filament.name, name)
|
||||
@@ -190,6 +194,33 @@ async def find_article_numbers(
|
||||
return [row[0] for row in rows.all() if row[0] is not None]
|
||||
|
||||
|
||||
async def find_by_color(
|
||||
*,
|
||||
db: AsyncSession,
|
||||
color_query_hex: str,
|
||||
similarity_threshold: float = 25,
|
||||
) -> list[models.Filament]:
|
||||
"""Find a list of filament objects by similarity to a color.
|
||||
|
||||
This performs a server-side search, where all filaments are loaded into memory, making it not so efficient.
|
||||
The similarity threshold is a value between 0 and 100, where 0 means the colors must be identical and 100 means
|
||||
pretty much all colors are considered similar.
|
||||
"""
|
||||
filaments, _ = await find(db=db)
|
||||
|
||||
color_query_lab = rgb_to_lab(hex_to_rgb(color_query_hex))
|
||||
|
||||
found_filaments: list[models.Filament] = []
|
||||
for filament in filaments:
|
||||
if filament.color_hex is None:
|
||||
continue
|
||||
color_lab = rgb_to_lab(hex_to_rgb(filament.color_hex))
|
||||
if delta_e(color_query_lab, color_lab) <= similarity_threshold:
|
||||
found_filaments.append(filament)
|
||||
|
||||
return found_filaments
|
||||
|
||||
|
||||
async def filament_changed(filament: models.Filament, typ: EventType) -> None:
|
||||
"""Notify websocket clients that a filament has changed."""
|
||||
await websocket_manager.send(
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from collections.abc import Sequence
|
||||
from enum import Enum
|
||||
from typing import Any, Optional, Union
|
||||
from typing import Any, Optional, TypeVar, Union
|
||||
|
||||
import sqlalchemy
|
||||
from sqlalchemy import Select
|
||||
@@ -105,3 +105,17 @@ def add_where_clause_int_opt(
|
||||
statements.append(field == value_part)
|
||||
stmt = stmt.where(sqlalchemy.or_(*statements))
|
||||
return stmt
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def add_where_clause_int_in(
|
||||
stmt: Select,
|
||||
field: attributes.InstrumentedAttribute[T],
|
||||
value: Optional[Sequence[T]],
|
||||
) -> Select:
|
||||
"""Add a where clause to a select statement for a field."""
|
||||
if value is not None and len(value) > 0:
|
||||
stmt = stmt.where(field.in_(value))
|
||||
return stmt
|
||||
|
||||
Reference in New Issue
Block a user