Lidar_Muxa/tools/rviz_layout.py

103 lines
4.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Раскладка панелей RViz: строка «QMainWindow State» для конфига без Qt.
Где и какой ширины стоят панели, RViz хранит в конфиге сериализацией Qt —
шестнадцатеричной строкой `QMainWindow::saveState()`. Руками её не набрать, а
Qt с привязками для Python в образе нет (и из WSL нет выхода к apt). Формат
при этом простой и стабильный, он собран здесь по исходникам Qt 5.15
(`QDockAreaLayout::saveState`, `QDockAreaLayoutInfo::saveState`): маркер
версии, маркер доков, по каждой зоне — размер и список панелей с именем,
флагами, положением и размером. Панель находится по имени объекта, а у RViz
это имя панели или дисплея («Displays», «Мозг мухи»).
python tools/rviz_layout.py
печатает строки для обоих конфигов: `config/flyguard.rviz` (панель Displays
слева) и `config/flyguard_brain.rviz` (плюс схема мозга справа во всю высоту).
"""
from __future__ import annotations
import argparse
import struct
QWIDGETSIZE_MAX = 16777215
LEFT, RIGHT = 0, 1 # QInternal::DockPosition
VERTICAL = 2 # Qt::Vertical — панели в зоне идут столбиком
CORNERS = (4, 4, 8, 8) # углы окна: верхняя, верхняя, нижняя, нижняя зона
def _i32(v: int) -> bytes:
return struct.pack(">i", v)
def _qstring(s: str) -> bytes:
b = s.encode("utf-16-be")
return struct.pack(">I", len(b)) + b
def state(areas, central: int, height: int) -> str:
"""areas — [(зона, ширина, [имя панели, ...])], central — ширина трёхмерного вида."""
out = _i32(0xff) + _i32(0) + b"\xfd" + _i32(len(areas))
for pos, width, docks in areas:
out += _i32(pos) + _i32(width) + _i32(height)
out += b"\xfc" + bytes([VERTICAL]) + _i32(len(docks))
step = height // len(docks)
for k, name in enumerate(docks):
out += b"\xfb" + _qstring(name) + b"\x01" # видима, не плавает
out += _i32(k * step) + _i32(step) + _i32(100) + _i32(QWIDGETSIZE_MAX)
out += _i32(central) + _i32(height)
for c in CORNERS:
out += _i32(c)
return out.hex()
def parse(hexs: str):
"""Обратный разбор — проверка, что строка собрана без хвостов."""
b = bytes.fromhex(hexs)
p = 0
def rd(fmt):
nonlocal p
v = struct.unpack_from(fmt, b, p)
p += struct.calcsize(fmt)
return v
assert rd(">ii") == (0xff, 0) and rd(">B") == (0xfd,)
n, = rd(">i")
areas = []
for _ in range(n):
pos, w, h = rd(">iii")
marker, _, cnt = rd(">BBi")
assert marker == 0xfc
docks = []
for _ in range(cnt):
assert rd(">B") == (0xfb,)
ln, = rd(">I")
docks.append(b[p:p + ln].decode("utf-16-be"))
p += ln
rd(">Biiii")
areas.append((pos, w, h, docks))
central = rd(">ii")
rd(">iiii")
assert p == len(b), "строка не разобрана до конца"
return areas, central
def main() -> None:
ap = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--width", type=int, default=1880, help="ширина окна, как Width в конфиге")
ap.add_argument("--height", type=int, default=900, help="высота зоны панелей")
ap.add_argument("--left", type=int, default=300, help="ширина панели Displays")
ap.add_argument("--brain", type=int, default=820, help="ширина схемы мозга")
a = ap.parse_args()
plain = state([(LEFT, a.left, ["Displays"])], a.width - a.left - 4, a.height)
left = a.left - 20
brain = state([(LEFT, left, ["Displays"]), (RIGHT, a.brain, ["Мозг мухи"])],
a.width - left - a.brain - 8, a.height)
for name, s in (("flyguard.rviz", plain), ("flyguard_brain.rviz", brain)):
parse(s)
print(f"{name}:\n QMainWindow State: {s}")
if __name__ == "__main__":
main()