@@ -1337,6 +1337,311 @@ async def q10_set_fan_level(ctx: click.Context, device_id: str, level: str) -> N
13371337 click .echo (f"Error: { e } " )
13381338
13391339
1340+ # =============================================================================
1341+ # Map Editor Commands
1342+ # =============================================================================
1343+
1344+
1345+ @session .command ()
1346+ @click .option ("--device_id" , required = True , help = "Device ID" )
1347+ @click .option ("--room" , required = True , help = "Room name to split" )
1348+ @click .option ("--direction" , type = click .Choice (["vertical" , "horizontal" ]), default = "vertical" )
1349+ @click .option ("--ratio" , type = float , default = 0.5 , help = "Split position (0.0-1.0)" )
1350+ @click .pass_context
1351+ @async_command
1352+ async def split_room (ctx , device_id : str , room : str , direction : str , ratio : float ):
1353+ """Split a room into two segments."""
1354+ from roborock .map import (
1355+ CoordinateTransformer ,
1356+ SplitRoomEdit ,
1357+ TranslationLayer ,
1358+ VirtualState ,
1359+ calculate_split_line ,
1360+ )
1361+ from roborock .map .geometry import BoundingBox
1362+
1363+ context : RoborockContext = ctx .obj
1364+ device_manager = await context .get_device_manager ()
1365+ device = await device_manager .get_device (device_id )
1366+
1367+ if device .v1_properties is None :
1368+ click .echo ("Device does not support V1 protocol" )
1369+ return
1370+
1371+ # Get current map
1372+ map_trait = device .v1_properties .map_content
1373+ await map_trait .refresh ()
1374+
1375+ if not map_trait .map_data :
1376+ click .echo ("No map data available" )
1377+ return
1378+
1379+ map_data = map_trait .map_data
1380+
1381+ # Find room by name
1382+ target_room = None
1383+ for room_id , r in (map_data .rooms or {}).items ():
1384+ room_name = getattr (r , "name" , f"Room { room_id } " )
1385+ if room_name .lower () == room .lower ():
1386+ target_room = r
1387+ target_room_id = room_id
1388+ break
1389+
1390+ if target_room is None :
1391+ click .echo (f"Room '{ room } ' not found. Available rooms:" )
1392+ for room_id , r in (map_data .rooms or {}).items ():
1393+ room_name = getattr (r , "name" , f"Room { room_id } " )
1394+ click .echo (f" - { room_name } (ID: { room_id } )" )
1395+ return
1396+
1397+ # Create coordinate transformer
1398+ transformer = CoordinateTransformer .from_map_data (map_data )
1399+ if transformer is None :
1400+ click .echo ("Failed to create coordinate transformer" )
1401+ return
1402+
1403+ # Calculate split line
1404+ room_bbox = BoundingBox (
1405+ min_x = target_room .x0 ,
1406+ max_x = target_room .x1 ,
1407+ min_y = target_room .y0 ,
1408+ max_y = target_room .y1 ,
1409+ )
1410+ split_line = calculate_split_line (room_bbox , direction , ratio )
1411+
1412+ # Create virtual state and add edit
1413+ virtual_state = VirtualState (map_data , transformer )
1414+ edit = SplitRoomEdit (
1415+ segment_id = target_room_id ,
1416+ x1 = split_line .p1 .x ,
1417+ y1 = split_line .p1 .y ,
1418+ x2 = split_line .p2 .x ,
1419+ y2 = split_line .p2 .y ,
1420+ )
1421+
1422+ success , error = virtual_state .add_edit (edit )
1423+ if not success :
1424+ click .echo (f"Failed to create edit: { error } " )
1425+ return
1426+
1427+ click .echo (f"Created split edit for room '{ room } ':" )
1428+ click .echo (f" Line: ({ edit .x1 :.0f} , { edit .y1 :.0f} ) -> ({ edit .x2 :.0f} , { edit .y2 :.0f} )" )
1429+ click .echo (f" Edit ID: { edit .edit_id } " )
1430+
1431+ # Preview mode - don't execute yet
1432+ click .echo ("\n Use --apply flag to execute (not yet implemented)" )
1433+
1434+
1435+ @session .command ()
1436+ @click .option ("--device_id" , required = True , help = "Device ID" )
1437+ @click .option ("--rooms" , required = True , help = "Comma-separated room names to merge" )
1438+ @click .pass_context
1439+ @async_command
1440+ async def merge_rooms (ctx , device_id : str , rooms : str ):
1441+ """Merge multiple rooms into one."""
1442+ from roborock .map import (
1443+ CoordinateTransformer ,
1444+ MergeRoomsEdit ,
1445+ VirtualState ,
1446+ )
1447+
1448+ context : RoborockContext = ctx .obj
1449+ device_manager = await context .get_device_manager ()
1450+ device = await device_manager .get_device (device_id )
1451+
1452+ if device .v1_properties is None :
1453+ click .echo ("Device does not support V1 protocol" )
1454+ return
1455+
1456+ map_trait = device .v1_properties .map_content
1457+ await map_trait .refresh ()
1458+
1459+ if not map_trait .map_data :
1460+ click .echo ("No map data available" )
1461+ return
1462+
1463+ map_data = map_trait .map_data
1464+ room_names = [r .strip () for r in rooms .split ("," )]
1465+
1466+ # Find room IDs
1467+ segment_ids = []
1468+ for room_name in room_names :
1469+ found = False
1470+ for room_id , r in (map_data .rooms or {}).items ():
1471+ name = getattr (r , "name" , f"Room { room_id } " )
1472+ if name .lower () == room_name .lower ():
1473+ segment_ids .append (room_id )
1474+ found = True
1475+ break
1476+ if not found :
1477+ click .echo (f"Room '{ room_name } ' not found" )
1478+ return
1479+
1480+ transformer = CoordinateTransformer .from_map_data (map_data )
1481+ virtual_state = VirtualState (map_data , transformer )
1482+ edit = MergeRoomsEdit (segment_ids = segment_ids )
1483+
1484+ success , error = virtual_state .add_edit (edit )
1485+ if not success :
1486+ click .echo (f"Failed to create edit: { error } " )
1487+ return
1488+
1489+ click .echo (f"Created merge edit for rooms: { room_names } " )
1490+ click .echo (f" Segment IDs: { segment_ids } " )
1491+ click .echo (f" Edit ID: { edit .edit_id } " )
1492+
1493+
1494+ @session .command ()
1495+ @click .option ("--device_id" , required = True , help = "Device ID" )
1496+ @click .option ("--room" , required = True , help = "Room name" )
1497+ @click .option ("--new-name" , required = True , help = "New room name" )
1498+ @click .pass_context
1499+ @async_command
1500+ async def rename_room (ctx , device_id : str , room : str , new_name : str ):
1501+ """Rename a room."""
1502+ from roborock .map import (
1503+ CoordinateTransformer ,
1504+ RenameRoomEdit ,
1505+ VirtualState ,
1506+ )
1507+
1508+ context : RoborockContext = ctx .obj
1509+ device_manager = await context .get_device_manager ()
1510+ device = await device_manager .get_device (device_id )
1511+
1512+ if device .v1_properties is None :
1513+ click .echo ("Device does not support V1 protocol" )
1514+ return
1515+
1516+ map_trait = device .v1_properties .map_content
1517+ await map_trait .refresh ()
1518+
1519+ if not map_trait .map_data :
1520+ click .echo ("No map data available" )
1521+ return
1522+
1523+ map_data = map_trait .map_data
1524+
1525+ # Find room
1526+ target_room_id = None
1527+ old_name = None
1528+ for room_id , r in (map_data .rooms or {}).items ():
1529+ name = getattr (r , "name" , f"Room { room_id } " )
1530+ if name .lower () == room .lower ():
1531+ target_room_id = room_id
1532+ old_name = name
1533+ break
1534+
1535+ if target_room_id is None :
1536+ click .echo (f"Room '{ room } ' not found" )
1537+ return
1538+
1539+ transformer = CoordinateTransformer .from_map_data (map_data )
1540+ virtual_state = VirtualState (map_data , transformer )
1541+ edit = RenameRoomEdit (
1542+ segment_id = target_room_id ,
1543+ new_name = new_name ,
1544+ old_name = old_name or "" ,
1545+ )
1546+
1547+ success , error = virtual_state .add_edit (edit )
1548+ if not success :
1549+ click .echo (f"Failed to create edit: { error } " )
1550+ return
1551+
1552+ click .echo (f"Created rename edit: '{ old_name } ' -> '{ new_name } '" )
1553+
1554+
1555+ @session .command ()
1556+ @click .option ("--device_id" , required = True , help = "Device ID" )
1557+ @click .option ("--x1" , type = int , required = True , help = "Wall start X (mm)" )
1558+ @click .option ("--y1" , type = int , required = True , help = "Wall start Y (mm)" )
1559+ @click .option ("--x2" , type = int , required = True , help = "Wall end X (mm)" )
1560+ @click .option ("--y2" , type = int , required = True , help = "Wall end Y (mm)" )
1561+ @click .pass_context
1562+ @async_command
1563+ async def add_virtual_wall (ctx , device_id : str , x1 : int , y1 : int , x2 : int , y2 : int ):
1564+ """Add a virtual wall."""
1565+ from roborock .map import (
1566+ CoordinateTransformer ,
1567+ VirtualState ,
1568+ VirtualWallEdit ,
1569+ )
1570+
1571+ context : RoborockContext = ctx .obj
1572+ device_manager = await context .get_device_manager ()
1573+ device = await device_manager .get_device (device_id )
1574+
1575+ if device .v1_properties is None :
1576+ click .echo ("Device does not support V1 protocol" )
1577+ return
1578+
1579+ map_trait = device .v1_properties .map_content
1580+ await map_trait .refresh ()
1581+
1582+ if not map_trait .map_data :
1583+ click .echo ("No map data available" )
1584+ return
1585+
1586+ map_data = map_trait .map_data
1587+ transformer = CoordinateTransformer .from_map_data (map_data )
1588+ virtual_state = VirtualState (map_data , transformer )
1589+
1590+ edit = VirtualWallEdit (x1 = float (x1 ), y1 = float (y1 ), x2 = float (x2 ), y2 = float (y2 ))
1591+
1592+ success , error = virtual_state .add_edit (edit )
1593+ if not success :
1594+ click .echo (f"Failed to create edit: { error } " )
1595+ return
1596+
1597+ click .echo (f"Created virtual wall edit: ({ x1 } , { y1 } ) -> ({ x2 } , { y2 } )" )
1598+
1599+
1600+ @session .command ()
1601+ @click .option ("--device_id" , required = True , help = "Device ID" )
1602+ @click .option ("--x1" , type = int , required = True , help = "Zone min X (mm)" )
1603+ @click .option ("--y1" , type = int , required = True , help = "Zone min Y (mm)" )
1604+ @click .option ("--x2" , type = int , required = True , help = "Zone max X (mm)" )
1605+ @click .option ("--y2" , type = int , required = True , help = "Zone max Y (mm)" )
1606+ @click .pass_context
1607+ @async_command
1608+ async def add_no_go_zone (ctx , device_id : str , x1 : int , y1 : int , x2 : int , y2 : int ):
1609+ """Add a no-go zone."""
1610+ from roborock .map import (
1611+ CoordinateTransformer ,
1612+ NoGoZoneEdit ,
1613+ VirtualState ,
1614+ )
1615+
1616+ context : RoborockContext = ctx .obj
1617+ device_manager = await context .get_device_manager ()
1618+ device = await device_manager .get_device (device_id )
1619+
1620+ if device .v1_properties is None :
1621+ click .echo ("Device does not support V1 protocol" )
1622+ return
1623+
1624+ map_trait = device .v1_properties .map_content
1625+ await map_trait .refresh ()
1626+
1627+ if not map_trait .map_data :
1628+ click .echo ("No map data available" )
1629+ return
1630+
1631+ map_data = map_trait .map_data
1632+ transformer = CoordinateTransformer .from_map_data (map_data )
1633+ virtual_state = VirtualState (map_data , transformer )
1634+
1635+ edit = NoGoZoneEdit (x1 = float (x1 ), y1 = float (y1 ), x2 = float (x2 ), y2 = float (y2 ))
1636+
1637+ success , error = virtual_state .add_edit (edit )
1638+ if not success :
1639+ click .echo (f"Failed to create edit: { error } " )
1640+ return
1641+
1642+ click .echo (f"Created no-go zone edit: ({ x1 } , { y1 } ) -> ({ x2 } , { y2 } )" )
1643+
1644+
13401645def main ():
13411646 return cli ()
13421647
0 commit comments