Sfoglia il codice sorgente

引入DXF可视化与交互模块,支持图纸查看

新增DXF解析、渲染与交互功能,集成netDxf库,实现DXF文件的导入、图形渲染、实体统计、缩放与选中操作。支持鼠标框选、单选、多选及高亮显示,实时展示实体信息,提升CAD图纸的可视化与分析体验。为后续DXF相关功能扩展奠定基础。
孝锋 徐 8 mesi fa
parent
commit
239865c1e5

+ 120 - 0
TeamAAS-VM/DxfModule/DxfParserService.cs

@@ -0,0 +1,120 @@
+using netDxf;
+using netDxf.Entities;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using DxfLine = netDxf.Entities.Line;
+
+namespace TeamAAS_VP.DxfModule
+{
+    public class DxfParserService
+    {
+        public DxfDocument Document { get; private set; }
+
+        public bool LoadDxfFile(string filePath)
+        {
+            try
+            {
+                if (!File.Exists(filePath))
+                    return false;
+
+                Document = DxfDocument.Load(filePath);
+                return Document != null;
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"Error loading DXF file: {ex.Message}");
+                return false;
+            }
+        }
+
+        public List<EntityObject> GetAllEntities()
+        {
+            if (Document == null)
+                return new List<EntityObject>();
+
+            var entities = new List<EntityObject>();
+
+            // netDxf uses Document.Layouts to access entity collections
+            foreach (var layout in Document.Layouts)
+            {
+                entities.AddRange(layout.AssociatedBlock.Entities);
+            }
+
+            return entities;
+        }
+
+        public Dictionary<string, int> GetEntityStatistics()
+        {
+            if (Document == null)
+                return new Dictionary<string, int>();
+
+            var entities = GetAllEntities();
+            var grouped = entities.GroupBy(e => e.Type.ToString());
+
+            var stats = new Dictionary<string, int>();
+            foreach (var group in grouped)
+            {
+                stats[group.Key] = group.Count();
+            }
+
+            return stats;
+        }
+
+        public (double minX, double minY, double maxX, double maxY) GetBounds()
+        {
+            var entities = GetAllEntities();
+            if (entities.Count == 0)
+                return (0, 0, 0, 0);
+
+            double minX = double.MaxValue;
+            double minY = double.MaxValue;
+            double maxX = double.MinValue;
+            double maxY = double.MinValue;
+
+            foreach (var entity in entities)
+            {
+                var bounds = GetEntityBounds(entity);
+                minX = Math.Min(minX, bounds.minX);
+                minY = Math.Min(minY, bounds.minY);
+                maxX = Math.Max(maxX, bounds.maxX);
+                maxY = Math.Max(maxY, bounds.maxY);
+            }
+
+            return (minX, minY, maxX, maxY);
+        }
+
+        private (double minX, double minY, double maxX, double maxY) GetEntityBounds(EntityObject entity)
+        {
+            switch (entity)
+            {
+                case DxfLine line:
+                    return (
+                        Math.Min(line.StartPoint.X, line.EndPoint.X),
+                        Math.Min(line.StartPoint.Y, line.EndPoint.Y),
+                        Math.Max(line.StartPoint.X, line.EndPoint.X),
+                        Math.Max(line.StartPoint.Y, line.EndPoint.Y)
+                    );
+                case Circle circle:
+                    return (
+                        circle.Center.X - circle.Radius,
+                        circle.Center.Y - circle.Radius,
+                        circle.Center.X + circle.Radius,
+                        circle.Center.Y + circle.Radius
+                    );
+                case Arc arc:
+                    return (
+                        arc.Center.X - arc.Radius,
+                        arc.Center.Y - arc.Radius,
+                        arc.Center.X + arc.Radius,
+                        arc.Center.Y + arc.Radius
+                    );
+                default:
+                    return (0, 0, 0, 0);
+            }
+        }
+    }
+}

+ 129 - 0
TeamAAS-VM/DxfModule/DxfView.xaml

@@ -0,0 +1,129 @@
+<UserControl x:Class="TeamAAS_VP.DxfModule.DxfView"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:TeamAAS_VP.DxfModule"
+             mc:Ignorable="d" 
+             d:DesignHeight="450" d:DesignWidth="800">
+    <Grid>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto" />
+            <RowDefinition Height="*" />
+            <RowDefinition Height="Auto" />
+            <RowDefinition Height="200" />
+        </Grid.RowDefinitions>
+
+        <!-- Toolbar -->
+        <ToolBar Grid.Row="0"
+                 Padding="5">
+            <Button Name="BtnLoadDxf"
+                    Content="导入DXF"
+                    Click="BtnLoadDxf_Click"
+                    Padding="10,5" />
+            <Separator />
+            <Button Name="BtnClearSelection"
+                    Content="清除选择"
+                    Click="BtnClearSelection_Click"
+                    Padding="10,5" />
+            <Button Name="BtnExtractSelected"
+                    Content="提取选中图形"
+                    Click="BtnExtractSelected_Click"
+                    Padding="10,5" />
+            <Separator />
+            <Button Name="BtnZoomIn"
+                    Content="放大"
+                    Click="BtnZoomIn_Click"
+                    Padding="10,5" />
+            <Button Name="BtnZoomOut"
+                    Content="缩小"
+                    Click="BtnZoomOut_Click"
+                    Padding="10,5" />
+            <Button Name="BtnFitToScreen"
+                    Content="适应窗口"
+                    Click="BtnFitToScreen_Click"
+                    Padding="10,5" />
+        </ToolBar>
+
+        <!-- Canvas Area -->
+        <Border Grid.Row="1"
+                BorderBrush="Gray"
+                BorderThickness="1"
+                Margin="5">
+            <ScrollViewer Name="ScrollViewer"
+                          HorizontalScrollBarVisibility="Auto"
+                          VerticalScrollBarVisibility="Auto">
+                <Canvas Name="DrawingCanvas"
+                        Background="Black"
+                        Width="2000"
+                        Height="2000"
+                        MouseWheel="DrawingCanvas_MouseWheel">
+                    <Canvas.LayoutTransform>
+                        <ScaleTransform x:Name="CanvasScaleTransform"
+                                        ScaleX="1"
+                                        ScaleY="1" />
+                    </Canvas.LayoutTransform>
+                </Canvas>
+            </ScrollViewer>
+        </Border>
+
+        <!-- Status Bar -->
+        <StatusBar Grid.Row="2"
+                   Padding="5">
+            <StatusBarItem>
+                <TextBlock Name="TxtStatus"
+                           Text="就绪" />
+            </StatusBarItem>
+            <Separator />
+            <StatusBarItem>
+                <TextBlock Name="TxtEntityCount"
+                           Text="实体数: 0" />
+            </StatusBarItem>
+            <Separator />
+            <StatusBarItem>
+                <TextBlock Name="TxtSelectedCount"
+                           Text="选中: 0" />
+            </StatusBarItem>
+            <Separator />
+            <StatusBarItem>
+                <TextBlock Name="TxtZoom"
+                           Text="缩放: 100%" />
+            </StatusBarItem>
+        </StatusBar>
+
+        <!-- Info Panel -->
+        <Grid Grid.Row="3"
+              Margin="5">
+            <Grid.ColumnDefinitions>
+                <ColumnDefinition Width="*" />
+                <ColumnDefinition Width="*" />
+            </Grid.ColumnDefinitions>
+
+            <!-- Statistics -->
+            <GroupBox Header="文件统计"
+                      Grid.Column="0"
+                      Margin="0,0,2,0"
+                      Style="{x:Null}">
+                <ScrollViewer VerticalScrollBarVisibility="Auto">
+                    <TextBlock Name="TxtStatistics"
+                               Padding="5"
+                               TextWrapping="Wrap"
+                               FontFamily="Consolas" />
+                </ScrollViewer>
+            </GroupBox>
+
+            <!-- Selected Entities Info -->
+            <GroupBox Header="选中图形信息"
+                      Grid.Column="1"
+                      Margin="2,0,0,0"
+                      Style="{x:Null}">
+                <ScrollViewer VerticalScrollBarVisibility="Auto">
+                    <TextBlock Name="TxtSelectedInfo"
+                               Padding="5"
+                               TextWrapping="Wrap"
+                               FontFamily="Consolas" />
+                </ScrollViewer>
+            </GroupBox>
+        </Grid>
+    </Grid>
+</UserControl>

+ 282 - 0
TeamAAS-VM/DxfModule/DxfView.xaml.cs

@@ -0,0 +1,282 @@
+using netDxf.Entities;
+using OxyPlot;
+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 WpfLine = System.Windows.Shapes.Line;
+using DxfLine = netDxf.Entities.Line;
+using DxfLwPolyline = netDxf.Entities.Polyline2D;
+using Microsoft.Win32;
+
+namespace TeamAAS_VP.DxfModule
+{
+    /// <summary>
+    /// DxfView.xaml 的交互逻辑
+    /// </summary>
+    public partial class DxfView : UserControl
+    {
+        private DxfParserService _dxfParser;
+        private InteractionController _interactionController;
+        private double _currentScale = 1.0;
+        private double _zoomFactor = 1.2;
+
+        public DxfView()
+        {
+            InitializeComponent();
+            _dxfParser = new DxfParserService();
+            InitializeInteractionController();
+        }
+        private void InitializeInteractionController()
+        {
+            _interactionController = new InteractionController(DrawingCanvas);
+            _interactionController.SelectionChanged += OnSelectionChanged;
+        }
+
+        private void BtnLoadDxf_Click(object sender, RoutedEventArgs e)
+        {
+            var openFileDialog = new OpenFileDialog
+            {
+                Filter = "DXF文件 (*.dxf)|*.dxf|所有文件 (*.*)|*.*",
+                Title = "选择DXF文件"
+            };
+
+            if (openFileDialog.ShowDialog() == true)
+            {
+                LoadDxfFile(openFileDialog.FileName);
+            }
+        }
+
+        private void LoadDxfFile(string filePath)
+        {
+            TxtStatus.Text = "正在加载DXF文件...";
+
+            DrawingCanvas.Children.Clear();
+            _interactionController?.ClearSelection();
+
+            if (_dxfParser.LoadDxfFile(filePath))
+            {
+                var entities = _dxfParser.GetAllEntities();
+                TxtStatus.Text = $"成功加载: {System.IO.Path.GetFileName(filePath)}";
+                TxtEntityCount.Text = $"实体数: {entities.Count}";
+
+                // Calculate bounds and scale
+                var bounds = _dxfParser.GetBounds();
+                double boundsWidth = bounds.maxX - bounds.minX;
+                double boundsHeight = bounds.maxY - bounds.minY;
+
+                if (boundsWidth > 0 && boundsHeight > 0)
+                {
+                    double scaleX = (DrawingCanvas.Width * 0.8) / boundsWidth;
+                    double scaleY = (DrawingCanvas.Height * 0.8) / boundsHeight;
+                    _currentScale = Math.Min(scaleX, scaleY);
+                }
+
+                // Render entities
+                foreach (var entity in entities)
+                {
+                    var renderedElements = GraphicsRenderer.RenderEntity(entity, _currentScale);
+                    foreach (var element in renderedElements)
+                    {
+                        DrawingCanvas.Children.Add(element);
+                    }
+                }
+
+                // Center the drawing
+                CenterDrawing(bounds);
+
+                // Update statistics
+                UpdateStatistics();
+            }
+            else
+            {
+                TxtStatus.Text = "加载DXF文件失败";
+                MessageBox.Show("无法加载DXF文件,请检查文件是否有效。", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
+            }
+        }
+
+        private void CenterDrawing((double minX, double minY, double maxX, double maxY) bounds)
+        {
+            double centerX = (bounds.minX + bounds.maxX) / 2 * _currentScale;
+            double centerY = -(bounds.minY + bounds.maxY) / 2 * _currentScale;
+
+            double offsetX = DrawingCanvas.Width / 2 - centerX;
+            double offsetY = DrawingCanvas.Height / 2 - centerY;
+
+            // Translate all elements
+            foreach (UIElement element in DrawingCanvas.Children)
+            {
+                if (element is WpfLine line)
+                {
+                    line.X1 += offsetX;
+                    line.X2 += offsetX;
+                    line.Y1 += offsetY;
+                    line.Y2 += offsetY;
+                }
+                else if (element is FrameworkElement fe)
+                {
+                    double left = Canvas.GetLeft(fe);
+                    double top = Canvas.GetTop(fe);
+                    Canvas.SetLeft(fe, left + offsetX);
+                    Canvas.SetTop(fe, top + offsetY);
+                }
+            }
+        }
+
+        private void UpdateStatistics()
+        {
+            var stats = _dxfParser.GetEntityStatistics();
+            var sb = new StringBuilder();
+
+            foreach (var stat in stats.OrderByDescending(s => s.Value))
+            {
+                if (stat.Value > 0)
+                {
+                    sb.AppendLine($"{stat.Key}: {stat.Value}");
+                }
+            }
+
+            TxtStatistics.Text = sb.ToString();
+        }
+
+        private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
+        {
+            TxtSelectedCount.Text = $"选中: {e.SelectedEntities.Count}";
+
+            var sb = new StringBuilder();
+
+            if (e.SelectedEntities.Count > 0)
+            {
+                sb.AppendLine($"已选择 {e.SelectedEntities.Count} 个图形实体:\n");
+
+                var grouped = e.SelectedEntities.GroupBy(entity => entity.Type.ToString());
+
+                foreach (var group in grouped)
+                {
+                    sb.AppendLine($"{group.Key}: {group.Count()}");
+                }
+
+                sb.AppendLine("\n详细信息:");
+
+                int index = 1;
+                foreach (var entity in e.SelectedEntities.Take(10))
+                {
+                    sb.AppendLine($"\n[{index}] {GetEntityInfo(entity)}");
+                    index++;
+                }
+
+                if (e.SelectedEntities.Count > 10)
+                {
+                    sb.AppendLine($"\n... 还有 {e.SelectedEntities.Count - 10} 个实体");
+                }
+            }
+            else
+            {
+                sb.AppendLine("未选择任何图形");
+            }
+
+            TxtSelectedInfo.Text = sb.ToString();
+        }
+
+        private string GetEntityInfo(EntityObject entity)
+        {
+            switch (entity)
+            {
+                case DxfLine line:
+                    return $"直线: ({line.StartPoint.X:F2}, {line.StartPoint.Y:F2}) → ({line.EndPoint.X:F2}, {line.EndPoint.Y:F2})";
+                case Circle circle:
+                    return $"圆: 中心({circle.Center.X:F2}, {circle.Center.Y:F2}), 半径={circle.Radius:F2}";
+                case Arc arc:
+                    return $"圆弧: 中心({arc.Center.X:F2}, {arc.Center.Y:F2}), 半径={arc.Radius:F2}, 角度={arc.StartAngle:F1}°~{arc.EndAngle:F1}°";
+                case Text text:
+                    return $"文本: \"{text.Value}\" 位置({text.Position.X:F2}, {text.Position.Y:F2})";
+                case MText mtext:
+                    return $"多行文本: \"{mtext.Value}\" 位置({mtext.Position.X:F2}, {mtext.Position.Y:F2})";
+                case DxfLwPolyline lwPoly:
+                    return $"轻量多段线: {lwPoly.Vertexes.Count} 个顶点{(lwPoly.IsClosed ? " (闭合)" : "")}";
+                case Spline spline:
+                // return $"样条曲线: {spline.ControlPoints.Count} 个控制点";
+                default:
+                    return $"{entity.Type}: {entity.CodeName}";
+            }
+        }
+
+        private void BtnClearSelection_Click(object sender, RoutedEventArgs e)
+        {
+            _interactionController?.ClearSelection();
+        }
+
+        private void BtnExtractSelected_Click(object sender, RoutedEventArgs e)
+        {
+            var selectedEntities = _interactionController?.GetSelectedEntities();
+
+            if (selectedEntities == null || selectedEntities.Count == 0)
+            {
+                MessageBox.Show("请先选择要提取的图形。", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
+                return;
+            }
+
+            var sb = new StringBuilder();
+            sb.AppendLine($"提取的图形信息 (共 {selectedEntities.Count} 个):\n");
+
+            var grouped = selectedEntities.GroupBy(p => p.Type.ToString());
+            foreach (var group in grouped)
+            {
+                sb.AppendLine($"\n{group.Key} ({group.Count()} 个):");
+                sb.AppendLine(new string('-', 50));
+
+                foreach (var entity in group)
+                {
+                    sb.AppendLine(GetEntityInfo(entity));
+                }
+            }
+
+            MessageBox.Show(sb.ToString(), "提取的图形信息", MessageBoxButton.OK, MessageBoxImage.Information);
+        }
+
+        private void BtnZoomIn_Click(object sender, RoutedEventArgs e)
+        {
+            Zoom(_zoomFactor);
+        }
+
+        private void BtnZoomOut_Click(object sender, RoutedEventArgs e)
+        {
+            Zoom(1.0 / _zoomFactor);
+        }
+
+        private void BtnFitToScreen_Click(object sender, RoutedEventArgs e)
+        {
+            CanvasScaleTransform.ScaleX = 1.0;
+            CanvasScaleTransform.ScaleY = 1.0;
+            UpdateZoomDisplay();
+        }
+
+        private void DrawingCanvas_MouseWheel(object sender, MouseWheelEventArgs e)
+        {
+            double zoom = e.Delta > 0 ? _zoomFactor : 1.0 / _zoomFactor;
+            Zoom(zoom);
+        }
+
+        private void Zoom(double factor)
+        {
+            CanvasScaleTransform.ScaleX *= factor;
+            CanvasScaleTransform.ScaleY *= factor;
+            UpdateZoomDisplay();
+        }
+
+        private void UpdateZoomDisplay()
+        {
+            TxtZoom.Text = $"缩放: {CanvasScaleTransform.ScaleX * 100:F0}%";
+        }
+    }
+}

+ 253 - 0
TeamAAS-VM/DxfModule/GraphicsRenderer.cs

@@ -0,0 +1,253 @@
+using netDxf.Entities;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Media;
+using WpfShapes = System.Windows.Shapes;
+using DxfLine = netDxf.Entities.Line;
+using WpfLine = System.Windows.Shapes.Line;
+using DxfEllipse = netDxf.Entities.Ellipse;
+using WpfEllipse = System.Windows.Shapes.Ellipse;
+using WpfPoint = System.Windows.Point;
+
+namespace TeamAAS_VP.DxfModule
+{
+    public class GraphicsRenderer
+    {
+        private const double DefaultStrokeThickness = 1.0;
+
+        public static List<UIElement> RenderEntity(EntityObject entity, double scale = 1.0)
+        {
+            var elements = new List<UIElement>();
+
+            switch (entity)
+            {
+                case DxfLine line:
+                    elements.Add(RenderLine(line, scale));
+                    break;
+                case Circle circle:
+                    elements.Add(RenderCircle(circle, scale));
+                    break;
+                case Arc arc:
+                    elements.Add(RenderArc(arc, scale));
+                    break;
+                case netDxf.Entities.Point point:
+                    elements.Add(RenderPoint(point, scale));
+                    break;
+                case Text text:
+                    elements.Add(RenderText(text, scale));
+                    break;
+                case MText mtext:
+                    elements.Add(RenderMText(mtext, scale));
+                    break;
+                case Spline spline:
+                    elements.AddRange(RenderSpline(spline, scale));
+                    break;
+            }
+
+            return elements;
+        }
+
+        private static UIElement RenderLine(DxfLine line, double scale)
+        {
+            var wpfLine = new WpfLine
+            {
+                X1 = line.StartPoint.X * scale,
+                Y1 = -line.StartPoint.Y * scale, // Flip Y axis
+                X2 = line.EndPoint.X * scale,
+                Y2 = -line.EndPoint.Y * scale,
+                Stroke = new SolidColorBrush(GetColor(line.Color)),
+                StrokeThickness = DefaultStrokeThickness,
+                Tag = line // Store original entity
+            };
+            return wpfLine;
+        }
+
+        private static UIElement RenderCircle(Circle circle, double scale)
+        {
+            var wpfEllipse = new WpfEllipse
+            {
+                Width = circle.Radius * 2 * scale,
+                Height = circle.Radius * 2 * scale,
+                Stroke = new SolidColorBrush(GetColor(circle.Color)),
+                StrokeThickness = DefaultStrokeThickness,
+                Fill = Brushes.Transparent,
+                Tag = circle
+            };
+
+            System.Windows.Controls.Canvas.SetLeft(wpfEllipse, (circle.Center.X - circle.Radius) * scale);
+            System.Windows.Controls.Canvas.SetTop(wpfEllipse, (-circle.Center.Y - circle.Radius) * scale);
+
+            return wpfEllipse;
+        }
+
+        private static UIElement RenderArc(Arc arc, double scale)
+        {
+            var path = new WpfShapes.Path
+            {
+                Stroke = new SolidColorBrush(GetColor(arc.Color)),
+                StrokeThickness = DefaultStrokeThickness,
+                Fill = Brushes.Transparent,
+                Tag = arc
+            };
+
+            var geometry = new PathGeometry();
+            var figure = new PathFigure();
+
+            // Calculate start and end points
+            double startAngleRad = arc.StartAngle * Math.PI / 180.0;
+            double endAngleRad = arc.EndAngle * Math.PI / 180.0;
+
+            double startX = (arc.Center.X + arc.Radius * Math.Cos(startAngleRad)) * scale;
+            double startY = (-arc.Center.Y - arc.Radius * Math.Sin(startAngleRad)) * scale;
+            double endX = (arc.Center.X + arc.Radius * Math.Cos(endAngleRad)) * scale;
+            double endY = (-arc.Center.Y - arc.Radius * Math.Sin(endAngleRad)) * scale;
+
+            figure.StartPoint = new WpfPoint(startX, startY);
+
+            var arcSegment = new ArcSegment
+            {
+                Point = new WpfPoint(endX, endY),
+                Size = new System.Windows.Size(arc.Radius * scale, arc.Radius * scale),
+                SweepDirection = SweepDirection.Counterclockwise,
+                IsLargeArc = Math.Abs(arc.EndAngle - arc.StartAngle) > 180
+            };
+
+            figure.Segments.Add(arcSegment);
+            geometry.Figures.Add(figure);
+            path.Data = geometry;
+
+            return path;
+        }
+
+        private static List<UIElement> RenderSpline(Spline spline, double scale)
+        {
+            var elements = new List<UIElement>();
+
+            // Approximate spline with lines using control points
+            var controlPoints = new List<netDxf.Vector3>(spline.ControlPoints);
+            if (controlPoints.Count < 2)
+                return elements;
+
+            for (int i = 0; i < controlPoints.Count - 1; i++)
+            {
+                var p1 = controlPoints[i];
+                var p2 = controlPoints[i + 1];
+
+                var line = new WpfLine
+                {
+                    X1 = p1.X * scale,
+                    Y1 = -p1.Y * scale,
+                    X2 = p2.X * scale,
+                    Y2 = -p2.Y * scale,
+                    Stroke = new SolidColorBrush(GetColor(spline.Color)),
+                    StrokeThickness = DefaultStrokeThickness,
+                    Tag = spline
+                };
+                elements.Add(line);
+            }
+
+            return elements;
+        }
+
+        private static UIElement RenderPoint(netDxf.Entities.Point point, double scale)
+        {
+            var ellipse = new WpfEllipse
+            {
+                Width = 3,
+                Height = 3,
+                Fill = new SolidColorBrush(GetColor(point.Color)),
+                Tag = point
+            };
+
+            System.Windows.Controls.Canvas.SetLeft(ellipse, point.Position.X * scale - 1.5);
+            System.Windows.Controls.Canvas.SetTop(ellipse, -point.Position.Y * scale - 1.5);
+
+            return ellipse;
+        }
+
+        private static UIElement RenderText(Text text, double scale)
+        {
+            var textBlock = new System.Windows.Controls.TextBlock
+            {
+                Text = text.Value,
+                Foreground = new SolidColorBrush(GetColor(text.Color)),
+                FontSize = text.Height * scale,
+                Tag = text
+            };
+
+            System.Windows.Controls.Canvas.SetLeft(textBlock, text.Position.X * scale);
+            System.Windows.Controls.Canvas.SetTop(textBlock, -text.Position.Y * scale);
+
+            return textBlock;
+        }
+
+        private static UIElement RenderMText(MText mtext, double scale)
+        {
+            var textBlock = new System.Windows.Controls.TextBlock
+            {
+                Text = mtext.Value,
+                Foreground = new SolidColorBrush(GetColor(mtext.Color)),
+                FontSize = mtext.Height * scale,
+                Tag = mtext
+            };
+
+            System.Windows.Controls.Canvas.SetLeft(textBlock, mtext.Position.X * scale);
+            System.Windows.Controls.Canvas.SetTop(textBlock, -mtext.Position.Y * scale);
+
+            return textBlock;
+        }
+
+        private static Color GetColor(netDxf.AciColor aciColor)
+        {
+            // Convert DXF AciColor to WPF Color
+            if (aciColor.IsByLayer || aciColor.IsByBlock)
+                return Colors.White;
+
+            var rgb = aciColor.ToColor();
+            return Color.FromRgb(rgb.R, rgb.G, rgb.B);
+        }
+
+        public static void HighlightElement(UIElement element, bool isSelected)
+        {
+            if (element is WpfShapes.Shape shape)
+            {
+                if (isSelected)
+                {
+                    shape.Stroke = Brushes.Yellow;
+                    shape.StrokeThickness = 2.0;
+                }
+                else
+                {
+                    // Restore original color from entity
+                    if (shape.Tag is EntityObject entity)
+                    {
+                        shape.Stroke = new SolidColorBrush(GetColor(entity.Color));
+                        shape.StrokeThickness = DefaultStrokeThickness;
+                    }
+                }
+            }
+            else if (element is System.Windows.Controls.TextBlock textBlock)
+            {
+                if (isSelected)
+                {
+                    textBlock.Foreground = Brushes.Yellow;
+                }
+                else
+                {
+                    if (textBlock.Tag is Text text)
+                    {
+                        textBlock.Foreground = new SolidColorBrush(GetColor(text.Color));
+                    }
+                    else if (textBlock.Tag is MText mtext)
+                    {
+                        textBlock.Foreground = new SolidColorBrush(GetColor(mtext.Color));
+                    }
+                }
+            }
+        }
+    }
+}

+ 268 - 0
TeamAAS-VM/DxfModule/InteractionController.cs

@@ -0,0 +1,268 @@
+using CSScripting;
+using OxyPlot.Utilities;
+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.Input;
+using System.Windows.Media;
+using WpfShapes = System.Windows.Shapes;
+using WpfPoint = System.Windows.Point;
+using netDxf.Entities;
+
+namespace TeamAAS_VP.DxfModule
+{
+    public class InteractionController
+    {
+        private readonly Canvas _canvas;
+        private readonly HashSet<UIElement> _selectedElements = new HashSet<UIElement>();
+        private WpfPoint _selectionStartPoint;
+        private WpfShapes.Rectangle _selectionRectangle;
+        private bool _isSelecting = false;
+
+        public event EventHandler<SelectionChangedEventArgs> SelectionChanged;
+
+        public InteractionController(Canvas canvas)
+        {
+            _canvas = canvas;
+            AttachEvents();
+        }
+
+        private void AttachEvents()
+        {
+            _canvas.MouseLeftButtonDown += Canvas_MouseLeftButtonDown;
+            _canvas.MouseLeftButtonUp += Canvas_MouseLeftButtonUp;
+            _canvas.MouseMove += Canvas_MouseMove;
+            _canvas.MouseRightButtonDown += Canvas_MouseRightButtonDown;
+        }
+
+        private void Canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+        {
+            _selectionStartPoint = e.GetPosition(_canvas);
+            _isSelecting = true;
+
+            // Check if clicking on an element
+            var hitElement = GetElementAtPoint(_selectionStartPoint);
+
+            if (hitElement != null)
+            {
+                // Single selection
+                if (!Keyboard.IsKeyDown(Key.LeftCtrl) && !Keyboard.IsKeyDown(Key.RightCtrl))
+                {
+                    ClearSelection();
+                }
+
+                ToggleSelection(hitElement);
+            }
+            else
+            {
+                // Start box selection
+                if (!Keyboard.IsKeyDown(Key.LeftCtrl) && !Keyboard.IsKeyDown(Key.RightCtrl))
+                {
+                    ClearSelection();
+                }
+
+                _selectionRectangle = new WpfShapes.Rectangle
+                {
+                    Stroke = Brushes.Blue,
+                    StrokeThickness = 1,
+                    StrokeDashArray = new DoubleCollection { 4, 2 },
+                    Fill = new SolidColorBrush(Color.FromArgb(30, 0, 0, 255))
+                };
+                _canvas.Children.Add(_selectionRectangle);
+            }
+        }
+
+        private void Canvas_MouseMove(object sender, MouseEventArgs e)
+        {
+            if (_isSelecting && _selectionRectangle != null)
+            {
+                var currentPoint = e.GetPosition(_canvas);
+
+                var x = Math.Min(_selectionStartPoint.X, currentPoint.X);
+                var y = Math.Min(_selectionStartPoint.Y, currentPoint.Y);
+                var width = Math.Abs(_selectionStartPoint.X - currentPoint.X);
+                var height = Math.Abs(_selectionStartPoint.Y - currentPoint.Y);
+
+                Canvas.SetLeft(_selectionRectangle, x);
+                Canvas.SetTop(_selectionRectangle, y);
+                _selectionRectangle.Width = width;
+                _selectionRectangle.Height = height;
+            }
+        }
+
+        private void Canvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
+        {
+            if (_isSelecting && _selectionRectangle != null)
+            {
+                var endPoint = e.GetPosition(_canvas);
+                var rect = new Rect(_selectionStartPoint, endPoint);
+
+                // Select elements within the rectangle
+                foreach (UIElement element in _canvas.Children)
+                {
+                    if (element == _selectionRectangle)
+                        continue;
+
+                    if (IsElementIntersecting(element, rect))
+                    {
+                        AddToSelection(element);
+                    }
+                }
+
+                _canvas.Children.Remove(_selectionRectangle);
+                _selectionRectangle = null;
+            }
+
+            _isSelecting = false;
+        }
+
+        private void Canvas_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
+        {
+            // Clear selection on right-click
+            ClearSelection();
+        }
+
+        private UIElement GetElementAtPoint(WpfPoint point)
+        {
+            HitTestResult result = VisualTreeHelper.HitTest(_canvas, point);
+
+            if (result != null && result.VisualHit is UIElement element)
+            {
+                // Make sure it's not the canvas itself
+                if (element != _canvas && element.GetType() != typeof(WpfShapes.Rectangle))
+                {
+                    return element;
+                }
+            }
+
+            return null;
+        }
+
+        private bool IsElementIntersecting(UIElement element, Rect selectionRect)
+        {
+            try
+            {
+                var elementBounds = GetElementBounds(element);
+                return selectionRect.IntersectsWith(elementBounds);
+            }
+            catch
+            {
+                return false;
+            }
+        }
+
+        private Rect GetElementBounds(UIElement element)
+        {
+            if (element is WpfShapes.Line line)
+            {
+                var x1 = line.X1;
+                var y1 = line.Y1;
+                var x2 = line.X2;
+                var y2 = line.Y2;
+
+                return new Rect(
+                    new WpfPoint(Math.Min(x1, x2), Math.Min(y1, y2)),
+                    new WpfPoint(Math.Max(x1, x2), Math.Max(y1, y2))
+                );
+            }
+            else if (element is WpfShapes.Ellipse ellipse)
+            {
+                var left = Canvas.GetLeft(ellipse);
+                var top = Canvas.GetTop(ellipse);
+                return new Rect(left, top, ellipse.Width, ellipse.Height);
+            }
+            else if (element is WpfShapes.Shape shape)
+            {
+                return shape.RenderedGeometry.Bounds;
+            }
+            else if (element is FrameworkElement fe)
+            {
+                var left = Canvas.GetLeft(fe);
+                var top = Canvas.GetTop(fe);
+                return new Rect(left, top, fe.ActualWidth, fe.ActualHeight);
+            }
+
+            return Rect.Empty;
+        }
+
+        private void ToggleSelection(UIElement element)
+        {
+            if (_selectedElements.Contains(element))
+            {
+                RemoveFromSelection(element);
+            }
+            else
+            {
+                AddToSelection(element);
+            }
+        }
+
+        private void AddToSelection(UIElement element)
+        {
+            if (element is FrameworkElement fe && fe.Tag is EntityObject)
+            {
+                _selectedElements.Add(element);
+                GraphicsRenderer.HighlightElement(element, true);
+                OnSelectionChanged();
+            }
+        }
+
+        private void RemoveFromSelection(UIElement element)
+        {
+            if (_selectedElements.Contains(element))
+            {
+                _selectedElements.Remove(element);
+                GraphicsRenderer.HighlightElement(element, false);
+                OnSelectionChanged();
+            }
+        }
+
+        public void ClearSelection()
+        {
+            foreach (var element in _selectedElements.ToList())
+            {
+                GraphicsRenderer.HighlightElement(element, false);
+            }
+            _selectedElements.Clear();
+            OnSelectionChanged();
+        }
+
+        public List<EntityObject> GetSelectedEntities()
+        {
+            return _selectedElements
+                .OfType<FrameworkElement>()
+                .Where(e => e.Tag is EntityObject)
+                .Select(e => (EntityObject)e.Tag)
+                .ToList();
+        }
+
+        public int SelectedCount => _selectedElements.Count;
+
+        private void OnSelectionChanged()
+        {
+            SelectionChanged?.Invoke(this, new SelectionChangedEventArgs(GetSelectedEntities()));
+        }
+
+        public void Detach()
+        {
+            _canvas.MouseLeftButtonDown -= Canvas_MouseLeftButtonDown;
+            _canvas.MouseLeftButtonUp -= Canvas_MouseLeftButtonUp;
+            _canvas.MouseMove -= Canvas_MouseMove;
+            _canvas.MouseRightButtonDown -= Canvas_MouseRightButtonDown;
+        }
+    }
+
+    public class SelectionChangedEventArgs : EventArgs
+    {
+        public List<EntityObject> SelectedEntities { get; }
+
+        public SelectionChangedEventArgs(List<EntityObject> selectedEntities)
+        {
+            SelectedEntities = selectedEntities;
+        }
+    }
+}

+ 13 - 0
TeamAAS-VM/TeamAAS-VP.csproj

@@ -254,6 +254,9 @@
       <SpecificVersion>False</SpecificVersion>
       <HintPath>Resources\MVSDK_Net.dll</HintPath>
     </Reference>
+    <Reference Include="netDxf, Version=2023.11.10.0, Culture=neutral, PublicKeyToken=618c63290969e781, processorArchitecture=MSIL">
+      <HintPath>..\packages\netDxf.2023.11.10\lib\net48\netDxf.dll</HintPath>
+    </Reference>
     <Reference Include="NPOI.Core, Version=2.7.4.0, Culture=neutral, PublicKeyToken=0df73ec7942b34e1, processorArchitecture=MSIL">
       <HintPath>..\packages\NPOI.2.7.4\lib\net472\NPOI.Core.dll</HintPath>
     </Reference>
@@ -577,6 +580,12 @@
     <Compile Include="Core\ScrewDriver\XYD_ScrewDriver.cs" />
     <Compile Include="Data\DatabaseInitializer.cs" />
     <Compile Include="Data\SystemDatabaseService.cs" />
+    <Compile Include="DxfModule\DxfParserService.cs" />
+    <Compile Include="DxfModule\DxfView.xaml.cs">
+      <DependentUpon>DxfView.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="DxfModule\GraphicsRenderer.cs" />
+    <Compile Include="DxfModule\InteractionController.cs" />
     <Compile Include="Enums\AnalysisMode.cs" />
     <Compile Include="Enums\DragHandleType.cs" />
     <Compile Include="Enums\ElectricScrewdriverBrand.cs" />
@@ -1086,6 +1095,10 @@
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
     </Page>
+    <Page Include="DxfModule\DxfView.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
     <Page Include="Resources\FontSize\DefaultFontSize.xaml">
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>

+ 1 - 0
TeamAAS-VM/packages.config

@@ -29,6 +29,7 @@
   <package id="Microsoft.NETFramework.ReferenceAssemblies" version="1.0.3" targetFramework="net48" developmentDependency="true" />
   <package id="Microsoft.NETFramework.ReferenceAssemblies.net48" version="1.0.3" targetFramework="net48" developmentDependency="true" />
   <package id="Microsoft.Xaml.Behaviors.Wpf" version="1.1.135" targetFramework="net48" />
+  <package id="netDxf" version="2023.11.10" targetFramework="net48" />
   <package id="Newtonsoft.Json" version="13.0.3" targetFramework="net48" />
   <package id="NModbus4" version="2.1.0" targetFramework="net48" />
   <package id="NPOI" version="2.7.4" targetFramework="net48" />