-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconga_handler.py
More file actions
148 lines (129 loc) · 5.27 KB
/
Copy pathconga_handler.py
File metadata and controls
148 lines (129 loc) · 5.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
"""
Conga Image Handler Class.
Extends HypferMapImageHandler for Conga vacuums.
Conga maps have pixelSize=1 and canvas size 800×800. Before rendering we
deep-copy the JSON and multiply pixelSize, canvas size, and all entity
coordinates by CONGA_SCALE so that every map pixel becomes a CONGA_SCALE×CONGA_SCALE
block, producing solid filled rooms. Entity positions stay consistent with
the scaled map coordinate space, so no post-upscale is required.
Version: 0.1.4
"""
from __future__ import annotations
import copy
from PIL import Image
from .config.shared import CameraShared
from .config.types import JsonType
from .conga_draw import CONGA_SCALE, CongaImageDraw
from .hypfer_handler import HypferMapImageHandler
from .map_data import HyperMapData
class CongaMapImageHandler(HypferMapImageHandler):
"""Image Handler for Conga vacuums.
Scales pixelSize, canvas size, and all entity coordinates by CONGA_SCALE
before handing the JSON to the standard Hypfer render pipeline.
"""
def __init__(self, shared_data: CameraShared) -> None:
"""Initialise the Conga image handler."""
super().__init__(shared_data)
self.imd = CongaImageDraw(self)
@staticmethod
def _scale_conga_json(m_json: JsonType, scale: int) -> JsonType:
"""Return a deep copy of m_json with coordinates scaled by *scale*.
Patches:
- pixelSize → scale
- size.x / size.y → original × scale
- every entity's points list → each value × scale
"""
scaled = copy.deepcopy(m_json)
scaled["pixelSize"] = scale
size = scaled.get("size", {})
scaled["size"] = {
"x": int(size.get("x", 800)) * scale,
"y": int(size.get("y", 800)) * scale,
}
for entity in scaled.get("entities", []):
pts = entity.get("points")
if isinstance(pts, list):
entity["points"] = [v * scale for v in pts]
return scaled
async def async_get_rooms_attributes(self):
"""Get room attributes with outline and centre coordinates divided back by CONGA_SCALE.
The parent extracts outlines from the pre-scaled JSON, so every coordinate is
multiplied by CONGA_SCALE. Dividing here restores the original vacuum space.
"""
rooms = await super().async_get_rooms_attributes()
if not rooms:
return rooms
for room_data in rooms.values():
room_data["outline"] = [
(x // CONGA_SCALE, y // CONGA_SCALE) for x, y in room_data["outline"]
]
room_data["x"] = room_data["x"] // CONGA_SCALE
room_data["y"] = room_data["y"] // CONGA_SCALE
return rooms
async def async_get_conga_from_json(
self, m_json: JsonType | None
) -> Image.Image | None:
"""Scale Conga JSON to pixel_size=CONGA_SCALE, then render normally."""
if m_json is None:
return None
scaled_json = self._scale_conga_json(m_json, CONGA_SCALE)
self.json_data = await HyperMapData.async_from_valetudo_json(scaled_json)
return await super().async_get_image_from_json(m_json=scaled_json)
def get_vacuum_points(self, rotation_angle: int) -> list[dict[str, int]]:
"""Calculate the calibration points from crop_area.
The vacuum points must be returned in the order that matches map_points:
[top-left, top-right, bottom-right, bottom-left] of the DISPLAYED image.
crop_area is in vacuum coordinates and doesn't rotate, so we need to
map the correct crop_area corners to the displayed image corners based
on rotation.
"""
if not self.crop_area:
return [
{"x": 0, "y": 0},
{"x": 0, "y": 0},
{"x": 0, "y": 0},
{"x": 0, "y": 0},
]
vacuum_points = [
{
"x": self.crop_area[0] // CONGA_SCALE + self.offset_x,
"y": self.crop_area[1] // CONGA_SCALE + self.offset_y,
},
{
"x": self.crop_area[2] // CONGA_SCALE - self.offset_x,
"y": self.crop_area[1] // CONGA_SCALE + self.offset_y,
},
{
"x": self.crop_area[2] // CONGA_SCALE - self.offset_x,
"y": self.crop_area[3] // CONGA_SCALE - self.offset_y,
},
{
"x": self.crop_area[0] // CONGA_SCALE + self.offset_x,
"y": self.crop_area[3] // CONGA_SCALE - self.offset_y,
},
]
match rotation_angle:
case 90:
vacuum_points = [
vacuum_points[1],
vacuum_points[2],
vacuum_points[3],
vacuum_points[0],
]
case 180:
vacuum_points = [
vacuum_points[2],
vacuum_points[3],
vacuum_points[0],
vacuum_points[1],
]
case 270:
vacuum_points = [
vacuum_points[3],
vacuum_points[0],
vacuum_points[1],
vacuum_points[2],
]
case _:
pass
return vacuum_points