first commit -push
This commit is contained in:
31
RobotNet.MapShares/Dtos/ActionDto.cs
Normal file
31
RobotNet.MapShares/Dtos/ActionDto.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
namespace RobotNet.MapShares.Dtos;
|
||||
|
||||
#nullable disable
|
||||
|
||||
public class ActionDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public Guid MapId { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Content { get; set; }
|
||||
}
|
||||
|
||||
public class ActionCreateModel : ActionDto { }
|
||||
|
||||
public class ActionModel
|
||||
{
|
||||
public string ActionId { get; set; } = "";
|
||||
public string ActionType { get; set; } = "";
|
||||
public string ActionDescription { get; set; } = "";
|
||||
public string BlockingType { get; set; } = "";
|
||||
public ActionParameter[] ActionParameters { get; set; } = [];
|
||||
}
|
||||
|
||||
public class ActionParameter
|
||||
{
|
||||
public string Key { get; set; } = "";
|
||||
public string Value { get; set; } = "";
|
||||
}
|
||||
86
RobotNet.MapShares/Dtos/EdgeDto.cs
Normal file
86
RobotNet.MapShares/Dtos/EdgeDto.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using RobotNet.MapShares.Enums;
|
||||
|
||||
namespace RobotNet.MapShares.Dtos;
|
||||
|
||||
public class EdgeDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid MapId { get; set; }
|
||||
public Guid StartNodeId { get; set; }
|
||||
public Guid EndNodeId { get; set; }
|
||||
|
||||
public double ControlPoint1X { get; set; }
|
||||
public double ControlPoint1Y { get; set; }
|
||||
public double ControlPoint2X { get; set; }
|
||||
public double ControlPoint2Y { get; set; }
|
||||
public TrajectoryDegree TrajectoryDegree { get; set; }
|
||||
|
||||
public double MaxHeight { get; set; }
|
||||
public double MinHeight { get; set; }
|
||||
public DirectionAllowed DirectionAllowed { get; set; }
|
||||
public bool RotationAllowed { get; set; }
|
||||
public double MaxRotationSpeed { get; set; }
|
||||
public double MaxSpeed { get; set; }
|
||||
public double AllowedDeviationXy { get; set; }
|
||||
public double AllowedDeviationTheta { get; set; }
|
||||
|
||||
public string Actions { get; set; } = "";
|
||||
|
||||
public NodeDto? StartNode { get; set; }
|
||||
public NodeDto? EndNode { get; set; }
|
||||
}
|
||||
|
||||
public class EdgeCreateModel
|
||||
{
|
||||
public Guid MapId { get; set; }
|
||||
public double X1 { get; set; }
|
||||
public double Y1 { get; set; }
|
||||
public double X2 { get; set; }
|
||||
public double Y2 { get; set; }
|
||||
public TrajectoryDegree TrajectoryDegree { get; set; }
|
||||
public double ControlPoint1X { get; set; }
|
||||
public double ControlPoint1Y { get; set; }
|
||||
public double ControlPoint2X { get; set; }
|
||||
public double ControlPoint2Y { get; set; }
|
||||
}
|
||||
|
||||
public class EdgeCreateDto
|
||||
{
|
||||
public IEnumerable<EdgeDto> EdgesDto { get; set; } = [];
|
||||
public IEnumerable<Guid> RemoveEdge { get; set; } = [];
|
||||
}
|
||||
|
||||
public class EdgeUpdateModel
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public double MaxSpeed { get; set; }
|
||||
public double MaxHeight { get; set; }
|
||||
public double MinHeight { get; set; }
|
||||
public bool RotationAllowed { get; set; }
|
||||
public double MaxRotationSpeed { get; set; }
|
||||
public double ControlPoint1X { get; set; }
|
||||
public double ControlPoint1Y { get; set; }
|
||||
public double ControlPoint2X { get; set; }
|
||||
public double ControlPoint2Y { get; set; }
|
||||
public DirectionAllowed DirectionAllowed { get; set; }
|
||||
public Guid[] Actions { get; set; } = [];
|
||||
public double AllowedDeviationXy { get; set; }
|
||||
public double AllowedDeviationTheta { get; set; }
|
||||
}
|
||||
|
||||
public class EdgeMapCopyModel : EdgeCreateModel
|
||||
{
|
||||
public Guid StartNodeId { get; set; }
|
||||
public Guid EndNodeId { get; set; }
|
||||
public double MaxSpeed { get; set; }
|
||||
public double MaxHeight { get; set; }
|
||||
public double MinHeight { get; set; }
|
||||
public bool RotationAllowed { get; set; }
|
||||
public double MaxRotationSpeed { get; set; }
|
||||
public DirectionAllowed DirectionAllowed { get; set; }
|
||||
public string Actions { get; set; } = "";
|
||||
public double AllowedDeviationXy { get; set; }
|
||||
public double AllowedDeviationTheta { get; set; }
|
||||
}
|
||||
|
||||
public class EdgeCaculatorModel : EdgeCreateModel { }
|
||||
49
RobotNet.MapShares/Dtos/ElementDto.cs
Normal file
49
RobotNet.MapShares/Dtos/ElementDto.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using RobotNet.MapShares.Property;
|
||||
|
||||
namespace RobotNet.MapShares.Dtos;
|
||||
|
||||
#nullable disable
|
||||
|
||||
public class ElementDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid MapId { get; set; }
|
||||
public Guid ModelId { get; set; }
|
||||
public string ModelName { get; set; }
|
||||
public Guid NodeId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string NodeName { get; set; }
|
||||
public double X { get; set; }
|
||||
public double Y { get; set; }
|
||||
public double Theta { get; set; }
|
||||
public double OffsetX { get; set; }
|
||||
public double OffsetY { get; set; }
|
||||
public bool IsOpen { get; set; }
|
||||
public string Content { get; set; }
|
||||
}
|
||||
|
||||
public class ElementCreateModel
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid MapId { get; set; }
|
||||
public Guid ModelId { get; set; }
|
||||
public Guid NodeId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public double OffsetX { get; set; }
|
||||
public double OffsetY { get; set; }
|
||||
}
|
||||
|
||||
public class ElementUpdateModel
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public double OffsetX { get; set; }
|
||||
public double OffsetY { get; set; }
|
||||
public bool IsOpen { get; set; }
|
||||
public string Content { get; set; }
|
||||
}
|
||||
|
||||
public class ElementPropertyUpdateModel
|
||||
{
|
||||
public ElementProperty[] Properties { get; set; } = [];
|
||||
}
|
||||
31
RobotNet.MapShares/Dtos/ElementModelDto.cs
Normal file
31
RobotNet.MapShares/Dtos/ElementModelDto.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
namespace RobotNet.MapShares.Dtos;
|
||||
|
||||
#nullable disable
|
||||
|
||||
public class ElementModelDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid MapId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public double Width { get; set; }
|
||||
public double Height { get; set; }
|
||||
public int Image1Width { get; set; }
|
||||
public int Image1Height { get; set; }
|
||||
public int Image2Width { get; set; }
|
||||
public int Image2Height { get; set; }
|
||||
public string Content { get; set; }
|
||||
}
|
||||
|
||||
public class ElementModelCreateModel
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public Guid MapId { get; set; }
|
||||
public double Width { get; set; }
|
||||
public double Height { get; set; }
|
||||
}
|
||||
|
||||
public class ElementModelUpdateModel : ElementModelCreateModel
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Content { get; set; }
|
||||
}
|
||||
66
RobotNet.MapShares/Dtos/MapDataExportDto.cs
Normal file
66
RobotNet.MapShares/Dtos/MapDataExportDto.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using RobotNet.MapShares.Enums;
|
||||
|
||||
namespace RobotNet.MapShares.Dtos;
|
||||
|
||||
#nullable disable
|
||||
|
||||
public class MapInfoExportDto
|
||||
{
|
||||
public double OriginX { get; set; }
|
||||
public double OriginY { get; set; }
|
||||
public double Resolution { get; set; }
|
||||
public double ViewX { get; set; }
|
||||
public double ViewY { get; set; }
|
||||
public double ViewWidth { get; set; }
|
||||
public double ViewHeight { get; set; }
|
||||
public string VDA5050 { get; set; }
|
||||
}
|
||||
|
||||
public class ElementModelExportDto : ElementModelDto
|
||||
{
|
||||
public byte[] ImageOpenData { get; set; } = [];
|
||||
public byte[] ImageCloseData { get; set; } = [];
|
||||
}
|
||||
|
||||
public class MapDataExportDto
|
||||
{
|
||||
public long NodeCount { get; set; }
|
||||
public NodeDto[] Nodes { get; set; } = [];
|
||||
public EdgeDto[] Edges { get; set; } = [];
|
||||
public ZoneDto[] Zones { get; set; } = [];
|
||||
public ActionDto[] Actions { get; set; } = [];
|
||||
public ElementModelExportDto[] ElementModels { get; set; } = [];
|
||||
public ElementDto[] Elements { get; set; } = [];
|
||||
public byte[] ImageData { get; set; } = [];
|
||||
}
|
||||
|
||||
public class MapSettingExportDto
|
||||
{
|
||||
public bool NodeNameAutoGenerate { get; set; }
|
||||
public string NodeNameTemplate { get; set; }
|
||||
public double NodeAllowedDeviationXy { get; set; }
|
||||
public double NodeAllowedDeviationTheta { get; set; }
|
||||
|
||||
public double EdgeMinLength { get; set; }
|
||||
public double EdgeStraightMaxSpeed { get; set; }
|
||||
public double EdgeCurveMaxSpeed { get; set; }
|
||||
public double EdgeMaxHeight { get; set; }
|
||||
public double EdgeMinHeight { get; set; }
|
||||
public double EdgeMaxRotationSpeed { get; set; }
|
||||
public DirectionAllowed EdgeDirectionAllowed { get; set; }
|
||||
public bool EdgeRotationAllowed { get; set; }
|
||||
public double EdgeAllowedDeviationXy { get; set; }
|
||||
public double EdgeAllowedDeviationTheta { get; set; }
|
||||
|
||||
public double ZoneMinSquare { get; set; }
|
||||
}
|
||||
|
||||
public class MapExportDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; } = "";
|
||||
public MapInfoExportDto Info { get; set; } = new();
|
||||
public MapDataExportDto Data { get; set; } = new();
|
||||
public MapSettingExportDto Setting { get; set; } = new();
|
||||
}
|
||||
97
RobotNet.MapShares/Dtos/MapDto.cs
Normal file
97
RobotNet.MapShares/Dtos/MapDto.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using RobotNet.MapShares.Enums;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace RobotNet.MapShares.Dtos;
|
||||
|
||||
#nullable disable
|
||||
|
||||
public class MapInfoDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid VersionId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
public double OriginX { get; set; }
|
||||
public double OriginY { get; set; }
|
||||
public double Resolution { get; set; }
|
||||
public double ViewX { get; set; }
|
||||
public double ViewY { get; set; }
|
||||
public double ViewWidth { get; set; }
|
||||
public double ViewHeight { get; set; }
|
||||
public double Width { get; set; }
|
||||
public double Height { get; set; }
|
||||
public bool Active { get; set; }
|
||||
public string VDA5050 { get; set; }
|
||||
}
|
||||
|
||||
public class MapCreateModel
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
[Required]
|
||||
public double OriginX { get; set; }
|
||||
|
||||
[Required]
|
||||
public double OriginY { get; set; }
|
||||
|
||||
[Required]
|
||||
public double Resolution { get; set; }
|
||||
}
|
||||
|
||||
public class MapUpdateModel
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public double OriginX { get; set; }
|
||||
public double OriginY { get; set; }
|
||||
public double Resolution { get; set; }
|
||||
}
|
||||
|
||||
public class MapActiveModel
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public bool Active { get; set; }
|
||||
}
|
||||
public class MapDataDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public double OriginX { get; set; }
|
||||
public double OriginY { get; set; }
|
||||
public double Resolution { get; set; }
|
||||
public double ImageWidth { get; set; }
|
||||
public double ImageHeight { get; set; }
|
||||
public bool Active { get; set; }
|
||||
|
||||
public NodeDto[] Nodes { get; set; } = [];
|
||||
public EdgeDto[] Edges { get; set; } = [];
|
||||
public ZoneDto[] Zones { get; set; } = [];
|
||||
public ActionDto[] Actions { get; set; } = [];
|
||||
public ElementDto[] Elements { get; set; } = [];
|
||||
}
|
||||
|
||||
public class MapSettingDefaultDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; } = "";
|
||||
public bool Active { get; set; }
|
||||
|
||||
public bool NodeNameAutoGenerate { get; set; }
|
||||
public string NodeNameTemplate { get; set; }
|
||||
public double NodeAllowedDeviationXy { get; set; }
|
||||
public double NodeAllowedDeviationTheta { get; set; }
|
||||
|
||||
public double EdgeMinLength { get; set; }
|
||||
public double EdgeStraightMaxSpeed { get; set; }
|
||||
public double EdgeCurveMaxSpeed { get; set; }
|
||||
public double EdgeMaxHeight { get; set; }
|
||||
public double EdgeMinHeight { get; set; }
|
||||
public double EdgeMaxRotationSpeed { get; set; }
|
||||
public DirectionAllowed EdgeDirectionAllowed { get; set; }
|
||||
public bool EdgeRotationAllowed { get; set; }
|
||||
public double EdgeAllowedDeviationXy { get; set; }
|
||||
public double EdgeAllowedDeviationTheta { get; set; }
|
||||
|
||||
public double ZoneMinSquare { get; set; }
|
||||
}
|
||||
34
RobotNet.MapShares/Dtos/NodeDto.cs
Normal file
34
RobotNet.MapShares/Dtos/NodeDto.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using RobotNet.MapShares.Enums;
|
||||
|
||||
namespace RobotNet.MapShares.Dtos;
|
||||
|
||||
#nullable disable
|
||||
|
||||
public class NodeDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid MapId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public double X { get; set; }
|
||||
public double Y { get; set; }
|
||||
public double Theta { get; set; }
|
||||
public Direction Direction { get; set; }
|
||||
public double AllowedDeviationXy { get; set; }
|
||||
public double AllowedDeviationTheta { get; set; }
|
||||
public string Actions { get; set; }
|
||||
public override string ToString() => Name;
|
||||
}
|
||||
|
||||
public class NodeCreateModel : NodeDto { }
|
||||
|
||||
public class NodeUpdateModel
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public double X { get; set; }
|
||||
public double Y { get; set; }
|
||||
public double Theta { get; set; }
|
||||
public double AllowedDeviationXy { get; set; }
|
||||
public double AllowedDeviationTheta { get; set; }
|
||||
public Guid[] Actions { get; set; } = [];
|
||||
}
|
||||
22
RobotNet.MapShares/Dtos/ZoneDto.cs
Normal file
22
RobotNet.MapShares/Dtos/ZoneDto.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using RobotNet.MapShares.Enums;
|
||||
|
||||
namespace RobotNet.MapShares.Dtos;
|
||||
|
||||
public class ZoneDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid MapId { get; set; }
|
||||
public ZoneType Type { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public double X1 { get; set; }
|
||||
public double Y1 { get; set; }
|
||||
public double X2 { get; set; }
|
||||
public double Y2 { get; set; }
|
||||
public double X3 { get; set; }
|
||||
public double Y3 { get; set; }
|
||||
public double X4 { get; set; }
|
||||
public double Y4 { get; set; }
|
||||
}
|
||||
|
||||
public class ZoneCreateModel : ZoneDto { }
|
||||
public class ZoneUpdateModel : ZoneDto { }
|
||||
13
RobotNet.MapShares/Enums/AlignState.cs
Normal file
13
RobotNet.MapShares/Enums/AlignState.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace RobotNet.MapShares.Enums;
|
||||
|
||||
public enum AlignState
|
||||
{
|
||||
HorizontalLeft,
|
||||
HorizontalRight,
|
||||
VerticalTop,
|
||||
VerticalBottom,
|
||||
HorizontalCenter,
|
||||
VerticalCenter,
|
||||
SplitNode,
|
||||
MergeNode,
|
||||
}
|
||||
7
RobotNet.MapShares/Enums/BlockingType.cs
Normal file
7
RobotNet.MapShares/Enums/BlockingType.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace RobotNet.MapShares.Enums;
|
||||
public enum BlockingType
|
||||
{
|
||||
NONE,
|
||||
SOFT,
|
||||
HARD
|
||||
}
|
||||
16
RobotNet.MapShares/Enums/ControlState.cs
Normal file
16
RobotNet.MapShares/Enums/ControlState.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace RobotNet.MapShares.Enums;
|
||||
|
||||
public enum ControlState
|
||||
{
|
||||
Undo,
|
||||
Save,
|
||||
FitScreen,
|
||||
ShowName,
|
||||
ShowGrid,
|
||||
ShowMapSlam,
|
||||
ZoomIn,
|
||||
ZoomOut,
|
||||
Delete,
|
||||
CheckMap
|
||||
}
|
||||
|
||||
9
RobotNet.MapShares/Enums/Direction.cs
Normal file
9
RobotNet.MapShares/Enums/Direction.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace RobotNet.MapShares.Enums;
|
||||
|
||||
public enum Direction
|
||||
{
|
||||
FORWARD,
|
||||
BACKWARD,
|
||||
NONE
|
||||
}
|
||||
|
||||
10
RobotNet.MapShares/Enums/DirectionAllowed.cs
Normal file
10
RobotNet.MapShares/Enums/DirectionAllowed.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace RobotNet.MapShares.Enums;
|
||||
|
||||
public enum DirectionAllowed
|
||||
{
|
||||
None,
|
||||
Forward,
|
||||
Backward,
|
||||
Both,
|
||||
}
|
||||
|
||||
16
RobotNet.MapShares/Enums/EditorState.cs
Normal file
16
RobotNet.MapShares/Enums/EditorState.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace RobotNet.MapShares.Enums;
|
||||
|
||||
public enum EditorState
|
||||
{
|
||||
View,
|
||||
Scaner,
|
||||
NavigationEdit,
|
||||
CreateStraighEdge,
|
||||
CreateCurveEdge,
|
||||
CreateDoubleCurveEdge,
|
||||
CreateZone,
|
||||
SettingZone,
|
||||
CheckError,
|
||||
Move,
|
||||
Copy,
|
||||
}
|
||||
8
RobotNet.MapShares/Enums/TrajectoryDegree.cs
Normal file
8
RobotNet.MapShares/Enums/TrajectoryDegree.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace RobotNet.MapShares.Enums;
|
||||
|
||||
public enum TrajectoryDegree
|
||||
{
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
}
|
||||
34
RobotNet.MapShares/Enums/ZoneType.cs
Normal file
34
RobotNet.MapShares/Enums/ZoneType.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
namespace RobotNet.MapShares.Enums;
|
||||
|
||||
public enum ZoneType
|
||||
{
|
||||
/// <summary>
|
||||
/// Khu vực vận hành. Di chuyển theo tốc độ cài đặt
|
||||
/// </summary>
|
||||
Operating = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Khu vực vận hành có rủi ro. Tốc độ tối đa (trung bình) cho phép là 0.6 m/s
|
||||
/// </summary>
|
||||
OperatingHazard = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Khu vực nguy hiểm. Tốc độ tối đa cho phép là 0.3 m/s (Sử dụng tại vị trí vào sạc hoặc những vị trí có không gian hẹp)
|
||||
/// </summary>
|
||||
Restricted = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Khu vực chuyền tải. Tốc độ tối đa cho phép la 0.3 m/s (Sử dụng tại những vị trí trao đổii tải hoặc có tương tác)
|
||||
/// </summary>
|
||||
LoadTransfer = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Khu vực đặc biệt, cấm người không có quyền trong khu vực này
|
||||
/// </summary>
|
||||
Confined = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Khu vực cấm. Không cho robot di chuyển vào khu vực này
|
||||
/// </summary>
|
||||
Forbidden = 5,
|
||||
}
|
||||
18
RobotNet.MapShares/JsonOptionExtends.cs
Normal file
18
RobotNet.MapShares/JsonOptionExtends.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace RobotNet.MapShares;
|
||||
|
||||
public class JsonOptionExtends
|
||||
{
|
||||
public static readonly JsonSerializerOptions Read = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
WriteIndented = true,
|
||||
};
|
||||
|
||||
public static readonly JsonSerializerOptions Write = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
WriteIndented = true,
|
||||
};
|
||||
}
|
||||
259
RobotNet.MapShares/MapEditorHelper.cs
Normal file
259
RobotNet.MapShares/MapEditorHelper.cs
Normal file
@@ -0,0 +1,259 @@
|
||||
using RobotNet.MapShares.Dtos;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace RobotNet.MapShares;
|
||||
|
||||
public partial class MapEditorHelper
|
||||
{
|
||||
[GeneratedRegex(@"^[a-zA-Z0-9\-_]*$")]
|
||||
private static partial Regex MyRegex();
|
||||
private static readonly Regex NameRegex = MyRegex();
|
||||
|
||||
public static (bool IsSuccess, string returnStr) NameChecking(string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name)) return (false, "Tên không được để trống.");
|
||||
if (name.Length < 3 || name.Length > 127) return (false, "Tên gồm từ 3 đến 127 ký tự");
|
||||
if (!NameRegex.IsMatch(name)) return (false, "Tên chỉ chứa các ký tự thường (a - z), các ký tự hoa (A - Z), chữ số và ký tự '-'");
|
||||
return (true, "");
|
||||
}
|
||||
|
||||
public static IEnumerable<string> NameValidation(string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name)) yield return "Tên không được để trống.";
|
||||
if (name.Length < 3 || name.Length > 127) yield return "Tên gồm từ 3 đến 127 ký tự";
|
||||
if (!NameRegex.IsMatch(name)) yield return "Tên chỉ chứa các ký tự thường (a - z), các ký tự hoa (A - Z), chữ số và ký tự '-'";
|
||||
}
|
||||
|
||||
public static IEnumerable<string> RobotIdValidation(string id)
|
||||
{
|
||||
if (string.IsNullOrEmpty(id) || string.IsNullOrWhiteSpace(id)) yield return "Id không được để trống.";
|
||||
if (id is not null && (id.Length < 1 || id.Length > 127)) yield return "Id gồm từ 1 đến 127 ký tự";
|
||||
if (id is not null && !NameRegex.IsMatch(id)) yield return "Id chỉ chứa các ký tự thường (a - z), kĩ tự in hoa (A - Z), chữ số và ký tự '-'";
|
||||
}
|
||||
|
||||
public static (double X, double Y) CaculateControlPoint(double x1, double y1, double x2, double y2, double angle, double length)
|
||||
{
|
||||
double dx = x2 - x1;
|
||||
double dy = y2 - y1;
|
||||
double a = Math.Atan2(dy, dx) + angle * Math.PI / 180;
|
||||
double d = length * Math.Sqrt(Math.Pow(dx, 2) + Math.Pow(dy, 2));
|
||||
return (x1 + Math.Cos(a) * d, y1 + Math.Sin(a) * d);
|
||||
}
|
||||
|
||||
public static (double X, double Y)? CaculateIntersectionPoint(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4)
|
||||
{
|
||||
if (x1 == x2 && x3 == x4)
|
||||
{
|
||||
if (x1 == x3)
|
||||
{
|
||||
return (x1, y1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (y1 == y2)
|
||||
{
|
||||
return (x3, y1);
|
||||
}
|
||||
|
||||
if (x3 == x4)
|
||||
{
|
||||
return (x3, y1);
|
||||
}
|
||||
|
||||
double mAB = (y2 - y1) / (x2 - x1);
|
||||
double mCD = (y4 - y3) / (x4 - x3);
|
||||
|
||||
if (mAB == mCD)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
double x = (mAB * x1 - mCD * x3 + y3 - y1) / (mAB - mCD);
|
||||
double y = mAB * (x - x1) + y1;
|
||||
|
||||
return (x, y);
|
||||
}
|
||||
|
||||
public static (double PX1, double PY1, double PX2, double PY2) CaculateDoubleControlPoint(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4)
|
||||
{
|
||||
var (X, Y) = CaculateIntersection(x1, y1, x2, y2, x3, y3);
|
||||
var cp2 = CaculateIntersection(x4, y4, x3, y3, x2, y2);
|
||||
|
||||
return (X, Y, cp2.X, cp2.Y);
|
||||
}
|
||||
|
||||
private static (double X, double Y) CaculateIntersection(double x1, double y1, double x2, double y2, double x3, double y3)
|
||||
{
|
||||
double length = Math.Sqrt(Math.Pow(x3 - x2, 2) + Math.Pow(y3 - y2, 2));
|
||||
var angle = Math.Atan2(y2 - y1, x2 - x1);
|
||||
|
||||
var x = x2 + length * Math.Cos(angle) * Math.Sqrt(2) / 2;
|
||||
var y = y2 + length * Math.Sin(angle) * Math.Sqrt(2) / 2;
|
||||
return (x, y);
|
||||
}
|
||||
|
||||
public static NodeDto? GetClosesNode(double x, double y, List<NodeDto> nodes)
|
||||
{
|
||||
NodeDto? finalNode = null;
|
||||
double minDistance = 99;
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
var distance = Math.Sqrt(Math.Pow(node.X - x, 2) + Math.Pow(node.Y - y, 2));
|
||||
if (distance < minDistance)
|
||||
{
|
||||
minDistance = distance;
|
||||
finalNode = node;
|
||||
}
|
||||
}
|
||||
if (minDistance <= 0.35 && finalNode is not null) return finalNode;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static bool NodeInScanZone(double xRef, double yRef, double x1, double y1, double x2, double y2)
|
||||
{
|
||||
double Xmin = Math.Min(x1, x2);
|
||||
double Xmax = Math.Max(x1, x2);
|
||||
double Ymin = Math.Min(y1, y2);
|
||||
double Ymax = Math.Max(y1, y2);
|
||||
|
||||
return (Xmin <= xRef && xRef <= Xmax) && (Ymin <= yRef && yRef <= Ymax);
|
||||
}
|
||||
|
||||
public static bool IsPointInside(double x, double y, ZoneDto zone)
|
||||
{
|
||||
int crossings = 0;
|
||||
|
||||
crossings += DoesRayCross(zone.X1, zone.Y1, zone.X2, zone.Y2, x, y) ? 1 : 0;
|
||||
crossings += DoesRayCross(zone.X2, zone.Y2, zone.X3, zone.Y3, x, y) ? 1 : 0;
|
||||
crossings += DoesRayCross(zone.X3, zone.Y3, zone.X4, zone.Y4, x, y) ? 1 : 0;
|
||||
crossings += DoesRayCross(zone.X4, zone.Y4, zone.X1, zone.Y1, x, y) ? 1 : 0;
|
||||
|
||||
return crossings % 2 == 1;
|
||||
}
|
||||
|
||||
public static bool DoesRayCross(double x1, double y1, double x2, double y2, double x, double y)
|
||||
{
|
||||
if (Math.Min(y1, y2) <= y && y < Math.Max(y1, y2))
|
||||
{
|
||||
double xIntersect = x1 + (y - y1) * (x2 - x1) / (y2 - y1);
|
||||
return x < xIntersect;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static double CalculateQuadrilateralArea(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4)
|
||||
{
|
||||
return 0.5 * Math.Abs(x1 * y2 + x2 * y3 + x3 * y4 + x4 * y1 - (y1 * x2 + y2 * x3 + y3 * x4 + y4 * x1));
|
||||
}
|
||||
|
||||
public static (double x, double y) CurveDegreeTwo(double t, double x1, double y1, double controlPointX, double controlPointY, double x2, double y2)
|
||||
{
|
||||
var x = (1 - t) * (1 - t) * x1 + 2 * t * (1 - t) * controlPointX + t * t * x2;
|
||||
var y = (1 - t) * (1 - t) * y1 + 2 * t * (1 - t) * controlPointY + t * t * y2;
|
||||
return (x, y);
|
||||
}
|
||||
|
||||
public static (double x, double y) CurveDegreeThree(double t, double x1, double y1, double controlPoint1X, double controlPoint1Y, double controlPoint2X, double controlPoint2Y, double x2, double y2)
|
||||
{
|
||||
var x = Math.Pow(1 - t, 3) * x1 + 3 * Math.Pow(1 - t, 2) * t * controlPoint1X + 3 * Math.Pow(t, 2) * (1 - t) * controlPoint2X + Math.Pow(t, 3) * x2; ;
|
||||
var y = Math.Pow(1 - t, 3) * y1 + 3 * Math.Pow(1 - t, 2) * t * controlPoint1Y + 3 * Math.Pow(t, 2) * (1 - t) * controlPoint2Y + Math.Pow(t, 3) * y2;
|
||||
return (x, y);
|
||||
}
|
||||
|
||||
public static (double x, double y) Curve(double t, EdgeCaculatorModel edge)
|
||||
{
|
||||
if (edge.TrajectoryDegree == Enums.TrajectoryDegree.One)
|
||||
{
|
||||
return (edge.X1 + t * (edge.X2 - edge.X1), edge.Y1 + t * (edge.Y2 - edge.Y1));
|
||||
}
|
||||
else if (edge.TrajectoryDegree == Enums.TrajectoryDegree.Two)
|
||||
{
|
||||
return CurveDegreeTwo(t, edge.X1, edge.Y1, edge.ControlPoint1X, edge.ControlPoint1Y, edge.X2, edge.Y2);
|
||||
}
|
||||
else
|
||||
{
|
||||
return CurveDegreeThree(t, edge.X1, edge.Y1, edge.ControlPoint1X, edge.ControlPoint1Y, edge.ControlPoint2X, edge.ControlPoint2Y, edge.X2, edge.Y2);
|
||||
}
|
||||
}
|
||||
|
||||
public static double GetEdgeLength(EdgeCaculatorModel edge)
|
||||
{
|
||||
if (edge.TrajectoryDegree == Enums.TrajectoryDegree.One)
|
||||
{
|
||||
return Math.Round(Math.Sqrt(Math.Pow(edge.X1 - edge.X2, 2) + Math.Pow(edge.Y1 - edge.Y2, 2)), 3);
|
||||
}
|
||||
else if (edge.TrajectoryDegree == Enums.TrajectoryDegree.Two)
|
||||
{
|
||||
var length = Math.Sqrt(Math.Pow(edge.X1 - edge.X2, 2) + Math.Pow(edge.Y1 - edge.Y2, 2));
|
||||
if (length == 0) return 0;
|
||||
double step = 0.1 / length;
|
||||
double distance = 0;
|
||||
|
||||
for (double t = step; t <= 1.001; t += step)
|
||||
{
|
||||
(double x1, double y1) = CurveDegreeTwo(t - step, edge.X1, edge.Y1, edge.ControlPoint1X, edge.ControlPoint1Y, edge.X2, edge.Y2);
|
||||
(double x2, double y2) = CurveDegreeTwo(t, edge.X1, edge.Y1, edge.ControlPoint1X, edge.ControlPoint1Y, edge.X2, edge.Y2);
|
||||
distance += Math.Sqrt(Math.Pow(x1 - x2, 2) + Math.Pow(y1 - y2, 2));
|
||||
}
|
||||
|
||||
return Math.Round(distance, 3);
|
||||
}
|
||||
else
|
||||
{
|
||||
var length = Math.Sqrt(Math.Pow(edge.X1 - edge.X2, 2) + Math.Pow(edge.Y1 - edge.Y2, 2));
|
||||
if (length == 0) return 0;
|
||||
double step = 0.1 / length;
|
||||
double distance = 0;
|
||||
for (double t = step; t <= 1.001; t += step)
|
||||
{
|
||||
var sTime = t - step;
|
||||
(var sx, var sy) = CurveDegreeThree(1 - sTime, edge.X1, edge.Y1, edge.ControlPoint1X, edge.ControlPoint1Y, edge.ControlPoint2X, edge.ControlPoint2Y, edge.X2, edge.Y2);
|
||||
sTime = t;
|
||||
(var ex, var ey) = CurveDegreeThree(1 - sTime, edge.X1, edge.Y1, edge.ControlPoint1X, edge.ControlPoint1Y, edge.ControlPoint2X, edge.ControlPoint2Y, edge.X2, edge.Y2);
|
||||
|
||||
distance += Math.Sqrt(Math.Pow(sx - ex, 2) + Math.Pow(sy - ey, 2));
|
||||
}
|
||||
return Math.Round(distance, 3);
|
||||
}
|
||||
}
|
||||
|
||||
public static NodeDto GetNearByNode(NodeDto orginNode, NodeDto node2, EdgeDto edge, [Range(0, 1)] double ratio)
|
||||
{
|
||||
NodeDto Start = edge.StartNodeId == orginNode.Id ? orginNode : node2;
|
||||
NodeDto End = edge.StartNodeId == orginNode.Id ? node2 : orginNode;
|
||||
var localRatio = edge.StartNodeId == orginNode.Id ? ratio : 1 - ratio;
|
||||
bool isReverse = edge.StartNodeId != orginNode.Id && edge.TrajectoryDegree == Enums.TrajectoryDegree.Three;
|
||||
var (x, y) = Curve(localRatio, new()
|
||||
{
|
||||
X1 = Start.X,
|
||||
Y1 = Start.Y,
|
||||
X2 = End.X,
|
||||
Y2 = End.Y,
|
||||
ControlPoint1X = !isReverse ? edge.ControlPoint1X : edge.ControlPoint2X,
|
||||
ControlPoint1Y = !isReverse ? edge.ControlPoint1Y : edge.ControlPoint2Y,
|
||||
ControlPoint2X = !isReverse ? edge.ControlPoint2X : edge.ControlPoint1X,
|
||||
ControlPoint2Y = !isReverse ? edge.ControlPoint2Y : edge.ControlPoint1Y,
|
||||
TrajectoryDegree = edge.TrajectoryDegree,
|
||||
});
|
||||
return new() { X = x, Y = y };
|
||||
}
|
||||
|
||||
public static double GetAngle(NodeDto originNode, NodeDto Node1, NodeDto Node2)
|
||||
{
|
||||
double BA_x = Node1.X - originNode.X;
|
||||
double BA_y = Node1.Y - originNode.Y;
|
||||
double BC_x = Node2.X - originNode.X;
|
||||
double BC_y = Node2.Y - originNode.Y;
|
||||
// Tính độ dài của các vector AB và BC
|
||||
double lengthAB = Math.Sqrt(BA_x * BA_x + BA_y * BA_y);
|
||||
double lengthBC = Math.Sqrt(BC_x * BC_x + BC_y * BC_y);
|
||||
// Tính tích vô hướng của AB và BC
|
||||
double dotProduct = BA_x * BC_x + BA_y * BC_y;
|
||||
if (lengthAB * lengthBC == 0) return 0;
|
||||
if (dotProduct / (lengthAB * lengthBC) > 1) return 0;
|
||||
if (dotProduct / (lengthAB * lengthBC) < -1) return 180;
|
||||
return Math.Acos(dotProduct / (lengthAB * lengthBC)) * (180.0 / Math.PI);
|
||||
}
|
||||
}
|
||||
43
RobotNet.MapShares/MapManagerExtensions.cs
Normal file
43
RobotNet.MapShares/MapManagerExtensions.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using RobotNet.MapShares.Property;
|
||||
using RobotNet.Script.Expressions;
|
||||
|
||||
namespace RobotNet.MapShares;
|
||||
|
||||
public static class MapManagerExtensions
|
||||
{
|
||||
public static Script.Expressions.ElementProperties GetElementProperties(bool isOpen, string content)
|
||||
{
|
||||
var dbool = new Dictionary<string, bool>();
|
||||
var ddouble = new Dictionary<string, double>();
|
||||
var dint = new Dictionary<string, int>();
|
||||
var dstr = new Dictionary<string, string>();
|
||||
|
||||
var elementProperties = System.Text.Json.JsonSerializer.Deserialize<ElementProperty[]>(content, JsonOptionExtends.Read) ?? [];
|
||||
|
||||
foreach (var ep in elementProperties)
|
||||
{
|
||||
if(ep.Type == "System.Boolean" || ep.Type == "Boolean")
|
||||
{
|
||||
dbool.Add(ep.Name, bool.Parse(ep.DefaultValue));
|
||||
}
|
||||
else if(ep.Type == "System.Double" || ep.Type == "Double")
|
||||
{
|
||||
ddouble.Add(ep.Name, double.Parse(ep.DefaultValue));
|
||||
}
|
||||
else if(ep.Type == "System.Int" || ep.Type == "Int")
|
||||
{
|
||||
dint.Add(ep.Name, int.Parse(ep.DefaultValue));
|
||||
}
|
||||
else if(ep.Type == "System.String" || ep.Type == "String")
|
||||
{
|
||||
dstr.Add(ep.Name, ep.DefaultValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException($"Unsupported property type: {ep.Type}");
|
||||
}
|
||||
}
|
||||
|
||||
return new ElementProperties(isOpen, dbool, ddouble, dint, dstr);
|
||||
}
|
||||
}
|
||||
8
RobotNet.MapShares/Models/ElementExpressionModel.cs
Normal file
8
RobotNet.MapShares/Models/ElementExpressionModel.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace RobotNet.MapShares.Models;
|
||||
|
||||
public class ElementExpressionModel
|
||||
{
|
||||
public string MapName { get; set; } = string.Empty;
|
||||
public string ModelName { get; set; } = string.Empty;
|
||||
public string Expression { get; set; } = string.Empty;
|
||||
}
|
||||
40
RobotNet.MapShares/Models/LoggerModel.cs
Normal file
40
RobotNet.MapShares/Models/LoggerModel.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace RobotNet.MapShares.Models;
|
||||
|
||||
public class LoggerModel
|
||||
{
|
||||
[JsonPropertyName("time")]
|
||||
public string? Time { get; set; }
|
||||
|
||||
[JsonPropertyName("level")]
|
||||
public string? Level { get; set; }
|
||||
|
||||
[JsonPropertyName("message")]
|
||||
public string? Message { get; set; }
|
||||
|
||||
[JsonPropertyName("exception")]
|
||||
public string? Exception { get; set; }
|
||||
|
||||
public string ColorClass => Level switch
|
||||
{
|
||||
"WARN" => "text-warning",
|
||||
"INFO" => "text-info",
|
||||
"DEBUG" => "text-success",
|
||||
"ERROR" => "text-danger",
|
||||
"FATAL" => "text-secondary",
|
||||
_ => "text-muted",
|
||||
};
|
||||
|
||||
public string BackgroundClass => Level switch
|
||||
{
|
||||
"WARN" => "bg-warning text-dark",
|
||||
"INFO" => "bg-info text-dark",
|
||||
"DEBUG" => "bg-success text-white",
|
||||
"ERROR" => "bg-danger text-white",
|
||||
"FATAL" => "bg-secondary text-white",
|
||||
_ => "bg-dark text-white",
|
||||
};
|
||||
|
||||
public bool HasException => !string.IsNullOrEmpty(Exception);
|
||||
}
|
||||
75
RobotNet.MapShares/Models/MapEditorBackupModel.cs
Normal file
75
RobotNet.MapShares/Models/MapEditorBackupModel.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using RobotNet.MapShares.Dtos;
|
||||
using RobotNet.MapShares.Enums;
|
||||
|
||||
namespace RobotNet.MapShares.Models;
|
||||
|
||||
public enum MapEditorBackupType
|
||||
{
|
||||
Node,
|
||||
ControlPoint1Edge,
|
||||
ControlPoint2Edge,
|
||||
Zone,
|
||||
MoveNode,
|
||||
MoveEdge,
|
||||
Copy,
|
||||
MergeNode,
|
||||
SplitNode
|
||||
}
|
||||
|
||||
public class MapEditorBackup
|
||||
{
|
||||
public MapEditorBackupType Type { get; set; }
|
||||
public Guid Id { get; set; }
|
||||
public object Obj { get; set; } = null!;
|
||||
}
|
||||
|
||||
public class MapEditorBackupModel
|
||||
{
|
||||
public MapEditorBackup[] Steps { get; set; } = [];
|
||||
}
|
||||
|
||||
public class PositionBackup
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public double X { get; set; }
|
||||
public double Y { get; set; }
|
||||
}
|
||||
|
||||
public class EdgeBackup
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public TrajectoryDegree TrajectoryDegree { get; set; }
|
||||
public double StartX { get; set; }
|
||||
public double StartY { get; set; }
|
||||
public double EndX { get; set; }
|
||||
public double EndY { get; set; }
|
||||
public double ControlPoint1X { get; set; }
|
||||
public double ControlPoint1Y { get; set; }
|
||||
public double ControlPoint2X { get; set; }
|
||||
public double ControlPoint2Y { get; set; }
|
||||
}
|
||||
|
||||
public class ZoneShapeBackup
|
||||
{
|
||||
public int NodeChange { get; set; }
|
||||
public double X1 { get; set; }
|
||||
public double Y1 { get; set; }
|
||||
public double X2 { get; set; }
|
||||
public double Y2 { get; set; }
|
||||
public double X3 { get; set; }
|
||||
public double Y3 { get; set; }
|
||||
public double X4 { get; set; }
|
||||
public double Y4 { get; set; }
|
||||
}
|
||||
|
||||
public class SplitNodeBackup
|
||||
{
|
||||
public Guid NodeId { get; set; }
|
||||
public Dictionary<Guid, NodeDto> EdgeSplit { get; set; } = [];
|
||||
}
|
||||
|
||||
public class MergeNodeUpdate
|
||||
{
|
||||
public Guid NodeId { get; set; }
|
||||
public Dictionary<Guid, Guid> EdgesMerge { get; set; } = [];
|
||||
}
|
||||
42
RobotNet.MapShares/Property/ElementProperty.cs
Normal file
42
RobotNet.MapShares/Property/ElementProperty.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
namespace RobotNet.MapShares.Property;
|
||||
|
||||
#nullable disable
|
||||
|
||||
public class ElementProperty
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string DefaultValue { get; set; }
|
||||
public bool ReadOnly { get; set; }
|
||||
|
||||
private static readonly Dictionary<string, Type> TypeMap = new()
|
||||
{
|
||||
{ "Boolean", typeof(bool) },
|
||||
{ "Double", typeof(double) },
|
||||
{ "Int", typeof(int) },
|
||||
{ "String", typeof(string) },
|
||||
{ "System.Boolean", typeof(bool) },
|
||||
{ "System.Double", typeof(double) },
|
||||
{ "System.Int", typeof(int) },
|
||||
{ "System.String", typeof(string) },
|
||||
};
|
||||
|
||||
public static string GetPropertyTypeName(Type type)
|
||||
{
|
||||
return TypeMap.FirstOrDefault(kv => kv.Value == type).Key ?? "Unknown";
|
||||
}
|
||||
|
||||
public static readonly Type[] PropertyTypes = [
|
||||
typeof(bool),
|
||||
typeof(double),
|
||||
typeof(int),
|
||||
typeof(string),
|
||||
];
|
||||
|
||||
public Type GetPropertyType()
|
||||
{
|
||||
return TypeMap.TryGetValue(Type ?? string.Empty, out var type) ? type : typeof(object);
|
||||
}
|
||||
|
||||
}
|
||||
14
RobotNet.MapShares/RobotNet.MapShares.csproj
Normal file
14
RobotNet.MapShares/RobotNet.MapShares.csproj
Normal file
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\RobotNet.Script.Expressions\RobotNet.Script.Expressions.csproj" />
|
||||
<ProjectReference Include="..\RobotNet.Shares\RobotNet.Shares.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user