Initial commit
This commit is contained in:
152
docs/MapEditor/Database_Design.md
Normal file
152
docs/MapEditor/Database_Design.md
Normal 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
|
||||
|
||||
67
docs/MapEditor/Design_Rationale.md
Normal file
67
docs/MapEditor/Design_Rationale.md
Normal 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
|
||||
|
||||
106
docs/MapEditor/ImportExport.md
Normal file
106
docs/MapEditor/ImportExport.md
Normal 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
|
||||
|
||||
100
docs/MapEditor/PathFinding.md
Normal file
100
docs/MapEditor/PathFinding.md
Normal 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
303
docs/MapEditor/README.md
Normal 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)
|
||||
94
docs/MapEditor/SVG_Canvas.md
Normal file
94
docs/MapEditor/SVG_Canvas.md
Normal 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
|
||||
|
||||
1234
docs/MapEditor/V2-DangNV/API_IMPLEMENTATION_GUIDE.md
Normal file
1234
docs/MapEditor/V2-DangNV/API_IMPLEMENTATION_GUIDE.md
Normal file
File diff suppressed because it is too large
Load Diff
805
docs/MapEditor/V2-DangNV/DATABASE_DESIGN_DISCUSSION.md
Normal file
805
docs/MapEditor/V2-DangNV/DATABASE_DESIGN_DISCUSSION.md
Normal 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
|
||||
959
docs/MapEditor/V2-DangNV/LAYOUTEDITOR_IMPLEMENTATION.md
Normal file
959
docs/MapEditor/V2-DangNV/LAYOUTEDITOR_IMPLEMENTATION.md
Normal 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()` và `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` và `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` và `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
|
||||
|
||||
879
docs/MapEditor/V2-DangNV/LAYOUTMANAGER_TECHNICAL.md
Normal file
879
docs/MapEditor/V2-DangNV/LAYOUTMANAGER_TECHNICAL.md
Normal 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**
|
||||
|
||||
587
docs/MapEditor/V2-DangNV/LAYOUTMANAGER_USER_GUIDE.md
Normal file
587
docs/MapEditor/V2-DangNV/LAYOUTMANAGER_USER_GUIDE.md
Normal 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
|
||||
|
||||
306
docs/MapEditor/V2-DangNV/README.md
Normal file
306
docs/MapEditor/V2-DangNV/README.md
Normal 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
|
||||
|
||||
1015
docs/MapEditor/V2-DangNV/VEHICLETYPE_API_ARCHITECTURE.md
Normal file
1015
docs/MapEditor/V2-DangNV/VEHICLETYPE_API_ARCHITECTURE.md
Normal file
File diff suppressed because it is too large
Load Diff
469
docs/MapEditor/V2-DangNV/VEHICLETYPE_UI_ARCHITECTURE.md
Normal file
469
docs/MapEditor/V2-DangNV/VEHICLETYPE_UI_ARCHITECTURE.md
Normal 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
|
||||
|
||||
96
docs/MapEditor/VDA5050_Integration.md
Normal file
96
docs/MapEditor/VDA5050_Integration.md
Normal 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
|
||||
|
||||
109
docs/MapEditor/VDMA_LIF_Standard.md
Normal file
109
docs/MapEditor/VDMA_LIF_Standard.md
Normal 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
|
||||
|
||||
Reference in New Issue
Block a user