Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
2b5bd0e
adding const.py and update types.py to separate const and types
sca075 Oct 23, 2025
c6ab4ee
last files for 12 isort / ruff and lint
sca075 Nov 4, 2025
e6b006c
remove duplicate import of const
sca075 Dec 19, 2025
a5ca571
const was not properly imported
sca075 Dec 20, 2025
0d4f63c
minor changes in rand256_handler.py, shared.py removed snapshot at in…
sca075 Dec 20, 2025
c412fd7
updated __init__.py
sca075 Dec 21, 2025
11c09e2
updated __init__.py all
sca075 Dec 21, 2025
cb056f4
updated __init__.py all
sca075 Dec 21, 2025
4e21656
update shared.py to load floor data from the config if present else u…
sca075 Dec 24, 2025
0505a8c
update TrimsCropData to use the floors when init, else default values.
sca075 Jan 8, 2026
9426afb
Merge branch 'main' into dev_main
sca075 Jan 8, 2026
3b9d5b2
feat: Add carpet zone detection and rendering support
sca075 Jan 11, 2026
3bea98e
feat: Add carpet zone detection and rendering support
sca075 Jan 11, 2026
de0effa
fix: Add missing CARPET to element_color_mapping
sca075 Jan 11, 2026
644b81e
Dead code removed. The _draw_carpets method was leftover from the ini…
sca075 Jan 11, 2026
f3af432
Merge branch 'main' into dev_main
sca075 Jan 11, 2026
a67cd56
Materials implementation and configuration
sca075 Jan 13, 2026
317511d
removed test and unused code _update_material_colors()
sca075 Jan 13, 2026
dbe642f
improved material.py drawings with mcvrender
sca075 Jan 13, 2026
9ac664b
isort and ruffed code and added configurable colours for material.py
sca075 Jan 16, 2026
a7ce494
Merge branch 'main' into dev_main
sca075 Jan 16, 2026
64c4743
updated types.py isort and ruff
sca075 Jan 16, 2026
7536d8f
updated drawable_elements.py with correct colors name for material
sca075 Jan 16, 2026
b009533
double import correction in colors.py and indexing
sca075 Jan 16, 2026
f30bafd
use MaterialColor instead of direct access.
sca075 Jan 16, 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
37 changes: 27 additions & 10 deletions SCR/valetudo_map_parser/config/colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
ALPHA_CHARGER,
ALPHA_CARPET,
ALPHA_GO_TO,
ALPHA_MATERIAL_TILE,
ALPHA_MATERIAL_WOOD,
ALPHA_MOVE,
ALPHA_NO_GO,
ALPHA_ROBOT,
Expand All @@ -38,6 +40,8 @@
COLOR_CHARGER,
COLOR_CARPET,
COLOR_GO_TO,
COLOR_MATERIAL_TILE,
COLOR_MATERIAL_WOOD,
COLOR_MOVE,
COLOR_NO_GO,
COLOR_ROBOT,
Expand Down Expand Up @@ -77,6 +81,8 @@
color_text = (255, 255, 255, 255)
color_grey = (125, 125, 125, 255)
color_black = (0, 0, 0, 255)
color_material_wood = (40, 40, 40, 38)
color_material_tile = (40, 40, 40, 45)
color_room_0 = (135, 206, 250, 255)
color_room_1 = (176, 226, 255, 255)
color_room_2 = (164, 211, 238, 255)
Expand Down Expand Up @@ -114,15 +120,18 @@
]

base_colors_array = [
color_wall,
color_zone_clean,
color_robot,
color_background,
color_move,
color_charger,
color_no_go,
color_go_to,
color_text,
color_wall, # [0]
color_zone_clean, # [1]
color_robot, # [2]
color_background, # [3]
color_move, # [4]
color_charger, # [5]
color_no_go, # [6]
color_go_to, # [7]
color_text, # [8]
color_carpet, # [9]
color_material_wood, # [10]
color_material_tile, # [11]
]

color_array = [
Expand Down Expand Up @@ -153,6 +162,9 @@ class SupportedColor(StrEnum):
NO_GO = "color_no_go"
ZONE_CLEAN = "color_zone_clean"
CARPET = "color_carpet"
OBSTACLE = "color_obstacle"
TILE = "color_material_tile"
WOOD = "color_material_wood"
MAP_BACKGROUND = "color_background"
TEXT = "color_text"
TRANSPARENT = "color_transparent"
Expand All @@ -175,7 +187,10 @@ class DefaultColors:
SupportedColor.GO_TO: (0, 255, 0),
SupportedColor.NO_GO: (255, 0, 0),
SupportedColor.ZONE_CLEAN: (255, 255, 255),
SupportedColor.CARPET: (67, 103, 125), # 50% of room_0 default color (135, 206, 250)
SupportedColor.CARPET: (67, 103, 125),
SupportedColor.OBSTACLE: (255, 0, 0),
SupportedColor.TILE: (40, 40, 40),
SupportedColor.WOOD: (40, 40, 40),
SupportedColor.MAP_BACKGROUND: (0, 125, 255),
SupportedColor.TEXT: (0, 0, 0),
SupportedColor.TRANSPARENT: (0, 0, 0),
Expand Down Expand Up @@ -310,6 +325,8 @@ def set_initial_colours(self, device_info: dict) -> None:
(COLOR_NO_GO, color_no_go, ALPHA_NO_GO),
(COLOR_GO_TO, color_go_to, ALPHA_GO_TO),
(COLOR_TEXT, color_text, ALPHA_TEXT),
(COLOR_MATERIAL_WOOD, color_material_wood, ALPHA_MATERIAL_WOOD),
(COLOR_MATERIAL_TILE, color_material_tile, ALPHA_MATERIAL_TILE),
]

room_color_keys = [
Expand Down
37 changes: 37 additions & 0 deletions SCR/valetudo_map_parser/config/drawable_elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class DrawableElement(IntEnum):
PREDICTED_PATH = 10
GO_TO_TARGET = 11
CARPET = 12
MATERIAL_OVERLAY = 13

# Rooms (101-115 for up to 15 rooms)
ROOM_1 = 101
Expand Down Expand Up @@ -98,6 +99,15 @@ def _set_default_properties(self):
DrawableElement.CARPET: SupportedColor.CARPET,
}

# Set default properties for material overlay
self._element_properties[DrawableElement.MATERIAL_OVERLAY] = {
"wood_color": (40, 40, 40), # RGB for wood lines
"wood_alpha": 38, # Alpha for wood lines
"tile_color": (40, 40, 40), # RGB for tile lines
"tile_alpha": 45, # Alpha for tile lines
"z_index": 11, # Draw materials above rooms but below walls
}

# Set z-index for each element type
z_indices = {
DrawableElement.FLOOR: 0,
Expand Down Expand Up @@ -219,6 +229,32 @@ def update_from_device_info(self, device_info: dict) -> None:
DrawableElement.CARPET: SupportedColor.CARPET,
}

# Update material overlay properties if present
if "color_material_wood" in device_info:
self.set_property(
DrawableElement.MATERIAL_OVERLAY,
"wood_color",
device_info["color_material_wood"],
)
if "alpha_material_wood" in device_info:
self.set_property(
DrawableElement.MATERIAL_OVERLAY,
"wood_alpha",
device_info["alpha_material_wood"],
)
if "color_material_tile" in device_info:
self.set_property(
DrawableElement.MATERIAL_OVERLAY,
"tile_color",
device_info["color_material_tile"],
)
if "alpha_material_tile" in device_info:
self.set_property(
DrawableElement.MATERIAL_OVERLAY,
"tile_alpha",
device_info["alpha_material_tile"],
)

# Update room colors from device info
for room_id in range(1, 16):
room_element = getattr(DrawableElement, f"ROOM_{room_id}")
Expand Down Expand Up @@ -274,6 +310,7 @@ def update_from_device_info(self, device_info: dict) -> None:
"disable_path": DrawableElement.PATH,
"disable_predicted_path": DrawableElement.PREDICTED_PATH,
"disable_go_to_target": DrawableElement.GO_TO_TARGET,
"disable_material_overlay": DrawableElement.MATERIAL_OVERLAY,
}

# Process base element disable flags
Expand Down
223 changes: 223 additions & 0 deletions SCR/valetudo_map_parser/config/material.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
from __future__ import annotations

from dataclasses import dataclass
from functools import lru_cache
from typing import Final, Optional

import numpy as np
from mvcrender.draw import line_u8

from .colors import color_material_tile, color_material_wood
from .types import Color, NumpyArray


@dataclass(frozen=True, slots=True)
class _MaterialSpec:
cells: int
kind: str # "wood_h", "wood_v", "tile"


@dataclass(frozen=True, slots=True)
class MaterialColors:
"""Material colors for rendering."""

wood_rgba: Color = color_material_wood
tile_rgba: Color = color_material_tile


# Create a singleton instance for easy access
_material_colors = MaterialColors()


class MaterialTileRenderer:
"""
Material patterns rendered as small RGBA tiles.

Wood is drawn as staggered rectangular planks (brick-like) with ONLY thin seams
(no extra inner grain line).
"""

_SPECS: Final[dict[str, _MaterialSpec]] = {
"wood_horizontal": _MaterialSpec(cells=36, kind="wood_h"),
"wood_vertical": _MaterialSpec(cells=36, kind="wood_v"),
"tile": _MaterialSpec(cells=4, kind="tile"),
}

@staticmethod
def _empty_rgba(h: int, w: int) -> NumpyArray:
return np.zeros((h, w, 4), dtype=np.uint8)

@staticmethod
def _thin_px(pixel_size: int) -> int:
"""Thin seam thickness in pixels (for pixel_size 5/7 -> 1 px)."""
return 1 if pixel_size <= 7 else 2

@staticmethod
def _draw_rect_outline(
tile: NumpyArray,
x0: int,
y0: int,
x1: int,
y1: int,
thickness: int,
rgba: Color,
) -> None:
"""Draw rectangle outline using mvcrender.line_u8."""
if x1 <= x0 or y1 <= y0:
return

# Draw four lines to form rectangle outline
# Top line
line_u8(tile, x0, y0, x1 - 1, y0, rgba, thickness)
# Bottom line
line_u8(tile, x0, y1 - 1, x1 - 1, y1 - 1, rgba, thickness)
# Left line
line_u8(tile, x0, y0, x0, y1 - 1, rgba, thickness)
# Right line
line_u8(tile, x1 - 1, y0, x1 - 1, y1 - 1, rgba, thickness)

@staticmethod
def _wood_planks_horizontal(
tile_px: int, pixel_size: int, color: Color
) -> NumpyArray:
"""
Horizontal wood planks as staggered rectangles.
ONLY thin seams (no inner lines).
"""
t = MaterialTileRenderer._empty_rgba(tile_px, tile_px)
seam = MaterialTileRenderer._thin_px(pixel_size)

# Plank size in CELLS (tweak here)
plank_h_cells = 3
plank_w_cells = 24 # longer planks -> looks less like tiles

plank_h = plank_h_cells * pixel_size
plank_w = plank_w_cells * pixel_size

rows = max(1, tile_px // plank_h)
cols = max(1, (tile_px + plank_w - 1) // plank_w)

for r in range(rows + 1):
y0 = r * plank_h
y1 = y0 + plank_h
offset = (plank_w // 2) if (r % 2 == 1) else 0

for c in range(cols + 1):
x0 = c * plank_w - offset
x1 = x0 + plank_w

cx0 = max(0, x0)
cy0 = max(0, y0)
cx1 = min(tile_px, x1)
cy1 = min(tile_px, y1)

MaterialTileRenderer._draw_rect_outline(
t, cx0, cy0, cx1, cy1, seam, color
)

return t

@staticmethod
def _wood_planks_vertical(
tile_px: int, pixel_size: int, color: Color
) -> NumpyArray:
"""Vertical wood planks as staggered rectangles, ONLY thin seams."""
t = MaterialTileRenderer._empty_rgba(tile_px, tile_px)
seam = MaterialTileRenderer._thin_px(pixel_size)

plank_w_cells = 3
plank_h_cells = 24

plank_w = plank_w_cells * pixel_size
plank_h = plank_h_cells * pixel_size

cols = max(1, tile_px // plank_w)
rows = max(1, (tile_px + plank_h - 1) // plank_h)

for c in range(cols + 1):
x0 = c * plank_w
x1 = x0 + plank_w
offset = (plank_h // 2) if (c % 2 == 1) else 0

for r in range(rows + 1):
y0 = r * plank_h - offset
y1 = y0 + plank_h

cx0 = max(0, x0)
cy0 = max(0, y0)
cx1 = min(tile_px, x1)
cy1 = min(tile_px, y1)

MaterialTileRenderer._draw_rect_outline(
t, cx0, cy0, cx1, cy1, seam, color
)

return t

@staticmethod
def _tile_pixels(cells: int, pixel_size: int, tile_rgba: Color) -> NumpyArray:
"""Draw tile grid using mvcrender.line_u8."""
size = cells * pixel_size
t = MaterialTileRenderer._empty_rgba(size, size)
th = MaterialTileRenderer._thin_px(pixel_size)
rgba = tile_rgba

# Draw horizontal line at top
line_u8(t, 0, 0, size - 1, 0, rgba, th)
# Draw vertical line at left
line_u8(t, 0, 0, 0, size - 1, rgba, th)

return t

@staticmethod
@lru_cache(maxsize=64)
def get_tile(
material: str, pixel_size: int, colors: MaterialColors = None
) -> Optional[NumpyArray]:
spec = MaterialTileRenderer._SPECS.get(material)
if spec is None or pixel_size <= 0:
return None

# Use provided colors or fall back to defaults
if colors is None:
colors = _material_colors

wood_color = colors.wood_rgba
tile_color = colors.tile_rgba

if spec.kind == "tile":
return MaterialTileRenderer._tile_pixels(spec.cells, pixel_size, tile_color)

tile_px = spec.cells * pixel_size
if spec.kind == "wood_h":
return MaterialTileRenderer._wood_planks_horizontal(
tile_px, pixel_size, wood_color
)
if spec.kind == "wood_v":
return MaterialTileRenderer._wood_planks_vertical(
tile_px, pixel_size, wood_color
)

return None

@staticmethod
def tile_block(tile: NumpyArray, r0: int, r1: int, c0: int, c1: int) -> NumpyArray:
th, tw, _ = tile.shape
rows = (np.arange(r0, r1) % th).astype(np.intp, copy=False)
cols = (np.arange(c0, c1) % tw).astype(np.intp, copy=False)
return tile[rows[:, None], cols[None, :], :]

@staticmethod
def apply_overlay_on_region(
image: NumpyArray,
tile: NumpyArray,
r0: int,
r1: int,
c0: int,
c1: int,
) -> None:
region = image[r0:r1, c0:c1]
overlay = MaterialTileRenderer.tile_block(tile, r0, r1, c0, c1)
mask = overlay[..., 3] > 0
if np.any(mask):
region[mask] = overlay[mask]
8 changes: 6 additions & 2 deletions SCR/valetudo_map_parser/config/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,11 +312,15 @@ def update_shared_data(self, device_info):
)
# Check for new floors_data first
floors_data = device_info.get("floors_data", None)
current_floor = device_info.get("current_floor", "floor_0") # Default fallback
current_floor = device_info.get(
"current_floor", "floor_0"
) # Default fallback

if floors_data:
# NEW: Use floors_data
floor_trims = floors_data.get(current_floor, DEFAULT_VALUES["trims_data"])
floor_trims = floors_data.get(
current_floor, DEFAULT_VALUES["trims_data"]
)
instance.trims = TrimsData.from_dict(floor_trims)
instance.current_floor = current_floor
else:
Expand Down
Loading