mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
417 lines
9.1 KiB
Python
417 lines
9.1 KiB
Python
import re
|
|
from dataclasses import dataclass
|
|
from typing import Any, Optional
|
|
|
|
|
|
def _camel_to_snake(name: str) -> str:
|
|
return re.sub(r"(?<!^)(?=[A-Z])", "_", name).lower()
|
|
|
|
|
|
def parse_displayable_fields(fields: list[dict[str, Any]]) -> dict[str, str]:
|
|
result: dict[str, str] = {}
|
|
for f in fields:
|
|
if f.get("type_as_string") == "sectionTitle":
|
|
continue
|
|
v: dict[str, Any] = f.get("value") or {}
|
|
if not v.get("has_value"):
|
|
continue
|
|
result[_camel_to_snake(str(f["id"]))] = str(v["value"])
|
|
return result
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# XML dataclasses (sourced from MagicPlan Exchange XML format)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@dataclass
|
|
class MagicPlanXMLRoomPoint:
|
|
snapped_x: float
|
|
snapped_y: float
|
|
height: float
|
|
uid: str
|
|
values: dict[str, str]
|
|
|
|
|
|
@dataclass
|
|
class MagicPlanXMLWallPoint:
|
|
"""Point in <exploded><wall> — absolute coords, no uid or values."""
|
|
|
|
x: float
|
|
y: float
|
|
height: float
|
|
|
|
|
|
@dataclass
|
|
class MagicPlanXMLDoor:
|
|
"""Door in <floorRoom> context — wall-relative position."""
|
|
|
|
wall_point_index: int
|
|
type: str
|
|
u: float
|
|
width: float
|
|
depth: float
|
|
height: float
|
|
orientation: int
|
|
snapped_type: str
|
|
snapped_position: float
|
|
snapped_width: float
|
|
snapped_depth: float
|
|
snapped_height: float
|
|
snapped_orientation: int
|
|
inset_x: float
|
|
inset_y: float
|
|
inset_z: float
|
|
symbol_instance: str
|
|
twin_wall_item_uid: Optional[str] = None
|
|
|
|
|
|
@dataclass
|
|
class MagicPlanXMLWindow:
|
|
"""Window in <floorRoom> context — wall-relative position."""
|
|
|
|
wall_point_index: int
|
|
type: str
|
|
u: float
|
|
width: float
|
|
depth: float
|
|
height: float
|
|
orientation: int
|
|
snapped_type: str
|
|
snapped_position: float
|
|
snapped_width: float
|
|
snapped_depth: float
|
|
snapped_height: float
|
|
snapped_orientation: int
|
|
inset_x: float
|
|
inset_y: float
|
|
inset_z: float
|
|
symbol_instance: str
|
|
|
|
|
|
@dataclass
|
|
class MagicPlanXMLExplodedOpening:
|
|
"""Door or window in <exploded> context — absolute coords, no snapped* fields."""
|
|
|
|
type: str
|
|
x1: float
|
|
y1: float
|
|
x2: float
|
|
y2: float
|
|
width: float
|
|
depth: float
|
|
height: float
|
|
inset_x: float
|
|
inset_y: float
|
|
orientation: int
|
|
symbol_instance: str
|
|
|
|
|
|
@dataclass
|
|
class MagicPlanXMLFurniture:
|
|
type: str
|
|
x: float
|
|
y: float
|
|
snapped_x: float
|
|
snapped_y: float
|
|
angle: float
|
|
width: float
|
|
depth: float
|
|
height: float
|
|
snapped_width: float
|
|
snapped_depth: float
|
|
snapped_height: float
|
|
size_lock_0: str
|
|
size_lock_1: str
|
|
size_lock_2: str
|
|
symbol_instance: str
|
|
|
|
|
|
@dataclass
|
|
class MagicPlanXMLMainDimension:
|
|
from_point: int
|
|
to_point: int
|
|
dir_x: float
|
|
dir_y: float
|
|
value: float
|
|
actual_value: float
|
|
is_set: bool
|
|
|
|
|
|
@dataclass
|
|
class MagicPlanXMLExplodedWall:
|
|
wall_type: str
|
|
points: list[MagicPlanXMLWallPoint]
|
|
|
|
|
|
@dataclass
|
|
class MagicPlanXMLExploded:
|
|
walls: list[MagicPlanXMLExplodedWall]
|
|
doors: list[MagicPlanXMLExplodedOpening]
|
|
windows: list[MagicPlanXMLExplodedOpening]
|
|
furniture: list[MagicPlanXMLFurniture]
|
|
|
|
|
|
@dataclass
|
|
class MagicPlanXMLSymbolInstance:
|
|
id: str
|
|
uid: str
|
|
parent_uid: str
|
|
symbol: str
|
|
values: dict[str, str]
|
|
|
|
|
|
@dataclass
|
|
class MagicPlanXMLRoom:
|
|
uid: str
|
|
type: str
|
|
x: float
|
|
y: float
|
|
rotation: float
|
|
was_modified: bool
|
|
linked_room_0: str
|
|
linked_room_1: str
|
|
area: float
|
|
perimeter: float
|
|
values: dict[str, str]
|
|
points: list[MagicPlanXMLRoomPoint]
|
|
doors: list[MagicPlanXMLDoor]
|
|
windows: list[MagicPlanXMLWindow]
|
|
furniture: list[MagicPlanXMLFurniture]
|
|
main_dimensions: list[MagicPlanXMLMainDimension]
|
|
|
|
|
|
@dataclass
|
|
class MagicPlanXMLFloor:
|
|
uid: str
|
|
name: str
|
|
floor_type: str
|
|
rotation: float
|
|
compass_angle: float
|
|
area_without_walls: float
|
|
area_with_interior_walls_only: float
|
|
area_with_walls: float
|
|
symbol_instance: MagicPlanXMLSymbolInstance
|
|
rooms: list[MagicPlanXMLRoom]
|
|
furniture: list[MagicPlanXMLFurniture]
|
|
exploded: MagicPlanXMLExploded
|
|
|
|
|
|
@dataclass
|
|
class MagicPlanXMLSummary:
|
|
"""Plan metadata returned by the list-plans API endpoint (old string-address format)."""
|
|
|
|
id: str
|
|
project_id: str
|
|
name: str
|
|
address: str
|
|
creation_date: str
|
|
update_date: str
|
|
workgroup_id: str
|
|
team_id: str
|
|
created_by: str
|
|
thumbnail_url: str
|
|
public_url: str
|
|
cloud_url: str
|
|
url_3d: str
|
|
|
|
|
|
@dataclass
|
|
class MagicPlanXMLDetail:
|
|
"""Full plan response: summary metadata + parsed XML plan."""
|
|
|
|
summary: MagicPlanXMLSummary
|
|
plan_xml: "MagicPlanXMLPlan"
|
|
|
|
|
|
@dataclass
|
|
class MagicPlanXMLPlan:
|
|
id: str
|
|
uid: str
|
|
name: str
|
|
type: str
|
|
interior_wall_width: float
|
|
exterior_wall_width: float
|
|
schematic: bool
|
|
has_land_survey_address: bool
|
|
last_patch_identifier: str
|
|
last_roll_identifier: str
|
|
values: dict[str, str]
|
|
floors: list[MagicPlanXMLFloor]
|
|
interior_room_floors: list[MagicPlanXMLFloor]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# JSON dataclasses (sourced from GET Plan API JSON response)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@dataclass
|
|
class MagicPlanWall:
|
|
uid: str
|
|
symbol_id: str
|
|
length: float
|
|
fields: dict[str, str]
|
|
custom_fields: dict[str, str]
|
|
|
|
|
|
@dataclass
|
|
class MagicPlanWallItem:
|
|
"""Door or window — distinguished by symbol_id."""
|
|
|
|
uid: str
|
|
symbol_id: str
|
|
symbol_name: str
|
|
width: float
|
|
depth: float
|
|
height: float
|
|
pos_x: float
|
|
pos_y: float
|
|
pos_z: float
|
|
rotation_z: float
|
|
fields: dict[str, str]
|
|
custom_fields: dict[str, str]
|
|
|
|
|
|
@dataclass
|
|
class MagicPlanFurniture:
|
|
uid: str
|
|
symbol_id: str
|
|
symbol_name: str
|
|
width: float
|
|
depth: float
|
|
height: float
|
|
pos_x: float
|
|
pos_y: float
|
|
pos_z: float
|
|
rotation_z: float
|
|
fields: dict[str, str]
|
|
custom_fields: dict[str, str]
|
|
|
|
|
|
@dataclass
|
|
class MagicPlanRoom:
|
|
uid: str
|
|
name: str
|
|
area: float
|
|
perimeter: float
|
|
height: float
|
|
width: float
|
|
volume: float
|
|
dimensions: str
|
|
door_count: int
|
|
window_count: int
|
|
walls_surface: float
|
|
walls_surface_without_openings: float
|
|
doors_surface: float
|
|
windows_surface: float
|
|
wall_items: list[MagicPlanWallItem]
|
|
furnitures: list[MagicPlanFurniture]
|
|
walls: list[MagicPlanWall]
|
|
fields: dict[str, str]
|
|
custom_fields: dict[str, str]
|
|
|
|
|
|
@dataclass
|
|
class MagicPlanFloor:
|
|
uid: str
|
|
name: str
|
|
floor_type: str
|
|
area: float
|
|
perimeter: float
|
|
area_without_walls: float
|
|
area_with_interior_walls_only: float
|
|
area_with_walls: float
|
|
wall_count: int
|
|
wall_count_with_interior_walls: int
|
|
door_count: int
|
|
window_count: int
|
|
room_count: int
|
|
furniture_count: int
|
|
doors_surface: float
|
|
walls_surface: float
|
|
walls_surface_without_openings: float
|
|
windows_surface: float
|
|
volume: float
|
|
rooms: list[MagicPlanRoom]
|
|
furnitures: list[MagicPlanFurniture]
|
|
symbol_instances: list[MagicPlanFurniture]
|
|
fields: dict[str, str]
|
|
custom_fields: dict[str, str]
|
|
|
|
|
|
@dataclass
|
|
class MagicPlanPlan:
|
|
"""GET /plans/{id} — merged data.plan + data.plan_detail.plan."""
|
|
|
|
id: str
|
|
project_id: str
|
|
uid: str
|
|
name: str
|
|
area: float
|
|
perimeter: float
|
|
area_without_walls: float
|
|
area_with_interior_walls_only: float
|
|
area_with_walls: float
|
|
wall_count: int
|
|
door_count: int
|
|
window_count: int
|
|
room_count: int
|
|
furniture_count: int
|
|
floor_count: int
|
|
doors_surface: float
|
|
walls_surface: float
|
|
walls_surface_without_openings: float
|
|
windows_surface: float
|
|
volume: float
|
|
living_area: float
|
|
below_grade_living_area: float
|
|
above_grade_living_area: float
|
|
address_street: Optional[str]
|
|
address_postal_code: Optional[str]
|
|
address_city: Optional[str]
|
|
address_country: Optional[str]
|
|
address_longitude: Optional[float]
|
|
address_latitude: Optional[float]
|
|
creation_date: str
|
|
update_date: str
|
|
workgroup_id: str
|
|
team_id: str
|
|
created_by_id: str
|
|
created_by_firstname: Optional[str]
|
|
created_by_lastname: Optional[str]
|
|
created_by_email: str
|
|
thumbnail_url: str
|
|
public_url: str
|
|
cloud_url: str
|
|
url_3d: str
|
|
floors: list[MagicPlanFloor]
|
|
fields: dict[str, str]
|
|
custom_fields: dict[str, str]
|
|
|
|
|
|
@dataclass
|
|
class MagicPlanSummary:
|
|
"""GET /plans list — lightweight, flat address and creator fields."""
|
|
|
|
id: str
|
|
project_id: str
|
|
name: str
|
|
address_street: Optional[str]
|
|
address_postal_code: Optional[str]
|
|
address_city: Optional[str]
|
|
address_country: Optional[str]
|
|
address_longitude: Optional[float]
|
|
address_latitude: Optional[float]
|
|
creation_date: str
|
|
update_date: str
|
|
workgroup_id: str
|
|
team_id: str
|
|
created_by_id: str
|
|
created_by_firstname: Optional[str]
|
|
created_by_lastname: Optional[str]
|
|
created_by_email: str
|
|
thumbnail_url: str
|
|
public_url: str
|
|
cloud_url: str
|
|
url_3d: str
|