valetudo-map-parser 0.1.9b50__tar.gz → 0.1.9b52__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. {valetudo_map_parser-0.1.9b50 → valetudo_map_parser-0.1.9b52}/PKG-INFO +1 -1
  2. {valetudo_map_parser-0.1.9b50 → valetudo_map_parser-0.1.9b52}/SCR/valetudo_map_parser/__init__.py +2 -0
  3. valetudo_map_parser-0.1.9b52/SCR/valetudo_map_parser/config/color_utils.py +105 -0
  4. {valetudo_map_parser-0.1.9b50 → valetudo_map_parser-0.1.9b52}/SCR/valetudo_map_parser/config/colors.py +55 -70
  5. valetudo_map_parser-0.1.9b52/SCR/valetudo_map_parser/config/drawable_elements.py +307 -0
  6. {valetudo_map_parser-0.1.9b50 → valetudo_map_parser-0.1.9b52}/SCR/valetudo_map_parser/config/enhanced_drawable.py +75 -193
  7. {valetudo_map_parser-0.1.9b50 → valetudo_map_parser-0.1.9b52}/SCR/valetudo_map_parser/config/shared.py +0 -1
  8. {valetudo_map_parser-0.1.9b50 → valetudo_map_parser-0.1.9b52}/SCR/valetudo_map_parser/config/types.py +19 -33
  9. {valetudo_map_parser-0.1.9b50 → valetudo_map_parser-0.1.9b52}/SCR/valetudo_map_parser/config/utils.py +0 -67
  10. {valetudo_map_parser-0.1.9b50 → valetudo_map_parser-0.1.9b52}/SCR/valetudo_map_parser/hypfer_draw.py +222 -57
  11. {valetudo_map_parser-0.1.9b50 → valetudo_map_parser-0.1.9b52}/SCR/valetudo_map_parser/hypfer_handler.py +48 -155
  12. valetudo_map_parser-0.1.9b52/SCR/valetudo_map_parser/hypfer_rooms_handler.py +406 -0
  13. {valetudo_map_parser-0.1.9b50 → valetudo_map_parser-0.1.9b52}/SCR/valetudo_map_parser/map_data.py +0 -30
  14. {valetudo_map_parser-0.1.9b50 → valetudo_map_parser-0.1.9b52}/SCR/valetudo_map_parser/rand25_handler.py +2 -84
  15. {valetudo_map_parser-0.1.9b50 → valetudo_map_parser-0.1.9b52}/pyproject.toml +1 -1
  16. valetudo_map_parser-0.1.9b50/SCR/valetudo_map_parser/config/color_utils.py +0 -49
  17. valetudo_map_parser-0.1.9b50/SCR/valetudo_map_parser/config/drawable_elements.py +0 -983
  18. {valetudo_map_parser-0.1.9b50 → valetudo_map_parser-0.1.9b52}/LICENSE +0 -0
  19. {valetudo_map_parser-0.1.9b50 → valetudo_map_parser-0.1.9b52}/NOTICE.txt +0 -0
  20. {valetudo_map_parser-0.1.9b50 → valetudo_map_parser-0.1.9b52}/README.md +0 -0
  21. {valetudo_map_parser-0.1.9b50 → valetudo_map_parser-0.1.9b52}/SCR/valetudo_map_parser/config/__init__.py +0 -0
  22. {valetudo_map_parser-0.1.9b50 → valetudo_map_parser-0.1.9b52}/SCR/valetudo_map_parser/config/auto_crop.py +0 -0
  23. {valetudo_map_parser-0.1.9b50 → valetudo_map_parser-0.1.9b52}/SCR/valetudo_map_parser/config/drawable.py +0 -0
  24. {valetudo_map_parser-0.1.9b50 → valetudo_map_parser-0.1.9b52}/SCR/valetudo_map_parser/config/optimized_element_map.py +0 -0
  25. {valetudo_map_parser-0.1.9b50 → valetudo_map_parser-0.1.9b52}/SCR/valetudo_map_parser/config/rand25_parser.py +0 -0
  26. {valetudo_map_parser-0.1.9b50 → valetudo_map_parser-0.1.9b52}/SCR/valetudo_map_parser/config/room_outline.py +0 -0
  27. {valetudo_map_parser-0.1.9b50 → valetudo_map_parser-0.1.9b52}/SCR/valetudo_map_parser/py.typed +0 -0
  28. {valetudo_map_parser-0.1.9b50 → valetudo_map_parser-0.1.9b52}/SCR/valetudo_map_parser/reimg_draw.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: valetudo-map-parser
3
- Version: 0.1.9b50
3
+ Version: 0.1.9b52
4
4
  Summary: A Python library to parse Valetudo map data returning a PIL Image object.
5
5
  License: Apache-2.0
6
6
  Author: Sandro Cantarella
@@ -15,11 +15,13 @@ from .config.types import (
15
15
  TrimCropData,
16
16
  UserLanguageStore,
17
17
  )
18
+ from .hypfer_rooms_handler import HypferRoomsHandler
18
19
  from .hypfer_handler import HypferMapImageHandler
19
20
  from .rand25_handler import ReImageHandler
20
21
 
21
22
 
22
23
  __all__ = [
24
+ "HypferRoomsHandler",
23
25
  "HypferMapImageHandler",
24
26
  "ReImageHandler",
25
27
  "RRMapParser",
@@ -0,0 +1,105 @@
1
+ """Utility functions for color operations in the map parser."""
2
+
3
+ from typing import Tuple, Optional
4
+
5
+ from .colors import ColorsManagement
6
+ from .types import NumpyArray, Color
7
+
8
+
9
+ def get_blended_color(
10
+ x0: int,
11
+ y0: int,
12
+ x1: int,
13
+ y1: int,
14
+ arr: Optional[NumpyArray],
15
+ color: Color,
16
+ ) -> Color:
17
+ """
18
+ Get a blended color for a pixel based on the current element map and the new element to draw.
19
+
20
+ This function:
21
+ 1. Gets the background colors at the start and end points (with offset to avoid sampling already drawn pixels)
22
+ 2. Directly blends the foreground color with the background using straight alpha
23
+ 3. Returns the average of the two blended colors
24
+
25
+ Returns:
26
+ Blended RGBA color to use for drawing
27
+ """
28
+ # Extract foreground color components
29
+ fg_r, fg_g, fg_b, fg_a = color
30
+ fg_alpha = fg_a / 255.0 # Convert to 0-1 range
31
+
32
+ # Fast path for fully opaque or transparent foreground
33
+ if fg_a == 255:
34
+ return color
35
+ if fg_a == 0:
36
+ # Sample background at midpoint
37
+ mid_x, mid_y = (x0 + x1) // 2, (y0 + y1) // 2
38
+ if 0 <= mid_y < arr.shape[0] and 0 <= mid_x < arr.shape[1]:
39
+ return tuple(arr[mid_y, mid_x])
40
+ return (0, 0, 0, 0) # Default if out of bounds
41
+
42
+ # Calculate direction vector for offset sampling
43
+ dx = x1 - x0
44
+ dy = y1 - y0
45
+ length = max(1, (dx**2 + dy**2) ** 0.5) # Avoid division by zero
46
+ offset = 5 # 5-pixel offset to avoid sampling already drawn pixels
47
+
48
+ # Calculate offset coordinates for start point (move away from the line)
49
+ offset_x0 = int(x0 - (offset * dx / length))
50
+ offset_y0 = int(y0 - (offset * dy / length))
51
+
52
+ # Calculate offset coordinates for end point (move away from the line)
53
+ offset_x1 = int(x1 + (offset * dx / length))
54
+ offset_y1 = int(y1 + (offset * dy / length))
55
+
56
+ # Sample background at offset start point
57
+ if 0 <= offset_y0 < arr.shape[0] and 0 <= offset_x0 < arr.shape[1]:
58
+ bg_color_start = arr[offset_y0, offset_x0]
59
+ # Direct straight alpha blending
60
+ start_r = int(fg_r * fg_alpha + bg_color_start[0] * (1 - fg_alpha))
61
+ start_g = int(fg_g * fg_alpha + bg_color_start[1] * (1 - fg_alpha))
62
+ start_b = int(fg_b * fg_alpha + bg_color_start[2] * (1 - fg_alpha))
63
+ start_a = int(fg_a + bg_color_start[3] * (1 - fg_alpha))
64
+ start_blended_color = (start_r, start_g, start_b, start_a)
65
+ else:
66
+ # If offset point is out of bounds, try original point
67
+ if 0 <= y0 < arr.shape[0] and 0 <= x0 < arr.shape[1]:
68
+ bg_color_start = arr[y0, x0]
69
+ start_r = int(fg_r * fg_alpha + bg_color_start[0] * (1 - fg_alpha))
70
+ start_g = int(fg_g * fg_alpha + bg_color_start[1] * (1 - fg_alpha))
71
+ start_b = int(fg_b * fg_alpha + bg_color_start[2] * (1 - fg_alpha))
72
+ start_a = int(fg_a + bg_color_start[3] * (1 - fg_alpha))
73
+ start_blended_color = (start_r, start_g, start_b, start_a)
74
+ else:
75
+ start_blended_color = color
76
+
77
+ # Sample background at offset end point
78
+ if 0 <= offset_y1 < arr.shape[0] and 0 <= offset_x1 < arr.shape[1]:
79
+ bg_color_end = arr[offset_y1, offset_x1]
80
+ # Direct straight alpha blending
81
+ end_r = int(fg_r * fg_alpha + bg_color_end[0] * (1 - fg_alpha))
82
+ end_g = int(fg_g * fg_alpha + bg_color_end[1] * (1 - fg_alpha))
83
+ end_b = int(fg_b * fg_alpha + bg_color_end[2] * (1 - fg_alpha))
84
+ end_a = int(fg_a + bg_color_end[3] * (1 - fg_alpha))
85
+ end_blended_color = (end_r, end_g, end_b, end_a)
86
+ else:
87
+ # If offset point is out of bounds, try original point
88
+ if 0 <= y1 < arr.shape[0] and 0 <= x1 < arr.shape[1]:
89
+ bg_color_end = arr[y1, x1]
90
+ end_r = int(fg_r * fg_alpha + bg_color_end[0] * (1 - fg_alpha))
91
+ end_g = int(fg_g * fg_alpha + bg_color_end[1] * (1 - fg_alpha))
92
+ end_b = int(fg_b * fg_alpha + bg_color_end[2] * (1 - fg_alpha))
93
+ end_a = int(fg_a + bg_color_end[3] * (1 - fg_alpha))
94
+ end_blended_color = (end_r, end_g, end_b, end_a)
95
+ else:
96
+ end_blended_color = color
97
+
98
+ # Use the average of the two blended colors
99
+ blended_color = (
100
+ (start_blended_color[0] + end_blended_color[0]) // 2,
101
+ (start_blended_color[1] + end_blended_color[1]) // 2,
102
+ (start_blended_color[2] + end_blended_color[2]) // 2,
103
+ (start_blended_color[3] + end_blended_color[3]) // 2,
104
+ )
105
+ return blended_color
@@ -407,61 +407,56 @@ class ColorsManagement:
407
407
  def blend_colors(background: Color, foreground: Color) -> Color:
408
408
  """
409
409
  Blend foreground color with background color based on alpha values.
410
-
411
- This is used when drawing elements that overlap on the map.
412
- The alpha channel determines how much of the foreground color is visible.
413
- Uses optimized calculations for better performance.
410
+ Optimized version with more fast paths and simplified calculations.
414
411
 
415
412
  :param background: Background RGBA color (r,g,b,a)
416
413
  :param foreground: Foreground RGBA color (r,g,b,a) to blend on top
417
414
  :return: Blended RGBA color
418
415
  """
419
- # Extract components
420
- bg_r, bg_g, bg_b, bg_a = background
421
- fg_r, fg_g, fg_b, fg_a = foreground
416
+ # Fast paths for common cases
417
+ fg_a = foreground[3]
422
418
 
423
- # Fast path for common cases
424
- if fg_a == 255:
419
+ if fg_a == 255: # Fully opaque foreground
425
420
  return foreground
426
- if fg_a == 0:
427
- return background
428
421
 
429
- # Calculate alpha blending
430
- # Convert alpha from [0-255] to [0-1] for calculations
431
- fg_alpha = fg_a / 255.0
432
- bg_alpha = bg_a / 255.0
422
+ if fg_a == 0: # Fully transparent foreground
423
+ return background
433
424
 
434
- # Calculate resulting alpha
435
- out_alpha = fg_alpha + bg_alpha * (1 - fg_alpha)
425
+ bg_a = background[3]
426
+ if bg_a == 0: # Fully transparent background
427
+ return foreground
436
428
 
437
- # Avoid division by zero
438
- if out_alpha < 0.0001:
439
- return Color[0, 0, 0, 0] # Fully transparent result
429
+ # Extract components (only after fast paths)
430
+ bg_r, bg_g, bg_b = background[:3]
431
+ fg_r, fg_g, fg_b = foreground[:3]
440
432
 
441
- # Calculate blended RGB components
442
- # Using a more efficient calculation method
443
- alpha_ratio = fg_alpha / out_alpha
444
- inv_alpha_ratio = 1.0 - alpha_ratio
433
+ # Pre-calculate the blend factor once (avoid repeated division)
434
+ blend = fg_a / 255.0
435
+ inv_blend = 1.0 - blend
445
436
 
446
- out_r = int(fg_r * alpha_ratio + bg_r * inv_alpha_ratio)
447
- out_g = int(fg_g * alpha_ratio + bg_g * inv_alpha_ratio)
448
- out_b = int(fg_b * alpha_ratio + bg_b * inv_alpha_ratio)
437
+ # Simple linear interpolation for RGB channels
438
+ # This is faster than the previous implementation
439
+ out_r = int(fg_r * blend + bg_r * inv_blend)
440
+ out_g = int(fg_g * blend + bg_g * inv_blend)
441
+ out_b = int(fg_b * blend + bg_b * inv_blend)
449
442
 
450
- # Convert alpha back to [0-255] range
451
- out_a = int(out_alpha * 255)
443
+ # Alpha blending - simplified calculation
444
+ out_a = int(fg_a + bg_a * inv_blend)
452
445
 
453
- # Ensure values are in valid range (using min/max for efficiency)
454
- out_r = max(0, min(255, out_r))
455
- out_g = max(0, min(255, out_g))
456
- out_b = max(0, min(255, out_b))
446
+ # No need for min/max checks as the blend math keeps values in range
447
+ # when input values are valid (0-255)
457
448
 
458
449
  return [out_r, out_g, out_b, out_a]
459
450
 
451
+ # Cache for recently sampled background colors
452
+ _bg_color_cache = {}
453
+ _cache_size = 1024 # Limit cache size to avoid memory issues
454
+
460
455
  @staticmethod
461
456
  def sample_and_blend_color(array, x: int, y: int, foreground: Color) -> Color:
462
457
  """
463
458
  Sample the background color from the array at coordinates (x,y) and blend with foreground color.
464
- Uses scipy.ndimage for efficient sampling when appropriate.
459
+ Optimized version with caching and faster sampling.
465
460
 
466
461
  Args:
467
462
  array: The RGBA numpy array representing the image
@@ -472,53 +467,43 @@ class ColorsManagement:
472
467
  Returns:
473
468
  Blended RGBA color
474
469
  """
475
- # Ensure coordinates are within bounds
470
+ # Fast path for fully opaque foreground - no need to sample or blend
471
+ if foreground[3] == 255:
472
+ return foreground
473
+
474
+ # Ensure array exists
476
475
  if array is None:
477
476
  return foreground
478
477
 
478
+ # Check if coordinates are within bounds
479
479
  height, width = array.shape[:2]
480
480
  if not (0 <= y < height and 0 <= x < width):
481
- return foreground # Return foreground if coordinates are out of bounds
482
-
483
- # Fast path for fully opaque foreground
484
- if foreground[3] == 255:
485
481
  return foreground
486
482
 
487
- # The array is in RGBA format with shape (height, width, 4)
488
- try:
489
- # Use scipy.ndimage for sampling with boundary handling
490
- # This is more efficient for large arrays and handles edge cases better
491
- if (
492
- array.size > 1000000
493
- ): # Only use for larger arrays where the overhead is worth it
494
- # Create coordinates array for the sampling point
495
- coordinates = np.array([[y, x]])
496
-
497
- # Sample each channel separately with nearest neighbor interpolation
498
- # This is faster than sampling all channels at once for large arrays
499
- r = ndimage.map_coordinates(
500
- array[..., 0], coordinates.T, order=0, mode="nearest"
501
- )[0]
502
- g = ndimage.map_coordinates(
503
- array[..., 1], coordinates.T, order=0, mode="nearest"
504
- )[0]
505
- b = ndimage.map_coordinates(
506
- array[..., 2], coordinates.T, order=0, mode="nearest"
507
- )[0]
508
- a = ndimage.map_coordinates(
509
- array[..., 3], coordinates.T, order=0, mode="nearest"
510
- )[0]
511
- background = (int(r), int(g), int(b), int(a))
512
- else:
513
- # For smaller arrays, direct indexing is faster
514
- background = tuple(array[y, x])
515
- except (IndexError, ValueError):
516
- # Fallback to direct indexing if ndimage fails
483
+ # Check cache for this coordinate
484
+ cache_key = (id(array), x, y)
485
+ cache = ColorsManagement._bg_color_cache
486
+
487
+ if cache_key in cache:
488
+ background = cache[cache_key]
489
+ else:
490
+ # Sample the background color using direct indexing (fastest method)
517
491
  try:
518
- background = tuple(array[y, x])
492
+ background = tuple(map(int, array[y, x]))
493
+
494
+ # Update cache (with simple LRU-like behavior)
495
+ if len(cache) >= ColorsManagement._cache_size:
496
+ # Remove a random entry if cache is full
497
+ cache.pop(next(iter(cache)))
498
+ cache[cache_key] = background
499
+
519
500
  except (IndexError, ValueError):
520
501
  return foreground
521
502
 
503
+ # Fast path for fully transparent foreground
504
+ if foreground[3] == 0:
505
+ return background
506
+
522
507
  # Blend the colors
523
508
  return ColorsManagement.blend_colors(background, foreground)
524
509
 
@@ -0,0 +1,307 @@
1
+ """
2
+ Drawable Elements Configuration.
3
+ Defines the elements that can be drawn on the map and their properties.
4
+ Version: 0.1.9
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from enum import IntEnum
10
+ from typing import Dict, List, Tuple, Union
11
+ import numpy as np
12
+ from .types import LOGGER
13
+
14
+ from .colors import DefaultColors, SupportedColor
15
+
16
+ # Type aliases
17
+ Color = Tuple[int, int, int, int] # RGBA color
18
+ PropertyDict = Dict[str, Union[Color, float, int]]
19
+
20
+
21
+ class DrawableElement(IntEnum):
22
+ """Enumeration of drawable map elements with unique integer codes."""
23
+
24
+ # Base elements
25
+ FLOOR = 1
26
+ WALL = 2
27
+ ROBOT = 3
28
+ CHARGER = 4
29
+ VIRTUAL_WALL = 5
30
+ RESTRICTED_AREA = 6
31
+ NO_MOP_AREA = 7
32
+ OBSTACLE = 8
33
+ PATH = 9
34
+ PREDICTED_PATH = 10
35
+ GO_TO_TARGET = 11
36
+
37
+ # Rooms (101-115 for up to 15 rooms)
38
+ ROOM_1 = 101
39
+ ROOM_2 = 102
40
+ ROOM_3 = 103
41
+ ROOM_4 = 104
42
+ ROOM_5 = 105
43
+ ROOM_6 = 106
44
+ ROOM_7 = 107
45
+ ROOM_8 = 108
46
+ ROOM_9 = 109
47
+ ROOM_10 = 110
48
+ ROOM_11 = 111
49
+ ROOM_12 = 112
50
+ ROOM_13 = 113
51
+ ROOM_14 = 114
52
+ ROOM_15 = 115
53
+
54
+
55
+ class DrawingConfig:
56
+ """Configuration for which elements to draw and their properties."""
57
+
58
+ def __init__(self):
59
+ """Initialize with all elements enabled by default."""
60
+ # Dictionary of element_code -> enabled status
61
+ self._enabled_elements = {element: True for element in DrawableElement}
62
+
63
+ # Dictionary of element_code -> drawing properties (color, opacity, etc.)
64
+ self._element_properties: Dict[DrawableElement, PropertyDict] = {}
65
+
66
+ # Initialize default properties
67
+ self._set_default_properties()
68
+
69
+ def _set_default_properties(self):
70
+ """Set default drawing properties for each element."""
71
+ # Set properties for rooms using DefaultColors
72
+ for room_id in range(1, 16):
73
+ room_element = getattr(DrawableElement, f"ROOM_{room_id}")
74
+ room_key = SupportedColor.room_key(room_id - 1)
75
+ rgb = DefaultColors.DEFAULT_ROOM_COLORS.get(room_key, (135, 206, 250))
76
+ alpha = DefaultColors.DEFAULT_ALPHA.get(f"alpha_room_{room_id - 1}", 255.0)
77
+
78
+ self._element_properties[room_element] = {
79
+ "color": (*rgb, int(alpha)),
80
+ "opacity": alpha / 255.0,
81
+ "z_index": 10, # Drawing order
82
+ }
83
+
84
+ # Map DrawableElement to SupportedColor
85
+ element_color_mapping = {
86
+ DrawableElement.FLOOR: SupportedColor.MAP_BACKGROUND,
87
+ DrawableElement.WALL: SupportedColor.WALLS,
88
+ DrawableElement.ROBOT: SupportedColor.ROBOT,
89
+ DrawableElement.CHARGER: SupportedColor.CHARGER,
90
+ DrawableElement.VIRTUAL_WALL: SupportedColor.NO_GO,
91
+ DrawableElement.RESTRICTED_AREA: SupportedColor.NO_GO,
92
+ DrawableElement.PATH: SupportedColor.PATH,
93
+ DrawableElement.PREDICTED_PATH: SupportedColor.PREDICTED_PATH,
94
+ DrawableElement.GO_TO_TARGET: SupportedColor.GO_TO,
95
+ DrawableElement.NO_MOP_AREA: SupportedColor.NO_GO, # Using NO_GO for no-mop areas
96
+ DrawableElement.OBSTACLE: SupportedColor.NO_GO, # Using NO_GO for obstacles
97
+ }
98
+
99
+ # Set z-index for each element type
100
+ z_indices = {
101
+ DrawableElement.FLOOR: 0,
102
+ DrawableElement.WALL: 20,
103
+ DrawableElement.ROBOT: 50,
104
+ DrawableElement.CHARGER: 40,
105
+ DrawableElement.VIRTUAL_WALL: 30,
106
+ DrawableElement.RESTRICTED_AREA: 25,
107
+ DrawableElement.NO_MOP_AREA: 25,
108
+ DrawableElement.OBSTACLE: 15,
109
+ DrawableElement.PATH: 35,
110
+ DrawableElement.PREDICTED_PATH: 34,
111
+ DrawableElement.GO_TO_TARGET: 45,
112
+ }
113
+
114
+ # Set properties for other elements using DefaultColors
115
+ for element, color_key in element_color_mapping.items():
116
+ rgb = DefaultColors.COLORS_RGB.get(color_key, (0, 0, 0))
117
+ alpha_key = f"alpha_{color_key}"
118
+ alpha = DefaultColors.DEFAULT_ALPHA.get(alpha_key, 255.0)
119
+
120
+ # Special case for semi-transparent elements
121
+ if element in [
122
+ DrawableElement.RESTRICTED_AREA,
123
+ DrawableElement.NO_MOP_AREA,
124
+ DrawableElement.PREDICTED_PATH,
125
+ ]:
126
+ alpha = 125.0 # Semi-transparent by default
127
+
128
+ self._element_properties[element] = {
129
+ "color": (*rgb, int(alpha)),
130
+ "opacity": alpha / 255.0,
131
+ "z_index": z_indices.get(element, 0),
132
+ }
133
+
134
+ def enable_element(self, element_code: DrawableElement) -> None:
135
+ """Enable drawing of a specific element."""
136
+ if element_code in self._enabled_elements:
137
+ self._enabled_elements[element_code] = True
138
+ LOGGER.info(
139
+ "Enabled element %s (%s)", element_code.name, element_code.value
140
+ )
141
+ LOGGER.info(
142
+ "Element %s is now enabled: %s",
143
+ element_code.name,
144
+ self._enabled_elements[element_code],
145
+ )
146
+
147
+ def disable_element(self, element_code: DrawableElement) -> None:
148
+ """Disable drawing of a specific element."""
149
+ if element_code in self._enabled_elements:
150
+ self._enabled_elements[element_code] = False
151
+ LOGGER.info(
152
+ "Disabled element %s (%s)", element_code.name, element_code.value
153
+ )
154
+ LOGGER.info(
155
+ "Element %s is now enabled: %s",
156
+ element_code.name,
157
+ self._enabled_elements[element_code],
158
+ )
159
+
160
+ def set_elements(self, element_codes: List[DrawableElement]) -> None:
161
+ """Enable only the specified elements, disable all others."""
162
+ # First disable all
163
+ for element in self._enabled_elements:
164
+ self._enabled_elements[element] = False
165
+
166
+ # Then enable specified ones
167
+ for element in element_codes:
168
+ if element in self._enabled_elements:
169
+ self._enabled_elements[element] = True
170
+
171
+ def is_enabled(self, element_code: DrawableElement) -> bool:
172
+ """Check if an element is enabled for drawing."""
173
+ enabled = self._enabled_elements.get(element_code, False)
174
+ LOGGER.debug(
175
+ "Checking if element %s is enabled: %s",
176
+ element_code.name if hasattr(element_code, "name") else element_code,
177
+ enabled,
178
+ )
179
+ return enabled
180
+
181
+ def set_property(
182
+ self, element_code: DrawableElement, property_name: str, value
183
+ ) -> None:
184
+ """Set a drawing property for an element."""
185
+ if element_code in self._element_properties:
186
+ self._element_properties[element_code][property_name] = value
187
+
188
+ def get_property(
189
+ self, element_code: DrawableElement, property_name: str, default=None
190
+ ):
191
+ """Get a drawing property for an element."""
192
+ if element_code in self._element_properties:
193
+ return self._element_properties[element_code].get(property_name, default)
194
+ return default
195
+
196
+ def get_enabled_elements(self) -> List[DrawableElement]:
197
+ """Get list of enabled element codes."""
198
+ return [
199
+ element for element, enabled in self._enabled_elements.items() if enabled
200
+ ]
201
+
202
+ def get_drawing_order(self) -> List[DrawableElement]:
203
+ """Get list of enabled elements in drawing order (by z_index)."""
204
+ enabled = self.get_enabled_elements()
205
+ return sorted(enabled, key=lambda e: self.get_property(e, "z_index", 0))
206
+
207
+ def update_from_device_info(self, device_info: dict) -> None:
208
+ """Update configuration based on device info dictionary."""
209
+ # Map DrawableElement to SupportedColor
210
+ element_color_mapping = {
211
+ DrawableElement.FLOOR: SupportedColor.MAP_BACKGROUND,
212
+ DrawableElement.WALL: SupportedColor.WALLS,
213
+ DrawableElement.ROBOT: SupportedColor.ROBOT,
214
+ DrawableElement.CHARGER: SupportedColor.CHARGER,
215
+ DrawableElement.VIRTUAL_WALL: SupportedColor.NO_GO,
216
+ DrawableElement.RESTRICTED_AREA: SupportedColor.NO_GO,
217
+ DrawableElement.PATH: SupportedColor.PATH,
218
+ DrawableElement.PREDICTED_PATH: SupportedColor.PREDICTED_PATH,
219
+ DrawableElement.GO_TO_TARGET: SupportedColor.GO_TO,
220
+ DrawableElement.NO_MOP_AREA: SupportedColor.NO_GO,
221
+ DrawableElement.OBSTACLE: SupportedColor.NO_GO,
222
+ }
223
+
224
+ # Update room colors from device info
225
+ for room_id in range(1, 16):
226
+ room_element = getattr(DrawableElement, f"ROOM_{room_id}")
227
+ color_key = SupportedColor.room_key(room_id - 1)
228
+ alpha_key = f"alpha_room_{room_id - 1}"
229
+
230
+ if color_key in device_info:
231
+ rgb = device_info[color_key]
232
+ alpha = device_info.get(alpha_key, 255.0)
233
+
234
+ # Create RGBA color
235
+ rgba = (*rgb, int(alpha))
236
+
237
+ # Update color and opacity
238
+ self.set_property(room_element, "color", rgba)
239
+ self.set_property(room_element, "opacity", alpha / 255.0)
240
+
241
+ LOGGER.debug(
242
+ "Updated room %d color to %s with alpha %s", room_id, rgb, alpha
243
+ )
244
+
245
+ # Update other element colors
246
+ for element, color_key in element_color_mapping.items():
247
+ if color_key in device_info:
248
+ rgb = device_info[color_key]
249
+ alpha_key = f"alpha_{color_key}"
250
+ alpha = device_info.get(alpha_key, 255.0)
251
+
252
+ # Special case for semi-transparent elements
253
+ if element in [
254
+ DrawableElement.RESTRICTED_AREA,
255
+ DrawableElement.NO_MOP_AREA,
256
+ DrawableElement.PREDICTED_PATH,
257
+ ]:
258
+ if alpha > 200: # If alpha is too high for these elements
259
+ alpha = 125.0 # Use a more appropriate default
260
+
261
+ # Create RGBA color
262
+ rgba = (*rgb, int(alpha))
263
+
264
+ # Update color and opacity
265
+ self.set_property(element, "color", rgba)
266
+ self.set_property(element, "opacity", alpha / 255.0)
267
+
268
+ LOGGER.debug(
269
+ "Updated element %s color to %s with alpha %s",
270
+ element.name,
271
+ rgb,
272
+ alpha,
273
+ )
274
+
275
+ # Check for disabled elements using specific boolean flags
276
+ # Map element disable flags to DrawableElement enum values
277
+ element_disable_mapping = {
278
+ "disable_floor": DrawableElement.FLOOR,
279
+ "disable_wall": DrawableElement.WALL,
280
+ "disable_robot": DrawableElement.ROBOT,
281
+ "disable_charger": DrawableElement.CHARGER,
282
+ "disable_virtual_walls": DrawableElement.VIRTUAL_WALL,
283
+ "disable_restricted_areas": DrawableElement.RESTRICTED_AREA,
284
+ "disable_no_mop_areas": DrawableElement.NO_MOP_AREA,
285
+ "disable_obstacles": DrawableElement.OBSTACLE,
286
+ "disable_path": DrawableElement.PATH,
287
+ "disable_predicted_path": DrawableElement.PREDICTED_PATH,
288
+ "disable_go_to_target": DrawableElement.GO_TO_TARGET,
289
+ }
290
+
291
+ # Process base element disable flags
292
+ for disable_key, element in element_disable_mapping.items():
293
+ if device_info.get(disable_key, False):
294
+ self.disable_element(element)
295
+ LOGGER.info(
296
+ "Disabled %s element from device_info setting", element.name
297
+ )
298
+
299
+ # Process room disable flags (1-15)
300
+ for room_id in range(1, 16):
301
+ disable_key = f"disable_room_{room_id}"
302
+ if device_info.get(disable_key, False):
303
+ room_element = getattr(DrawableElement, f"ROOM_{room_id}")
304
+ self.disable_element(room_element)
305
+ LOGGER.info(
306
+ "Disabled ROOM_%d element from device_info setting", room_id
307
+ )