diff --git a/backend/magic_plan/magic_plan_client.py b/backend/magic_plan/magic_plan_client.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/magic_plan/models.py b/backend/magic_plan/models.py new file mode 100644 index 00000000..46cf0fef --- /dev/null +++ b/backend/magic_plan/models.py @@ -0,0 +1,188 @@ +from dataclasses import dataclass +from typing import Optional + + +@dataclass +class MagicPlanRoomPoint: + snapped_x: float + snapped_y: float + height: float + uid: str + values: dict[str, str] # e.g. loadBearingWall, addElevationToReport + + +@dataclass +class MagicPlanWallPoint: + """Point in — absolute coords, no uid or values.""" + x: float + y: float + height: float + + +@dataclass +class MagicPlanDoor: + """Door in context — wall-relative position.""" + wall_point_index: int + type: str + u: float # position along wall (0-1) + width: float # m + depth: float # m + height: float # m + orientation: int + snapped_type: str + snapped_position: float + snapped_width: float # m + snapped_depth: float # m + snapped_height: float # m + snapped_orientation: int + inset_x: float + inset_y: float + inset_z: float + symbol_instance: str + twin_wall_item_uid: Optional[str] = None + + +@dataclass +class MagicPlanWindow: + """Window in context — wall-relative position.""" + wall_point_index: int + type: str + u: float # position along wall (0-1) + width: float # m + depth: float # m + height: float # m + orientation: int + snapped_type: str + snapped_position: float + snapped_width: float # m + snapped_depth: float # m + snapped_height: float # m + snapped_orientation: int + inset_x: float + inset_y: float + inset_z: float + symbol_instance: str + + +@dataclass +class MagicPlanExplodedOpening: + """Door or window in context — absolute coords, no snapped* fields.""" + type: str + x1: float + y1: float + x2: float + y2: float + width: float # m + depth: float # m + height: float # m + inset_x: float + inset_y: float + orientation: int + symbol_instance: str + + +@dataclass +class MagicPlanFurniture: + type: str + x: float + y: float + snapped_x: float + snapped_y: float + angle: float + width: float # m + depth: float # m + height: float # m + snapped_width: float # m + snapped_depth: float # m + snapped_height: float # m + size_lock_0: str + size_lock_1: str + size_lock_2: str + symbol_instance: str + + +@dataclass +class MagicPlanMainDimension: + from_point: int + to_point: int + dir_x: float + dir_y: float + value: float + actual_value: float + is_set: bool + + +@dataclass +class MagicPlanExplodedWall: + wall_type: str # "exterior" | "interior" + points: list[MagicPlanWallPoint] + + +@dataclass +class MagicPlanExploded: + walls: list[MagicPlanExplodedWall] + doors: list[MagicPlanExplodedOpening] + windows: list[MagicPlanExplodedOpening] + furniture: list[MagicPlanFurniture] + + +@dataclass +class MagicPlanSymbolInstance: + id: str + uid: str + parent_uid: str + symbol: str + values: dict[str, str] # ceilingHeight, width, height, depth, distanceUnit, etc. + + +@dataclass +class MagicPlanRoom: + uid: str + type: str + x: float + y: float + rotation: float + was_modified: bool + linked_room_0: str + linked_room_1: str + area: float # m² + perimeter: float # m + values: dict[str, str] # ceilingHeight, label, etc. + points: list[MagicPlanRoomPoint] + doors: list[MagicPlanDoor] + windows: list[MagicPlanWindow] + furniture: list[MagicPlanFurniture] + main_dimensions: list[MagicPlanMainDimension] + + +@dataclass +class MagicPlanFloor: + uid: str + name: str + floor_type: str # "0"=ground, "1"=upper, "2"=basement + rotation: float + compass_angle: float + area_without_walls: float # m² + area_with_interior_walls_only: float # m² + area_with_walls: float # m² + symbol_instance: MagicPlanSymbolInstance + rooms: list[MagicPlanRoom] + furniture: list[MagicPlanFurniture] # floor-level furniture (not inside any room) + exploded: MagicPlanExploded + + +@dataclass +class MagicPlanPlan: + id: str + uid: str + name: str + type: str + interior_wall_width: float # m + exterior_wall_width: float # m + schematic: bool + has_land_survey_address: bool + last_patch_identifier: str + last_roll_identifier: str + values: dict[str, str] # date, statistics.*, distanceUnit, etc. + floors: list[MagicPlanFloor] + interior_room_floors: list[MagicPlanFloor] # from diff --git a/backend/magic_plan/xml_example.xml b/backend/magic_plan/xml_example.xml new file mode 100644 index 00000000..28088fc8 --- /dev/null +++ b/backend/magic_plan/xml_example.xml @@ -0,0 +1,985 @@ + + + 2026-04-20 + 2.134 + 100 + 0 + 0 + + + Ground Floor + + + 2.323164 + + + + + doors + 2.084107 + + + + + doors + 2.02225 + + + + + doors + 1.124639 + 0.949396 + + + + + 0.397258 + 0.746826 + furniture + 1.279293 + 0.746826 + 0.677733 + + + + + 0.368615 + 0.723145 + furniture + 1.311519 + 0.723145 + 0.398439 + + + + + 0.604444 + 0.1 + furniture + 0.851311 + 0.1 + 0.601075 + + + + + furniture + + + + + appliances + + + + + appliances + + + + + appliances + + + + + hvac + 0.1500 + + + + + doors + 2.084107 + + + + + doors + 2.006357 + + + + + doors + 1.00597 + 1.026259 + + + + + appliances + + + + + furniture + + + + + hvac + 0.0000 + + + + + hvac + 0.1500 + + + + + hvac + 0.1500 + + + + + 2.323164 + + + + + + + + + + + + + + + + + + + + + + 3.285283 + + + + + + + + + + + + + + + + + + + + + + + + + exterior + + + + + exterior + + + + + exterior + + + + + exterior + + + + + exterior + + + + + exterior + + + + + interior + + + + + exterior + + + + + exterior + + + + + exterior + + + + + exterior + + + + + + + + + + + + + + + + + + + + + + + 1st Floor + + + 2.382467 + + + + + doors + 2.074757 + + + + + plumbing + 1.5000 + + + + + doors + 2.081704 + + + + + doors + 1.227592 + 0.88319 + + + + + hvac + 0.1500 + + + + + 677d01685458a + + + + + doors + 2.081704 + + + + + doors + 2.074757 + + + + + doors + 2.195494 + + + + + doors + 2.195494 + + + + + doors + 0.915306 + 1.180143 + + + + + 0.1 + 0.676758 + appliances + 1.399472 + 0.676758 + 1.253952 + + + + + hvac + 0.1500 + + + + + 2.382467 + + + + + + + + + + + + + 2.382467 + + + + + + + + + + + + + + + 2.382467 + + + + + + + + + + + + + + 2.382467 + + + + + + + + + + + + + + + + + + + exterior + + + + + exterior + + + + + exterior + + + + + exterior + + + + + interior + + + + + interior + + + + + interior + + + + + interior + + + + + exterior + + + + + exterior + + + + + exterior + + + + + exterior + + + + + exterior + + + + + interior + + + + + exterior + + + + + + + + + + + + + + + + Ground Floor + + + 2.323164 + + + + + doors + 2.084107 + + + + + doors + 2.02225 + + + + + doors + 1.124639 + 0.949396 + + + + + 0.397258 + 0.746826 + furniture + 1.279293 + 0.746826 + 0.677733 + + + + + 0.368615 + 0.723145 + furniture + 1.311519 + 0.723145 + 0.398439 + + + + + 0.604444 + 0.1 + furniture + 0.851311 + 0.1 + 0.601075 + + + + + furniture + + + + + appliances + + + + + appliances + + + + + appliances + + + + + hvac + 0.1500 + + + + + doors + 2.084107 + + + + + doors + 2.006357 + + + + + doors + 1.00597 + 1.026259 + + + + + appliances + + + + + furniture + + + + + hvac + 0.0000 + + + + + hvac + 0.1500 + + + + + hvac + 0.1500 + + + + + 2.323164 + + + + + + + + + + + + + + + + + + + + + + 3.285283 + + + + + + + + + + + + + + + + + + + + + + + + + exterior + + + + + exterior + + + + + exterior + + + + + exterior + + + + + exterior + + + + + exterior + + + + + interior + + + + + exterior + + + + + exterior + + + + + exterior + + + + + exterior + + + + + + + + + + + + + + + + + + + + + + + 1st Floor + + + 2.382467 + + + + + doors + 2.074757 + + + + + plumbing + 1.5000 + + + + + doors + 2.081704 + + + + + doors + 1.227592 + 0.88319 + + + + + hvac + 0.1500 + + + + + 677d01685458a + + + + + doors + 2.081704 + + + + + doors + 2.074757 + + + + + doors + 2.195494 + + + + + doors + 2.195494 + + + + + doors + 0.915306 + 1.180143 + + + + + 0.1 + 0.676758 + appliances + 1.399472 + 0.676758 + 1.253952 + + + + + hvac + 0.1500 + + + + + 2.382467 + + + + + + + + + + + + + 2.382467 + + + + + + + + + + + + + + + 2.382467 + + + + + + + + + + + + + + 2.382467 + + + + + + + + + + + + + + + + + + + exterior + + + + + exterior + + + + + exterior + + + + + exterior + + + + + interior + + + + + interior + + + + + interior + + + + + interior + + + + + exterior + + + + + exterior + + + + + exterior + + + + + exterior + + + + + exterior + + + + + interior + + + + + exterior + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/backend/magic_plan/xml_example_2.xml b/backend/magic_plan/xml_example_2.xml new file mode 100644 index 00000000..b4014e86 --- /dev/null +++ b/backend/magic_plan/xml_example_2.xml @@ -0,0 +1 @@ +"\n2026-04-162.13410000Ground Floor2.423333doors2.095132doors2.116748doors2.07892doors1.0413441.129445appliancesappliancesdoors1.868133doors0.8742110.939139doors0.7959061.0349912.4233332.415RoomexteriorexteriorexteriorexteriorexteriorexteriorexteriorexteriorexteriorexteriorexteriorexteriorGround Floor2.423333doors2.095132doors2.116748doors2.07892doors1.0413441.129445appliancesappliancesdoors1.868133doors0.8742110.939139doors0.7959061.0349912.4233332.415Roomexteriorexteriorexteriorexteriorexteriorexteriorexteriorexteriorexteriorexteriorexteriorexterior\n" \ No newline at end of file diff --git a/backend/magic_plan/xml_example_3.xml b/backend/magic_plan/xml_example_3.xml new file mode 100644 index 00000000..22dcd696 --- /dev/null +++ b/backend/magic_plan/xml_example_3.xml @@ -0,0 +1 @@ +"\n2026-04-162.13410000Ground Floor2.366039677d01685458am1mm2doorsm32.046953m1mm2doorsm32.049024mmm2doorsm32.08851m1mm2doorsm31.998668m1mm2doorsm32.099428m1mm2doorsm32.086379m1mm2doorsm32.118055m1mm2doorsm32.123336m1mm2doorsm31.986039677d01685458a677d01685458ammm2doorsm32.08851m1mm2doorsm30.9267931.19895677d01685458ammm2hvacm30.0000m1mm2doorsm31.998668m1mm2doorsm30.9400381.190655677d01685458ammm2hvacm30.0000m1mm2doorsm32.020562m1mm2doorsm32.086379677d01685458adoors2.01773m1mm2doorsm32.020562m1mm2doorsm32.004822677d01685458adoors2.01773m1mm2doorsm32.046953m1mm2doorsm30.8718441.200662677d01685458ammm2hvacm30.0000m1mm2doorsm32.099428m1mm2doorsm31.1390810.932646677d01685458a677d01685458am1mm2doorsm32.118055m1mm2doorsm32.123336677d01685458am1mm2doorsm32.049024m1mm2doorsm31.0796020.985677677d01685458ammm2hvacm30.0000677d01685458a2.3660392.366039112.366039112.3660392.3660392.3660392.3660392.3660392.366039exteriorexteriorexteriorexteriorexteriorexteriorexteriorinteriorinteriorinteriorinteriorinteriorexteriorexteriorexteriorexteriorexteriorexteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorGround Floor2.366039677d01685458am1mm2doorsm32.046953m1mm2doorsm32.049024mmm2doorsm32.08851m1mm2doorsm31.998668m1mm2doorsm32.099428m1mm2doorsm32.086379m1mm2doorsm32.118055m1mm2doorsm32.123336m1mm2doorsm31.986039677d01685458a677d01685458ammm2doorsm32.08851m1mm2doorsm30.9267931.19895677d01685458ammm2hvacm30.0000m1mm2doorsm31.998668m1mm2doorsm30.9400381.190655677d01685458ammm2hvacm30.0000m1mm2doorsm32.020562m1mm2doorsm32.086379677d01685458adoors2.01773m1mm2doorsm32.020562m1mm2doorsm32.004822677d01685458adoors2.01773m1mm2doorsm32.046953m1mm2doorsm30.8718441.200662677d01685458ammm2hvacm30.0000m1mm2doorsm32.099428m1mm2doorsm31.1390810.932646677d01685458a677d01685458am1mm2doorsm32.118055m1mm2doorsm32.123336677d01685458am1mm2doorsm32.049024m1mm2doorsm31.0796020.985677677d01685458ammm2hvacm30.0000677d01685458a2.3660392.366039112.366039112.3660392.3660392.3660392.3660392.3660392.366039exteriorexteriorexteriorexteriorexteriorexteriorexteriorinteriorinteriorinteriorinteriorinteriorexteriorexteriorexteriorexteriorexteriorexteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinteriorinterior\n" \ No newline at end of file diff --git a/backend/magic_plan/xml_schema.py b/backend/magic_plan/xml_schema.py new file mode 100644 index 00000000..add2292b --- /dev/null +++ b/backend/magic_plan/xml_schema.py @@ -0,0 +1,48 @@ +# MagicPlan Exchange XML Schema Reference +# Derived from xml_example.xml and https://apidocs.magicplan.app/guide/basic-concepts/plan-exchange-xml-format +# +# +# attrs: name, id, uid, type, interiorWallWidth (m), exteriorWallWidth (m), +# schematic, hasLandSurveyAddress, lastPatchIdentifier, lastRollIdentifier +# children: , +, +# +# (plan-level metadata) +# children: text +# known keys: date, statistics.areaOfHeight, statistics.basement.account, +# statistics.exteriorWalls, statistics.interiorWalls +# +# +# attrs: uid, floorType (0=ground 1=upper), rotation, compassAngle, +# areaWithoutWalls (m²), areaWithInteriorWallsOnly (m²), areaWithWalls (m²) +# children: , , +, +# +# +# attrs: type (room label e.g. "Kitchen"), uid, x, y, rotation, +# wasModified, linkedRoom0, linkedRoom1, area (m²), perimeter (m) +# children: (key: ceilingHeight m), +, *, *, *, * +# +# (room corner polygon) +# attrs: snappedX (m), snappedY (m), height (m), uid +# +# +# attrs: point, type, u, width (m), depth (m), height (m), orientation, +# snappedType, snappedPosition, snappedWidth, snappedDepth, snappedHeight, snappedOrientation, +# insetX, insetY, insetZ, twinWallItemUid, symbolInstance +# +# +# attrs: point, type, u, width (m), depth (m), height (m), orientation, +# snappedType, snappedPosition, snappedWidth, snappedDepth, snappedHeight, snappedOrientation, +# insetX, insetY, insetZ, symbolInstance +# +# (wall geometry per floor) +# children: , , , +# children: , (text: "exterior" | "interior") +# +# +# children: (same structure as top-level floor, interior wall room shapes) +# +# All distances in metres. All areas in m². + +FLOOR_TYPE_GROUND = "0" +FLOOR_TYPE_UPPER = "1" +FLOOR_TYPE_BASEMENT = "2"