Replace debug QObject models with Pydantic.

This requires less duplication and paves the way for us replacing
QWebChannel with FastAPI, which in turn gets us closer to not needing
Qt.
This commit is contained in:
Dan Albert 2022-02-13 14:33:49 -08:00
parent 1adafac35f
commit 6ebda41922
6 changed files with 111 additions and 179 deletions

View File

@ -1,67 +1,33 @@
from __future__ import annotations
from PySide2.QtCore import Property, QObject, Signal
from pydantic import BaseModel, Field
from game import Game
from game.ato import Flight
from game.flightplan import HoldZoneGeometry
from .config import ENABLE_EXPENSIVE_DEBUG_TOOLS
from .leaflet import LeafletLatLon, LeafletPoly
from .leaflet import LeafletPoly
from .shapelyutil import ShapelyUtil
class HoldZonesJs(QObject):
homeBubbleChanged = Signal()
targetBubbleChanged = Signal()
joinBubbleChanged = Signal()
excludedZonesChanged = Signal()
permissibleZonesChanged = Signal()
preferredLinesChanged = Signal()
def __init__(
self,
home_bubble: LeafletPoly,
target_bubble: LeafletPoly,
join_bubble: LeafletPoly,
excluded_zones: list[LeafletPoly],
permissible_zones: list[LeafletPoly],
preferred_lines: list[list[LeafletLatLon]],
) -> None:
super().__init__()
self._home_bubble = home_bubble
self._target_bubble = target_bubble
self._join_bubble = join_bubble
self._excluded_zones = excluded_zones
self._permissible_zones = permissible_zones
self._preferred_lines = preferred_lines
@Property(list, notify=homeBubbleChanged)
def homeBubble(self) -> LeafletPoly:
return self._home_bubble
@Property(list, notify=targetBubbleChanged)
def targetBubble(self) -> LeafletPoly:
return self._target_bubble
@Property(list, notify=joinBubbleChanged)
def joinBubble(self) -> LeafletPoly:
return self._join_bubble
@Property(list, notify=excludedZonesChanged)
def excludedZones(self) -> list[LeafletPoly]:
return self._excluded_zones
@Property(list, notify=permissibleZonesChanged)
def permissibleZones(self) -> list[LeafletPoly]:
return self._permissible_zones
@Property(list, notify=preferredLinesChanged)
def preferredLines(self) -> list[list[LeafletLatLon]]:
return self._preferred_lines
class HoldZonesJs(BaseModel):
home_bubble: LeafletPoly = Field(alias="homeBubble")
target_bubble: LeafletPoly = Field(alias="targetBubble")
join_bubble: LeafletPoly = Field(alias="joinBubble")
excluded_zones: list[LeafletPoly] = Field(alias="excludedZones")
permissible_zones: list[LeafletPoly] = Field(alias="permissibleZones")
preferred_lines: list[LeafletPoly] = Field(alias="preferredLines")
@classmethod
def empty(cls) -> HoldZonesJs:
return HoldZonesJs([], [], [], [], [], [])
return HoldZonesJs(
homeBubble=[],
targetBubble=[],
joinBubble=[],
excludedZones=[],
permissibleZones=[],
preferredLines=[],
)
@classmethod
def for_flight(cls, flight: Flight, game: Game) -> HoldZonesJs:
@ -77,10 +43,18 @@ class HoldZonesJs(QObject):
target.position, home.position, ip, join, game.blue, game.theater
)
return HoldZonesJs(
ShapelyUtil.poly_to_leaflet(geometry.home_bubble, game.theater),
ShapelyUtil.poly_to_leaflet(geometry.target_bubble, game.theater),
ShapelyUtil.poly_to_leaflet(geometry.join_bubble, game.theater),
ShapelyUtil.polys_to_leaflet(geometry.excluded_zones, game.theater),
ShapelyUtil.polys_to_leaflet(geometry.permissible_zones, game.theater),
ShapelyUtil.lines_to_leaflet(geometry.preferred_lines, game.theater),
homeBubble=ShapelyUtil.poly_to_leaflet(geometry.home_bubble, game.theater),
targetBubble=ShapelyUtil.poly_to_leaflet(
geometry.target_bubble, game.theater
),
joinBubble=ShapelyUtil.poly_to_leaflet(geometry.join_bubble, game.theater),
excludedZones=ShapelyUtil.polys_to_leaflet(
geometry.excluded_zones, game.theater
),
permissibleZones=ShapelyUtil.polys_to_leaflet(
geometry.permissible_zones, game.theater
),
preferredLines=ShapelyUtil.lines_to_leaflet(
geometry.preferred_lines, game.theater
),
)

View File

@ -1,6 +1,6 @@
from __future__ import annotations
from PySide2.QtCore import Property, QObject, Signal
from pydantic import BaseModel, Field
from game import Game
from game.ato import Flight
@ -10,44 +10,15 @@ from .leaflet import LeafletPoly
from .shapelyutil import ShapelyUtil
class IpZonesJs(QObject):
homeBubbleChanged = Signal()
ipBubbleChanged = Signal()
permissibleZoneChanged = Signal()
safeZonesChanged = Signal()
def __init__(
self,
home_bubble: LeafletPoly,
ip_bubble: LeafletPoly,
permissible_zone: LeafletPoly,
safe_zones: list[LeafletPoly],
) -> None:
super().__init__()
self._home_bubble = home_bubble
self._ip_bubble = ip_bubble
self._permissible_zone = permissible_zone
self._safe_zones = safe_zones
@Property(list, notify=homeBubbleChanged)
def homeBubble(self) -> LeafletPoly:
return self._home_bubble
@Property(list, notify=ipBubbleChanged)
def ipBubble(self) -> LeafletPoly:
return self._ip_bubble
@Property(list, notify=permissibleZoneChanged)
def permissibleZone(self) -> LeafletPoly:
return self._permissible_zone
@Property(list, notify=safeZonesChanged)
def safeZones(self) -> list[LeafletPoly]:
return self._safe_zones
class IpZonesJs(BaseModel):
home_bubble: LeafletPoly = Field(alias="homeBubble")
ipBubble: LeafletPoly = Field(alias="ipBubble")
permissibleZone: LeafletPoly = Field(alias="permissibleZone")
safeZones: list[LeafletPoly] = Field(alias="safeZones")
@classmethod
def empty(cls) -> IpZonesJs:
return IpZonesJs([], [], [], [])
return IpZonesJs(homeBubble=[], ipBubble=[], permissibleZone=[], safeZones=[])
@classmethod
def for_flight(cls, flight: Flight, game: Game) -> IpZonesJs:
@ -57,8 +28,10 @@ class IpZonesJs(QObject):
home = flight.departure
geometry = IpZoneGeometry(target.position, home.position, game.blue)
return IpZonesJs(
ShapelyUtil.poly_to_leaflet(geometry.home_bubble, game.theater),
ShapelyUtil.poly_to_leaflet(geometry.ip_bubble, game.theater),
ShapelyUtil.poly_to_leaflet(geometry.permissible_zone, game.theater),
ShapelyUtil.polys_to_leaflet(geometry.safe_zones, game.theater),
homeBubble=ShapelyUtil.poly_to_leaflet(geometry.home_bubble, game.theater),
ipBubble=ShapelyUtil.poly_to_leaflet(geometry.ip_bubble, game.theater),
permissibleZone=ShapelyUtil.poly_to_leaflet(
geometry.permissible_zone, game.theater
),
safeZones=ShapelyUtil.polys_to_leaflet(geometry.safe_zones, game.theater),
)

View File

@ -1,67 +1,34 @@
from __future__ import annotations
from PySide2.QtCore import Property, QObject, Signal
from pydantic import Field
from pydantic.main import BaseModel
from game import Game
from game.ato import Flight
from game.flightplan import JoinZoneGeometry
from .config import ENABLE_EXPENSIVE_DEBUG_TOOLS
from .leaflet import LeafletLatLon, LeafletPoly
from .leaflet import LeafletPoly
from .shapelyutil import ShapelyUtil
class JoinZonesJs(QObject):
homeBubbleChanged = Signal()
targetBubbleChanged = Signal()
ipBubbleChanged = Signal()
excludedZonesChanged = Signal()
permissibleZonesChanged = Signal()
preferredLinesChanged = Signal()
def __init__(
self,
home_bubble: LeafletPoly,
target_bubble: LeafletPoly,
ip_bubble: LeafletPoly,
excluded_zones: list[LeafletPoly],
permissible_zones: list[LeafletPoly],
preferred_lines: list[list[LeafletLatLon]],
) -> None:
super().__init__()
self._home_bubble = home_bubble
self._target_bubble = target_bubble
self._ip_bubble = ip_bubble
self._excluded_zones = excluded_zones
self._permissible_zones = permissible_zones
self._preferred_lines = preferred_lines
@Property(list, notify=homeBubbleChanged)
def homeBubble(self) -> LeafletPoly:
return self._home_bubble
@Property(list, notify=targetBubbleChanged)
def targetBubble(self) -> LeafletPoly:
return self._target_bubble
@Property(list, notify=ipBubbleChanged)
def ipBubble(self) -> LeafletPoly:
return self._ip_bubble
@Property(list, notify=excludedZonesChanged)
def excludedZones(self) -> list[LeafletPoly]:
return self._excluded_zones
@Property(list, notify=permissibleZonesChanged)
def permissibleZones(self) -> list[LeafletPoly]:
return self._permissible_zones
@Property(list, notify=preferredLinesChanged)
def preferredLines(self) -> list[list[LeafletLatLon]]:
return self._preferred_lines
class JoinZonesJs(BaseModel):
home_bubble: LeafletPoly = Field(alias="homeBubble")
target_bubble: LeafletPoly = Field(alias="targetBubble")
ip_bubble: LeafletPoly = Field(alias="ipBubble")
excluded_zones: list[LeafletPoly] = Field(alias="excludedZones")
permissible_zones: list[LeafletPoly] = Field(alias="permissibleZones")
preferred_lines: list[LeafletPoly] = Field(alias="preferredLines")
@classmethod
def empty(cls) -> JoinZonesJs:
return JoinZonesJs([], [], [], [], [], [])
return JoinZonesJs(
homeBubble=[],
targetBubble=[],
ipBubble=[],
excludedZones=[],
permissibleZones=[],
preferredLines=[],
)
@classmethod
def for_flight(cls, flight: Flight, game: Game) -> JoinZonesJs:
@ -74,10 +41,18 @@ class JoinZonesJs(QObject):
ip = flight.package.waypoints.ingress
geometry = JoinZoneGeometry(target.position, home.position, ip, game.blue)
return JoinZonesJs(
ShapelyUtil.poly_to_leaflet(geometry.home_bubble, game.theater),
ShapelyUtil.poly_to_leaflet(geometry.target_bubble, game.theater),
ShapelyUtil.poly_to_leaflet(geometry.ip_bubble, game.theater),
ShapelyUtil.polys_to_leaflet(geometry.excluded_zones, game.theater),
ShapelyUtil.polys_to_leaflet(geometry.permissible_zones, game.theater),
ShapelyUtil.lines_to_leaflet(geometry.preferred_lines, game.theater),
homeBubble=ShapelyUtil.poly_to_leaflet(geometry.home_bubble, game.theater),
targetBubble=ShapelyUtil.poly_to_leaflet(
geometry.target_bubble, game.theater
),
ipBubble=ShapelyUtil.poly_to_leaflet(geometry.ip_bubble, game.theater),
excludedZones=ShapelyUtil.polys_to_leaflet(
geometry.excluded_zones, game.theater
),
permissibleZones=ShapelyUtil.polys_to_leaflet(
geometry.permissible_zones, game.theater
),
preferredLines=ShapelyUtil.lines_to_leaflet(
geometry.preferred_lines, game.theater
),
)

View File

@ -1,5 +1,6 @@
from __future__ import annotations
import json
import logging
from typing import List, Optional, Tuple
@ -396,17 +397,19 @@ class MapModel(QObject):
def unculledZones(self) -> list[UnculledZone]:
return self._unculled_zones
@Property(IpZonesJs, notify=ipZonesChanged)
def ipZones(self) -> IpZonesJs:
return self._ip_zones
@Property(str, notify=ipZonesChanged)
def ipZones(self) -> str:
return json.dumps(self._ip_zones.dict(by_alias=True))
@Property(JoinZonesJs, notify=joinZonesChanged)
def joinZones(self) -> JoinZonesJs:
return self._join_zones
@Property(str, notify=joinZonesChanged)
def joinZones(self) -> str:
# Must be dumped as a string and deserialized in js because QWebChannel can't
# handle a dict. Can be cleaned up by switching from QWebChannel to FastAPI.
return json.dumps(self._join_zones.dict(by_alias=True))
@Property(HoldZonesJs, notify=holdZonesChanged)
def holdZones(self) -> HoldZonesJs:
return self._hold_zones
@Property(str, notify=holdZonesChanged)
def holdZones(self) -> str:
return json.dumps(self._hold_zones.dict(by_alias=True))
def reset_combats(self) -> None:
self._air_combats = []

View File

@ -25,6 +25,7 @@ platformdirs==2.5.0
pluggy==1.0.0
pre-commit==2.17.0
py==1.11.0
pydantic==1.9.0
-e git+https://github.com/pydcs/dcs@63863a88e0a43cb0a310dbab3ce2c7800a099dbb#egg=pydcs
pyinstaller==4.9
pyinstaller-hooks-contrib==2022.1

View File

@ -1106,25 +1106,27 @@ function drawIpZones() {
return;
}
L.polygon(game.ipZones.homeBubble, {
const iz = JSON.parse(game.ipZones);
L.polygon(iz.homeBubble, {
color: Colors.Highlight,
fillOpacity: 0.1,
interactive: false,
}).addTo(ipZones);
L.polygon(game.ipZones.ipBubble, {
L.polygon(iz.ipBubble, {
color: "#bb89ff",
fillOpacity: 0.1,
interactive: false,
}).addTo(ipZones);
L.polygon(game.ipZones.permissibleZone, {
L.polygon(iz.permissibleZone, {
color: "#ffffff",
fillOpacity: 0.1,
interactive: false,
}).addTo(ipZones);
for (const zone of game.ipZones.safeZones) {
for (const zone of iz.safeZones) {
L.polygon(zone, {
color: Colors.Green,
fillOpacity: 0.1,
@ -1140,25 +1142,27 @@ function drawJoinZones() {
return;
}
L.polygon(game.joinZones.homeBubble, {
const jz = JSON.parse(game.joinZones);
L.polygon(jz.homeBubble, {
color: Colors.Highlight,
fillOpacity: 0.1,
interactive: false,
}).addTo(joinZones);
L.polygon(game.joinZones.targetBubble, {
L.polygon(jz.targetBubble, {
color: "#bb89ff",
fillOpacity: 0.1,
interactive: false,
}).addTo(joinZones);
L.polygon(game.joinZones.ipBubble, {
L.polygon(jz.ipBubble, {
color: "#ffffff",
fillOpacity: 0.1,
interactive: false,
}).addTo(joinZones);
for (const zone of game.joinZones.excludedZones) {
for (const zone of jz.excludedZones) {
L.polygon(zone, {
color: "#ffa500",
fillOpacity: 0.2,
@ -1167,14 +1171,14 @@ function drawJoinZones() {
}).addTo(joinZones);
}
for (const zone of game.joinZones.permissibleZones) {
for (const zone of jz.permissibleZones) {
L.polygon(zone, {
color: Colors.Green,
interactive: false,
}).addTo(joinZones);
}
for (const line of game.joinZones.preferredLines) {
for (const line of jz.preferredLines) {
L.polyline(line, {
color: Colors.Green,
interactive: false,
@ -1189,25 +1193,27 @@ function drawHoldZones() {
return;
}
L.polygon(game.holdZones.homeBubble, {
const hz = JSON.parse(game.holdZones);
L.polygon(hz.homeBubble, {
color: Colors.Highlight,
fillOpacity: 0.1,
interactive: false,
}).addTo(holdZones);
L.polygon(game.holdZones.targetBubble, {
L.polygon(hz.targetBubble, {
color: Colors.Highlight,
fillOpacity: 0.1,
interactive: false,
}).addTo(holdZones);
L.polygon(game.holdZones.joinBubble, {
L.polygon(hz.joinBubble, {
color: Colors.Highlight,
fillOpacity: 0.1,
interactive: false,
}).addTo(holdZones);
for (const zone of game.holdZones.excludedZones) {
for (const zone of hz.excludedZones) {
L.polygon(zone, {
color: "#ffa500",
fillOpacity: 0.2,
@ -1216,14 +1222,14 @@ function drawHoldZones() {
}).addTo(holdZones);
}
for (const zone of game.holdZones.permissibleZones) {
for (const zone of hz.permissibleZones) {
L.polygon(zone, {
color: Colors.Green,
interactive: false,
}).addTo(holdZones);
}
for (const line of game.holdZones.preferredLines) {
for (const line of hz.preferredLines) {
L.polyline(line, {
color: Colors.Green,
interactive: false,