tg!49+GCRNLJW6d40lq(i8J$zWy2}A1Uc7?-d
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
literal 0
HcmV?d00001
diff --git a/VisionFrame/Assets/Images/Logo_64.png b/VisionFrame/Assets/Images/Logo_64.png
new file mode 100644
index 0000000000000000000000000000000000000000..4ede25abbd483dd48f879e5435a9a518b732e00c
GIT binary patch
literal 10311
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
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/VisionFrame/Assets/Styles/ComboBoxStyles.xaml b/VisionFrame/Assets/Styles/ComboBoxStyles.xaml
new file mode 100644
index 0000000..bba5010
--- /dev/null
+++ b/VisionFrame/Assets/Styles/ComboBoxStyles.xaml
@@ -0,0 +1,158 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/VisionFrame/Assets/Styles/TabControlStyles.xaml b/VisionFrame/Assets/Styles/TabControlStyles.xaml
new file mode 100644
index 0000000..cd6e9cb
--- /dev/null
+++ b/VisionFrame/Assets/Styles/TabControlStyles.xaml
@@ -0,0 +1,298 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/VisionFrame/Models/CatalogModel.cs b/VisionFrame/Models/CatalogModel.cs
new file mode 100644
index 0000000..2430ec1
--- /dev/null
+++ b/VisionFrame/Models/CatalogModel.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace VisionFrame.Models
+{
+ internal class CatalogModel
+ {
+ public bool IsSelected { get; set; }
+ public string Name { get; set; }
+ public string Description { get; set; }
+ public string Icon { get; set; }
+
+ public List Components { get; set; }
+ }
+}
diff --git a/VisionFrame/Models/ComponentModel.cs b/VisionFrame/Models/ComponentModel.cs
new file mode 100644
index 0000000..c064468
--- /dev/null
+++ b/VisionFrame/Models/ComponentModel.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace VisionFrame.Models
+{
+ internal class ComponentModel
+ {
+ public string Icon { get; set; }
+ public string Name { get; set; }
+ public string TargetNode { get; set; }
+ public string TargetModel { get; set; }
+
+ public double W { get; set; }
+ public double H { get; set; }
+ }
+}
diff --git a/VisionFrame/Models/FlowArgModel.cs b/VisionFrame/Models/FlowArgModel.cs
new file mode 100644
index 0000000..249501b
--- /dev/null
+++ b/VisionFrame/Models/FlowArgModel.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Zhaoxi.VisionFrame.Models
+{
+ public class FlowArgModel
+ {
+ public string ArgName { get; set; }
+ public string ArgType { get; set; }
+ public object Value { get; set; }
+ }
+}
diff --git a/VisionFrame/Models/LinkModel.cs b/VisionFrame/Models/LinkModel.cs
new file mode 100644
index 0000000..5a9847e
--- /dev/null
+++ b/VisionFrame/Models/LinkModel.cs
@@ -0,0 +1,56 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace VisionFrame.Models
+{
+ public class LinkModel : ObservableObject
+ {
+ public string StartAnchor { get; set; }
+ private string _endAnchor;
+
+ public string EndAnchor
+ {
+ get { return _endAnchor; }
+ set { SetProperty(ref _endAnchor, value); }
+ }
+
+
+ public string StartId { get; set; }
+ public string EndId { get; set; }
+
+ private double _startX;
+
+ public double StartX
+ {
+ get { return _startX; }
+ set { SetProperty(ref _startX, value); }
+ }
+ private double _startY;
+
+ public double StartY
+ {
+ get { return _startY; }
+ set { SetProperty(ref _startY, value); }
+ }
+ private double _endX;
+
+ public double EndX
+ {
+ get { return _endX; }
+ set { SetProperty(ref _endX, value); }
+ }
+ private double _endY;
+
+ public double EndY
+ {
+ get { return _endY; }
+ set { SetProperty(ref _endY, value); }
+ }
+
+ public string Condition { get; set; }
+ }
+}
diff --git a/VisionFrame/Models/LogModel.cs b/VisionFrame/Models/LogModel.cs
new file mode 100644
index 0000000..96ddc2d
--- /dev/null
+++ b/VisionFrame/Models/LogModel.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace VisionFrame.Models
+{
+ public class LogModel
+ {
+ public DateTime Time { get; set; }
+ public string NodeName { get; set; }
+ public string LogMessage { get; set; }
+ }
+}
diff --git a/VisionFrame/Models/NodeModel.cs b/VisionFrame/Models/NodeModel.cs
new file mode 100644
index 0000000..69f6dda
--- /dev/null
+++ b/VisionFrame/Models/NodeModel.cs
@@ -0,0 +1,64 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Input;
+using Zhaoxi.VisionFrame.Base;
+
+namespace Zhaoxi.VisionFrame.Models
+{
+ public class NodeModel : ObservableObject
+ {
+ // 每个节点实例 唯一编号
+ public string NodeId { get; } = Guid.NewGuid().ToString();
+
+ public object TargetNodeObject { get; set; }
+
+
+ private double _x;
+
+ public double X
+ {
+ get { return _x; }
+ set { SetProperty(ref _x, value); }
+ }
+ private double _y;
+
+ public double Y
+ {
+ get { return _y; }
+ set { SetProperty(ref _y, value); }
+ }
+
+ public double W { get; set; }
+ public double H { get; set; }
+
+
+ private bool _isSelected = false;
+
+ public bool IsSelected
+ {
+ get { return _isSelected; }
+ set { SetProperty(ref _isSelected, value); }
+ }
+
+
+ public bool ShowAnchorT { get; set; } = true;
+ public bool ShowAnchorB { get; set; } = true;
+ public bool ShowAnchorL { get; set; } = true;
+ public bool ShowAnchorR { get; set; } = true;
+
+ public void SetAnchorShow(string anchor, bool show)
+ {
+ PropertyInfo pi = this.GetType().GetProperty("ShowAnchor" + anchor);
+ if (pi != null)
+ {
+ pi.SetValue(this, show);
+ this.OnPropertyChanged("ShowAnchor" + anchor);
+ }
+ }
+ }
+}
diff --git a/VisionFrame/Nodes/LineNode.xaml b/VisionFrame/Nodes/LineNode.xaml
new file mode 100644
index 0000000..b10f496
--- /dev/null
+++ b/VisionFrame/Nodes/LineNode.xaml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
diff --git a/VisionFrame/Nodes/LineNode.xaml.cs b/VisionFrame/Nodes/LineNode.xaml.cs
new file mode 100644
index 0000000..740da45
--- /dev/null
+++ b/VisionFrame/Nodes/LineNode.xaml.cs
@@ -0,0 +1,676 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+
+namespace VisionFrame.Nodes
+{
+ ///
+ /// LineNode.xaml 的交互逻辑
+ ///
+ public partial class LineNode : UserControl, INotifyPropertyChanged
+ {
+ public double X1
+ {
+ get { return (double)GetValue(X1Property); }
+ set { SetValue(X1Property, value); }
+ }
+ public static readonly DependencyProperty X1Property =
+ DependencyProperty.Register("X1", typeof(double),
+ typeof(LineNode),
+ new PropertyMetadata(0.0, OnPropertyChanged));
+
+ public double Y1
+ {
+ get { return (double)GetValue(Y1Property); }
+ set { SetValue(Y1Property, value); }
+ }
+ public static readonly DependencyProperty Y1Property =
+ DependencyProperty.Register("Y1", typeof(double),
+ typeof(LineNode),
+ new PropertyMetadata(0.0, OnPropertyChanged));
+
+
+
+ public double X2
+ {
+ get { return (double)GetValue(X2Property); }
+ set { SetValue(X2Property, value); }
+ }
+ public static readonly DependencyProperty X2Property =
+ DependencyProperty.Register("X2", typeof(double),
+ typeof(LineNode),
+ new PropertyMetadata(0.0, OnPropertyChanged));
+
+ public double Y2
+ {
+ get { return (double)GetValue(Y2Property); }
+ set { SetValue(Y2Property, value); }
+ }
+ public static readonly DependencyProperty Y2Property =
+ DependencyProperty.Register("Y2", typeof(double),
+ typeof(LineNode),
+ new PropertyMetadata(0.0, OnPropertyChanged));
+
+
+ private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ (d as LineNode).InvalidateVisual();
+ }
+
+ public string StartAnchor
+ {
+ get { return (string)GetValue(StartAnchorProperty); }
+ set { SetValue(StartAnchorProperty, value); }
+ }
+ public static readonly DependencyProperty StartAnchorProperty =
+ DependencyProperty.Register("StartAnchor", typeof(string),
+ typeof(LineNode),
+ new PropertyMetadata(""));
+
+ public string EndAnchor
+ {
+ get { return (string)GetValue(EndAnchorProperty); }
+ set { SetValue(EndAnchorProperty, value); }
+ }
+ public static readonly DependencyProperty EndAnchorProperty =
+ DependencyProperty.Register("EndAnchor", typeof(string),
+ typeof(LineNode),
+ new PropertyMetadata(""));
+
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ public Thickness CancelLocation { get; set; }
+
+
+
+ public LineNode()
+ {
+ InitializeComponent();
+ }
+
+ protected override void OnRender(DrawingContext drawingContext)
+ {
+ List points = new List();
+
+ // 起始与终止锚点坐标
+ Point start = new Point(X1, Y1);
+ Point end = new Point(X2, Y2);
+ //
+ if (EndAnchor == "N")
+ {
+ if (X2 < X1)
+ end += new Vector(2, 0);
+ else if (X2 > X1)
+ end += new Vector(-2, 0);
+
+ if (Y2 > Y1)
+ end += new Vector(0, -2);
+ else if (Y2 < Y1)
+ end += new Vector(0, 2);
+ }
+
+ // 起始延伸坐标
+ Point start_extend = new Point(X1, Y1);
+ if (StartAnchor == "T")
+ start_extend = new Point(X1, Y1 - 20);
+ else if (StartAnchor == "B")
+ start_extend = new Point(X1, Y1 + 20);
+ else if (StartAnchor == "L")
+ start_extend = new Point(X1 - 20, Y1);
+ else if (StartAnchor == "R")
+ start_extend = new Point(X1 + 20, Y1);
+
+ // 终点延伸坐标
+ Point end_extend = new Point(end.X, end.Y);
+ if (EndAnchor == "T")
+ end_extend = new Point(end.X, end.Y - 20);
+ else if (EndAnchor == "B")
+ end_extend = new Point(end.X, end.Y + 20);
+ else if (EndAnchor == "L")
+ end_extend = new Point(end.X - 20, end.Y);
+ else if (EndAnchor == "R")
+ end_extend = new Point(end.X + 20, end.Y);
+
+ // 添加第一点
+ points.Add(start);
+
+ // 添加延伸点(针对第一点),起点是哪个锚点
+ points.Add(start_extend);
+
+
+ // 添加动态点
+ // 锚点名称 TBLR 反射
+ MethodInfo mi = this.GetType().GetMethod(StartAnchor + "2" + EndAnchor,
+ BindingFlags.NonPublic | BindingFlags.Instance);
+ if (mi == null) return;
+ Point[] ps = (Point[])mi.Invoke(this, new object[] {
+ start_extend,
+ end_extend
+ });
+ points.AddRange(ps);
+ //points.AddRange(this.B2T(new Point(X1, Y1 + 20), new Point(X2, Y2 - 20)));
+
+
+ // 添加一个延伸点(针对最终点),终点是哪个锚点
+ points.Add(end_extend);
+
+ // 添加最终点
+ points.Add(end);
+
+
+ //StreamGeometry geometry = new StreamGeometry();
+ //using (StreamGeometryContext ctx = geometry.Open())
+ //{
+ // Point first = points.FirstOrDefault();
+ // ctx.BeginFigure(first, false, false);
+ // ctx.PolyLineTo(points, true, true);
+ //}
+
+ var geometry = GetBrokenGeometry(points.ToArray(), false, false);
+
+ if (EndAnchor != "N")
+ {
+ //points[points.Count - 2];// 终延
+ //points[points.Count - 1];// 终
+ Point[] arrow_points = this.GetArrowPoints(points[points.Count - 2], points[points.Count - 1]);
+ var arrow_geo = this.GetBrokenGeometry(arrow_points, true, true);
+ PathGeometry path = PathGeometry.CreateFromGeometry(geometry);
+ // 整合
+ path.AddGeometry(arrow_geo);
+ path.Freeze();
+
+ geometry = path;
+ }
+
+
+ //drawingContext.DrawLine(new Pen(Brushes.Red, 2), new Point(X1, Y1), new Point(X2, Y2));
+ drawingContext.DrawGeometry(
+ Brushes.Orange,
+ new Pen(Brushes.Orange, 2),
+ geometry);
+
+
+ if (ps.Length == 2)
+ {
+ var p = ps[0] + (ps[1] - ps[0]) / 2;
+ CancelLocation = new Thickness(p.X - 7, p.Y - 7, 0, 0);
+ }
+ else if (ps.Length == 1)
+ CancelLocation = new Thickness(ps[0].X - 7, ps[0].Y - 7, 0, 0);
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CancelLocation)));
+ }
+
+ private Geometry GetBrokenGeometry(Point[] points, bool isFill, bool isClose)
+ {
+ StreamGeometry geometry = new StreamGeometry();
+ using (StreamGeometryContext ctx = geometry.Open())
+ {
+ Point first = points.FirstOrDefault();
+ ctx.BeginFigure(first, isFill, isClose);
+ ctx.PolyLineTo(points, true, true);
+ }
+ return geometry;
+ }
+ private Point[] GetArrowPoints(Point start, Point end)
+ {
+ Vector vec = start - end;
+ // 规范化 单位化
+ vec.Normalize();
+ vec *= 8;
+
+ Matrix matrix = new Matrix();
+ matrix.Rotate(20);
+ Point p1 = end + vec * matrix;
+ matrix.Rotate(-40);
+ Point p2 = end + vec * matrix;
+
+ return new Point[] { p1, end, p2 };
+ }
+
+ #region 下锚点
+ // 下对上
+ private Point[] B2T(Point start, Point end)
+ {
+ double cx = (end.X - start.X) / 2;
+ double cy = (end.Y - start.Y) / 2;
+
+ List ps = new List();
+ if (end.Y > start.Y)
+ {
+ ps.Add(new Point(start.X, start.Y + cy));
+ ps.Add(new Point(end.X, start.Y + cy));
+ }
+ else if (end.Y < start.Y)
+ {
+ ps.Add(new Point(start.X + cx, start.Y));
+ ps.Add(new Point(start.X + cx, end.Y));
+ }
+
+ return ps.ToArray();
+ }
+
+ // 下对左
+ private Point[] B2L(Point start, Point end)
+ {
+ double cx = (end.X - start.X) / 2;
+ double cy = (end.Y - start.Y) / 2;
+
+ List ps = new List();
+ if (start.X > end.X)
+ {
+ ps.Add(new Point(end.X, start.Y));
+ }
+ else if (start.Y < end.Y)
+ {
+ ps.Add(new Point(start.X, end.Y));
+ }
+ else
+ {
+ ps.Add(new Point(start.X + cx, start.Y));
+ ps.Add(new Point(end.X - cx, end.Y));
+ }
+ return ps.ToArray();
+ }
+
+ // 下对右
+ private Point[] B2R(Point start, Point end)
+ {
+ double cx = (end.X - start.X) / 2;
+ double cy = (end.Y - start.Y) / 2;
+
+ List ps = new List();
+
+ if (start.X < end.X)
+ {
+ ps.Add(new Point(end.X, start.Y));
+ }
+ else if (start.Y < end.Y)
+ {
+ ps.Add(new Point(start.X, end.Y));
+ }
+ else
+ {
+ ps.Add(new Point(start.X + cx, start.Y));
+ ps.Add(new Point(end.X - cx, end.Y));
+ }
+ return ps.ToArray();
+ }
+
+ // 下对下
+ private Point[] B2B(Point start, Point end)
+ {
+ double cy = end.Y - start.Y;
+
+ List ps = new List();
+ if (start.Y > end.Y)
+ {
+ ps.Add(new Point(end.X, start.Y));
+ }
+ else if (start.Y < end.Y)
+ {
+ ps.Add(new Point(start.X, end.Y));
+ }
+
+ return ps.ToArray();
+ }
+
+ // 下对空
+ private Point[] B2N(Point start, Point end)
+ {
+ List ps = new List();
+ if (start.Y > end.Y)
+ {
+ ps.Add(new Point(end.X, start.Y));
+ }
+ else
+ {
+ ps.Add(new Point(start.X, end.Y));
+ }
+
+ return ps.ToArray();
+ }
+ #endregion
+
+ #region 右锚点
+ // From右锚点To上锚点
+ private Point[] R2T(Point start, Point end)
+ {
+ double cx = (end.X - start.X) / 2;
+
+ List ps = new List();
+
+ if (start.X > end.X)
+ {
+ ps.Add(new Point(start.X, end.Y));
+ }
+ else if (start.Y < end.Y)
+ {
+ ps.Add(new Point(end.X, start.Y));
+ }
+ else
+ {
+ ps.Add(new Point(start.X + cx, start.Y));
+ ps.Add(new Point(end.X - cx, end.Y));
+ }
+
+ return ps.ToArray();
+ }
+
+ // From右锚点To左锚点
+ private Point[] R2L(Point start, Point end)
+ {
+ double cx = (end.X - start.X) / 2;
+ double cy = (end.Y - start.Y) / 2;
+
+ List ps = new List();
+ if (start.X > end.X)
+ {
+ ps.Add(new Point(start.X, start.Y + cy));
+ ps.Add(new Point(end.X, end.Y - cy));
+ }
+ else
+ {
+ ps.Add(new Point(start.X + cx, start.Y));
+ ps.Add(new Point(end.X - cx, end.Y));
+ }
+
+ return ps.ToArray();
+ }
+
+ // From右锚点To右锚点
+ private Point[] R2R(Point start, Point end)
+ {
+ List ps = new List();
+ if (start.X > end.X)
+ {
+ ps.Add(new Point(start.X, end.Y));
+ }
+ else
+ {
+ ps.Add(new Point(end.X, start.Y));
+ }
+
+ return ps.ToArray();
+ }
+
+ // From右锚点To下锚点
+ private Point[] R2B(Point start, Point end)
+ {
+ double cx = (end.X - start.X) / 2;
+ double cy = (end.Y - start.Y) / 2;
+
+ List ps = new List();
+
+ if (start.X > end.X && start.Y > end.Y)
+ {
+ ps.Add(new Point(start.X, start.Y + cy));
+ ps.Add(new Point(end.X, start.Y + cy));
+ }
+ else if (start.X < end.X && start.Y < end.Y)
+ {
+ ps.Add(new Point(start.X + cx, start.Y));
+ ps.Add(new Point(end.X - cx, end.Y));
+ }
+ else if (start.X < end.X)
+ {
+ ps.Add(new Point(end.X, start.Y));
+ }
+ else
+ {
+ ps.Add(new Point(start.X, end.Y));
+ }
+
+ return ps.ToArray();
+ }
+
+ // Fromd右锚点To NULL
+ private Point[] R2N(Point start, Point end)
+ {
+ List ps = new List();
+ if (start.X > end.X)
+ {
+ ps.Add(new Point(start.X, end.Y));
+ }
+ else
+ {
+ ps.Add(new Point(end.X, start.Y));
+ }
+
+ return ps.ToArray();
+ }
+ #endregion
+
+ #region 左锚点
+ // From左锚点To上锚点
+ private Point[] L2T(Point start, Point end)
+ {
+ double cx = (end.X - start.X) / 2;
+ double cy = (end.Y - start.Y) / 2;
+
+ List ps = new List();
+ if (start.X > end.X && start.Y > end.Y)
+ {
+ ps.Add(new Point(start.X + cx, start.Y));
+ ps.Add(new Point(end.X - cx, end.Y));
+ }
+ else if (start.X < end.X && start.Y < end.Y)
+ {
+ ps.Add(new Point(start.X, start.Y + cy));
+ ps.Add(new Point(end.X, end.Y - cy));
+ }
+ else if (start.X < end.X)
+ {
+ ps.Add(new Point(start.X, end.Y));
+ }
+ else
+ {
+ ps.Add(new Point(end.X, start.Y));
+ }
+ return ps.ToArray();
+ }
+
+ // From左锚点To左锚点
+ private Point[] L2L(Point start, Point end)
+ {
+ List ps = new List();
+ if (start.X > end.X)
+ {
+ ps.Add(new Point(end.X, start.Y));
+ }
+ else
+ {
+ ps.Add(new Point(start.X, end.Y));
+ }
+
+ return ps.ToArray();
+ }
+
+ // From左锚点To右锚点
+ private Point[] L2R(Point start, Point end)
+ {
+ double cx = (end.X - start.X) / 2;
+ double cy = (end.Y - start.Y) / 2;
+
+ List ps = new List();
+ if (start.X < end.X)
+ {
+ ps.Add(new Point(start.X, start.Y + cy));
+ ps.Add(new Point(end.X, start.Y + cy));
+ }
+ else
+ {
+ ps.Add(new Point(start.X + cx, start.Y));
+ ps.Add(new Point(start.X + cx, end.Y));
+ }
+
+ return ps.ToArray();
+ }
+
+ // From左锚点To下锚点
+ private Point[] L2B(Point start, Point end)
+ {
+ double cx = (end.X - start.X) / 2;
+ double cy = (end.Y - start.Y) / 2;
+
+ List ps = new List();
+ if (start.X < end.X && start.Y > end.Y)
+ {
+ ps.Add(new Point(start.X, start.Y + cy));
+ ps.Add(new Point(end.X, end.Y - cy));
+ }
+ else if (start.X > end.X && start.Y < end.Y)
+ {
+ ps.Add(new Point(start.X + cx, start.Y));
+ ps.Add(new Point(end.X - cx, end.Y));
+ }
+ else if (start.X > end.X)
+ {
+ ps.Add(new Point(end.X, start.Y));
+ }
+ else
+ {
+ ps.Add(new Point(start.X, end.Y));
+ }
+ return ps.ToArray();
+ }
+
+ // Fromd左锚点To NULL
+ private Point[] L2N(Point start, Point end)
+ {
+ List ps = new List();
+ if (start.X > end.X)
+ {
+ ps.Add(new Point(end.X, start.Y));
+ }
+ else
+ {
+ ps.Add(new Point(start.X, end.Y));
+ }
+
+ return ps.ToArray();
+ }
+ #endregion
+
+ #region 上锚点
+ // From上锚点To上锚点
+ private Point[] T2T(Point start, Point end)
+ {
+ List ps = new List();
+ if (start.Y > end.Y)
+ {
+ ps.Add(new Point(start.X, end.Y));
+ }
+ else
+ {
+ ps.Add(new Point(end.X, start.Y));
+ }
+ return ps.ToArray();
+ }
+
+ // From上锚点To下锚点
+ private Point[] T2B(Point start, Point end)
+ {
+ double cx = (end.X - start.X) / 2;
+ double cy = (end.Y - start.Y) / 2;
+
+ List ps = new List();
+ if (start.Y > end.Y)
+ {
+ ps.Add(new Point(start.X, start.Y + cy));
+ ps.Add(new Point(end.X, end.Y - cy));
+ }
+ else
+ {
+ ps.Add(new Point(start.X + cx, start.Y));
+ ps.Add(new Point(end.X - cx, end.Y));
+ }
+ return ps.ToArray();
+ }
+
+ // From上锚点To左锚点
+ private Point[] T2L(Point start, Point end)
+ {
+ double cx = (end.X - start.X) / 2;
+ double cy = (end.Y - start.Y) / 2;
+
+ List ps = new List();
+ if (start.Y > end.Y && start.X > end.X)
+ {
+ ps.Add(new Point(start.X, start.Y + cy));
+ ps.Add(new Point(end.X, end.Y - cy));
+ }
+ else if (start.Y < end.Y && start.X < end.X)
+ {
+ ps.Add(new Point(start.X + cx, start.Y));
+ ps.Add(new Point(end.X - cx, end.Y));
+ }
+ else if (start.X < end.X)
+ {
+ ps.Add(new Point(start.X, end.Y));
+ }
+ else
+ {
+ ps.Add(new Point(end.X, start.Y));
+ }
+ return ps.ToArray();
+ }
+
+ // From上锚点To右锚点
+ private Point[] T2R(Point start, Point end)
+ {
+ double cx = (end.X - start.X) / 2;
+ double cy = (end.Y - start.Y) / 2;
+
+ List ps = new List();
+ if (start.Y > end.Y && start.X > end.X)
+ {
+ ps.Add(new Point(start.X, end.Y));
+ }
+ else if (start.Y < end.Y && start.X < end.X)
+ {
+ ps.Add(new Point(end.X, start.Y));
+ }
+ else if (start.X < end.X)
+ {
+ ps.Add(new Point(start.X, start.Y + cy));
+ ps.Add(new Point(end.X, end.Y - cy));
+ }
+ else
+ {
+ ps.Add(new Point(start.X + cx, start.Y));
+ ps.Add(new Point(end.X - cx, end.Y));
+ }
+ return ps.ToArray();
+ }
+
+ // Fromd上锚点To NULL
+ private Point[] T2N(Point start, Point end)
+ {
+ List ps = new List();
+ if (start.Y > end.Y)
+ {
+ ps.Add(new Point(start.X, end.Y));
+ }
+ else
+ {
+ ps.Add(new Point(end.X, start.Y));
+ }
+
+ return ps.ToArray();
+ }
+ #endregion
+ }
+}
diff --git a/VisionFrame/Nodes/StartNode.xaml b/VisionFrame/Nodes/StartNode.xaml
new file mode 100644
index 0000000..d2a4af6
--- /dev/null
+++ b/VisionFrame/Nodes/StartNode.xaml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/VisionFrame/Nodes/StartNode.xaml.cs b/VisionFrame/Nodes/StartNode.xaml.cs
new file mode 100644
index 0000000..31ca1d1
--- /dev/null
+++ b/VisionFrame/Nodes/StartNode.xaml.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+using VisionFrame.Base;
+using VisionFrame.Models;
+
+namespace VisionFrame.Nodes
+{
+ ///
+ /// StartNode.xaml 的交互逻辑
+ ///
+ public partial class StartNode : NodeBase
+ {
+ public StartNode()
+ {
+ InitializeComponent();
+ }
+
+ //public ICommand AnchorDownCommand { get; set; }
+ //public ICommand AnchorUpCommand { get; set; }
+
+ private void Ellipse_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+ {
+ // T L R B
+ AnchorDownCommand?.Execute((this.DataContext as NodeModelBase).NodeId + ";B");
+ e.Handled = true;
+ }
+ }
+}
diff --git a/VisionFrame/Nodes/StartNodeModel.cs b/VisionFrame/Nodes/StartNodeModel.cs
new file mode 100644
index 0000000..52c1b92
--- /dev/null
+++ b/VisionFrame/Nodes/StartNodeModel.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using VisionFrame.Base;
+
+namespace VisionFrame.Nodes
+{
+ public class StartNodeModel : NodeModelBase
+ {
+ }
+}
diff --git a/VisionFrame/ViewModels/FlowTabViewModel.cs b/VisionFrame/ViewModels/FlowTabViewModel.cs
new file mode 100644
index 0000000..b03f31c
--- /dev/null
+++ b/VisionFrame/ViewModels/FlowTabViewModel.cs
@@ -0,0 +1,1113 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using Microsoft.Win32;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Media.Media3D;
+using System.Xml.Linq;
+using VisionFrame.Base;
+using VisionFrame.Base.Models;
+using VisionFrame.Models;
+using VisionFrame.Nodes;
+
+namespace VisionFrame.ViewModels
+{
+ public class FlowTabViewModel : ObservableObject, IFlowContext
+ {
+ private bool _isCurrent;
+
+ public bool IsCurrent
+ {
+ get { return _isCurrent; }
+ set
+ {
+ SetProperty(ref _isCurrent, value);
+
+ if (!value) TitleEditVisible = Visibility.Hidden;
+ }
+ }
+
+ #region Title与编辑
+ private string _title;
+
+ public string Title
+ {
+ get { return _title; }
+ set { SetProperty(ref _title, value); }
+ }
+
+
+ private Visibility _titleEditVisible = Visibility.Hidden;
+
+ public Visibility TitleEditVisible
+ {
+ get { return _titleEditVisible; }
+ set { SetProperty(ref _titleEditVisible, value); }
+ }
+
+
+ public ICommand SelectCommand { get; set; }
+ public ICommand EditTitleCommand { get; set; }
+ public ICommand AcceptTitleCommand { get; set; }
+ #endregion
+
+ #region 节点创建
+ public ObservableCollection NodeList { get; set; } =
+ new ObservableCollection();
+
+ public ICommand DragDropCommand { get; set; }
+
+ public ICommand DeleteElementCommand { get; set; }
+ #endregion
+
+ #region 连线集合
+ public ObservableCollection LinkList { get; set; } =
+ new ObservableCollection();
+ #endregion
+
+ #region 节点移动相关动作
+ public ICommand NodeMouseDownCommand { get; set; }
+ public ICommand NodeMouseMoveCommand { get; set; }
+ public ICommand NodeMouseUpCommand { get; set; }
+ #endregion
+
+ #region 锚点操作
+ public ICommand AnchorDownCommand { get; set; }
+ public ICommand AnchorUpCommand { get; set; }
+ #endregion
+
+ #region 流程控制
+ public ICommand RunCommand { get; set; }
+ public ICommand CircleCommand { get; set; }
+ public ICommand StopCommand { get; set; }
+ public ICommand StepCommand { get; set; }
+ #endregion
+
+ #region 流程参数管理
+ public ObservableCollection ArgumentList { get; set; } =
+ new ObservableCollection();
+
+ public ICommand AddArgumentCommand { get; set; }
+ public ICommand DelArgumentCommand { get; set; }
+
+ public List ArgTypeList { get; set; } = new List
+ {
+ "Int32","IntPtr","Double","String","Boolean"
+ };
+
+
+ // 参数处理相关命令
+ public ICommand SelectFolderCommand { get; set; }
+ #endregion
+
+ private ImageSource _previewImage;
+ ///
+ /// 预览图像
+ ///
+ public ImageSource PreviewImage
+ {
+ get { return _previewImage; }
+ set { SetProperty(ref _previewImage, value); }
+ }
+
+
+ public ObservableCollection LogList { get; set; } =
+ new ObservableCollection();
+
+
+ //画布缩放
+ #region 画布缩放
+ private double _mainZoom = 1.0;
+
+ public double MainZoom
+ {
+ get { return _mainZoom; }
+ set { SetProperty(ref _mainZoom, value); }
+ }
+
+ private double _dragX = 0;
+
+ public double DragX
+ {
+ get { return _dragX; }
+ set { SetProperty(ref _dragX, value); }
+ }
+
+ private double _dragY = 0;
+
+ public double DragY
+ {
+ get { return _dragY; }
+ set { SetProperty(ref _dragY, value); }
+ }
+
+ public ICommand MouseWheelCommand { get; set; }
+
+ public ICommand CanvasMouseDownCommand { get; set; }
+ public ICommand CanvasMouseUpCommand { get; set; }
+ public ICommand CanvasMouseMoveCommand { get; set; }
+
+ public ICommand ZoomIncreaseCommand { get; set; }
+ public ICommand ZoomReduceCommand { get; set; }
+ public ICommand ZoomResetCommand { get; set; }
+ #endregion
+
+ #region 鹰眼视图
+ private double _hwakeyeZoom = 1.0;
+
+ public double HwakeyeZoom
+ {
+ get { return _hwakeyeZoom; }
+ set { SetProperty(ref _hwakeyeZoom, value); }
+ }
+
+ private double _hwakeyeDragX;
+
+ public double HwakeyeDragX
+ {
+ get { return _hwakeyeDragX; }
+ set { SetProperty(ref _hwakeyeDragX, value); }
+ }
+ private double _hwakeyeDragY;
+
+ public double HwakeyeDragY
+ {
+ get { return _hwakeyeDragY; }
+ set { SetProperty(ref _hwakeyeDragY, value); }
+ }
+
+ #endregion
+
+
+ public List Operator { get; set; } = new List
+ {
+ "=","<",">","<=",">=","!="
+ };
+
+ public FlowTabViewModel()
+ {
+ SelectCommand = new RelayCommand(() =>
+ {
+ this.IsCurrent = true;
+ });
+ DragDropCommand = new RelayCommand