Initial commit

This commit is contained in:
2026-07-13 09:25:40 +07:00
parent c08ff54676
commit bccfb156d7
1938 changed files with 641646 additions and 0 deletions

View File

@@ -0,0 +1,152 @@
# Database Design / Thiết kế Database
## 📋 Overview / Tổng quan
MapEditor sử dụng normalized relational schema để lưu trữ map data thay vì JSON blob.
## 🎯 Design Philosophy / Triết lý Thiết kế
### ❌ Anti-pattern (Not Used)
```
Maps table:
- id
- name
- vdma_lif_json TEXT <-- Store entire JSON blob
```
**Problems với JSON blob approach**:
- Cannot query specific elements
- No foreign key constraints
- Poor performance for complex queries
- Cannot index nested data
### ✅ Our Approach - Normalized Relational Schema
**Benefits**:
- Query any element directly
- Foreign keys ensure data integrity
- Efficient indexes
- Easy to join với robot positions, orders, analytics
## 📊 Entity Relationship Diagram
```mermaid
erDiagram
Maps ||--o{ Stations : contains
Maps ||--o{ Edges : contains
Maps ||--o{ Zones : contains
Maps ||--o{ VehicleTypes : defines
Stations ||--o{ InteractionNodes : has
Stations ||--o{ Edges : "start from"
Stations ||--o{ Edges : "end at"
InteractionNodes ||--o{ Actions : contains
Maps {
uuid id PK
string layoutId UK "VDMA LIF layoutId"
string layoutName
string layoutVersion
int layoutLevel "Floor number"
float referenceX "Origin X"
float referenceY "Origin Y"
}
Stations {
uuid id PK
uuid mapId FK
string stationId UK "VDMA LIF stationId"
string stationType
float positionX
float positionY
float positionTheta
}
InteractionNodes {
uuid id PK
uuid stationId FK
string interactionNodeId UK
float positionX
float positionY
float positionTheta
string vehicleTypeIds "JSON array"
}
Actions {
uuid id PK
uuid interactionNodeId FK
string actionType
string blockingType
string actionParameters "JSON"
}
Edges {
uuid id PK
uuid mapId FK
string edgeId UK
string startStationId FK
string endStationId FK
string trajectory "JSON NURBS"
float maxSpeed
boolean bidirectional
}
Zones {
uuid id PK
uuid mapId FK
string zoneId UK
string zoneType
string geometry "JSON polygon"
}
VehicleTypes {
uuid id PK
uuid mapId FK
string vehicleTypeId UK
float vehicleLength
float vehicleWidth
}
```
## 🔑 Key Design Decisions
**1. Dual ID System**:
- `id` (UUID): Database primary key
- `{entity}Id` (String): VDMA LIF identifier, business key
**2. Position Decomposition**:
- Store as separate columns: `positionX`, `positionY`, `positionTheta`
- Enable spatial queries và indexing
**3. Trajectory as JSON**:
- Store NURBS trajectory as JSON string
- Complex structure, rarely queried independently
**4. Vehicle Type IDs as JSON Array**:
- Store vehicleTypeIds as JSON array
- Typically small arrays, loaded together with edge/node
**5. Actions Hierarchy**:
- Actions belong to InteractionNodes (not Stations directly)
- Follows VDMA LIF structure exactly
## 📈 Indexes for Performance
**Critical Indexes**:
- Maps: layoutId (unique)
- Stations: mapId, stationId, stationType, (positionX, positionY)
- Edges: mapId, edgeId, startStationId, endStationId
- InteractionNodes: stationId, interactionNodeId
## 🔗 Related Documents / Tài liệu Liên quan
- [MapEditor Overview](README.md) - Tổng quan MapEditor
- [VDMA LIF Standard](VDMA_LIF_Standard.md) - Chuẩn VDMA LIF
- [PathFinding](PathFinding.md) - Sử dụng database để pathfinding
---
**Last Updated**: 2025-11-13

View File

@@ -0,0 +1,67 @@
# Design Rationale / Lý do Thiết kế
## 📋 Overview / Tổng quan
Tài liệu này giải thích các quyết định thiết kế quan trọng của MapEditor.
## 🎯 Why VDMA LIF Standard?
| Rationale | Explanation |
|-----------|-------------|
| **Industry Standard** | Widely adopted trong EU logistics and manufacturing |
| **Interoperability** | Exchange maps với AutoCAD, other fleet systems |
| **Future-proof** | Active standard với ongoing development |
| **Tool Support** | CAD tools can export VDMA LIF |
| **Comprehensive** | Covers all requirements |
| **Open Specification** | Publicly available, không vendor lock-in |
## 🎨 Why Blazor WASM + SVG Canvas?
| Rationale | Explanation |
|-----------|-------------|
| **No Plugins** | Runs trong any modern browser |
| **C# on Client** | Share code với server |
| **SVG Native** | Scalable vector graphics, perfect for maps |
| **WASM Performance** | Near-native speed |
| **Interactive** | Easy event handling |
| **Accessibility** | SVG elements accessible |
| **Export Quality** | SVG can be exported to PDF, PNG |
## 💾 Why Normalized Database Schema?
| Rationale | Explanation |
|-----------|-------------|
| **Query Flexibility** | Find all charging stations easily |
| **Data Integrity** | Foreign keys prevent orphaned data |
| **Performance** | Indexes optimize queries |
| **Maintenance** | Update individual stations easily |
| **Analytics** | Join với robot positions, metrics |
| **Scalability** | Large maps still performant |
## 🔍 Why PathFinding Integration?
| Rationale | Explanation |
|-----------|-------------|
| **Validation** | Ensure routes exist before dispatch |
| **Optimization** | Find shortest/fastest path |
| **Conflict Avoidance** | Calculate alternative routes |
| **Map Quality** | Detect connectivity issues |
| **User Feedback** | Show estimated time and distance |
**Algorithm Choice - A***:
- Optimal: Guaranteed shortest path
- Efficient: Heuristic guides search
- Flexible: Adjustable cost function
- Standard: Well-known algorithm
## 🔗 Related Documents / Tài liệu Liên quan
- [MapEditor Overview](README.md) - Tổng quan MapEditor
- [VDMA LIF Standard](VDMA_LIF_Standard.md) - Chuẩn VDMA LIF
- [Database Design](Database_Design.md) - Database schema rationale
- [PathFinding](PathFinding.md) - PathFinding rationale
---
**Last Updated**: 2025-11-13

View File

@@ -0,0 +1,106 @@
# Import/Export Workflow / Quy trình Import/Export
## 📋 Overview / Tổng quan
MapEditor hỗ trợ import và export VDMA LIF JSON format để trao đổi map data với các hệ thống khác.
## 📥 Import Process / Quy trình Import
```mermaid
flowchart TD
Start([User Upload<br/>VDMA LIF JSON]) --> Parse[Parse JSON<br/>Deserialize to models]
Parse --> ValidFormat{Valid JSON<br/>Structure?}
ValidFormat -->|No| ErrorFormat[Error: Invalid JSON<br/>Abort import]
ValidFormat -->|Yes| ValidSchema{Valid VDMA LIF<br/>Schema?}
ValidSchema -->|No| ErrorSchema[Error: Schema mismatch<br/>Abort import]
ValidSchema -->|Yes| ValidRefs{Valid<br/>References?}
ValidRefs -->|No| ErrorRefs[Error: Broken references<br/>Abort import]
ValidRefs -->|Yes| Transaction[Begin Database Transaction]
Transaction --> SaveMap[Create Map Entity]
SaveMap --> SaveVTypes[Create VehicleTypes]
SaveVTypes --> SaveStations[Create Stations]
SaveStations --> SaveINodes[Create InteractionNodes]
SaveINodes --> SaveActions[Create Actions]
SaveActions --> SaveEdges[Create Edges]
SaveEdges --> SaveZones[Create Zones]
SaveZones --> ValidateMap[Validate Map<br/>Connectivity check]
ValidateMap --> ValidCheck{Validation<br/>Passed?}
ValidCheck -->|No| Rollback[Rollback Transaction<br/>Abort import]
ValidCheck -->|Yes| Commit[Commit Transaction]
Commit --> Success([Import Complete<br/>Return Map ID])
style Start fill:#e6ffe6
style Success fill:#e6ffe6
style ErrorFormat fill:#ffe6e6
style Rollback fill:#ffe6e6
```
## 📤 Export Process / Quy trình Export
```mermaid
flowchart TD
Start([User Click Export<br/>Select Map ID]) --> LoadMap[Load Map Entity]
LoadMap --> LoadRelated[Load Related Entities<br/>Parallel queries]
LoadRelated --> LoadVTypes[Query VehicleTypes]
LoadRelated --> LoadStations[Query Stations]
LoadRelated --> LoadEdges[Query Edges]
LoadRelated --> LoadZones[Query Zones]
LoadStations --> LoadINodes[Query InteractionNodes]
LoadINodes --> LoadActions[Query Actions]
LoadVTypes --> Transform[Transform to VDMA LIF Models]
LoadActions --> Transform
LoadEdges --> Transform
LoadZones --> Transform
Transform --> Build[Build VDMA LIF Structure<br/>metaInformation, layout, arrays]
Build --> Serialize[Serialize to JSON<br/>camelCase, pretty print]
Serialize --> Validate{Valid VDMA LIF<br/>Output?}
Validate -->|No| ErrorExport[Internal Error]
Validate -->|Yes| Download[Generate Download<br/>Filename: mapId.json]
Download --> Success([Export Complete])
style Start fill:#e6ffe6
style Success fill:#e6ffe6
style ErrorExport fill:#ffe6e6
```
## ✅ Data Integrity Guarantees
**Import Validations**:
1. JSON Syntax: Valid JSON format
2. Schema Compliance: Required fields present
3. Reference Integrity: Edges reference existing stations
4. Geometric Validity: Positions, trajectories valid
5. Unique Constraints: No duplicate IDs
**Export Guarantees**:
1. Completeness: All related entities included
2. Format Compliance: Valid VDMA LIF schema
3. Reference Resolution: All IDs properly mapped
4. Transaction Safety: All-or-nothing import
## 🔗 Related Documents / Tài liệu Liên quan
- [MapEditor Overview](README.md) - Tổng quan MapEditor
- [VDMA LIF Standard](VDMA_LIF_Standard.md) - Format specification
- [Database Design](Database_Design.md) - Database schema
---
**Last Updated**: 2025-11-13

View File

@@ -0,0 +1,100 @@
# PathFinding Architecture / Kiến trúc Tìm đường
## 📋 Overview / Tổng quan
MapEditor tích hợp A* algorithm để tính toán routes giữa các stations trên map.
## 🔍 A* Algorithm Workflow
```mermaid
graph TD
Start([PathFinding Request<br/>startStationId<br/>endStationId<br/>vehicleTypeId]) --> LoadData[Load Graph Data<br/>Query Stations table<br/>Query Edges table]
LoadData --> FilterVehicle{Vehicle Type<br/>Filtering?}
FilterVehicle -->|Yes| FilterEdges[Filter Edges<br/>vehicleTypeIds contains type]
FilterVehicle -->|No| BuildGraph
FilterEdges --> BuildGraph[Build Graph Structure<br/>Adjacency list]
BuildGraph --> InitAStar[Initialize A*<br/>openSet, closedSet<br/>gScore, fScore]
InitAStar --> Loop{Open Set<br/>Not Empty?}
Loop -->|No| NoPath([No Path Found])
Loop -->|Yes| Current[Dequeue lowest fScore]
Current --> CheckGoal{current ==<br/>endStation?}
CheckGoal -->|Yes| Reconstruct[Reconstruct Path<br/>Backtrack via cameFrom]
CheckGoal -->|No| Expand[Expand Neighbors]
Reconstruct --> Result([Return Path<br/>edgeIds[], stationIds[]<br/>totalDistance, estimatedTime])
Expand --> CalcG[Calculate gScore]
CalcG --> CheckBetter{gScore better?}
CheckBetter -->|Yes| Update[Update scores<br/>Add to openSet]
Update --> Loop
style Start fill:#e6ffe6
style Result fill:#e6ffe6
style NoPath fill:#ffe6e6
```
## 📊 Graph Representation
**Adjacency List Structure**:
- Nodes Map: stationId → Station object
- Edges Map: edgeId → Edge object
- Adjacency List: stationId → List of Edge
## 🎯 Heuristic Function
**Euclidean Distance**:
```
h(station, goal) = sqrt((goal.x - station.x)² + (goal.y - station.y)²)
```
**Properties**:
- Admissible: Never overestimates
- Consistent: Satisfies triangle inequality
- Guarantees: Optimal path với A*
## ⚖️ Edge Weight Calculation
**Weight = Travel Time**:
```
weight(edge) = edge.length / edge.maxSpeed
```
**Rationale**: Optimize for fastest path (considers both distance AND speed limits)
## 🚗 Vehicle Type Filtering
**Filter Logic**:
- Edge có vehicleTypeIds empty/null → Allow all vehicles
- Edge có vehicleTypeIds → Check if contains requestedType
- If match → Include in graph
- If no match → Exclude from graph
## ✅ Path Validation
**Validation Steps**:
1. Connectivity: Path exists?
2. Edge Sequence: Edges connect properly?
3. Speed Limits: Robot capabilities compatible?
4. Orientation: Robot can achieve required orientations?
**Return Path Object**:
- edgeIds: string[]
- stationIds: string[]
- totalDistance: float
- estimatedTime: float
- validationStatus: Valid | Warning | Invalid
## 🔗 Related Documents / Tài liệu Liên quan
- [MapEditor Overview](README.md) - Tổng quan MapEditor
- [Database Design](Database_Design.md) - Graph data từ database
- [VDA 5050 Integration](VDA5050_Integration.md) - Convert path to VDA 5050 order
---
**Last Updated**: 2025-11-13

303
docs/MapEditor/README.md Normal file
View File

@@ -0,0 +1,303 @@
# MapEditor Documentation / Tài liệu MapEditor
## 📋 Overview / Tổng quan
**MapEditor** là shared library cung cấp công cụ web-based để tạo, chỉnh sửa và quản lý bản đồ nhà máy theo chuẩn VDMA LIF (Layout Interchange Format). Module này được sử dụng bởi **FleetManager** để định nghĩa không gian hoạt động của robot AMR.
## 🎯 Problem & Solution / Vấn đề & Giải pháp
### 🔴 Challenges / Thách thức
**Trong môi trường nhà máy thực tế**:
- Bản đồ nhà máy phức tạp với hàng trăm điểm (stations, nodes, edges)
- Cần import/export data từ nhiều nguồn khác nhau (CAD tools, other fleet systems)
- Operators không phải programmers, cần giao diện trực quan
- Phải tuân thủ chuẩn VDMA LIF để tích hợp với hệ thống khác
- Database cần normalized schema để query hiệu quả (không lưu JSON blob)
- PathFinding để validate routes và generate VDA 5050 orders
- Multi-map support cho nhiều tầng, nhiều khu vực
### ✅ MapEditor Solution / Giải pháp MapEditor
**Visual Editor**: Blazor WASM + SVG canvas
- Vẽ và chỉnh sửa map objects trực quan (drag, drop, resize)
- Pan, zoom, layer management
- Real-time validation với visual feedback
**VDMA LIF Standard**: Import/export JSON format
- Tuân thủ VDMA 40499-1 và 40499-2 specifications
- Interoperability với CAD tools và other fleet systems
- Complete map data: stations, edges, zones, vehicle types
**Normalized Database**: PostgreSQL với relational schema
- Separate tables cho Maps, Stations, InteractionNodes, Edges, Actions
- Foreign key constraints đảm bảo data integrity
- Efficient queries (find all charging stations, edges by speed limit)
- No JSON blob storage (except for complex nested data như trajectory)
**PathFinding Integration**: A* algorithm
- Validate route existence trước khi dispatch missions
- Calculate shortest/fastest paths
- Support bidirectional và unidirectional edges
- Consider vehicle type compatibility
**VDA 5050 Integration**: Generate Order messages
- Convert map data (stations, edges) thành VDA 5050 nodes và edges
- Include actions từ stations vào order
- Apply vehicle type filtering
### 🎪 Use Cases / Trường hợp Sử dụng
**Initial Setup**:
- Import VDMA LIF JSON từ AutoCAD hoặc design tool
- Visual editing để adjust positions, thêm bớt elements
- Define stations (pickup, dropoff, charging, parking)
- Configure edges (paths, speed limits, directions)
- Define zones (restricted areas, slow-speed zones)
- Set up vehicle types (dimensions, envelopes)
**Operations** (FleetManager):
- Query database để tạo VDA 5050 Orders cho robots
- PathFinding service validate routes trước khi dispatch
- Operators view map trên dashboard với real-time robot positions
- Export VDMA LIF để backup hoặc share với other systems
**Maintenance**:
- Update map khi factory layout thay đổi
- Add/remove stations khi production changes
- Adjust edge configurations (speed limits, orientations)
- Manage multiple map versions (version control)
## 🏗️ System Architecture / Kiến trúc Hệ thống
### Component Overview / Tổng quan Thành phần
```mermaid
graph TB
subgraph Browser["🌐 Browser - Blazor WASM"]
UI[MapEditor UI<br/>Map list, toolbox<br/>Property inspector]
Canvas[SVG Canvas<br/>Visual rendering<br/>Interactive editing]
ImportExport[Import/Export UI<br/>File upload/download<br/>Format validation]
end
subgraph Server["🖥️ Server - ASP.NET Core API"]
MapAPI[Map Management API<br/>CRUD operations<br/>Validation endpoints]
Parser[VDMA LIF Parser<br/>JSON ↔ Domain Models<br/>Schema validation]
PathFinder[PathFinding Service<br/>A* algorithm<br/>Route validation<br/>Cost calculation]
VDA5050Gen[VDA 5050 Generator<br/>Map → Order nodes<br/>Map → Order edges<br/>Action mapping]
Validator[Map Validator<br/>Connectivity check<br/>Reference integrity<br/>Geometric validation]
end
subgraph Database["💾 PostgreSQL Database"]
direction TB
Maps[(Maps<br/>Layout metadata)]
Stations[(Stations<br/>Physical locations)]
INodes[(InteractionNodes<br/>Approach points)]
Edges[(Edges<br/>Navigation paths)]
Actions[(Actions<br/>Robot behaviors)]
Zones[(Zones<br/>Special areas)]
VTypes[(VehicleTypes<br/>Robot specs)]
end
UI --> Canvas
UI --> ImportExport
Canvas -->|REST API| MapAPI
ImportExport -->|Upload JSON| Parser
MapAPI <--> Parser
MapAPI <--> PathFinder
MapAPI <--> VDA5050Gen
MapAPI <--> Validator
MapAPI <--> Maps
MapAPI <--> Stations
MapAPI <--> INodes
MapAPI <--> Edges
MapAPI <--> Actions
MapAPI <--> Zones
MapAPI <--> VTypes
PathFinder --> Stations
PathFinder --> Edges
VDA5050Gen --> Stations
VDA5050Gen --> INodes
VDA5050Gen --> Edges
VDA5050Gen --> Actions
style Browser fill:#e6f3ff
style Server fill:#fff0e6
style Database fill:#e6ffe6
```
### Data Flow / Luồng Dữ liệu
**Import Flow** (VDMA LIF JSON → Database):
```mermaid
sequenceDiagram
participant User
participant UI
participant Parser
participant Validator
participant DB
User->>UI: Upload VDMA LIF JSON
UI->>Parser: Parse JSON
Parser->>Parser: Deserialize to models
Parser->>Validator: Validate structure
alt Invalid format
Validator-->>UI: Return errors
UI-->>User: Show validation errors
else Valid format
Validator->>DB: Begin transaction
Validator->>DB: Insert Map entity
Validator->>DB: Insert Stations
Validator->>DB: Insert InteractionNodes
Validator->>DB: Insert Edges
Validator->>DB: Insert Actions
Validator->>DB: Insert Zones
Validator->>DB: Insert VehicleTypes
Validator->>DB: Commit transaction
DB-->>UI: Return Map ID
UI-->>User: Show success, open editor
end
```
**Export Flow** (Database → VDMA LIF JSON):
```mermaid
sequenceDiagram
participant User
participant UI
participant MapAPI
participant DB
participant Parser
User->>UI: Click Export Map
UI->>MapAPI: GET /api/maps/{id}/export
MapAPI->>DB: Load Map + related entities
DB-->>MapAPI: Map, Stations, Nodes, Edges, etc.
MapAPI->>Parser: Convert to VDMA LIF models
Parser->>Parser: Build JSON structure
Parser->>Parser: Serialize with camelCase
Parser-->>UI: VDMA LIF JSON string
UI->>User: Download file
```
**Order Generation Flow** (Map → VDA 5050 Order):
```mermaid
sequenceDiagram
participant FleetMgr as FleetManager
participant PathFinder
participant Generator as VDA5050 Generator
participant DB
FleetMgr->>PathFinder: FindPath(startStation, endStation)
PathFinder->>DB: Load Stations & Edges
DB-->>PathFinder: Station list, Edge list
PathFinder->>PathFinder: Run A* algorithm
PathFinder-->>FleetMgr: Route (station IDs, edge IDs)
FleetMgr->>Generator: GenerateOrder(route, vehicleType)
Generator->>DB: Load InteractionNodes for stations
Generator->>DB: Load Actions for nodes
DB-->>Generator: Nodes + Actions
Generator->>Generator: Map to VDA 5050 format
Generator-->>FleetMgr: VDA 5050 Order message
FleetMgr->>FleetMgr: Send Order to robot via MQTT
```
## 📚 Cấu trúc Tài liệu / Documentation Structure
Tài liệu MapEditor được tổ chức thành các module riêng biệt để dễ dàng tra cứu và bảo trì:
```
docs/MapEditor/
├── README.md # File này - Tổng quan MapEditor
├── VDMA_LIF_Standard.md # Chuẩn VDMA LIF
├── Database_Design.md # Thiết kế Database
├── SVG_Canvas.md # Kiến trúc Canvas SVG
├── PathFinding.md # Kiến trúc PathFinding
├── ImportExport.md # Quy trình Import/Export
├── VDA5050_Integration.md # Tích hợp VDA 5050
└── Design_Rationale.md # Lý do Thiết kế
```
## 📐 [VDMA LIF Standard](VDMA_LIF_Standard.md) - Chuẩn VDMA LIF
VDMA LIF (Layout Interchange Format) là chuẩn quốc tế để mô tả factory layout cho AGV/AMR systems.
📖 **[Xem chi tiết →](VDMA_LIF_Standard.md)**
## 🗄️ [Database Design](Database_Design.md) - Thiết kế Database
MapEditor sử dụng normalized relational schema thay vì JSON blob để đảm bảo query flexibility và data integrity.
📖 **[Xem chi tiết →](Database_Design.md)**
## 🎨 [SVG Canvas Architecture](SVG_Canvas.md) - Kiến trúc Canvas SVG
MapEditor sử dụng SVG canvas với Blazor WASM để render và edit maps với interactive features.
📖 **[Xem chi tiết →](SVG_Canvas.md)**
## 🔍 [PathFinding Architecture](PathFinding.md) - Kiến trúc Tìm đường
MapEditor tích hợp A* algorithm để tính toán routes giữa các stations trên map.
📖 **[Xem chi tiết →](PathFinding.md)**
## 🔄 [Import/Export Workflow](ImportExport.md) - Quy trình Import/Export
MapEditor hỗ trợ import và export VDMA LIF JSON format với validation và transaction safety.
📖 **[Xem chi tiết →](ImportExport.md)**
## 🔗 [VDA 5050 Integration](VDA5050_Integration.md) - Tích hợp VDA 5050
MapEditor convert map data thành VDA 5050 Order messages để gửi đến robot.
📖 **[Xem chi tiết →](VDA5050_Integration.md)**
## 🎯 [Design Rationale](Design_Rationale.md) - Lý do Thiết kế
Giải thích các quyết định thiết kế quan trọng: VDMA LIF, Blazor WASM + SVG, normalized database, PathFinding.
📖 **[Xem chi tiết →](Design_Rationale.md)**
## 📖 Related Documents / Tài liệu Liên quan
- [Architecture Overview](../architecture/README.md) - System architecture overview
- [FleetManager Documentation](../fleetmanager/README.md) - Usage context and integration
- [VDA 5050 Implementation](../vda5050/README.md) - Order message generation
- [Development Guide](../development/README.md) - Implementation guidelines
- [ScriptEngine Documentation](../scriptengine/README.md) - Scripting integration
## 🌐 External References / Tham khảo Ngoài
**Standards**:
- [VDMA 40499-1](https://www.vdma.org/) - Common Definitions for LIF
- [VDMA 40499-2](https://www.vdma.org/) - Layout Interchange Format Specification
- [VDA 5050](https://www.vda.de/) - Communication Interface for AMR Systems
- [GitHub: VDMA LIF](https://github.com/continua-systems/vdma-lif) - Reference implementation
**Algorithms**:
- [A* Search Algorithm](https://en.wikipedia.org/wiki/A*_search_algorithm) - PathFinding
- [NURBS](https://en.wikipedia.org/wiki/Non-uniform_rational_B-spline) - Trajectory representation
**Technologies**:
- [Blazor WebAssembly](https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor) - Client framework
- [SVG Specification](https://www.w3.org/TR/SVG2/) - Vector graphics format
- [PostgreSQL](https://www.postgresql.org/) - Database system
- [Entity Framework Core](https://learn.microsoft.com/en-us/ef/core/) - ORM
---
**Status**: Architecture & Design Document (No Implementation Code)
**Focus**: Concepts, Architecture, Design Rationale, Mermaid Diagrams
**Last Updated**: 2025-11-13
**Version**: 2.1 (Modular documentation structure)

View File

@@ -0,0 +1,94 @@
# SVG Canvas Architecture / Kiến trúc Canvas SVG
## 📋 Overview / Tổng quan
MapEditor sử dụng SVG canvas để render và edit maps với interactive features.
## 🎨 Rendering Strategy / Chiến lược Render
```mermaid
graph TD
DataLoad[Load Map Data<br/>from Server API] --> Layers[Layer Management<br/>Organize visual elements]
Layers --> BG[Background Layer<br/>Factory floor image<br/>Optional grid overlay]
Layers --> ZoneL[Zone Layer<br/>Polygon areas<br/>Fill + stroke colors]
Layers --> EdgeL[Edge Layer<br/>Lines with arrows<br/>Color by direction]
Layers --> StationL[Station Layer<br/>Icons by type<br/>Labels]
Layers --> OverlayL[Overlay Layer<br/>Selection highlights<br/>Hover tooltips]
BG --> Render[SVG Rendering Engine]
ZoneL --> Render
EdgeL --> Render
StationL --> Render
OverlayL --> Render
Render --> Interaction[Interaction Handler<br/>Mouse events<br/>Touch events]
Interaction --> Select[Selection<br/>Single/multi-select<br/>Highlight selected]
Interaction --> Hover[Hover<br/>Show tooltips<br/>Preview info]
Interaction --> Drag[Drag<br/>Move objects<br/>Update coordinates]
Interaction --> PanZoom[Pan & Zoom<br/>Canvas navigation<br/>Wheel/pinch gestures]
Select --> Inspector[Object Inspector<br/>Property panel<br/>Edit values]
Drag --> Inspector
style BG fill:#f5f5f5
style ZoneL fill:#ffe6f0
style EdgeL fill:#fff0e6
style StationL fill:#e6f3ff
style OverlayL fill:#f0e6ff
```
## 📐 Coordinate Systems / Hệ tọa độ
**Two Coordinate Systems**:
1. **Screen Coordinates** (Canvas pixels):
- Origin: Top-left corner
- X-axis: Right (positive)
- Y-axis: Down (positive)
- Used for: Rendering, mouse events
2. **World Coordinates** (Physical meters):
- Origin: Map referencePoint
- X-axis: Right (positive)
- Y-axis: Up (positive)
- Used for: VDMA LIF data, robot positions
**Transformation**: World ↔ Screen với scale, translate, và Y-axis flip
## 🎨 Visual Styling Conventions
**Station Icons by Type**:
- Charging: Lightning bolt, yellow
- Pickup: Box icon, green
- Dropoff: Outbox icon, red
- Parking: P icon, blue
**Edge Visualization**:
- Bidirectional: Gray line, no arrow
- Unidirectional: Blue line, arrow at end
- Selected: Orange outline, dashed
**Zone Appearance**:
- Safety Zone: Blue fill, semi-transparent
- Restricted Zone: Red fill, diagonal stripes
- Speed Limit Zone: Yellow fill
## 🔄 Editing Workflows
**Object Creation**: Tool select → Click canvas → Create → Property edit → Validate
**Object Manipulation**: Click → Select → Drag/Edit → Validate → Save
**Multi-Object Operations**: Box selection, Shift+Click, Batch edit, Align tools
## 🔗 Related Documents / Tài liệu Liên quan
- [MapEditor Overview](README.md) - Tổng quan MapEditor
- [VDMA LIF Standard](VDMA_LIF_Standard.md) - Map data structure
---
**Last Updated**: 2025-11-13

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,805 @@
# MapManager Database Design - Discussion Summary
**Project:** RobotNet10.MapManager
**Date:** 2024-11-26
**Participants:** AI Assistant & DangNV
**Topic:** Database design for VDMA LIF 1.0.0 compliant map management system
**Final Version:** 2.0 (GUID-based naming)
---
## 📋 Overview
This document summarizes the complete discussion and design decisions for the RobotNet10.MapManager database schema, which manages AGV/AMR maps according to VDMA LIF (Layout Interchange Format) 1.0.0 standard.
**FINAL IMPLEMENTATION:** GUID 8-character based automatic naming system (optimized for Import/Export scenarios)
---
## 🎯 Project Goals
1. **VDMA LIF Compliance**: 100% adherence to lif-schema.json specification
2. **Multi-Level Support**: Handle buildings with multiple floors
3. **Version Control**: Track layout changes over time
4. **VehicleType Customization**: Per-vehicle properties for nodes and edges
5. **Scalability**: Support up to 100k+ nodes and edges per level
6. **Import/Export Ready**: Seamless VDMA LIF JSON import/export without conflicts
---
## 📚 Reference Documents
- **VDMA LIF Schema**: `lif-schema.json` (provided by user)
- **VDMA LIF Specification**: `FuI_Guideline_LIF_GB_final.pdf`
- **Target Framework**: .NET 10.0
- **ORM**: Entity Framework Core 9.0.0
- **Database**: SQLite (design-time), SQL Server (production)
---
## 🔍 Key Discussion Points & Evolution
### 1. Layout Hierarchy Structure
**Decision: Option B - Hierarchy**
```
Layout (Building/Facility)
└── LayoutVersion (Version History)
└── LayoutLevel (Floor/Level)
├── Nodes
├── Edges
└── Stations
```
**Rationale:**
- Clear separation: Layout represents the facility, not a specific version
- Multiple versions per layout for history tracking
- Multiple levels per version for multi-floor buildings
- VDMA LIF export: layoutId is shared across all levels within a version
---
### 2. VehicleType Architecture
**Decision:** Separate `VehicleTypes` table as master data ✅
**Structure:**
```
VehicleTypes (Master data)
├── Used in: NodeVehicleProperties (junction table)
└── Used in: EdgeVehicleProperties (junction table)
```
**Key Points:**
- VehicleType = Robot type (e.g., AMR-T800, AMR-F100)
- One map can support multiple VehicleTypes
- Properties are vehicle-specific:
- `vehicleTypeNodeProperties`: theta, actions (JSON)
- `vehicleTypeEdgeProperties`: orientation, speed limits, trajectory, etc.
- No physical specs (width, length) - focus on schema-defined properties only
---
### 3. Zones vs Stations
**Initial:** Zones concept was discussed
**Final Decision:** Use **Stations** per VDMA LIF schema ✅
**Structure:**
```
Stations
└── StationInteractionNodes (Many-to-Many with Nodes)
```
**Rationale:**
- VDMA LIF schema defines "stations" not "zones"
- Stations have interactionNodeIds array
- Represents loading/unloading points
---
### 4. Actions Storage
**Decision:** Store as JSON within vehicleType properties ✅
**Not separate tables** because:
- Actions structure varies by action type
- VDMA LIF defines actions as array within properties
- Flexibility for different action parameters
- Export/Import simplicity
**Format:**
```json
{
"actions": [
{
"actionType": "pick",
"actionParameters": [...]
}
]
}
```
---
### 5. Layout Flags & Versioning
**Layouts:**
- ~~`IsArchived`~~ ❌ Removed by user request
- `IsActive` ✅ Added - Indicates if layout is currently active
**LayoutVersions:**
- `IsActive` ✅ - Only ONE active version per layout
- When active → READ-ONLY (cannot edit)
**LayoutLevels:**
- `LevelOrder` ✅ Kept - For flexible UI sorting (not tied to layoutLevelId)
---
### 6. **Editor Settings - Major Design Evolution** ⭐
This went through significant iteration:
#### **Phase 1: Counter + Template Approach** (Initial Design)
**Proposed Fields:**
```
LayoutLevelEditorSettings:
- EdgeCount (long)
- EdgeNameTemplate (string) e.g., "Edge_{0:D4}"
- NodeCount (long)
- NodeNameTemplate (string) e.g., "Node_{0:D4}"
- EdgeMinLengthCreate (double)
- EdgeNameAutoGenerate (bool)
- NodeNameAutoGenerate (bool)
- ImageWidth, ImageHeight (double?)
```
**Pros:**
- ✅ Human-readable: Node_0001, Node_0002
- ✅ Sortable chronologically
- ✅ Template flexibility
**Cons:**
-**Import Problem**: When importing VDMA LIF with existing nodes, counter conflicts occur
- ❌ Need atomic increment (complexity)
- ❌ 4 extra fields for counter state
- ❌ Template validation required
---
#### **Phase 2: GUID-based Approach** (Final) ✅✅✅
**User Requirements:**
1. Large project expected (10k+ items per level)
2. Import/Export is critical - counter causes conflicts
3. Human-readability NOT important
4. Sortability NOT needed
5. Reference by name: nice to have but not critical
**Analysis Performed:**
- **GUID 4 chars:** ❌ 7% collision @ 100 items (too risky)
- **GUID 6 chars:** ⚠️ 3% collision @ 10k items (risky)
- **GUID 8 chars:** ✅ 0.0012% collision @ 10k items (safe with retry)
**Final Decision: GUID 8 Characters**
**Simplified Fields:**
```
LayoutLevelEditorSettings:
- EdgeNameAutoGenerate (bool)
- NodeNameAutoGenerate (bool)
- EdgeMinLengthCreate (double) - Meters
- OriginX (double) - Coordinate origin X in meters
- OriginY (double) - Coordinate origin Y in meters
- Resolution (double) - Meters per pixel (default: 0.05)
- BoundsMinX, BoundsMaxX (double?) - Coordinate bounds in meters
- BoundsMinY, BoundsMaxY (double?) - Coordinate bounds in meters
- ImageWidth (double?) - Pixels
- ImageHeight (double?) - Pixels
- CreatedDate, ModifiedDate (DateTime)
```
**14 fields total** (3 required coordinate fields + 4 optional bounds)
**Name Format:**
```
Node_a7f2e3b1 (8-char GUID)
Edge_3d8f9a2c (8-char GUID)
```
**Benefits:**
-**Import-friendly**: No counter conflicts
-**Concurrent-safe**: Parallel generation, no database locks
-**Simpler**: 4 fewer fields, no template validation
-**Scalable**: Safe up to 100k+ items with retry logic
-**Fast**: No atomic increment overhead
**Collision Safety:**
```
10,000 items: 0.0012% collision (1 in 83,000 cases)
50,000 items: 0.03% collision (1 in 3,000 cases)
100,000 items: 0.12% collision (1 in 800 cases)
With 2 retries: Practically zero collision
```
**Service Implementation:**
- Max 5 retries
- Logging for collision monitoring
- Exception if all retries fail (extremely unlikely)
---
## 🗄️ Final Database Schema
### **11 Tables**
#### **Core VDMA LIF Tables (10)**
1. **Layouts**
- Id, LayoutId, LayoutName, Description
- IsActive, CreatedDate, ModifiedDate, CreatedBy, ModifiedBy
2. **LayoutVersions**
- Id, LayoutId (FK), Version, LayoutDescription
- CreatedBy, CreatedDate, IsActive
3. **LayoutLevels**
- Id, VersionId (FK), LayoutLevelId, LevelOrder
4. **VehicleTypes**
- Id, VehicleTypeId, VehicleTypeName, Description
- Specifications (JSON), IsActive, CreatedDate
5. **Nodes**
- Id, LevelId (FK), NodeId, NodeName, NodeDescription
- MapId, X, Y
6. **Edges**
- Id, LevelId (FK), EdgeId, StartNodeId (FK), EndNodeId (FK)
- EdgeName, EdgeDescription (extensions)
7. **Stations**
- Id, LevelId (FK), StationId, StationName, StationDescription
- StationHeight, X, Y, Theta
8. **StationInteractionNodes**
- Id, StationId (FK), NodeId (FK)
9. **NodeVehicleProperties**
- Id, NodeId (FK), VehicleTypeId (FK)
- Theta, Actions (JSON)
10. **EdgeVehicleProperties**
- Id, EdgeId (FK), VehicleTypeId (FK)
- VehicleOrientation, OrientationType (enum: GLOBAL, TANGENTIAL), RotationAllowed
- RotationAtStartNodeAllowed (enum: NONE, CCW, CW, BOTH), RotationAtEndNodeAllowed (enum: NONE, CCW, CW, BOTH)
- MaxSpeed, MaxRotationSpeed, MinHeight, MaxHeight
- LoadRestriction_Unloaded, LoadRestriction_Loaded, LoadRestriction_LoadSetNames (JSON)
- Trajectory (JSON - NURBS format)
#### **Editor Extension Table (1)** ⭐
11. **LayoutLevelEditorSettings** (UI-specific, NOT exported to VDMA LIF)
- Id, LevelId (FK)
- **EdgeMinLengthCreate** (double) - Meters
- **EdgeNameAutoGenerate** (bool)
- **NodeNameAutoGenerate** (bool)
- **OriginX** (double) - Coordinate origin X in meters
- **OriginY** (double) - Coordinate origin Y in meters
- **Resolution** (double) - Meters per pixel
- **BoundsMinX, BoundsMaxX** (double?) - X boundaries in meters
- **BoundsMinY, BoundsMaxY** (double?) - Y boundaries in meters
- **ImageWidth** (double?) - Pixels
- **ImageHeight** (double?) - Pixels
- CreatedDate, ModifiedDate
**Total: 79 columns across 11 tables** (includes 2 enum fields, 7 coordinate system fields)
---
## 🔗 Relationships
### **Hierarchy**
```
Layouts (1) ─→ (∞) LayoutVersions (CASCADE)
LayoutVersions (1) ─→ (∞) LayoutLevels (CASCADE)
LayoutLevels (1) ─→ (∞) Nodes, Edges, Stations (CASCADE)
LayoutLevels (1) ─→ (1) LayoutLevelEditorSettings (CASCADE)
```
### **VehicleType**
```
VehicleTypes (1) ─→ (∞) NodeVehicleProperties (CASCADE)
VehicleTypes (1) ─→ (∞) EdgeVehicleProperties (CASCADE)
```
### **Nodes & Edges**
```
Nodes (1) ─→ (∞) Edges.StartNodeId (RESTRICT)
Nodes (1) ─→ (∞) Edges.EndNodeId (RESTRICT)
Nodes (1) ─→ (∞) NodeVehicleProperties (CASCADE)
Edges (1) ─→ (∞) EdgeVehicleProperties (CASCADE)
```
### **Stations**
```
Stations (1) ─→ (∞) StationInteractionNodes (CASCADE)
Nodes (1) ─→ (∞) StationInteractionNodes (RESTRICT)
```
---
## 📊 Indexes (28 total)
### **Primary Keys (11)**
All tables have GUID primary keys
### **Unique Constraints (10)**
- Layouts.LayoutId
- LayoutVersions.(LayoutId, Version)
- LayoutLevels.(VersionId, LayoutLevelId)
- LayoutLevelEditorSettings.LevelId
- VehicleTypes.VehicleTypeId
- Nodes.(LevelId, NodeId)
- Edges.(LevelId, EdgeId)
- Stations.(LevelId, StationId)
- StationInteractionNodes.(StationId, NodeId)
- NodeVehicleProperties.(NodeId, VehicleTypeId)
- EdgeVehicleProperties.(EdgeId, VehicleTypeId)
### **Performance Indexes (7)**
- Layouts.IsActive
- LayoutVersions.IsActive
- LayoutLevels.LevelOrder
- VehicleTypes.IsActive
- Nodes.NodeId, Nodes.MapId, Nodes.(X, Y)
- Edges.StartNodeId, Edges.EndNodeId
---
## ✅ Design Decisions Summary
| # | Decision | Rationale |
|---|----------|-----------|
| 1 | Layout → Version → Level hierarchy | Clear separation, version control |
| 2 | Stations (not Zones) | VDMA LIF schema compliance |
| 3 | Actions as JSON | Flexibility, schema alignment |
| 4 | IsActive flag | Track active layout/version |
| 5 | LevelOrder kept | Flexible UI sorting |
| 6 | VehicleTypes simplified | No physical specs, focus on schema |
| 7 | EdgeName/EdgeDescription | UI extensions |
| 8 | **GUID 8-char naming** ⭐ | **Import-friendly, concurrent-safe, scalable** |
| 9 | **No counter/template** ⭐ | **Simplified, no import conflicts** |
| 10 | **Coordinate System in EditorSettings** ⭐ | **World (meters) vs Image (pixels), editor-specific** |
| 11 | **Configurable Origin & Resolution** ⭐ | **Flexible alignment, different scales per level** |
| 12 | **Optional Bounds** | **Validate coordinates, define operational area** |
---
## 🔄 Import/Export Logic
### **Export (Database → VDMA LIF JSON)**
```
Input: LayoutId + LayoutVersion
Output: Single JSON file with all layoutLevelIds
Structure:
{
"metaInformation": {...},
"layouts": [
{
"layoutId": "warehouse_main",
"layoutVersion": "1.0",
"layoutLevelId": "floor_1",
"nodes": [...],
"edges": [...],
"stations": [...]
},
{
"layoutId": "warehouse_main",
"layoutVersion": "1.0",
"layoutLevelId": "floor_2",
...
}
]
}
```
**Important:** ❌ Do NOT export `LayoutLevelEditorSettings` (internal only)
### **Import (VDMA LIF JSON → Database)**
```
1. Create/Update Layout (by layoutId)
2. Create/Update LayoutVersion (by layoutVersion)
3. For each layoutLevelId:
a. Create/Update LayoutLevel
b. Import Nodes (with GUID names if auto-generated)
c. Import Edges
d. Import Stations
e. Import VehicleType properties
4. Auto-create LayoutLevelEditorSettings with defaults
```
**No counter conflicts** - GUID-based names work seamlessly ✅
---
## 🗺️ Coordinate System Design ⭐
### **Overview**
The MapManager uses a **dual coordinate system** approach to handle both physical world coordinates (for robot navigation) and image/screen coordinates (for UI rendering).
### **Coordinate Systems**
#### **1. World Coordinates (Physical Space)**
- **Unit**: METERS (per VDMA LIF standard)
- **Storage**: All Nodes, Edges, Stations store X, Y in meters
- **Origin**: Defined by OriginX, OriginY in LayoutLevelEditorSettings
- **Axis Convention**:
- X-axis: Right (positive)
- Y-axis: Up (positive) - Mathematical/Engineering convention
- **Used for**: VDMA LIF data, robot navigation, path planning
#### **2. Image Coordinates (Rendering Space)**
- **Unit**: PIXELS
- **Storage**: ImageWidth, ImageHeight in LayoutLevelEditorSettings
- **Origin**: Top-left corner (standard image/screen convention)
- **Axis Convention**:
- X-axis: Right (positive)
- Y-axis: Down (positive) - Image/Screen convention
- **Used for**: Background image rendering, UI interactions
### **Coordinate Transformation**
**World → Image Pixel:**
```csharp
double imageX = (worldX - settings.OriginX) / settings.Resolution;
double imageY = (settings.ImageHeight ?? 0) - ((worldY - settings.OriginY) / settings.Resolution);
// Note: Y-axis is flipped (world Y-up vs image Y-down)
```
**Image Pixel → World:**
```csharp
double worldX = (imageX * settings.Resolution) + settings.OriginX;
double worldY = ((settings.ImageHeight ?? 0) - imageY) * settings.Resolution + settings.OriginY;
```
### **LayoutLevelEditorSettings Coordinate Fields**
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| **OriginX** | double | 0.0 | World coordinate origin X in meters |
| **OriginY** | double | 0.0 | World coordinate origin Y in meters |
| **Resolution** | double | 0.05 | Meters per pixel (0.05 = 5cm per pixel) |
| **BoundsMinX** | double? | null | Minimum X boundary in meters (optional) |
| **BoundsMaxX** | double? | null | Maximum X boundary in meters (optional) |
| **BoundsMinY** | double? | null | Minimum Y boundary in meters (optional) |
| **BoundsMaxY** | double? | null | Maximum Y boundary in meters (optional) |
| **ImageWidth** | double? | null | Background image width in pixels |
| **ImageHeight** | double? | null | Background image height in pixels |
### **Resolution Examples**
| Resolution | Meaning | Use Case |
|------------|---------|----------|
| 0.01 | 1 pixel = 1 cm | High precision, small areas |
| 0.05 | 1 pixel = 5 cm | **Default**, balanced |
| 0.10 | 1 pixel = 10 cm | Large warehouses |
| 0.50 | 1 pixel = 50 cm | Very large outdoor areas |
### **Coordinate Bounds**
Optional boundaries to constrain valid coordinates for a level:
**Purpose:**
- Prevent robots from being assigned invalid positions
- Define operational area limits
- Validate imported data
**Example:**
```
Warehouse Level 1:
- BoundsMinX: -10.0 meters (10m west of origin)
- BoundsMaxX: 100.0 meters (100m east of origin)
- BoundsMinY: -5.0 meters (5m south of origin)
- BoundsMaxY: 50.0 meters (50m north of origin)
- Total area: 110m × 55m = 6,050 square meters
```
**Validation:**
```csharp
bool IsWithinBounds(double x, double y, LayoutLevelEditorSettings settings)
{
if (settings.BoundsMinX.HasValue && x < settings.BoundsMinX.Value) return false;
if (settings.BoundsMaxX.HasValue && x > settings.BoundsMaxX.Value) return false;
if (settings.BoundsMinY.HasValue && y < settings.BoundsMinY.Value) return false;
if (settings.BoundsMaxY.HasValue && y > settings.BoundsMaxY.Value) return false;
return true;
}
```
### **Design Rationale**
| Decision | Rationale |
|----------|-----------|
| **Meters in database** | VDMA LIF standard, robot navigation uses meters |
| **Origin configurable** | Different maps have different reference points |
| **Resolution per level** | Each floor may need different scale/precision |
| **Y-axis flip in conversion** | World (Y-up) vs Image (Y-down) standards |
| **Optional bounds** | Not always needed, flexibility |
| **Part of EditorSettings** | Coordinate mapping is UI/editor concern, not VDMA LIF data |
### **Import/Export Behavior**
**Export (Database → VDMA LIF):**
- ✅ Export Node.X, Node.Y directly (already in meters)
- ✅ Export Station.X, Station.Y directly (already in meters)
- ❌ Do NOT export OriginX, OriginY, Resolution (editor-specific)
- ❌ Do NOT export Bounds (editor-specific)
**Import (VDMA LIF → Database):**
- ✅ Import coordinates directly to Node.X, Node.Y (meters)
- ✅ Use default Origin (0, 0) and Resolution (0.05)
- ✅ User can adjust Origin/Resolution after import for UI alignment
### **Common Scenarios**
#### **Scenario 1: New Map from Scratch**
```
1. Create LayoutLevel
2. EditorSettings auto-created with defaults:
- OriginX = 0.0, OriginY = 0.0
- Resolution = 0.05 (5cm/pixel)
3. User places nodes → stored in meters from (0,0)
```
#### **Scenario 2: Import Existing VDMA LIF**
```
1. Import nodes with world coordinates (meters)
2. EditorSettings created with defaults
3. User uploads background image
4. User adjusts OriginX, OriginY to align image with nodes
5. User adjusts Resolution if scale doesn't match
```
#### **Scenario 3: Large Warehouse**
```
1. Import facility map (1000m × 500m)
2. Background image: 2000px × 1000px
3. Calculate Resolution: 1000m / 2000px = 0.5 m/pixel
4. Set OriginX = 0, OriginY = 0 (bottom-left corner)
5. Set Bounds: MinX=0, MaxX=1000, MinY=0, MaxY=500
```
---
## 🚀 Implementation Status
### **Phase 1: Core VDMA LIF** ✅ COMPLETE
- [x] 10 Entity classes
- [x] MapDbContext configuration
- [x] Migration: InitialCreate (10 tables)
- [x] Build & test successful
### **Phase 2: Editor Settings** ✅ COMPLETE
- [x] LayoutLevelEditorSettings entity (GUID-based)
- [x] LayoutLevelNamingService (GUID generation + retry)
- [x] Migration: AddLayoutLevelEditorSettings (1 table)
- [x] Build & test successful
### **Phase 2.5: Coordinate System** ✅ COMPLETE
- [x] Coordinate system fields in LayoutLevelEditorSettings
- [x] OriginX, OriginY, Resolution (required)
- [x] BoundsMinX/MaxX, BoundsMinY/MaxY (optional)
- [x] Migration: AddCoordinateSystemFields
- [x] Documentation updated with coordinate system design
- [x] Build & test successful
### **Phase 3: REST API Implementation** ✅ COMPLETE
- [x] Complete DTOs (28 files)
- [x] Service layer (11 services, ~1,560 lines)
- [x] Controllers (7 controllers, 37 endpoints)
- [x] Complex business logic (edge auto-detection, cascade delete)
- [x] Configuration & DI setup
- [x] Build & test successful
### **Phase 3.5: Enum Types for EdgeVehicleProperty** ✅ COMPLETE
- [x] Created OrientationType enum (GLOBAL, TANGENTIAL)
- [x] Created RotationDirection enum (NONE, CCW, CW, BOTH)
- [x] Updated EdgeVehicleProperty entity to use enums
- [x] Updated EdgeVehiclePropertyDto to use enums
- [x] Migration: ConvertEnumFieldsToEnums
- [x] Build & test successful
### **Phase 4: Import/Export** ⏳ PARTIAL
- [x] Export endpoint design complete
- [ ] Import VDMA LIF endpoint (POST /api/layouts/import)
- [ ] VDMA LIF JSON parser
- [ ] Validation against lif-schema.json
### **Phase 5: Integration** ⏳ TODO
- [ ] MapEditor UI integration
- [ ] End-to-end testing
- [ ] Performance testing
---
## 📈 Performance Considerations
### **Scale Targets**
- 100k nodes per level: ✅ Supported
- 100k edges per level: ✅ Supported
- 50 floors per layout: ✅ Supported
- 1000 layouts: ✅ Supported
### **GUID Collision Safety**
```
At 10k items: 0.0012% collision
At 50k items: 0.03% collision
At 100k items: 0.12% collision
With 2 retries: <0.001% collision (negligible)
```
### **Optimizations**
- Strategic indexing (28 indexes)
- Proper cascade delete rules
- Check constraint on edges
- Efficient GUID generation (parallel)
- No database locking (vs counter approach)
---
## 📝 Code Files
### **Entities (11 files)**
```
Data/
├── Layout.cs (71 lines)
├── LayoutVersion.cs (64 lines)
├── LayoutLevel.cs (52 lines)
├── LayoutLevelEditorSettings.cs (156 lines) ⭐ [+66 lines for coordinate system]
├── VehicleType.cs (62 lines)
├── Node.cs (72 lines)
├── Edge.cs (70 lines)
├── Station.cs (73 lines)
├── StationInteractionNode.cs (37 lines)
├── NodeVehicleProperty.cs (59 lines)
└── EdgeVehicleProperty.cs (125 lines)
```
### **Services (1 file)**
```
Services/
└── LayoutLevelNamingService.cs (140 lines) ⭐
- GenerateNodeNameAsync() - GUID 8-char + retry
- GenerateEdgeNameAsync() - GUID 8-char + retry
- PreviewNodeNames() - Show examples
- PreviewEdgeNames() - Show examples
- GetLevelStatisticsAsync() - Monitoring
```
### **DbContext (2 files)**
```
Data/
├── MapDbContext.cs (202 lines)
└── MapDbContextFactory.cs (19 lines)
```
### **Migrations (5 files)**
```
Data/Migrations/
├── 20251126062346_InitialCreate.cs (449 lines)
├── 20251126062346_InitialCreate.Designer.cs
├── 20251126074422_AddLayoutLevelEditorSettings.cs (54 lines) ⭐
├── 20251126074422_AddLayoutLevelEditorSettings.Designer.cs ⭐
├── 20251126080906_AddCoordinateSystemFields.cs (92 lines) ⭐ [NEW]
├── 20251126080906_AddCoordinateSystemFields.Designer.cs ⭐ [NEW]
└── MapDbContextModelSnapshot.cs
```
---
## 🎓 Key Learnings
### **1. Import/Export First Design**
- Original counter approach didn't account for import scenarios
- GUID approach solves this elegantly
- Design for data interchange, not just internal use
### **2. Simplicity Wins**
- Removed 4 fields (templates + counters)
- Simpler is better when trade-offs are acceptable
- User confirmed human-readability not critical
### **3. Scale Appropriately**
- 8-character GUID is sweet spot for this use case
- Not too short (high collision), not too long (unnecessary)
- Consider actual requirements, not theoretical extremes
### **4. VDMA LIF Extensions**
- Clearly separate VDMA LIF data from UI extensions
- Document which fields are NOT exported
- Maintain 100% schema compliance where it matters
---
## 📊 Final Statistics
| Metric | Count |
|--------|-------|
| **Tables** | 11 |
| **Columns** | 79 |
| **Foreign Keys** | 14 |
| **Indexes** | 28 |
| **Check Constraints** | 1 |
| **Entity Classes** | 11 (~850 lines) |
| **Service Classes** | 12 (~1,700 lines) |
| **Migrations** | 4 (~695 lines) |
| **Enum Types** | 2 (OrientationType, RotationDirection) |
| **Total Code** | ~5,500 lines |
---
## 🏆 Success Criteria
- ✅ 100% VDMA LIF 1.0.0 compliant
- ✅ Import/Export ready (no conflicts)
- ✅ Scalable (100k+ items per level)
- ✅ Concurrent-safe (parallel generation)
- ✅ Simple (7 fields vs 11 in editor settings)
- ✅ Fast (no database locks, parallel GUID generation)
- ✅ Monitored (collision logging for production)
- ✅ Clean build (0 warnings, 0 errors)
---
## 🚀 Deployment
### **Apply Migrations**
```bash
cd srcs/RobotNet10
dotnet ef database update --project Commons/RobotNet10.MapManager
```
This creates all 11 tables with 28 indexes and 14 foreign key relationships.
---
## 📞 References
- **Complete Guide (for AI):** `MAPMANAGER_COMPLETE_GUIDE.md`
- **Implementation:** `srcs/RobotNet10/Commons/RobotNet10.MapManager/`
- **API Documentation:** `srcs/RobotNet10/Commons/RobotNet10.MapManager/README_API.md`
- **VDMA LIF Schema:** `lif-schema.json`
- **VDMA LIF Guide:** `FuI_Guideline_LIF_GB_final.pdf`
---
**Status:** ✅ DESIGN & API COMPLETE
**Version:** 3.1 (Complete REST API + Enum Types)
**Date:** 2024-11-26
**Ready for:** Production deployment & MapEditor integration
**Latest Updates:**
- ✅ Coordinate System integrated (Origin, Resolution, Bounds)
- ✅ NodeProximityRadius added (0.35m default)
- ✅ Complete REST API (7 controllers, 37 endpoints)
- ✅ Full service layer (12 services, ~1,700 lines)
- ✅ Complex logic: Edge auto-detection, cascade delete
-**Enum Types**: OrientationType (GLOBAL, TANGENTIAL), RotationDirection (NONE, CCW, CW, BOTH)
-**Type Safety**: EdgeVehicleProperty uses enums instead of strings
- ✅ Build SUCCESS (0 warnings, 0 errors)
**Next:**
- Import VDMA LIF endpoint
- MapEditor UI integration
- End-to-end testing

View File

@@ -0,0 +1,959 @@
# LayoutEditor Implementation - Development Log
_Last Updated: 2024-12-03_
## 📋 Overview
Tài liệu này ghi lại quá trình xây dựng **LayoutEditor** - trang chỉnh sửa bản đồ cho robot AGV/AMR. LayoutEditor cho phép người dùng vẽ và chỉnh sửa nodes, edges, stations trên canvas SVG với background image.
**Note:** Quá trình xây dựng LayoutManager đã được lưu riêng trước đó.
---
## 📐 Design Specifications (From Initial Discussion)
### **1. Coordinate System**
| Item | Value |
|------|-------|
| Web Origin | Top-Left (0,0) |
| Layout Origin | Bottom-Left (0,0) |
| Transform | `WebY = ImageHeight - LayoutY` |
| Mouse Display | World Coordinates (meters) - góc trên trái |
### **2. SVG Layers (Bottom → Top)**
1. Background Image (SLAM Map)
2. Grid
3. Edges + Trajectories
4. Trajectory Control Points (khi edit)
5. Nodes
6. Station Overlays
7. Selection Highlights
8. Temporary Drawing (create edge preview)
### **3. Node Properties**
| Property | Editable | Type |
|----------|----------|------|
| NodeId | ✅ | string |
| NodeName | ✅ | string |
| NodeDescription | ✅ | string |
| X, Y | ✅ | double (meters) |
| MapId | ✅ | string? |
| Station Info | ❌ (read-only) | display |
| VehicleProperties | ✅ | per VehicleType |
### **4. Edge Properties**
| Property | Editable | Type |
|----------|----------|------|
| EdgeId | ✅ | string |
| EdgeName | ✅ | string |
| EdgeDescription | ✅ | string |
| StartNodeId | ❌ | Guid (read-only) |
| EndNodeId | ❌ | Guid (read-only) |
| Length | ❌ | double (auto-calc) |
| VehicleProperties | ✅ | per VehicleType |
### **5. Edge Vehicle Properties**
| Property | Type |
|----------|------|
| VehicleOrientation | double? |
| OrientationType | enum (GLOBAL, TANGENTIAL) |
| RotationAllowed | bool? |
| RotationAtStartNodeAllowed | enum (NONE, CCW, CW, BOTH) |
| RotationAtEndNodeAllowed | enum (NONE, CCW, CW, BOTH) |
| MaxSpeed, MaxRotationSpeed | double? |
| MinHeight, MaxHeight | double? |
| LoadRestriction_* | bool?, string? |
| Trajectory | JSON (NURBS) |
### **6. Node Vehicle Properties**
| Property | Type |
|----------|------|
| Theta | double? (radians) |
| Actions | JSON (Form Builder) |
### **7. Display Settings**
| Setting | Default | Persist |
|---------|---------|---------|
| Show Edge Names | ✅ | Session |
| Show Node Names | ✅ | Session |
| Show Grid | ✅ | Session |
| Show Background | ✅ | Session |
| Grid Spacing | 1.0m | Session |
| Selected VehicleType | First | Session |
### **8. Visual Representations**
| Element | Style |
|---------|-------|
| Node (normal) | Circle, scale with zoom |
| Node (with Station) | Circle, different color (green) |
| Edge | Line with arrow at end |
| Edge Direction | Arrow at EndNode |
| Edge Name | Text above, center of edge |
| Node Name | Text below, center of node |
| Selection | Highlight ring/glow |
| Trajectory | NURBS curve (selected VehicleType only) |
| Control Points | Small circles (when editing) |
| Create Edge Preview | Dashed line from node1 to mouse (Option B) |
### **9. Operations**
| Operation | Behavior |
|-----------|----------|
| Select | Click = select single, Ctrl+Click = add to selection |
| Scanner | Drag rectangle to multi-select (objects **completely** within rectangle - Option C) |
| CreateEdge 1-Way | Click node1 → node2, creates 1 edge |
| CreateEdge 2-Way | Click node1 → node2, creates 2 edges (A→B, B→A) |
| Copy | Duplicate nodes with new IDs, duplicate edges, NO stations |
| Move | Drag selected nodes (no snap to grid) |
| Merge | Combine selected nodes at center position |
| Split | Split 1 node into N nodes (N = edge count), auto offset |
| Align H-Left | Align selected nodes to leftmost X |
| Align H-Right | Align selected nodes to rightmost X |
| Align H-Center | Align selected nodes to average X |
| Align V-Top | Align selected nodes to topmost Y |
| Align V-Bottom | Align selected nodes to bottommost Y |
| Align V-Center | Align selected nodes to average Y |
### **10. Undo/Redo**
| Supported | Not Supported |
|-----------|---------------|
| Move operations | Create/Delete |
| Alignment operations | Property changes |
### **11. Keyboard Shortcuts**
| Key | Action |
|-----|--------|
| Ctrl+Z | Undo |
| Ctrl+Y | Redo |
| Ctrl+S | Save |
| Ctrl+C | Copy selected nodes/edges |
| Ctrl+M | Move mode (if nodes selected) |
| Delete | Delete selected |
| Escape | Cancel current operation (CreateEdge, Copy) |
### **12. Viewport**
| Setting | Value |
|---------|-------|
| Initial | Zoom to fit image bounds |
| Pan | Middle mouse drag OR toolbar button |
| Zoom | Mouse wheel OR toolbar buttons (zoom at cursor position) |
| Fit | Toolbar button |
### **13. Trajectory Editor**
| Setting | Value |
|---------|-------|
| Display | Only when edge selected (single select mode, not multi-select) |
| VehicleType | Show trajectory of selected VehicleType |
| Degree | User selectable (1, 2, 3) |
| Control Points | Managed via Right Panel buttons |
| Edit | Drag control points on canvas |
| Default Creation | Auto-generate: 2 control points tại StartNode và EndNode (straight line) - Option A |
### **14. Trajectory Degree Change Logic**
**Khi TĂNG Degree (thêm control points):**
- **Degree 1 → Degree 2:** Thêm 1 point tại giữa curve (t = 0.5)
- **Degree 2 → Degree 3:** Thêm 1 point tại t = 0.33 hoặc t = 0.67
- Chèn điểm mới ở giữa 2 điểm có khoảng cách lớn nhất để giữ biên dạng ít thay đổi
**Khi GIẢM Degree (xóa control points):**
- **Degree 3 → Degree 2:** Giữ P0 và P_last, tính P_middle = weighted average của các control points cũ
- **Degree 2 → Degree 1:** Chỉ giữ P0 và P_last, curve thành đường thẳng
- Giảm từ n points xuống n-1 points bằng cách tính trung bình các cặp điểm liền kề
### **15. Actions Form Builder**
| Feature | Behavior |
|---------|----------|
| ActionType | Dropdown (VehicleType ActionDefaults) + Custom text input |
| Parameters | Key-Value pairs, add/remove individual parameters |
| Defaults | Pre-filled from VehicleType ActionDefaults, có thể thêm/xóa tiếp |
| Validation | JSON syntax + structure |
### **16. CheckLayout Validations**
| Check | Description |
|-------|-------------|
| Edge Min Length | Edge length < EdgeMinLengthCreate in settings |
| (More to be added) | ... |
### **17. Toolbar Order (Final)**
```
[Scanner][CreateEdge 1-Way▼][Select] │ [Zoom][Fit] │
[H-Left][H-Right][H-Center][V-Top][V-Bottom][V-Center] [Copy][Move] │
[Merge][Split] │
VehicleType: [AMR-T800 ▼] │
[Undo][Redo] [Save] [Delete] [Check] [Exit]
```
**Note:** Display Options và Grid Spacing đã được chuyển vào Settings Tab (không còn trên Toolbar).
### **18. Exit Button**
- **Behavior:** Quay về LayoutManager page (Option A)
### **19. Component Structure**
```
RobotNet10.MapEditor/
├─ Components/
│ └─ LayoutEditor/
│ ├─ LayoutEditorComponent.razor ← Main container
│ ├─ LayoutEditorComponent.razor.css
│ ├─ EditorToolbar.razor ← Toolbar with all buttons
│ ├─ EditorToolbar.razor.css
│ ├─ MousePositionDisplay.razor ← World coordinates display
│ ├─ SvgEditorCanvas.razor ← Main SVG canvas
│ ├─ SvgEditorCanvas.razor.css
│ ├─ RightPanel/
│ │ ├─ EditorRightPanel.razor ← Container for tabs
│ │ ├─ PropertiesTab.razor ← Selected object properties
│ │ ├─ NodePropertiesEditor.razor
│ │ ├─ EdgePropertiesEditor.razor
│ │ ├─ VehiclePropertiesEditor.razor
│ │ ├─ TrajectoryEditor.razor ← NURBS control points
│ │ └─ SettingsTab.razor ← Layout level settings
│ └─ Dialogs/
│ ├─ ActionsFormDialog.razor ← Actions form builder
│ ├─ CheckLayoutResultDialog.razor ← Validation results
│ └─ UnsavedChangesDialog.razor ← Confirm leave
├─ Services/
│ └─ State/
│ ├─ LayoutEditorState.cs ← Centralized state
│ ├─ EditorCommand.cs ← For Undo/Redo
│ └─ EditorMode.cs ← Enum: Select, CreateEdge, etc.
└─ wwwroot/
└─ js/
└─ svgEditor.js ← JavaScript for SVG interactions
```
### **20. Host Page**
```
RobotNet10.RobotApp.Client/
└─ Pages/
└─ LayoutEditor.razor ← Host page with route
@page "/layout-editor/{LevelId:guid}"
```
---
## ❓ Key Design Decisions (Q&A Summary)
### **Q8: Trajectory Display**
- **Q8.1:** Hiển thị khi edge được select ở chế độ edit (không phải multi-select ở scanner)
- **Q8.2:** Option B - Trajectory chỉ hiển thị cho VehicleType được chọn
- **Q8.3:** Degree có thể thay đổi (1, 2, 3), số lượng control points tương ứng với degree
### **Q9: Node Properties**
- **Q9.1:** Option B - Station info hiển thị trong Node Properties Tab
### **Q10: Edge/Node Names**
- **Q10.1:** Option D - Edge names hiển thị ở trên, center của edge
- **Q10.2:** Scale với zoom level
### **Q11: Station Info Display**
- **Q11.1:** Option B - Station info trong Node Properties Tab
- **Q11.2:** Tab Node Properties, trong đó sẽ có cả thông tin của Station
### **Q12: Actions Form Builder**
- **Q12.1:** Option C - VehicleType có ActionDefaults, dropdown hiển thị defaults + cho phép nhập custom text
- **Q12.2:** Có thể thêm/xóa từng parameter. Nếu ActionDefault đã có parameters thì sẽ thêm/xóa tiếp vào những parameters default đó
### **Q13: Keyboard Shortcuts**
- **Q13.1:** Có các shortcuts: Ctrl+Z (Undo), Ctrl+Y (Redo), Ctrl+S (Save), Delete (Delete selected)
### **Q14: Selection Behavior**
- **Q14.1:** Option A - Nếu chỉ Click thì sẽ select sang đối tượng mới và unselect đối tượng cũ
### **Q15: Box Select**
- **Q15.1:** Option A - Box select trong Scanner mode
### **Q16: NURBS Degree Change**
- **Q16.1:** Khi thay đổi Degree, control points được thêm/xóa tự động với logic giữ biên dạng ít thay đổi nhất (xem section 14 ở trên)
### **Q17: Default Trajectory**
- **Q17.1:** Option A - Auto-generate: 2 control points tại StartNode và EndNode (straight line)
### **Q18: Box Select Behavior**
- **Q18.1:** Option C - Objects **hoàn toàn** nằm trong rectangle
### **Q19: Exit Button**
- **Q19:** Option A - Quay về LayoutManager page
### **Q20: Create Edge Preview**
- **Q20:** Option B - Đường nét liền màu khác (e.g., gray) từ node đầu tiên đến vị trí mouse
---
## 🎯 Implementation Phases
### **Phase 1: Foundation (Core Structure)** ✅ COMPLETE
**Status:** Hoàn thành trong cuộc hội thoại ban đầu
**Deliverables:**
- `LayoutEditorComponent.razor` - Main container với left canvas, right panel
- `EditorToolbar.razor` - Full toolbar với tất cả buttons
- `LayoutEditorState.cs` - Centralized state management
- Host page: `RobotApp.Client/Pages/LayoutEditor.razor` với route `/layout-editor/{LevelId:guid}`
- Navigation từ LayoutManager page
**Key Features:**
- State management với data loading (Level, Nodes, Edges, Stations, VehicleTypes)
- Selection management (single/multi-select)
- Viewport control structure (`ViewportState` class)
- Coordinate transforms (SVG ↔ World)
- Undo/Redo stack (Command pattern với `EditorCommand` abstract class)
- Editor modes enum (`Select`, `Scanner`, `CreateEdge1Way`, `CreateEdge2Way`, `Pan`, `Move`, `Copy`)
**State Management Structure:**
- `LayoutEditorState` - Centralized state với:
- Data: `Level`, `Nodes`, `Edges`, `Stations`, `VehicleTypes`
- Selection: `SelectedNodeIds`, `SelectedEdgeIds`
- Viewport: `ViewportState` (ViewBoxX, ViewBoxY, ViewBoxWidth, ViewBoxHeight, ZoomLevel)
- Display options: `ShowEdgeNames`, `ShowNodeNames`, `ShowGrid`, `ShowBackgroundImage`, `GridSpacing`
- Undo/Redo: `UndoStack`, `RedoStack`
- Editor mode: `CurrentMode`
- Methods: `InitializeAsync()`, `Pan()`, `Zoom()`, `FitToScreen()`, `SelectNodes()`, `SelectEdges()`, etc.
**Command Pattern:**
- `EditorCommand` abstract class với `Execute()``Undo()` methods
- `MoveNodesCommand` - Example command cho move operations
- Commands được push vào `UndoStack` khi execute
---
### **Phase 2: SVG Canvas (Basic)** ✅ COMPLETE
**Status:** Hoàn thành trong cuộc hội thoại ban đầu
**Deliverables:**
- `SvgEditorCanvas.razor` - SVG rendering component
- `MousePositionDisplay.razor` - World coordinates display (góc trên trái)
- `svgEditor.js` - JavaScript interop module
**Key Features:**
- Background image rendering (Y-axis flip để match world coordinates)
- Grid rendering (togglable, configurable spacing)
- Nodes rendering (circles, green color for nodes with stations)
- Edges rendering (lines with direction arrows at EndNode)
- Edge/Node names (togglable, scale with zoom level)
- Box select rectangle visualization (Scanner mode)
- Create edge preview line (dashed/gray line from start node to mouse)
- Mouse position tracking (world coordinates in meters, displayed top-left)
**Coordinate System:**
- **World Coordinates:** Bottom-left origin (0,0), Y increases upward (meters)
- **SVG Coordinates:** Top-left origin (0,0), Y increases downward (pixels)
- **Transform:** `svgY = physicalHeight - worldY` (flip Y axis)
**JavaScript Interop (`svgEditor.js`):**
- Mouse events: `mousemove`, `mousedown`, `mouseup`, `wheel`
- Keyboard shortcuts: `keydown` (Ctrl+Z/Y/S, Delete, Escape)
- Coordinate conversion: `screenToSvg()`, `screenToSvgArray()` (exported for Blazor)
- SVG element reference management
**Rendering Layers (implemented):**
1. Background Image (if available)
2. Grid (if `ShowGrid` = true)
3. Edges (with arrows and names if enabled)
4. Nodes (circles with names if enabled)
5. Selection highlights (rings around selected nodes)
6. Box select rectangle (when in Scanner mode)
7. Create edge preview line (when creating edge)
**Event Handling:**
- `OnMouseMove` - Updates mouse position, handles box select, pan, create edge preview
- `OnMouseDown` - Handles selection, starts pan, starts create edge
- `OnMouseUp` - Ends pan, completes box select, completes create edge
- `OnWheel` - Handles zoom (with cursor position preservation)
---
### **Phase 3: Viewport Controls** ⏳ IN PROGRESS
**Status:** Đã implement cơ bản, đang polish và fix bugs
#### **3.1. Pan (Middle Mouse Drag)** ✅ FIXED
**Vấn đề ban đầu:**
- Pan bị giật và không di chuyển đúng theo chuột
- Delta bị cộng dồn khi di chuyển chuột
**Nguyên nhân:**
- Tính delta từ điểm bắt đầu pan mỗi lần mouse move
- Khi ViewBox thay đổi sau mỗi lần pan, việc convert `panStartScreen` sang SVG cho giá trị khác
- Gây ra việc cộng dồn delta
**Giải pháp:**
- Thay đổi từ tính delta từ điểm bắt đầu sang **incremental delta**
- Lưu `panLastScreen` (vị trí screen của lần move trước)
- Mỗi lần mouse move:
1. Convert `panLastScreen``currentScreen` sang SVG (dùng ViewBox hiện tại)
2. Tính delta = `lastSvg - currentSvg`
3. Pan ViewBox theo delta
4. Update `panLastScreen = currentScreen`
**Code Changes:**
```csharp
// Thêm panLastScreen để track vị trí trước đó
private (double X, double Y)? panLastScreen;
// Trong OnMouseMove:
if (isPanning && panLastScreen.HasValue)
{
// Tính incremental delta từ lần move trước
_ = PanIncrementalAsync(panLastScreen.Value.X, panLastScreen.Value.Y, screenX, screenY);
panLastScreen = (screenX, screenY);
}
// Trong OnMouseDown (button == 1):
panStartScreen = (screenX, screenY);
panLastScreen = (screenX, screenY); // Initialize
```
**JavaScript Changes:**
- Thêm `screenToSvgArray()` function để export cho Blazor
- `OnMouseMove``OnMouseDown` nhận thêm `screenX, screenY` parameters
#### **3.2. Zoom (Mouse Wheel)** ✅ FIXED
**Vấn đề ban đầu:**
- Zoom không giữ tọa độ mouse không đổi
- Zoom không chính xác tại vị trí cursor
**Giải pháp:**
- Tính tỷ lệ vị trí cursor trong viewBox hiện tại
- Giữ điểm cursor không đổi trong world coordinates khi zoom
- Logic:
```csharp
// Tính ratio của cursor trong viewBox
var ratioX = (svgCenterX - ViewBoxX) / ViewBoxWidth;
var ratioY = (svgCenterY - ViewBoxY) / ViewBoxHeight;
// New dimensions
var newWidth = ViewBoxWidth / factor;
var newHeight = ViewBoxHeight / factor;
// Adjust ViewBox để giữ cursor tại cùng vị trí world
ViewBoxX = svgCenterX - ratioX * newWidth;
ViewBoxY = svgCenterY - ratioY * newHeight;
```
#### **3.3. Grid Rendering** ✅ FIXED
**Thay đổi:**
- `stroke="#999"` (đậm hơn, từ #ccc)
- `stroke-width="0.04"` (đậm gấp đôi, từ 0.02)
- `opacity="0.7"` (rõ hơn, từ 0.5)
- Thêm `stroke-dasharray="0.1,0.1"` (nét đứt)
---
### **Phase 4: Selection** ⏳ PARTIAL
**Status:** Cơ bản đã có, cần enhancement
**Đã có:**
- Single select (click)
- Multi-select (Ctrl+click)
- Box select (Scanner mode)
- Selection visual feedback
**Cần làm:**
- Enhancement và polish
- Better visual feedback
---
### **Phase 5: Create Edge** ✅ COMPLETE
**Status:** Đã hoàn thành
**Đã implement:**
- CreateEdge 1-Way mode với API call (`CreateEdgeAsync`)
- CreateEdge 2-Way mode với API call (tạo 2 edges ngược chiều)
- Edge preview while creating (preview line + preview nodes)
- Smart node detection (tích hợp API - tự động tìm node trong `NodeProximityRadius` hoặc tạo mới)
- Preview node hiển thị tại vị trí click đầu tiên và vị trí mouse
- Escape key để cancel CreateEdge operation
**API Integration:**
- `POST /api/edges` - Create edge với smart node detection
- Backend tự động tìm hoặc tạo nodes tại tọa độ start/end
- Nếu điểm nằm trong `NodeProximityRadius` của node hiện có → kết nối với node đó
- Nếu không → tạo node mới tại tọa độ đó
**Visual Features:**
- Preview line: dashed line từ start node đến mouse position
- Preview nodes: semi-transparent circles tại start và end positions
- 2-way edges: hiển thị thành 2 đường song song với offset
---
### **Phase 6: Edit Operations** ⏳ PARTIAL
**Status:** Đã implement một phần
#### **6.1. Move Nodes** ✅ COMPLETE
- **Trigger:** Ctrl + Drag (hoặc Move mode với Ctrl+M)
- **Behavior:** Di chuyển node theo chuột, khi thả Ctrl thì giữ nguyên vị trí hiện tại
- **Undo/Redo:** Hỗ trợ qua `MoveNodesCommand`
- **Change Tracking:** Đánh dấu nodes đã modified để Save sau
#### **6.2. Copy Nodes/Edges** ✅ COMPLETE
- **Trigger:** Toolbar button hoặc Ctrl+C (khi có selection)
- **Mode:** `EditorMode.Copy` - dedicated mode với preview
- **Behavior:**
- Click và drag để định offset
- Preview nodes và edges tại vị trí mới
- Thả chuột để hoàn thành copy
- Escape để cancel
- **API:** `POST /api/layout-data/copy-nodes` - Backend xử lý toàn bộ logic
- **Logic:**
- Tạo tất cả nodes mới với offset trước (validate coordinates, copy vehicle properties)
- Sau đó tạo tất cả edges mới (sử dụng node ID mapping)
- Xử lý 2-way edges: copy cả forward và reverse edge
- Không copy stations
- **Selection:** Tự động select các nodes/edges mới sau khi copy
#### **6.3. Delete Selected** ✅ COMPLETE
- **Trigger:** Delete button hoặc Delete key
- **API:** `DELETE /api/edges/{id}` hoặc batch delete
- **Behavior:** Xóa edges, backend tự động xóa orphaned nodes
- **Confirmation:** Dialog xác nhận trước khi xóa
#### **6.4. Merge Nodes** ✅ COMPLETE
- **Trigger:** Toolbar button (khi có 2+ nodes selected)
- **API:** `POST /api/layout-data/merge-nodes`
- **Behavior:**
- Gộp tất cả selected nodes thành 1 node tại vị trí center
- Redirect tất cả edges đến node mới
- Gộp vehicle properties từ tất cả nodes
- Nếu nhiều nodes có stations → dialog chọn node giữ station
- Distance check: nếu nodes ngoài `NodeProximityRadius` → confirmation dialog
- **Selection:** Tự động select node mới sau khi merge
#### **6.5. Split Node** ✅ COMPLETE
- **Trigger:** Toolbar button (khi có 1 node selected với 2+ edges)
- **API:** `POST /api/layout-data/split-node`
- **Behavior:**
- Split 1 node thành N nodes (N = số edges connected)
- Mỗi edge được redirect đến node mới tương ứng
- Copy vehicle properties đến tất cả nodes mới
- Nếu node có station → dialog chọn node nhận station
- Validation: node phải có ít nhất 2 edges mới được split
- **Selection:** Tự động select tất cả nodes mới sau khi split
#### **6.6. Alignment Functions** ❌ NOT STARTED
- **Status:** Buttons đã có, chưa implement logic
---
### **Phase 7: Right Panel - Properties** ⏳ PARTIAL
**Status:** UI đã có, cần tích hợp API
#### **7.1. Settings Tab** ✅ FIXED & ENHANCED
**Thay đổi trong cuộc hội thoại này:**
**a) Chuyển Display Options về Settings Tab:**
- Xóa Display Options khỏi Toolbar
- Thêm Display Options vào Settings Tab (sau Grid Settings)
- Bao gồm: Show Edge Names, Show Node Names, Show Grid, Show Background Map
- Là session state (không lưu vào database)
**b) Auto-generation Settings - Editable:**
- Chuyển từ read-only sang editable
- Thêm local state để quản lý giá trị chỉnh sửa:
- `nodeNameAutoGenerate` (bool)
- `edgeNameAutoGenerate` (bool)
- `edgeMinLengthCreate` (double)
- `nodeProximityRadius` (double)
- Thêm nút "Save Settings" với loading indicator
- Tích hợp với API để lưu settings
**c) Thứ tự sections:**
1. Grid Settings
2. Display Options
3. Auto-generation Settings (editable với Save button)
4. Layout Level Info (read-only)
5. Statistics
**d) API Integration:**
- Tạo `EditorSettingsInfo` model mới
- Thêm `EditorSettings` property vào `UpdateLayoutLevelRequest`
- Cập nhật `LayoutService.UpdateLevelAsync()` để xử lý editor settings
- Settings được lưu vào database khi click Save
**Code Structure:**
```csharp
// Local editable state
private bool nodeNameAutoGenerate;
private bool edgeNameAutoGenerate;
private double edgeMinLengthCreate;
private double nodeProximityRadius;
// Initialize từ settings khi component load
protected override void OnParametersSet()
{
if (State.Level?.EditorSettings != null)
{
var settings = State.Level.EditorSettings;
nodeNameAutoGenerate = settings.NodeNameAutoGenerate;
// ... initialize other fields
}
}
// Save to API
private async Task SaveEditorSettings()
{
var request = new UpdateLayoutLevelRequest
{
EditorSettings = new EditorSettingsInfo
{
NodeNameAutoGenerate = nodeNameAutoGenerate,
// ... other fields
}
};
var updatedLevel = await ApiService.UpdateLevelAsync(State.Level.Id, request);
State.Level = updatedLevel; // Update state
}
```
**Đã có:**
- Node properties editor (UI)
- Edge properties editor (UI)
- VehicleType dropdown
- Vehicle properties editor (UI)
- Station info display
**Cần làm:**
- Save properties to API
- Validation
- Error handling
---
### **Phase 8: Trajectory Editor** ❌ NOT STARTED
**Status:** Chưa thực hiện
**Cần implement:**
- Display trajectory (NURBS curve)
- Control points visualization
- Drag control points
- Add/Remove control points
- Change degree (với logic giữ biên dạng ít thay đổi)
---
### **Phase 9: Actions Form Builder** ❌ NOT STARTED
**Status:** Chưa thực hiện
**Cần implement:**
- Actions form dialog
- ActionType dropdown (VehicleType defaults + custom text)
- Parameters key-value editor
- Validation
---
### **Phase 10: Undo/Redo & Save** ⏳ PARTIAL
**Status:** Structure đã có, Save API đã implement
**Đã có:**
- Command pattern structure
- Undo stack
- Redo stack
- Save button (UI)
- Save API: `POST /api/layout-data/save` - Batch save với transaction
- Change tracking: `_modifiedNodeIds`, `_modifiedEdgeIds`
- `HasUnsavedChanges` flag
**Save API Details:**
- **Endpoint:** `POST /api/layout-data/save`
- **Request:** `SaveLayoutDataRequest` với:
- `NodesToCreate`, `NodesToUpdate`, `NodesToDelete`
- `EdgesToCreate`, `EdgesToUpdate`, `EdgesToDelete`
- **Conflict Handling:** Force Overwrite (Option D)
- **Transaction:** Toàn bộ operation trong 1 transaction, rollback nếu có lỗi
- **Response:** `SaveLayoutDataResponse` với số lượng items created/updated
**Change Tracking:**
- `MarkNodeModified(Guid nodeId)` - Đánh dấu node đã thay đổi
- `MarkEdgeModified(Guid edgeId)` - Đánh dấu edge đã thay đổi
- `SaveAsync()` - Thu thập tất cả modified/new/deleted items và gọi API
- **Note:** Các API calls trực tiếp (CreateEdge, DeleteEdge, CopyNodes, MergeNodes, SplitNode) KHÔNG set `HasUnsavedChanges` vì đã lưu vào database
**Cần làm:**
- Unsaved changes warning khi exit
- Undo/Redo cho các operations khác (Align, etc.)
---
### **Phase 11: Polish** ⏳ PARTIAL
**Status:** Một phần đã có
**Đã có:**
- Keyboard shortcuts (Ctrl+Z/Y/S/C/M, Delete, Escape)
- Display options
- Settings tab (đã sửa và enhance)
- Exit button
- Text rendering với fontSize phụ thuộc ZoomLevel và Resolution
- 2-way edges visualization (2 đường song song)
- Box select trong Scanner mode (select cả nodes và edges)
**Text Font Size:**
- **Formula:** `fontSizeSVG = baseFontSizeWorld / (Resolution * ZoomLevel)`
- **baseFontSizeWorld:** 0.24 meters (cho node names và edge names)
- **Resolution:** meters per pixel (từ `EditorSettings.Resolution`)
- **ZoomLevel:** hệ số zoom hiện tại
- **Result:** Text có cùng kích thước visual với mọi Resolution, zoom theo ZoomLevel
**Cần làm:**
- CheckLayout validation logic
- Performance optimization
---
## 🏗️ Architecture Overview
### **Component Hierarchy**
```
LayoutManager (click Edit)
→ Navigate to /layout-editor/{levelId}
→ LayoutEditorComponent
├── EditorToolbar (top)
├── SVG Canvas (left) + MousePositionDisplay
└── Right Panel
├── Properties Tab
│ ├── NodePropertiesEditor (single node)
│ └── EdgePropertiesEditor (single edge)
└── Settings Tab
├── Grid Settings
├── Display Options
├── Auto-generation Settings
├── Layout Level Info
└── Statistics
```
### **Data Flow**
1. **Initialization:**
- User clicks "Edit" on LayoutManager
- Navigate to `/layout-editor/{levelId}`
- `LayoutEditorComponent` loads
- `LayoutEditorState.InitializeAsync()` fetches:
- Level info (with EditorSettings)
- Layout data (Nodes, Edges, Stations)
- VehicleTypes
- Background image (if available)
- Viewport initialized to fit image bounds
2. **User Interactions:**
- Mouse events → JavaScript (`svgEditor.js`) → Blazor (`SvgEditorCanvas.razor`)
- State changes → `LayoutEditorState` → Notify components via `OnStateChanged` event
- UI updates → Blazor re-renders affected components
3. **Save Operations:**
- User edits properties → Local state changes
- Click "Save Settings" → API call (`MapManagerApiService.UpdateLevelEditorSettingsAsync`)
- Backend updates database → Returns updated Level
- State updated → UI refreshed
### **State Management Pattern**
- **Centralized State:** `LayoutEditorState` (scoped service)
- **Event-driven:** Components subscribe to `OnStateChanged` event
- **Immutable Updates:** State methods return new state or update properties and notify
- **Command Pattern:** Undo/Redo via `EditorCommand` abstract class
### **Coordinate Transformation**
- **World → SVG:** `WorldToSvg(worldX, worldY)` → `(worldX, physicalHeight - worldY)`
- **SVG → World:** `SvgToWorld(svgX, svgY)` → `(svgX, physicalHeight - svgY)`
- **Screen → SVG:** JavaScript `screenToSvgArray(screenX, screenY)` using `getScreenCTM()`
### **API Integration**
- **Service:** `MapManagerApiService` (injected)
- **Endpoints Used:**
- `GET /api/layouts/{layoutId}/levels/{levelId}` - Get level info
- `GET /api/layouts/{layoutId}/levels/{levelId}/data` - Get layout data
- `PUT /api/layouts/{layoutId}/levels/{levelId}` - Update level (including editor settings)
- `GET /api/layouts/{layoutId}/levels/{levelId}/background-image` - Get background image
- `POST /api/edges` - Create edge (with smart node detection)
- `DELETE /api/edges/{id}` - Delete edge (single)
- `DELETE /api/edges/batch` - Delete edges (batch)
- `POST /api/layout-data/save` - Batch save nodes/edges (transactional)
- `POST /api/layout-data/copy-nodes` - Copy nodes/edges with offset
- `POST /api/layout-data/merge-nodes` - Merge multiple nodes into one
- `POST /api/layout-data/split-node` - Split one node into multiple nodes
---
## 🔧 Technical Details
### **Coordinate System**
- **World Coordinates:** Bottom-left origin (0,0), Y increases upward, units in meters
- **SVG Coordinates:** Top-left origin (0,0), Y increases downward, units in pixels
- **Transform:** `svgY = physicalHeight - worldY` (flip Y axis)
### **Pan Logic (Fixed)**
- Sử dụng incremental delta thay vì delta từ điểm bắt đầu
- Lưu `panLastScreen` để track vị trí trước đó
- Convert screen coordinates sang SVG mỗi lần để đảm bảo chính xác khi ViewBox thay đổi
### **Zoom Logic (Fixed)**
- Zoom tại vị trí cursor
- Giữ tọa độ mouse không đổi trong world space
- Tính ratio của cursor trong viewBox và adjust ViewBox position
### **Grid Styling**
- Stroke: `#999` (đậm)
- Stroke width: `0.04` (đậm gấp đôi)
- Opacity: `0.7` (rõ)
- Stroke dasharray: `0.1,0.1` (nét đứt)
### **Text Rendering (Node/Edge Names)**
- **Font Size Formula:** `fontSizeSVG = baseFontSizeWorld / (Resolution * ZoomLevel)`
- `baseFontSizeWorld`: 0.24 meters
- `Resolution`: meters per pixel (from `EditorSettings.Resolution`)
- `ZoomLevel`: current zoom factor
- **Result:** Text có cùng kích thước visual với mọi Resolution, zoom theo ZoomLevel
- **Color:** Red (#f44336)
- **Font:** Segoe UI, font-weight 500
- **Letter Spacing:** -0.02em (characters gần nhau hơn)
- **Position:**
- Node names: Below node center
- Edge names: Above edge center
### **Settings Tab Structure**
1. Grid Settings (editable)
2. Display Options (session state)
3. Auto-generation Settings (editable với Save button)
4. Layout Level Info (read-only)
5. Statistics (read-only)
---
## 📊 Implementation Status Summary
| Phase | Status | Progress |
|-------|--------|----------|
| Phase 1: Foundation | ✅ Complete | 100% |
| Phase 2: SVG Canvas | ✅ Complete | 100% |
| Phase 3: Viewport Controls | ✅ Complete | 100% |
| Phase 4: Selection | ⏳ Partial | ~80% |
| Phase 5: Create Edge | ✅ Complete | 100% |
| Phase 6: Edit Operations | ⏳ Partial | ~70% |
| Phase 7: Properties | ⏳ Partial | ~70% |
| Phase 8: Trajectory Editor | ❌ Not Started | 0% |
| Phase 9: Actions Form Builder | ❌ Not Started | 0% |
| Phase 10: Undo/Redo & Save | ⏳ Partial | ~70% |
| Phase 11: Polish | ⏳ Partial | ~60% |
**Overall Progress:** ~65-70%
---
## 🐛 Bugs Fixed in This Session
### **1. Pan Logic - Cộng Dồn Delta**
**Problem:** Pan bị cộng dồn quãng đường di chuyển
**Root Cause:** Tính delta từ điểm bắt đầu mỗi lần thay vì incremental
**Solution:** Chuyển sang incremental delta từ lần move trước
### **2. Zoom Logic - Tọa Độ Mouse Thay Đổi**
**Problem:** Zoom không giữ tọa độ mouse không đổi
**Solution:** Tính ratio và adjust ViewBox để giữ cursor tại cùng vị trí world
### **3. Grid Styling**
**Problem:** Grid quá nhạt và nét liền
**Solution:** Đậm hơn, opacity cao hơn, thêm stroke-dasharray
### **4. Settings Tab - Display Options**
**Problem:** Display Options ở Toolbar thay vì Settings
**Solution:** Chuyển về Settings Tab, xóa khỏi Toolbar
### **5. Settings Tab - Auto-generation Settings Read-only**
**Problem:** Auto-generation Settings chỉ hiển thị, không thể edit
**Solution:** Làm editable với local state và Save button, tích hợp API
### **6. Create Edge - Smart Node Detection**
**Problem:** CreateEdge không hoạt động, chỉ tạo được trên nodes đã có
**Solution:**
- Backend API tự động detect nodes trong `NodeProximityRadius` hoặc tạo mới
- Frontend chỉ cần gửi world coordinates, backend xử lý logic
### **7. 2-Way Edges Visualization**
**Problem:** 2-way edges hiển thị thành 1 line với 2 arrowheads
**Solution:** Tính toán offset để hiển thị thành 2 đường song song riêng biệt
### **8. Text Font Size - Resolution Independence**
**Problem:** Text size thay đổi khi Resolution thay đổi
**Solution:** Công thức `fontSizeSVG = baseFontSizeWorld / (Resolution * ZoomLevel)` để text có cùng kích thước visual với mọi Resolution
### **9. HasUnsavedChanges - Direct API Calls**
**Problem:** `HasUnsavedChanges` được set sau các API calls trực tiếp (CreateEdge, DeleteEdge, etc.)
**Solution:** Xóa `HasUnsavedChanges = true` sau các API calls trực tiếp vì đã lưu vào database rồi
### **10. CopyNodesAsync - Refactor**
**Problem:** Copy logic phức tạp ở frontend, dùng temp edges
**Solution:**
- Refactor để backend xử lý toàn bộ logic
- Tạo nodes trực tiếp vào database (validate coordinates, copy vehicle properties)
- Tạo edges trực tiếp với node ID mapping
- Xử lý 2-way edges tự động
---
## 📝 Key Files Modified in This Session
### **Components:**
- `Components/LayoutEditor/RightPanel/SettingsTab.razor` - Editable settings với Save
- `Components/LayoutEditor/SvgEditorCanvas.razor` - Fixed pan/zoom logic, CreateEdge, Copy preview, text rendering
- `Components/LayoutEditor/EditorToolbar.razor` - Removed Display Options, added Move mode button
### **Services:**
- `Services/State/LayoutEditorState.cs` - Fixed zoom logic, CreateEdgeAsync, DeleteEdgesAsync, CompleteCopyAsync, MergeNodesAsync, SplitNodeAsync, SaveAsync, change tracking
### **JavaScript:**
- `wwwroot/js/svgEditor.js` - Added screenToSvgArray export, screen coordinates, Ctrl+C/M handlers
### **Shared Models:**
- `RobotNet10.MapEditor.Shared/Models/EditorSettingsInfo.cs` - New model
- `RobotNet10.MapEditor.Shared/DTOs/Requests/UpdateLayoutLevelRequest.cs` - Added EditorSettings
- `RobotNet10.MapEditor.Shared/DTOs/Requests/SaveLayoutDataRequest.cs` - New model
- `RobotNet10.MapEditor.Shared/DTOs/Responses/SaveLayoutDataResponse.cs` - New model
- `RobotNet10.MapEditor.Shared/DTOs/Requests/CopyNodesRequest.cs` - New model
- `RobotNet10.MapEditor.Shared/DTOs/Responses/CopyNodesResponse.cs` - New model
- `RobotNet10.MapEditor.Shared/DTOs/Requests/MergeNodesRequest.cs` - New model
- `RobotNet10.MapEditor.Shared/DTOs/Requests/SplitNodeRequest.cs` - New model
### **Backend:**
- `Commons/RobotNet10.MapManager/Services/LayoutService.cs` - Update editor settings logic
- `Commons/RobotNet10.MapManager/Services/LayoutDataService.cs` - SaveLayoutDataAsync, CopyNodesAsync (refactored), MergeNodesAsync, SplitNodeAsync
- `Commons/RobotNet10.MapManager/Controllers/LayoutDataController.cs` - New endpoints: save, copy-nodes, merge-nodes, split-node
---
## 🎯 Next Steps
### **Immediate (Phase 3 completion):**
- [ ] Test pan/zoom thoroughly
- [ ] Verify no accumulation issues
- [ ] Performance testing
### **Short-term (Phase 4-6):**
- [x] Enhance selection features (Shift+Click multi-select, box select edges)
- [x] Implement Create Edge với API
- [x] Implement Edit Operations (Move, Copy, Delete, Merge, Split)
- [ ] Implement Alignment functions (6 directions)
### **Medium-term (Phase 7-9):**
- [x] Properties save to API (via Save button - batch save)
- [ ] Trajectory Editor (NURBS)
- [ ] Actions Form Builder
### **Long-term (Phase 10-11):**
- [x] Save API với transaction và change tracking
- [ ] Complete Undo/Redo cho tất cả operations
- [ ] CheckLayout validation
- [ ] Performance optimization
- [ ] Additional polish
---
## 📚 References
- **Database Design:** `DATABASE_DESIGN_DISCUSSION.md`
- **API Guide:** `API_IMPLEMENTATION_GUIDE.md`
- **LayoutManager Implementation:** `LAYOUTMANAGER_TECHNICAL.md` (đã lưu trước đó)
---
**Last Updated:** 2024-12-XX
**Session Focus:** CreateEdge, Copy/Move/Delete/Merge/Split operations, Save API, Text rendering with Resolution independence, CopyNodesAsync refactor

View File

@@ -0,0 +1,879 @@
# LayoutManager - Technical Documentation
**Version:** 1.0
**Last Updated:** 2024-12-02
**Target Audience:** Developers
---
## 📋 Table of Contents
1. [Architecture Overview](#architecture-overview)
2. [Component Structure](#component-structure)
3. [State Management](#state-management)
4. [API Integration](#api-integration)
5. [Implementation Details](#implementation-details)
6. [Extension Guide](#extension-guide)
---
## Architecture Overview
### Tech Stack
```
┌─────────────────────────────────────────────────┐
│ Frontend (Blazor WASM) │
├─────────────────────────────────────────────────┤
│ RobotNet10.RobotApp.Client (Host) │
│ └─ RobotNet10.MapEditor (Component Library) │
│ ├─ Pages/ │
│ │ └─ LayoutManager.razor (Route) │
│ ├─ Components/ │
│ │ ├─ LayoutManagerComponent.razor │
│ │ ├─ LayoutTreePanel.razor │
│ │ ├─ LayoutPreviewPanel.razor │
│ │ ├─ SvgPreviewCanvas.razor │
│ │ └─ Dialogs/ │
│ ├─ Services/ │
│ │ ├─ State/ │
│ │ │ └─ LayoutManagerState.cs │
│ │ └─ API/ │
│ │ └─ MapManagerApiService.cs │
│ └─ Models/ │
│ └─ TreeItemModel.cs │
└─────────────────────────────────────────────────┘
↕ HTTP/REST
┌─────────────────────────────────────────────────┐
│ Backend (ASP.NET Core API) │
├─────────────────────────────────────────────────┤
│ RobotNet10.MapManager │
│ ├─ Controllers/ │
│ │ ├─ LayoutManagerController.cs │
│ │ ├─ LayoutDataController.cs │
│ │ └─ ImagesController.cs │
│ ├─ Services/ │
│ │ ├─ LayoutService.cs │
│ │ ├─ IImageStorageService.cs │
│ │ └─ FileSystemImageStorageService.cs │
│ └─ Data/ │
│ └─ MapManagerDbContext.cs │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ Database (SQLite) │
│ - Layouts, LayoutVersions, LayoutLevels │
│ - Nodes, Edges, Stations │
│ - LayoutLevelEditorSettings │
└─────────────────────────────────────────────────┘
```
### Design Patterns
1. **Component Pattern:** Separation of Page vs Component
- `LayoutManager.razor` (Page): Routing, render mode, providers
- `LayoutManagerComponent.razor` (Component): Business logic, UI
2. **State Management:** Centralized state with event notification
- `LayoutManagerState`: Single source of truth
- `OnStateChanged` event for reactive updates
3. **API Service:** HTTP client wrapper
- `MapManagerApiService`: Encapsulates all API calls
- Typed DTOs for request/response
4. **Repository Pattern:** Backend data access
- `ILayoutService`: Business logic interface
- EF Core for data persistence
---
## Component Structure
### File Organization
```
RobotNet10.MapEditor/
├─ Pages/
│ └─ LayoutManager.razor ← Route entry point
├─ Components/
│ └─ LayoutManager/
│ ├─ LayoutManagerComponent.razor ← Main component
│ ├─ LayoutTreePanel.razor ← Hierarchical tree
│ ├─ LayoutPreviewPanel.razor ← Preview + actions
│ └─ Dialogs/
│ ├─ CreateLayoutDialog.razor
│ ├─ CreateVersionDialog.razor
│ ├─ CreateLevelDialog.razor
│ ├─ EditLevelDialog.razor
│ ├─ ImportLayoutDialog.razor
│ └─ ExportLayoutDialog.razor
├─ Components/Shared/
│ └─ SvgPreviewCanvas.razor ← SVG rendering
├─ Services/
│ ├─ State/
│ │ └─ LayoutManagerState.cs ← State management
│ └─ API/
│ └─ MapManagerApiService.cs ← HTTP client
└─ Models/
└─ TreeItemModel.cs ← Tree node model
```
### Component Hierarchy
```
LayoutManager.razor (Page)
└─ LayoutManagerComponent.razor
├─ LayoutTreePanel.razor
│ ├─ CreateLayoutDialog (MudDialog)
│ ├─ CreateVersionDialog (MudDialog)
│ ├─ CreateLevelDialog (MudDialog)
│ └─ EditLevelDialog (MudDialog)
└─ LayoutPreviewPanel.razor
├─ SvgPreviewCanvas.razor
├─ ImportLayoutDialog (MudDialog)
└─ ExportLayoutDialog (MudDialog)
```
---
## State Management
### LayoutManagerState.cs
**Responsibilities:**
- Hold current UI state (selected layout/version/level)
- Load data from API
- Cache preview data and images
- Notify components of changes via events
**Key Properties:**
```csharp
public class LayoutManagerState
{
// Data
public List<LayoutDto> Layouts { get; private set; }
public LayoutDto? SelectedLayout { get; private set; }
public LayoutVersionDto? SelectedVersion { get; private set; }
public LayoutLevelDto? SelectedLevel { get; private set; }
// Preview Data
public LayoutDataDto? PreviewData { get; private set; }
public byte[]? PreviewImage { get; private set; }
// Loading States
public bool IsLoading { get; private set; }
public bool IsLoadingPreview { get; private set; }
// Event for reactive updates
public event Action? OnStateChanged;
}
```
**Key Methods:**
```csharp
// Load all layouts from API
public async Task LoadLayoutsAsync(string? search = null)
// Select a level and load its preview
public async Task SelectLevelAsync(LayoutLevelDto level)
// CRUD operations
public async Task CreateLayoutAsync(CreateLayoutRequest request)
public async Task CreateVersionAsync(Guid layoutId, CreateLayoutVersionRequest request)
public async Task CreateLevelAsync(Guid versionId, CreateLayoutLevelRequest request)
public async Task DeleteLayoutAsync(Guid layoutId)
public async Task DeleteVersionAsync(Guid versionId)
public async Task DeleteLevelAsync(Guid levelId)
```
**Usage Pattern:**
```csharp
@inject LayoutManagerState State
@implements IDisposable
protected override async Task OnInitializedAsync()
{
// Subscribe to state changes
State.OnStateChanged += StateHasChanged;
// Load initial data
await State.LoadLayoutsAsync();
}
public void Dispose()
{
// Unsubscribe to prevent memory leaks
State.OnStateChanged -= StateHasChanged;
}
```
---
## API Integration
### MapManagerApiService.cs
**Base Configuration:**
```csharp
// Program.cs
builder.Services.AddHttpClient<MapManagerApiService>(client =>
{
var baseUrl = builder.Configuration["MapManagerApi:BaseUrl"] ?? "https://localhost:5001";
client.BaseAddress = new Uri(baseUrl);
});
builder.Services.AddScoped<LayoutManagerState>();
```
**API Methods:**
```csharp
public class MapManagerApiService
{
// Layouts
Task<List<LayoutDto>> SearchLayoutsAsync(string? search = null)
Task<LayoutDto> CreateLayoutAsync(CreateLayoutRequest request)
Task<LayoutDto> UpdateLayoutAsync(Guid layoutId, UpdateLayoutRequest request)
Task DeleteLayoutAsync(Guid layoutId)
Task<LayoutDto> ActivateLayoutAsync(Guid layoutId)
Task<LayoutDto> DeactivateLayoutAsync(Guid layoutId)
// Versions
Task<LayoutVersionDto> CreateVersionAsync(Guid layoutId, CreateLayoutVersionRequest request)
Task<List<LayoutVersionDto>> GetVersionsAsync(Guid layoutId)
Task DeleteVersionAsync(Guid versionId)
// Levels
Task<LayoutLevelDto> CreateLevelAsync(Guid versionId, CreateLayoutLevelRequest request)
Task<LayoutLevelDto> CreateLevelWithImageAsync(
Guid versionId, string layoutLevelId, int levelOrder,
double resolution, double originX, double originY,
Stream imageStream, string fileName)
Task<LayoutLevelDto> UpdateLevelAsync(Guid levelId, UpdateLayoutLevelRequest request)
Task DeleteLevelAsync(Guid levelId)
// Layout Data
Task<LayoutDataDto> GetLayoutDataAsync(Guid layoutLevelId)
// Images
Task<byte[]?> GetLayoutImageAsync(Guid layoutLevelId)
Task UploadLayoutImageAsync(Guid layoutLevelId, Stream imageStream, string fileName)
Task DeleteLayoutImageAsync(Guid layoutLevelId)
}
```
### HTTP Request Flow
**Example: Create Level with Image**
```
1. Frontend: User fills CreateLevelDialog
2. Frontend: Call CreateLevelWithImageAsync()
3. HTTP: POST /api/layouts/versions/{versionId}/levels/with-image
Content-Type: multipart/form-data
Body:
- layoutLevelId: "floor_1"
- levelOrder: 0
- resolution: 0.05
- originX: 0
- originY: 0
- file: [PNG binary]
4. Backend: LayoutManagerController.CreateLevelWithImage()
a. Extract image dimensions (ImageSharp)
b. Create LayoutLevel entity
c. Create LayoutLevelEditorSettings entity
d. Save to database
e. Upload image to storage
f. (Rollback if image upload fails)
5. Backend: Return 201 Created with LayoutLevelDto
6. Frontend: Update state, refresh UI
7. Frontend: Show success notification
```
---
## Implementation Details
### 1. Tree View Implementation
**Challenge:** MudBlazor `MudTreeView` has complex data binding.
**Solution:** Custom tree rendering with nested MudPaper + MudStack
```razor
@foreach (var layout in State.Layouts)
{
<MudPaper>
<MudStack Row="true">
<MudIconButton Icon="..." OnClick="() => ToggleLayout(layout.Id)" />
<MudIcon Icon="@Icons.Material.Filled.Map" />
<MudText>@layout.LayoutName</MudText>
<MudMenu>...</MudMenu>
</MudStack>
</MudPaper>
@if (expandedLayouts.Contains(layout.Id))
{
<MudStack Class="ml-6">
@foreach (var version in layout.Versions)
{
<!-- Nested version rendering -->
}
</MudStack>
}
}
```
**State:**
```csharp
private HashSet<Guid> expandedLayouts = new();
private HashSet<Guid> expandedVersions = new();
private void ToggleLayout(Guid layoutId)
{
if (expandedLayouts.Contains(layoutId))
expandedLayouts.Remove(layoutId);
else
expandedLayouts.Add(layoutId);
}
```
---
### 2. SVG Preview Canvas
**Challenge:** Responsive SVG that fits container without overflow.
**Solution:** Dynamic viewBox calculation
```razor
<svg width="100%" height="100%" viewBox="@ViewBoxString" ...>
<image href="@GetImageDataUrl()"
x="0" y="0"
width="@GetImageWidth()"
height="@GetImageHeight()" />
<!-- Nodes, Edges, Stations -->
</svg>
```
**ViewBox Logic:**
```csharp
private string ViewBoxString
{
get
{
// If image exists, use physical size (meters)
if (EditorSettings?.ImageWidth.HasValue == true)
{
var width = EditorSettings.ImageWidth.Value * EditorSettings.Resolution;
var height = EditorSettings.ImageHeight.Value * EditorSettings.Resolution;
return $"0 0 {width:F2} {height:F2}";
}
// Otherwise, calculate from nodes
if (LayoutData?.Nodes.Count > 0)
{
var minX = LayoutData.Nodes.Min(n => n.X);
var maxX = LayoutData.Nodes.Max(n => n.X);
var minY = LayoutData.Nodes.Min(n => n.Y);
var maxY = LayoutData.Nodes.Max(n => n.Y);
var padding = Math.Max((maxX - minX), (maxY - minY)) * 0.1;
return $"{minX - padding:F2} {minY - padding:F2} ...";
}
// Default
return "0 0 100 50";
}
}
```
**Key Points:**
- `width="100%" height="100%"` → SVG scales to container
- `viewBox` defines coordinate system (meters, not pixels)
- Image dimensions in meters: `PixelSize × Resolution`
- `preserveAspectRatio="none"` → Image stretches to fill viewBox
---
### 3. Image Upload with Dimension Extraction
**Frontend (Client-Side):**
```csharp
private async Task OnImageSelected(InputFileChangeEventArgs e)
{
var file = e.File;
// Read file as byte array
using var stream = file.OpenReadStream(maxAllowedSize: 10 * 1024 * 1024);
using var ms = new MemoryStream();
await stream.CopyToAsync(ms);
var bytes = ms.ToArray();
// Extract dimensions from PNG header (bytes 16-23)
if (bytes[0] == 0x89 && bytes[1] == 0x50 && bytes[2] == 0x4E && bytes[3] == 0x47)
{
imageWidth = (bytes[16] << 24) | (bytes[17] << 16) | (bytes[18] << 8) | bytes[19];
imageHeight = (bytes[20] << 24) | (bytes[21] << 16) | (bytes[22] << 8) | bytes[23];
}
}
```
**Backend (Server-Side with ImageSharp):**
```csharp
// In FileSystemImageStorageService.cs
public async Task<(int width, int height)> GetImageDimensionsAsync(Stream imageStream)
{
if (imageStream.CanSeek)
imageStream.Position = 0;
using var image = await Image.LoadAsync(imageStream);
return (image.Width, image.Height);
}
// In LayoutManagerController.cs
[HttpPost("versions/{versionId:guid}/levels/with-image")]
public async Task<ActionResult<LayoutLevelDto>> CreateLevelWithImage(
Guid versionId, [FromForm] string layoutLevelId, ... , IFormFile file)
{
// Step 1: Extract dimensions
int imageWidth, imageHeight;
using (var stream = file.OpenReadStream())
{
(imageWidth, imageHeight) = await _imageStorageService.GetImageDimensionsAsync(stream);
}
// Step 2: Create level with dimensions
var request = new CreateLayoutLevelRequest
{
LayoutLevelId = layoutLevelId,
CoordinateSystem = new CoordinateSystemInfo
{
Resolution = resolution,
OriginX = originX,
OriginY = originY,
ImageWidth = imageWidth,
ImageHeight = imageHeight,
BoundsMinX = 0,
BoundsMaxX = imageWidth * resolution,
BoundsMinY = 0,
BoundsMaxY = imageHeight * resolution
}
};
var level = await _layoutService.CreateLevelAsync(versionId, request);
// Step 3: Upload image
try
{
using (var stream = file.OpenReadStream())
{
await _imageStorageService.SaveImageAsync(level.Id, stream);
}
}
catch
{
// Rollback: Delete created level
await _layoutService.DeleteLevelAsync(level.Id);
throw;
}
return CreatedAtAction(nameof(GetLevel), new { levelId = level.Id }, dto);
}
```
---
### 4. File Download (JavaScript Interop)
**Challenge:** Download byte[] as file from Blazor WASM.
**Solution:** Generate data URL and trigger download via JS
```csharp
private async Task DownloadImage()
{
if (State.PreviewImage == null) return;
var base64 = Convert.ToBase64String(State.PreviewImage);
var fileName = $"{State.SelectedLevel.LayoutLevelId}_background.png";
await JS.InvokeVoidAsync("eval",
$@"
const link = document.createElement('a');
link.href = 'data:image/png;base64,{base64}';
link.download = '{fileName}';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
");
}
```
**Alternative (Cleaner):**
Create `wwwroot/download.js`:
```javascript
window.downloadFile = (fileName, base64Data) => {
const link = document.createElement('a');
link.href = `data:image/png;base64,${base64Data}`;
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
```
Then in Blazor:
```csharp
await JS.InvokeVoidAsync("downloadFile", fileName, base64);
```
---
## Extension Guide
### Adding New Dialog
1. **Create Dialog Component:**
```razor
@* NewFeatureDialog.razor *@
@inject MapManagerApiService ApiService
@inject ISnackbar Snackbar
<MudDialog>
<DialogContent>
<!-- Form fields -->
</DialogContent>
<DialogActions>
<MudButton OnClick="Cancel">Cancel</MudButton>
<MudButton Color="Color.Primary" OnClick="Submit">Submit</MudButton>
</DialogActions>
</MudDialog>
@code {
[CascadingParameter] private IMudDialogInstance? MudDialog { get; set; }
[Parameter] public SomeDto Data { get; set; }
private void Cancel() => MudDialog?.Cancel();
private async Task Submit()
{
// Call API
// Close dialog
MudDialog?.Close(DialogResult.Ok(result));
}
}
```
2. **Register in Parent Component:**
```csharp
private async Task OpenNewFeatureDialog()
{
var dialog = await DialogService.ShowAsync<NewFeatureDialog>(
"Title",
new DialogParameters { ["Data"] = someData });
var result = await dialog.Result;
if (result != null && !result.Canceled)
{
Snackbar.Add("Success!", Severity.Success);
await RefreshData();
}
}
```
---
### Adding New API Endpoint
1. **Backend Controller:**
```csharp
[HttpPost("custom-action")]
public async Task<IActionResult> CustomAction([FromBody] CustomRequest request)
{
var result = await _service.DoSomethingAsync(request);
return Ok(result);
}
```
2. **Frontend API Service:**
```csharp
public async Task<CustomResponse> CustomActionAsync(CustomRequest request)
{
var response = await _httpClient.PostAsJsonAsync(
$"{_baseUrl}api/layouts/custom-action", request);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<CustomResponse>();
}
```
3. **Use in State:**
```csharp
public async Task PerformCustomActionAsync(CustomRequest request)
{
IsLoading = true;
NotifyStateChanged();
try
{
var result = await _apiService.CustomActionAsync(request);
// Update state
}
finally
{
IsLoading = false;
NotifyStateChanged();
}
}
```
---
### Performance Optimization
**1. Debounce Search:**
```csharp
private Timer? searchTimer;
private void OnSearchKeyUp(KeyboardEventArgs e)
{
searchTimer?.Dispose();
searchTimer = new Timer(async _ =>
{
await InvokeAsync(async () =>
{
await State.LoadLayoutsAsync(searchText);
});
}, null, 500, Timeout.Infinite); // 500ms debounce
}
```
**2. Lazy Load Images:**
```csharp
// Only load image when level selected
public async Task SelectLevelAsync(LayoutLevelDto level)
{
SelectedLevel = level;
IsLoadingPreview = true;
NotifyStateChanged();
// Load preview data
PreviewData = await _apiService.GetLayoutDataAsync(level.Id);
// Load image separately (can be large)
PreviewImage = await _apiService.GetLayoutImageAsync(level.Id);
IsLoadingPreview = false;
NotifyStateChanged();
}
```
**3. Cache API Responses:**
```csharp
private Dictionary<Guid, LayoutDataDto> _previewCache = new();
public async Task<LayoutDataDto> GetLayoutDataCachedAsync(Guid levelId)
{
if (_previewCache.TryGetValue(levelId, out var cached))
return cached;
var data = await _apiService.GetLayoutDataAsync(levelId);
_previewCache[levelId] = data;
return data;
}
```
---
## Testing
### Unit Tests
```csharp
[Fact]
public async Task CreateLevel_WithImage_ShouldExtractDimensions()
{
// Arrange
var service = new FileSystemImageStorageService(logger);
using var stream = File.OpenRead("test_1024x768.png");
// Act
var (width, height) = await service.GetImageDimensionsAsync(stream);
// Assert
Assert.Equal(1024, width);
Assert.Equal(768, height);
}
```
### Integration Tests
```csharp
[Fact]
public async Task E2E_CreateLayoutWithLevel()
{
// Create layout
var layout = await apiService.CreateLayoutAsync(new CreateLayoutRequest
{
LayoutId = "test",
LayoutName = "Test"
});
// Create version
var version = await apiService.CreateVersionAsync(layout.Id, new CreateLayoutVersionRequest
{
Version = "1.0"
});
// Create level with image
using var imageStream = File.OpenRead("test.png");
var level = await apiService.CreateLevelWithImageAsync(
version.Id, "floor_1", 0, 0.05, 0, 0, imageStream, "test.png");
// Assert
Assert.NotNull(level);
Assert.Equal("floor_1", level.LayoutLevelId);
Assert.NotNull(level.EditorSettings);
Assert.True(level.EditorSettings.ImageWidth > 0);
}
```
---
## Troubleshooting
### Common Issues
**1. CORS Errors:**
```
Access to XMLHttpRequest at 'https://localhost:5001/api/layouts' from origin 'https://localhost:5002'
has been blocked by CORS policy
```
**Fix:** Configure CORS in backend `Program.cs`:
```csharp
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
{
policy.WithOrigins("https://localhost:5002")
.AllowAnyMethod()
.AllowAnyHeader();
});
});
app.UseCors();
```
**2. Image Upload 413 Payload Too Large:**
**Fix:** Increase max request size:
```csharp
// Program.cs
builder.Services.Configure<FormOptions>(options =>
{
options.MultipartBodyLengthLimit = 10 * 1024 * 1024; // 10 MB
});
// Also in web.config for IIS
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="10485760" />
</requestFiltering>
</security>
</system.webServer>
```
**3. State Not Updating:**
Check:
- Subscribed to `OnStateChanged` event?
- Calling `StateHasChanged()` in event handler?
- Disposed subscription to prevent memory leaks?
```csharp
protected override async Task OnInitializedAsync()
{
State.OnStateChanged += StateHasChanged; // ✅
await State.LoadLayoutsAsync();
}
public void Dispose()
{
State.OnStateChanged -= StateHasChanged; // ✅ Important!
}
```
---
## Best Practices
1. **Always validate input before API calls**
2. **Handle exceptions and show user-friendly messages**
3. **Use loading indicators for async operations**
4. **Dispose subscriptions and timers**
5. **Keep components small and focused**
6. **Extract reusable logic into services**
7. **Use typed DTOs, avoid magic strings**
8. **Log errors for debugging**
9. **Test with real data (large images, many nodes)**
10. **Profile performance for bottlenecks**
---
## Future Improvements
- [ ] Batch operations (delete multiple levels)
- [ ] Undo/Redo for state changes
- [ ] Keyboard shortcuts
- [ ] Drag & drop image upload
- [ ] Image cropping/editing in browser
- [ ] Multi-select in tree (Ctrl+Click)
- [ ] Export selected layouts to ZIP
- [ ] Real-time collaboration (SignalR)
- [ ] Offline support (IndexedDB cache)
- [ ] Mobile-responsive layout
---
## References
- [MudBlazor Documentation](https://mudblazor.com/)
- [Blazor WebAssembly Guide](https://learn.microsoft.com/en-us/aspnet/core/blazor/)
- [ImageSharp Documentation](https://docs.sixlabors.com/api/ImageSharp/)
- [SVG Specification](https://www.w3.org/TR/SVG2/)
- [VDMA LIF Standard](../VDMA_LIF_Standard.md)
---
**Questions? Contact: dev-team@phenikaa.com**

View File

@@ -0,0 +1,587 @@
# LayoutManager - User Guide
**Version:** 1.0
**Last Updated:** 2024-12-02
**Author:** AI Assistant
---
## 📋 Table of Contents
1. [Overview](#overview)
2. [User Interface](#user-interface)
3. [Features](#features)
4. [Workflows](#workflows)
5. [Tips & Best Practices](#tips--best-practices)
---
## Overview
**LayoutManager** là công cụ quản lý bản đồ (layouts) cho robot AGV/AMR. Nó cho phép:
- Tạo và quản lý layouts với versioning
- Upload background images (floor plans, SLAM maps)
- Cấu hình coordinate system (resolution, origin)
- Preview layouts với nodes, edges, stations
- Export/Import VDMA LIF format
### Key Concepts
```
Layout (Warehouse, Factory, ...)
└─ Version (v1.0, v2.0, ...)
└─ Level (floor_1, floor_2, ...)
├─ Background Image (PNG)
├─ Coordinate System (Resolution, Origin)
└─ Map Elements (Nodes, Edges, Stations)
```
**Terminology:**
- **Layout:** Container cao nhất (e.g., "Warehouse A", "Factory Floor")
- **Version:** Phiên bản của layout, hỗ trợ rollback/versioning
- **Level:** Tầng/lớp của map (e.g., "Ground Floor", "Basement")
- **Background Image:** Ảnh nền (floor plan hoặc SLAM map)
- **Resolution:** Tỷ lệ chuyển đổi pixels → meters
- **Origin:** Gốc tọa độ trong hệ thống (meters)
---
## User Interface
### Page Layout
```
┌─────────────────────────────────────────────────────────────┐
│ Layout Manager [Search...] [Import] [Add] │
├──────────────┬──────────────────────────────────────────────┤
│ │ │
│ TREE │ PREVIEW │
│ PANEL │ PANEL │
│ │ │
│ Layouts │ ┌──────────────────────────────┐ │
│ ├─ Layout1 │ │ │ │
│ │ └─ v1.0 │ │ Background Image │ │
│ │ └─L1 │ │ + Nodes + Edges │ │
│ └─ Layout2 │ │ │ │
│ │ └──────────────────────────────┘ │
│ │ [Download] [Replace Image] │
│ │ │
│ │ Layout Info | Elements | Settings │
│ │ │
│ │ [Edit Layout] [Export LIF] [Refresh] │
└──────────────┴──────────────────────────────────────────────┘
```
### Components
#### **1. Toolbar**
- **Search Box:** Filter layouts by name
- **Import Button:** Import VDMA LIF files (coming soon)
- **Add Layout:** Create new layout
#### **2. Tree Panel (Left)**
Hierarchical view:
```
📋 Layouts
├─ 🗺️ Factory Layout [Active]
│ └─ 📜 v1.0 [Active]
│ ├─ 🏢 floor_1
│ └─ 🏢 floor_2
└─ 🗺️ Warehouse
└─ 📜 v1.0
└─ 🏢 ground_floor
```
**Icons:**
- 🗺️ Layout
- 📜 Version
- 🏢 Level
**Context Menus:**
- **Layout:** Add Version, Activate/Deactivate, Delete
- **Version:** Add Level, Delete
- **Level:** Edit Settings, Delete
#### **3. Preview Panel (Right)**
**Preview Canvas:**
- Background image (if uploaded)
- Nodes (red circles)
- Edges (blue lines)
- Stations (green squares)
**Action Buttons:**
- **Download Image:** Download background PNG
- **Replace Image:** Upload new background PNG
- **Edit Layout:** Open LayoutEditor (coming soon)
- **Export LIF:** Export to VDMA LIF format (coming soon)
- **Refresh:** Reload preview data
**Information Grid:**
```
┌──────────────────────────────────────────────────────────┐
│ LAYOUT INFO ELEMENTS LAYOUT SETTINGS │
│ Layout: Factory Nodes: 45 Resolution: 0.05 │
│ Version: 1.0 Edges: 60 Origin: (0, 0) m │
│ Level: floor_1 Stations: 12 Image: 1024×768 │
│ Physical: 51.2×38.4│
└──────────────────────────────────────────────────────────┘
```
---
## Features
### 1. Create Layout
**Steps:**
1. Click **"Add Layout"** button
2. Fill in dialog:
- **Layout ID:** Unique identifier (e.g., `warehouse_a`)
- **Layout Name:** Display name (e.g., "Warehouse A")
- **Description:** Optional description
3. Click **"Create"**
**Result:** New layout appears in tree
**Notes:**
- Layout ID must be unique
- Default status: Inactive
- Created by current logged-in user
---
### 2. Create Version
**Steps:**
1. Right-click **Layout** → "Add Version"
2. Fill in dialog:
- **Version:** Version number (e.g., `1.0`, `2.1`)
- **Description:** Optional notes about this version
3. Click **"Create"**
**Result:** New version appears under layout
**Notes:**
- Version can be any string
- First version is automatically active
- Multiple versions can exist, but only one active per layout
---
### 3. Create Level with Image
**Steps:**
1. Right-click **Version** → "Add Level"
2. Fill in dialog:
**Basic Info:**
- **Level ID:** Unique identifier (e.g., `floor_1`)
- **Level Order:** Display order (0, 1, 2, ...)
**Image Upload (REQUIRED):**
- Click **"Choose PNG File"**
- Select PNG file (max 10MB)
- ✅ Dimensions auto-extracted (e.g., 1024 × 768 px)
**Coordinate System:**
- **Resolution:** Meters per pixel (default: 0.05 m/px)
- **Origin X:** X coordinate of origin (default: 0 m)
- **Origin Y:** Y coordinate of origin (default: 0 m)
- 📊 Physical size calculated: `ImageSize × Resolution`
3. Click **"Create Level"**
4. ⏳ Wait for upload (progress indicator shows)
**Result:**
- Level created with image
- Preview shows background image
- Settings saved with image dimensions
**Notes:**
- Image upload is **REQUIRED** (cannot create level without image)
- Backend extracts ImageWidth, ImageHeight automatically
- Physical bounds calculated: `[0, 0] → [ImageWidth × Resolution, ImageHeight × Resolution]`
**Example:**
```
Image: 1024 × 768 pixels
Resolution: 0.05 m/px
→ Physical Size: 51.2 × 38.4 meters
→ Bounds: (0, 0) → (51.2, 38.4)
```
---
### 4. Edit Level Settings
**Steps:**
1. Right-click **Level** → "Edit Settings"
2. Modify:
- **Resolution:** Change m/px ratio
- **Origin X, Y:** Adjust coordinate system origin
3. See real-time physical size update
4. Click **"Save Changes"**
**Result:**
- Settings updated
- Preview recalculates display
- Physical size reflects new resolution
**Use Cases:**
- Adjust resolution after measuring real-world distances
- Shift origin to align with building coordinates
- Recalibrate after finding measurement errors
**Notes:**
- Image dimensions NOT editable (fixed when uploaded)
- To change image, use "Replace Image" button
---
### 5. Download Background Image
**Steps:**
1. Select **Level** in tree
2. Preview shows image
3. Click **"Download Image"**
4. File saves to Downloads folder (e.g., `floor_1_background.png`)
**Use Cases:**
- Backup original images
- Share floor plans with team
- Use in other tools (CAD, graphics editors)
---
### 6. Replace Background Image
**Steps:**
1. Select **Level** in tree
2. Click **"Replace Image"**
3. Choose new PNG file
4. ⏳ Upload progress
5. ✅ Preview automatically refreshes
**Result:**
- New image replaces old one
- Image dimensions updated
- Physical size recalculated
**Use Cases:**
- Update floor plan after renovations
- Replace low-res with high-res image
- Correct uploaded wrong file
**Notes:**
- Old image is overwritten (not versioned)
- Image dimensions can change
- Resolution/origin settings preserved
---
### 7. Activate/Deactivate Layout
**Steps:**
1. Right-click **Layout** → "Activate" or "Deactivate"
2. Badge updates (green "Active" or no badge)
**Active vs Inactive:**
- **Active:** Layout is currently in use, can be used by robots
- **Inactive:** Layout archived, cannot be used
**Rules:**
- Only one layout can be active at a time (future: multiple active)
- Must deactivate before deleting
- Active layouts have visual badge
---
### 8. Delete Operations
#### **Delete Level**
1. Right-click **Level** → "Delete"
2. Confirm dialog
3. Level removed, image deleted
#### **Delete Version**
1. Right-click **Version** → "Delete"
2. Confirm dialog
3. Version + all levels removed
#### **Delete Layout**
1. Must be **deactivated** first
2. Right-click **Layout** → "Delete"
3. Confirm dialog
4. Layout + all versions + levels removed
**Safety:**
- Cannot delete active layouts
- Confirmation dialog prevents accidents
- Cascade delete removes children
---
## Workflows
### Workflow 1: New Map from Floor Plan
**Scenario:** You have a PNG floor plan, need to create navigable map.
```
1. Prepare PNG floor plan (clean, high contrast)
2. Create Layout ("Factory A")
3. Create Version ("1.0")
4. Create Level with Image
- Upload floor plan PNG
- Set resolution (measure 1 meter = X pixels)
- Set origin (usually 0,0 or building corner)
5. Open LayoutEditor (future)
- Add nodes (waypoints)
- Connect edges (paths)
- Define stations (pickup/dropoff)
6. Test & Deploy
```
**Tips:**
- Measure resolution: Put tape measure on floor, count pixels in photo
- Typical resolution: 0.01 - 0.1 m/px
- Origin at bottom-left corner simplifies coordinates
---
### Workflow 2: SLAM Map Integration
**Scenario:** Robot generated SLAM map, need to import.
```
1. Export SLAM map as PNG from robot software
2. Note SLAM map metadata:
- Resolution (from SLAM config)
- Origin (from SLAM config)
3. Create Layout → Version → Level
- Upload SLAM map PNG
- Enter exact resolution from SLAM
- Enter exact origin from SLAM
4. Verify alignment:
- Real-world distances match calculated
- Origin aligns with robot's coordinate system
5. Add nodes at known positions
6. Deploy
```
**Tips:**
- SLAM resolution usually in config file (e.g., `resolution: 0.05`)
- SLAM origin often in map YAML (e.g., `origin: [-10.0, -10.0, 0.0]`)
- Verify by measuring known features (doors, walls)
---
### Workflow 3: Update Existing Map
**Scenario:** Floor layout changed, need to update map.
```
1. Select existing Level
2. Option A: Minor changes
- Open LayoutEditor
- Adjust nodes/edges
Option B: Major changes (new floor plan)
- Click "Replace Image"
- Upload new floor plan
- Adjust resolution/origin if needed
3. Update nodes/edges to match new layout
4. Test with robot
5. If good: Keep version
If issues: Create new version, revert if needed
```
**Tips:**
- Always test after image replacement
- Consider creating new version for major changes
- Keep old version as backup
---
### Workflow 4: Multi-Floor Building
**Scenario:** Building with multiple floors.
```
Layout: "Building A"
└─ Version: "1.0"
├─ Level: "basement" (order: 0)
│ - Image: basement_plan.png
│ - Origin: (0, 0)
├─ Level: "ground_floor" (order: 1)
│ - Image: ground_plan.png
│ - Origin: (0, 0)
└─ Level: "floor_2" (order: 2)
- Image: floor2_plan.png
- Origin: (0, 0)
```
**Key Points:**
- Use **Level Order** to sort floors (0 = lowest)
- Use **same resolution** for all floors if possible
- Use **same origin** convention (e.g., SW corner of building)
- Each level has independent image and coordinate system
---
## Tips & Best Practices
### Image Preparation
**DO:**
- Use high-resolution images (at least 1024 px on shortest side)
- Clean floor plan (remove furniture, labels if possible)
- High contrast (walls dark, floor light or vice versa)
- Accurate scale (measure real-world distances)
- PNG format (lossless, supports transparency)
**DON'T:**
- Use JPEG (lossy compression, artifacts)
- Include skewed/distorted photos (correct perspective first)
- Mix different scales in one image
- Use images with text overlays (remove first)
### Resolution Guidelines
| Environment | Typical Resolution | Notes |
|-------------|-------------------|-------|
| Small indoor | 0.01 - 0.02 m/px | High precision |
| Medium indoor | 0.05 m/px | Good balance |
| Large warehouse | 0.1 m/px | Larger area coverage |
| Outdoor | 0.2 - 0.5 m/px | Lower precision OK |
**How to measure:**
1. Place object of known size in scene (e.g., 1m ruler)
2. Count pixels in photo
3. Resolution = RealSize / PixelCount
**Example:**
- Ruler: 1 meter
- Pixels: 20 pixels
- Resolution: 1m / 20px = 0.05 m/px
### Naming Conventions
**Layout IDs:**
- Use lowercase, underscores
- Examples: `warehouse_a`, `factory_floor_1`, `office_building_a`
**Layout Names:**
- Use Title Case, spaces OK
- Examples: "Warehouse A", "Factory Floor 1", "Office Building A"
**Versions:**
- Semantic versioning: `Major.Minor` (e.g., 1.0, 1.1, 2.0)
- Or date-based: `2024.12.02`
- Or descriptive: `production`, `testing`, `backup`
**Level IDs:**
- Descriptive: `floor_1`, `basement`, `ground_floor`, `roof`
- Or numbered: `level_0`, `level_1`, `level_2`
### Version Management
**When to create new version:**
- Major layout changes (walls added/removed)
- Complete re-mapping
- Switching from floor plan to SLAM map
- Before risky changes (for rollback)
**When to update existing version:**
- Minor adjustments (node positions)
- Adding new nodes/edges
- Tweaking resolution/origin
- Bug fixes
### Data Organization
```
Production System:
└─ Warehouse Layout [Active]
├─ v2.1 [Active] ← Current production
├─ v2.0 ← Previous stable
└─ v1.0 ← Original
Testing System:
└─ Warehouse Layout [Active]
└─ v3.0-beta [Active] ← Testing new layout
```
**Strategy:**
- Keep 2-3 old versions for rollback
- Use testing layout for experiments
- Activate in production only after thorough testing
### Performance Tips
- **Image size:** Keep < 2048×2048 px for good performance
- **File size:** Keep < 5 MB for fast upload
- **Compression:** Use PNG with optimized compression
- **Lazy loading:** Only preview shows on selection (not all at once)
### Troubleshooting
**Problem: Image looks distorted**
- Cause: Wrong aspect ratio or preserveAspectRatio setting
- Fix: Check image dimensions, re-upload if needed
**Problem: Coordinates don't match reality**
- Cause: Wrong resolution or origin
- Fix: Measure real-world distance, recalculate resolution
**Problem: Upload fails**
- Cause: File too large (> 10 MB) or not PNG
- Fix: Compress image, convert to PNG
**Problem: Preview blank**
- Cause: No image uploaded or image load failed
- Fix: Check console for errors, re-upload image
---
## Keyboard Shortcuts (Future)
| Shortcut | Action |
|----------|--------|
| `Ctrl+N` | New Layout |
| `Ctrl+F` | Focus Search |
| `Del` | Delete Selected |
| `F5` | Refresh Preview |
| `Ctrl+E` | Edit Level Settings |
---
## Related Documentation
- [API Implementation Guide](./API_IMPLEMENTATION_GUIDE.md)
- [Database Design](./DATABASE_DESIGN_DISCUSSION.md)
- [Testing Guide](./TESTING_GUIDE.md)
- [LayoutEditor Guide](./LAYOUTEDITOR_USER_GUIDE.md) (coming soon)
---
**Need Help?**
- Check Console (F12) for error messages
- Review [Testing Guide](./TESTING_GUIDE.md) for common issues
- Contact: support@phenikaa.com

View File

@@ -0,0 +1,306 @@
# MapEditor Documentation (V2)
**Version:** 4.0
**Date:** 2024-12-02
**Status:** ✅ Backend Complete | 🚧 Frontend In Progress
---
## 📚 Documentation Files
### **Backend Documentation**
#### 1. **DATABASE_DESIGN_DISCUSSION.md** ⭐
**Purpose:** Database schema design & rationale
**Contents:**
- Discussion summary with user (DangNV)
- Complete database schema (11 tables, 79 columns)
- Design evolution (counter → GUID naming)
- Coordinate system design
- Key decision points with rationale
- Alternative approaches considered
- Implementation phases and status
- VDMA LIF compliance details
**Size:** ~800 lines
**Audience:** Database designers, architects, developers
**Use Case:** Understanding database design decisions
---
#### 2. **API_IMPLEMENTATION_GUIDE.md** ⭐
**Purpose:** REST API implementation for MapEditor
**Contents:**
- 7 Controllers (42+ endpoints) detailed documentation
- Services layer architecture (14 services)
- Smart edge creation logic (auto node detection)
- Cascade delete logic (orphan cleanup)
- Enum types (OrientationType, RotationDirection)
- DTOs & Shared project (31 files)
- Configuration & dependency injection
- Deployment guide
**Size:** ~650 lines
**Audience:** Backend developers, API consumers, AI assistants
**Use Case:** Implementing/consuming the REST API
---
### **Frontend Documentation** 🆕
#### 3. **LAYOUTMANAGER_USER_GUIDE.md** ⭐ NEW
**Purpose:** User guide for LayoutManager page
**Contents:**
- UI overview & component layout
- Step-by-step feature guides
- Create Layout/Version/Level
- Upload & manage images
- Edit level settings
- Download/replace images
- Workflows (floor plan, SLAM map, multi-floor)
- Tips & best practices
- Troubleshooting guide
**Size:** ~650 lines
**Audience:** End users, QA testers, product managers
**Use Case:** Learning how to use LayoutManager
---
#### 4. **LAYOUTMANAGER_TECHNICAL.md** ⭐ NEW
**Purpose:** Technical documentation for developers
**Contents:**
- Architecture overview (Blazor + ASP.NET Core)
- Component structure & hierarchy
- State management (LayoutManagerState)
- API integration (MapManagerApiService)
- Implementation details:
- Custom tree view rendering
- SVG preview with responsive viewBox
- Image upload with dimension extraction
- File download via JavaScript interop
- Extension guide (adding dialogs, endpoints)
- Performance optimization tips
- Testing strategies
**Size:** ~750 lines
**Audience:** Frontend developers, AI assistants
**Use Case:** Understanding & extending LayoutManager code
---
#### 5. **TESTING_GUIDE.md** 🆕
**Purpose:** Testing checklist & troubleshooting
**Contents:**
- Quick start (run backend + frontend)
- Comprehensive test checklist (50+ test cases)
- Common issues & fixes
- Expected results & benchmarks
- Test data recommendations
**Size:** ~300 lines
**Audience:** QA testers, developers
**Use Case:** Testing LayoutManager functionality
---
## 🎯 Quick Start
### For End Users
1. **Learn the UI:** Read `LAYOUTMANAGER_USER_GUIDE.md`
2. **Test the App:** Follow `TESTING_GUIDE.md`
3. **Access App:** Navigate to `/layout-manager` in browser
### For AI Assistants
**Backend:**
1. Database Design → `DATABASE_DESIGN_DISCUSSION.md`
2. API Implementation → `API_IMPLEMENTATION_GUIDE.md`
**Frontend:**
1. UI Architecture → `LAYOUTMANAGER_TECHNICAL.md`
2. User Workflows → `LAYOUTMANAGER_USER_GUIDE.md`
### For Backend Developers
1. **Understand the System:**
- Database: Read `DATABASE_DESIGN_DISCUSSION.md`
- API: Read `API_IMPLEMENTATION_GUIDE.md`
2. **Implement Features:**
- Controllers: See `API_IMPLEMENTATION_GUIDE.md` → Controllers section
- Services: See `API_IMPLEMENTATION_GUIDE.md` → Services section
- Database: See `DATABASE_DESIGN_DISCUSSION.md` → Schema section
3. **Deploy:**
- Apply migrations: `dotnet ef database update`
- Configure `appsettings.json`
- Run: `dotnet run`
### For Frontend Developers
1. **API Reference:** Read `API_IMPLEMENTATION_GUIDE.md`
2. **Component Architecture:** Read `LAYOUTMANAGER_TECHNICAL.md`
3. **DTOs:** Use types from `RobotNet10.MapEditor.Shared` project
4. **Extend UI:** See `LAYOUTMANAGER_TECHNICAL.md` → Extension Guide
### For QA/Testers
1. **Testing Checklist:** Read `TESTING_GUIDE.md`
2. **User Guide:** Read `LAYOUTMANAGER_USER_GUIDE.md`
3. **Report Issues:** Use TESTING_GUIDE troubleshooting section
---
## 📊 Project Statistics
### Backend
| Metric | Value |
|--------|-------|
| **Database Tables** | 11 |
| **Database Columns** | 79 |
| **Foreign Keys** | 14 |
| **Indexes** | 28 |
| **API Controllers** | 7 |
| **API Endpoints** | 42+ |
| **Service Classes** | 14 |
| **DTO Classes** | 31 |
| **Enum Types** | 3 |
| **Migrations** | 4 |
| **Backend Code** | ~6,000 lines |
### Frontend (LayoutManager)
| Metric | Value |
|--------|-------|
| **Pages** | 1 |
| **Components** | 8 |
| **Dialogs** | 5 |
| **Services** | 2 |
| **State Classes** | 1 |
| **Models** | 1 |
| **Frontend Code** | ~2,500 lines |
| **Documentation** | ~2,400 lines |
---
## 🚀 Key Features
### Backend
- ✅ VDMA LIF 1.0.0 Compliant
- ✅ Multi-level layout support
- ✅ Version control
- ✅ Smart edge creation (auto node detection)
- ✅ Cascade delete with orphan cleanup
- ✅ Type-safe enums
- ✅ Dual coordinate system (World meters + Image pixels)
- ✅ Image management with ImageSharp
- ✅ Auto dimension extraction from PNG
- ✅ Scalable (100k+ nodes/edges per level)
- ✅ Import/Export VDMA LIF JSON
### Frontend (LayoutManager)
- ✅ Hierarchical tree view (Layout → Version → Level)
- ✅ Create layouts with image upload (single request)
- ✅ Auto-extract image dimensions (client + server)
- ✅ Edit coordinate system (Resolution, Origin)
- ✅ SVG preview canvas (responsive, no overflow)
- ✅ Download/Replace background images
- ✅ Real-time preview refresh
- ✅ Context menus for quick actions
- ✅ Search & filter layouts
- ✅ Activate/Deactivate layouts
- ✅ Clean, modern UI (MudBlazor)
---
## 📞 Related Files
### Implementation
- **Backend (MapManager):** `srcs/RobotNet10/Commons/RobotNet10.MapManager/`
- **Frontend (MapEditor):** `srcs/RobotNet10/Components/RobotNet10.MapEditor/`
- **Host App (RobotApp):** `srcs/RobotNet10/RobotApp/RobotNet10.RobotApp.Client/`
- **Shared DTOs:** `srcs/RobotNet10/RobotNet10.MapEditor.Shared/`
### Documentation (This Folder)
- **Database Design:** `DATABASE_DESIGN_DISCUSSION.md`
- **API Implementation:** `API_IMPLEMENTATION_GUIDE.md`
- **User Guide:** `LAYOUTMANAGER_USER_GUIDE.md` 🆕
- **Technical Guide:** `LAYOUTMANAGER_TECHNICAL.md` 🆕
- **Testing Guide:** `TESTING_GUIDE.md` 🆕
### Other
- **VDMA LIF Schema:** `srcs/RobotNet10/Commons/RobotNet10.MapManager/lif-schema.json`
- **Integration Guide:** `srcs/RobotNet10/Components/RobotNet10.MapEditor/INTEGRATION_GUIDE.md`
---
## 🔄 Version History
| Version | Date | Description |
|---------|------|-------------|
| 1.0 | 2024-11-26 | Initial schema (10 tables) |
| 2.0 | 2024-11-26 | GUID naming + EditorSettings |
| 2.5 | 2024-11-26 | Coordinate system |
| 3.0 | 2024-11-26 | Complete REST API (7 controllers) |
| 3.1 | 2024-11-26 | Enum types + Documentation consolidation |
| **4.0** | **2024-12-02** | **LayoutManager Frontend + Comprehensive Docs** 🆕 |
---
## ✅ Status
### Backend
**Database:** ✅ Complete (11 tables, 4 migrations)
**API:** ✅ Complete (7 controllers, 42+ endpoints)
**Services:** ✅ Complete (14 services)
**DTOs:** ✅ Complete (31 DTOs, 3 enums)
**Image Processing:** ✅ Complete (ImageSharp integration)
**Build:** ✅ SUCCESS (0 warnings, 0 errors)
### Frontend
**LayoutManager Page:** ✅ Complete
**Components:** ✅ Complete (8 components, 5 dialogs)
**State Management:** ✅ Complete
**API Integration:** ✅ Complete
**SVG Preview:** ✅ Complete (responsive, no overflow)
**Image Upload:** ✅ Complete (with dimension extraction)
**Build:** ✅ SUCCESS (0 warnings, 0 errors)
### Documentation
**Database Design:** ✅ Complete
**API Guide:** ✅ Complete
**User Guide:** ✅ Complete (650 lines) 🆕
**Technical Guide:** ✅ Complete (750 lines) 🆕
**Testing Guide:** ✅ Complete (300 lines) 🆕
**Total Docs:** ~2,400 lines 🆕
---
## 🚧 Next Steps
- [ ] LayoutEditor page (SVG canvas editor)
- [ ] Import/Export VDMA LIF (UI)
- [ ] Real-time collaboration (SignalR)
- [ ] Undo/Redo functionality
- [ ] Keyboard shortcuts
- [ ] Mobile-responsive improvements
---
**Last Updated:** 2024-12-02
**Maintained by:** AI Assistant & DangNV
**Status:** ✅ LayoutManager Ready for Production Testing

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,469 @@
# Vehicle Type Management UI - Architecture Design
**Project:** RobotNet10.MapEditor
**Component:** Vehicle Type Management UI
**Version:** 1.0
**Date:** 2024-12-01
**Status:** 📋 Architecture Design Phase
---
## 📋 Mục Lục
1. [Tổng Quan](#tổng-quan)
2. [Cấu Trúc UI](#cấu-trúc-ui)
3. [Components Chi Tiết](#components-chi-tiết)
4. [Integration Points](#integration-points)
5. [State Management](#state-management)
6. [API Integration](#api-integration)
7. [Actions Editor Design](#actions-editor-design)
---
## 🎯 Tổng Quan
### Mục Đích
Xây dựng giao diện web để quản lý VehicleType (thêm, sửa, xóa) với các tính năng:
- **CRUD Operations**: Create, Read, Update, Delete VehicleType
- **Search & Filter**: Tìm kiếm và lọc theo trạng thái
- **Usage Tracking**: Hiển thị thông tin sử dụng (Node/Edge properties)
- **Actions Editor**: UI để thêm/sửa/xóa Actions (JSON editor với form builder)
- **Integration**: Tích hợp vào LayoutEditor để quản lý Node/Edge VehicleProperties
### Vị Trí Trong Ứng Dụng
- **VehicleType Management**: Page riêng (`/vehicle-types`)
- **LayoutEditor Integration**: VehicleType selector và Actions editor trong Node/Edge properties panels
---
## 🏗️ Cấu Trúc UI
### 1. VehicleType Management Page
```
VehicleTypeManagerComponent.razor
├── Layout: MudContainer (Full width)
│ ├── Header Section
│ │ ├── Title: "Vehicle Type Management"
│ │ └── Create Button
│ │
│ ├── Main Content (MudGrid)
│ │ ├── Left Panel (MudGrid xs="12" md="8")
│ │ │ └── VehicleTypeListPanel.razor
│ │ │
│ │ └── Right Panel (MudGrid xs="12" md="4")
│ │ └── VehicleTypeDetailsPanel.razor
│ │
│ └── Dialogs
│ ├── CreateVehicleTypeDialog.razor
│ ├── EditVehicleTypeDialog.razor
│ └── DeleteVehicleTypeDialog.razor
```
### 2. Component Hierarchy
```
VehicleTypeManagerComponent.razor (Main Page)
├── VehicleTypeListPanel.razor (Left Panel)
│ ├── SearchBar.razor
│ │ ├── MudTextField (Search input)
│ │ └── MudButton (Clear)
│ │
│ ├── FilterBar.razor
│ │ ├── MudSelect (Filter by Active Status)
│ │ └── MudButton (Clear filters)
│ │
│ └── VehicleTypeTable.razor
│ ├── MudTable (Sortable)
│ │ ├── Columns:
│ │ │ - VehicleTypeId (sortable)
│ │ │ - VehicleTypeName (sortable)
│ │ │ - IsActive (Chip, sortable)
│ │ │ - Usage Count (Badge, sortable)
│ │ │ - Actions (Icon buttons)
│ │ └── Rows: VehicleTypeDto[]
│ │
│ └── MudPagination
│ └── Items per page selector
│ └── Import/Export Bar
│ ├── MudButton (Import JSON)
│ └── MudButton (Export JSON)
├── VehicleTypeDetailsPanel.razor (Right Panel)
│ ├── Basic Info Section
│ │ ├── VehicleTypeId (read-only)
│ │ ├── VehicleTypeName
│ │ ├── Description
│ │ └── IsActive (Chip)
│ │
│ ├── Usage Statistics Section
│ │ ├── Node Properties Count
│ │ ├── Edge Properties Count
│ │ ├── Total Usage Count
│ │ └── Can Delete indicator
│ │
│ ├── Specifications Section (expandable)
│ │ └── JSON viewer/formatted display
│ │
│ └── Actions Preview Section (expandable)
│ └── JSON viewer/formatted display
└── Dialogs/
├── CreateVehicleTypeDialog.razor
│ ├── MudDialog
│ ├── Form (MudForm)
│ │ ├── VehicleTypeId (required, validated)
│ │ ├── VehicleTypeName (required, validated)
│ │ ├── Description (optional)
│ │ └── ActionsEditor.razor (optional)
│ │ ├── Form Builder Mode
│ │ └── JSON Preview Panel
│ │
│ └── Actions (Cancel, Create)
├── ImportVehicleTypesDialog.razor
│ ├── MudDialog
│ ├── File upload (JSON)
│ ├── Preview imported data
│ └── Actions (Cancel, Import)
├── EditVehicleTypeDialog.razor
│ ├── Same structure as CreateDialog
│ ├── Pre-filled with existing data
│ └── Confirmation dialog (if has unsaved changes)
└── DeleteVehicleTypeDialog.razor
├── MudDialog
├── Confirmation message
├── Usage warning (if in use)
└── Actions (Cancel, Delete)
```
---
## 🧩 Components Chi Tiết
### 1. VehicleTypeListPanel.razor
**Purpose:** Hiển thị danh sách VehicleType dạng table với search và filter
**Features:**
- Search bar (tìm trong VehicleTypeId và VehicleTypeName)
- Filter by Active Status (All, Active, Inactive)
- Table với sortable columns
- Row actions: View Details, Edit, Delete, View Usage
- Loading state
- Empty state
**Props:**
```csharp
[Parameter] public VehicleTypeManagerState State { get; set; } = null!;
[Parameter] public EventCallback<VehicleTypeDto> OnSelectVehicleType { get; set; }
[Parameter] public EventCallback<VehicleTypeDto> OnEdit { get; set; }
[Parameter] public EventCallback<VehicleTypeDto> OnDelete { get; set; }
```
### 2. VehicleTypeDetailsPanel.razor
**Purpose:** Hiển thị chi tiết VehicleType được chọn
**Features:**
- Basic info display
- Usage statistics (from usage API)
- Specifications preview (JSON formatted)
- Actions preview (JSON formatted với syntax highlighting)
- Quick actions (Edit, Delete buttons)
**Props:**
```csharp
[Parameter] public VehicleTypeDto? SelectedVehicleType { get; set; }
[Parameter] public VehicleTypeUsageInfoDto? UsageInfo { get; set; }
[Parameter] public EventCallback OnEdit { get; set; }
[Parameter] public EventCallback OnDelete { get; set; }
```
### 3. CreateVehicleTypeDialog.razor
**Purpose:** Dialog để tạo VehicleType mới
**Form Fields:**
- VehicleTypeId: TextField (required, max 64, regex validation)
- VehicleTypeName: TextField (required, max 256)
- Description: TextArea (optional, max 10000)
- Specifications: JSON Editor (optional, max 50000)
- Actions: ActionsEditor component (optional, max 50000)
**Validation:**
- Client-side validation với MudBlazor validation
- Real-time validation feedback
- Error messages từ API
### 4. EditVehicleTypeDialog.razor
**Purpose:** Dialog để sửa VehicleType
**Same as CreateDialog but:**
- Pre-filled với existing data
- VehicleTypeId is read-only (immutable)
- Can update IsActive status
### 5. DeleteVehicleTypeDialog.razor
**Purpose:** Dialog xác nhận xóa VehicleType
**Features:**
- Confirmation message
- Usage warning nếu đang được sử dụng
- Display usage details (NodePropertiesCount, EdgePropertiesCount)
- Disable delete button nếu có references
### 6. ActionsEditor.razor (Shared Component)
**Purpose:** Reusable component để edit Actions JSON
**Features:**
- **Form Builder Mode** (Primary - Default):
- Add/Remove action items
- Form fields cho mỗi action:
- actionType (TextField)
- actionDescription (TextArea)
- requirementType (Select: REQUIRED, CONDITIONAL, OPTIONAL)
- blockingType (TextField)
- actionParameters (Key-value pairs editor)
- **JSON Preview Panel** (below form):
- Real-time JSON preview
- Formatted display
- Read-only
- Copy to clipboard button
- **JSON Editor Mode** (Advanced - Optional):
- TextArea với JSON formatting
- Syntax highlighting (if possible)
- JSON validation
- Sync với Form Builder Mode
**Props:**
```csharp
[Parameter] public string? ActionsJson { get; set; }
[Parameter] public EventCallback<string?> ActionsJsonChanged { get; set; }
[Parameter] public bool ReadOnly { get; set; } = false;
[Parameter] public bool ShowAdvancedMode { get; set; } = true;
```
---
## 🔗 Integration Points
### 1. LayoutEditor Integration
**NodePropertiesEditor.razor:**
- ✅ VehicleType selector đã có
- ⏳ Cần thêm Actions editor dialog
- ⏳ Cần load VehicleTypes từ API
**EdgePropertiesEditor.razor:**
- ✅ VehicleType selector đã có
- ⏳ Cần thêm Actions editor dialog (nếu cần)
- ⏳ Cần load VehicleTypes từ API
**Actions Editor Dialog:**
- Shared component `ActionsEditor.razor`
- Mở từ "Edit Actions" button trong NodePropertiesEditor
- Save về NodeVehiclePropertyDto.Actions (JSON string)
### 2. API Integration
**MapManagerApiService.cs:**
Cần thêm các methods:
```csharp
// VehicleType CRUD
Task<List<VehicleTypeDto>> GetVehicleTypesAsync(bool? isActive = null);
Task<VehicleTypeDto?> GetVehicleTypeAsync(Guid id);
Task<VehicleTypeDto?> GetVehicleTypeByStringIdAsync(string vehicleTypeId);
Task<List<VehicleTypeDto>> SearchVehicleTypesAsync(string query);
Task<VehicleTypeDto> CreateVehicleTypeAsync(CreateVehicleTypeRequest request);
Task<VehicleTypeDto> UpdateVehicleTypeAsync(Guid id, UpdateVehicleTypeRequest request);
Task DeleteVehicleTypeAsync(Guid id);
Task<VehicleTypeUsageInfoDto> GetVehicleTypeUsageAsync(Guid id);
```
---
## 📊 State Management
### VehicleTypeManagerState.cs
**Purpose:** Quản lý state cho VehicleType Management page
**Properties:**
```csharp
public class VehicleTypeManagerState
{
// Data
public List<VehicleTypeDto> VehicleTypes { get; private set; } = new();
public VehicleTypeDto? SelectedVehicleType { get; private set; }
public VehicleTypeUsageInfoDto? SelectedUsageInfo { get; private set; }
// Filters
public string? SearchQuery { get; set; }
public bool? FilterIsActive { get; set; }
// UI State
public bool IsLoading { get; private set; }
public bool IsSaving { get; private set; }
public string? ErrorMessage { get; private set; }
// Events
public event Action? OnStateChanged;
// Methods
Task LoadVehicleTypesAsync();
Task SearchVehicleTypesAsync(string query);
Task FilterByActiveStatusAsync(bool? isActive);
Task SelectVehicleTypeAsync(Guid id);
Task CreateVehicleTypeAsync(CreateVehicleTypeRequest request);
Task UpdateVehicleTypeAsync(Guid id, UpdateVehicleTypeRequest request);
Task DeleteVehicleTypeAsync(Guid id);
Task LoadUsageInfoAsync(Guid id);
}
```
---
## 🎨 Actions Editor Design
### Form Builder Mode
**UI Structure:**
```
ActionsEditor.razor
├── Mode Toggle (Form Builder / JSON Editor)
├── Form Builder Mode
│ ├── Actions List (MudList)
│ │ └── ActionItem.razor (for each action)
│ │ ├── actionType (TextField)
│ │ ├── actionDescription (TextArea)
│ │ ├── requirementType (Select: REQUIRED/CONDITIONAL/OPTIONAL)
│ │ ├── blockingType (TextField)
│ │ └── actionParameters (KeyValueEditor)
│ │ ├── Add Parameter button
│ │ └── Parameter rows (key, value)
│ │
│ └── Add Action button
└── JSON Editor Mode (Advanced)
├── MudTextArea (JSON text)
└── Validation feedback
```
**ActionItem.razor:**
- Collapsible card
- Form fields cho action properties
- Delete button
- Move up/down buttons (reorder)
**KeyValueEditor.razor:**
- Table với key-value pairs
- Add/Remove rows
- Validation
### JSON Structure
```json
[
{
"actionType": "pick",
"actionDescription": "Pick up item",
"requirementType": "REQUIRED",
"blockingType": "HARD",
"actionParameters": [
{"key": "itemId", "value": "12345"},
{"key": "height", "value": "1.5"}
]
}
]
```
---
## 📝 Implementation Checklist
### Phase 1: API Service Extension
- [ ] Extend MapManagerApiService với VehicleType methods
- [ ] Test API calls
### Phase 2: State Management
- [ ] Create VehicleTypeManagerState.cs
- [ ] Implement state management methods
- [ ] Test state updates
### Phase 3: Main Page & List Panel
- [ ] Create VehicleTypeManagerComponent.razor
- [ ] Create VehicleTypeListPanel.razor
- [ ] Create SearchBar.razor
- [ ] Create FilterBar.razor
- [ ] Create VehicleTypeTable.razor (with sorting)
- [ ] Implement pagination
- [ ] Implement search & filter logic
- [ ] Implement sorting logic
- [ ] Implement import/export (JSON)
### Phase 4: Details Panel
- [ ] Create VehicleTypeDetailsPanel.razor
- [ ] Implement usage info display
- [ ] Implement Actions JSON preview
### Phase 5: Dialogs
- [ ] Create CreateVehicleTypeDialog.razor
- [ ] Create EditVehicleTypeDialog.razor (with confirmation)
- [ ] Create DeleteVehicleTypeDialog.razor
- [ ] Create ImportVehicleTypesDialog.razor
- [ ] Implement form validation
### Phase 6: Actions Editor
- [ ] Create ActionsEditor.razor
- [ ] Create ActionItem.razor
- [ ] Create KeyValueEditor.razor
- [ ] Implement form builder mode (primary)
- [ ] Implement JSON preview panel (real-time)
- [ ] Implement JSON editor mode (advanced, optional)
- [ ] Implement JSON validation
### Phase 7: LayoutEditor Integration
- [ ] Update NodePropertiesEditor với Actions editor dialog
- [ ] Update EdgePropertiesEditor (nếu cần)
- [ ] Load VehicleTypes trong LayoutEditorState
- [ ] Test integration
### Phase 8: Testing & Polish
- [ ] Test all CRUD operations
- [ ] Test search & filter
- [ ] Test Actions editor
- [ ] Test LayoutEditor integration
- [ ] UI/UX improvements
---
## 🚀 Next Steps
1. **Clarify Requirements:**
- Confirm "form khác" là form nào?
- Confirm Actions editor requirements (form builder vs JSON editor)
- Confirm UI/UX preferences
2. **Start Implementation:**
- Begin with API service extension
- Then state management
- Then UI components
---
**Status:** 📋 Ready for Implementation
**Version:** 1.0
**Last Updated:** 2024-12-01

View File

@@ -0,0 +1,96 @@
# VDA 5050 Integration / Tích hợp VDA 5050
## 📋 Overview / Tổng quan
MapEditor convert map data thành VDA 5050 Order messages để gửi đến robot.
## 🔄 Conversion Concept
```mermaid
graph LR
subgraph "VDMA LIF Map"
MapStations[Stations<br/>Physical locations]
MapINodes[InteractionNodes<br/>Approach points]
MapActions[Actions<br/>Behaviors]
MapEdges[Edges<br/>Navigation paths]
end
subgraph "VDA 5050 Order"
OrderNodes[Nodes<br/>Waypoints]
OrderEdges[Edges<br/>Paths]
OrderActions[Actions<br/>Tasks]
end
MapINodes -->|Map position<br/>+ deviations| OrderNodes
MapActions -->|Copy action<br/>properties| OrderActions
MapEdges -->|Map trajectory<br/>+ constraints| OrderEdges
OrderActions -.->|Embedded in| OrderNodes
style MapStations fill:#e6f3ff
style MapINodes fill:#e6ffe6
style MapActions fill:#f0e6ff
style MapEdges fill:#fff0e6
style OrderNodes fill:#e6f3ff
style OrderEdges fill:#fff0e6
style OrderActions fill:#f0e6ff
```
## 🔄 Order Generation Flow
```mermaid
sequenceDiagram
participant FM as FleetManager
participant PF as PathFinder
participant Gen as OrderGenerator
participant DB as Database
FM->>PF: FindPath(startStation, endStation)
PF->>DB: Load Stations & Edges
PF->>PF: Run A* algorithm
PF-->>FM: Path (stationIds[], edgeIds[])
FM->>Gen: GenerateOrder(path, vehicleType)
Gen->>DB: Load InteractionNodes for stations
Gen->>DB: Load Actions for nodes
Gen->>Gen: Build Order structure
Gen-->>FM: VDA 5050 Order object
```
## 📋 Mapping Rules
**InteractionNode → VDA 5050 Node**:
- nodeId: InteractionNode.interactionNodeId
- sequenceId: Even numbers (0, 2, 4, ...)
- nodePosition: From InteractionNode position
- actions: From Actions table
**Edge → VDA 5050 Edge**:
- edgeId: Edge.edgeId
- sequenceId: Odd numbers (1, 3, 5, ...)
- trajectory: From Edge.trajectory
- maxSpeed: From Edge.maxSpeed
**Action → VDA 5050 Action**:
- actionType: Action.actionType
- blockingType: Action.blockingType
- actionParameters: Action.actionParameters
## 🚗 Vehicle Type Filtering
**Filtering Logic**:
- Load InteractionNodes for each station
- Check vehicleTypeIds compatibility
- Filter edges by vehicleTypeIds
- Build Order với filtered elements only
## 🔗 Related Documents / Tài liệu Liên quan
- [MapEditor Overview](README.md) - Tổng quan MapEditor
- [PathFinding](PathFinding.md) - Tính toán path trước khi generate order
- [VDA 5050 Integration](../vda5050/README.md) - Chi tiết về VDA 5050 protocol
---
**Last Updated**: 2025-11-13

View File

@@ -0,0 +1,109 @@
# VDMA LIF Standard / Chuẩn VDMA LIF
## 📋 Overview / Tổng quan
VDMA LIF (Layout Interchange Format) là chuẩn quốc tế để mô tả factory layout cho AGV/AMR systems.
## 🎯 Conceptual Model / Mô hình Khái niệm
```mermaid
graph LR
Map[Map<br/>Factory Layout<br/>Coordinate system] --> Stations[Stations<br/>Physical locations<br/>pickup, dropoff, charging]
Map --> Edges[Edges<br/>Navigation paths<br/>Connections between stations]
Map --> Zones[Zones<br/>Special areas<br/>Restricted, slow-speed]
Map --> VTypes[VehicleTypes<br/>Robot specifications<br/>Dimensions, envelopes]
Stations --> INodes[InteractionNodes<br/>Approach points<br/>Position + deviation]
INodes --> Actions[Actions<br/>Robot behaviors<br/>pick, drop, charge, wait]
Edges -.->|references| Stations
style Map fill:#ffe6e6
style Stations fill:#e6f3ff
style INodes fill:#e6ffe6
style Edges fill:#fff0e6
style Actions fill:#f0e6ff
style Zones fill:#ffe6f0
style VTypes fill:#f0ffe6
```
## 📐 Object Hierarchy / Phân cấp Đối tượng
```mermaid
graph TD
LIF[VDMA LIF Document]
LIF --> Meta[MetaInformation<br/>Project ID, Creator, Timestamp]
LIF --> Layout[Layout Properties<br/>layoutId, layoutName<br/>layoutVersion, layoutDescription<br/>layoutLevel]
LIF --> CoordRef[Coordinate Reference Point<br/>Origin x, y]
LIF --> VTypes[VehicleTypes Array]
LIF --> Stations[Stations Array]
LIF --> Edges[Edges Array]
LIF --> Zones[Zones Array]
VTypes --> VType[VehicleType<br/>vehicleTypeId, description<br/>vehicleGeometry<br/>envelopes2d]
Stations --> Station[Station<br/>stationId, stationType<br/>stationPosition]
Station --> INodes[InteractionNodes Array]
INodes --> INode[InteractionNode<br/>interactionNodeId<br/>nodePosition<br/>vehicleTypeIds]
INode --> Actions[Actions Array]
Actions --> Action[Action<br/>actionType, blockingType<br/>actionParameters]
Edges --> Edge[Edge<br/>edgeId, startStationId, endStationId<br/>trajectory, maxSpeed<br/>bidirectional, vehicleTypeIds]
Zones --> Zone[Zone<br/>zoneId, zoneType<br/>polygon geometry]
style LIF fill:#ffe6e6
style Layout fill:#e6f3ff
style Station fill:#e6ffe6
style INode fill:#fff0e6
style Action fill:#f0e6ff
style Edge fill:#ffe6f0
style Zone fill:#f0ffe6
```
## 🔑 Core Concepts / Khái niệm Cốt lõi
**Map / Layout**:
- Top-level container cho tất cả map elements
- Defines coordinate system (origin, resolution)
- Properties: layoutId, name, version, level (floor number)
**Station** (Physical location):
- Represents a physical point trong factory
- Properties: stationId, stationType, stationPosition (x, y, theta)
- Contains one hoặc nhiều InteractionNodes
**InteractionNode** (Approach point):
- Specific position nơi robot interacts với station
- Multiple nodes per station cho different vehicle types
- Properties: interactionNodeId, position, allowedDeviations
- Contains Actions to execute
**Action** (Robot behavior):
- Defines what robot does tại InteractionNode
- Properties: actionType, blockingType, actionParameters
- Blocking types: HARD, SOFT, NONE
**Edge** (Navigation path):
- Connection between two stations
- Properties: edgeId, startStationId, endStationId, trajectory
- bidirectional: true/false
**Zone** (Special area):
- 2D polygon area với special properties
- Zone types: safetyZone, restrictedZone, speedLimitZone
**VehicleType** (Robot specification):
- Defines robot dimensions và capabilities
- Referenced by vehicleTypeIds trong stations, nodes, edges
## 🔗 Related Documents / Tài liệu Liên quan
- [MapEditor Overview](README.md) - Tổng quan MapEditor
- [Database Design](Database_Design.md) - Cấu trúc database cho VDMA LIF
- [Import/Export](ImportExport.md) - Import/Export VDMA LIF JSON
---
**Last Updated**: 2025-11-13