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.Animation; 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; using System.Drawing; using TeamAAS_VP.Resources.Languages; namespace TeamAAS_VP.DxfModule { /// /// DxfView.xaml 的交互逻辑 /// 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(); } /// /// 选中的点位 /// public List Points { get; private set; }=new List(); /// /// 需要导入的点位表开始点编号 /// public int StartIndex { get;private set; } = 1; 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 = Lang.选择DXF文件 }; if (openFileDialog.ShowDialog() == true) { LoadDxfFile(openFileDialog.FileName); } } private void LoadDxfFile(string filePath) { TxtStatus.Text = Lang.正在加载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 = Lang.加载DXF文件失败; MessageBox.Show(Lang.无法加载DXF文件请检查文件是否有效, Lang.错误, 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 = string.Format(Lang.选中0,e.SelectedEntities.Count); var sb = new StringBuilder(); if (e.SelectedEntities.Count > 0) { sb.AppendLine(string.Format(Lang.已选择0个图形实体, 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"+Lang.详细信息); 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" + string.Format(Lang.还有0个实体, e.SelectedEntities.Count - 10)); } //如果选中的是圆,则输出圆心坐标,如果是直线,则输出中心点坐标,如果是矩形,则输出中心点坐标,如果是点,则输出点坐标,如果是圆弧,则输出中心点坐标 if (Points!=null) { Points = new List(); } Points.Clear(); foreach (var item in e.SelectedEntities) { if (item is Circle circle) { Points.Add(new PointF((float)circle.Center.X, (float)circle.Center.Y)); } else if (item is DxfLine line) { float centerX = (float)((line.StartPoint.X + line.EndPoint.X) / 2); float centerY = (float)((line.StartPoint.Y + line.EndPoint.Y) / 2); Points.Add(new PointF(centerX, centerY)); } else if (item is DxfLwPolyline polyline && polyline.IsClosed) { //计算多边形中心点 float centerX = 0; float centerY = 0; foreach (var vertex in polyline.Vertexes) { centerX += (float)vertex.Position.X; centerY += (float)vertex.Position.Y; } centerX /= polyline.Vertexes.Count; centerY /= polyline.Vertexes.Count; Points.Add(new PointF(centerX, centerY)); } else if (item is netDxf.Entities.Point point) { Points.Add(new PointF((float)point.Position.X, (float)point.Position.Y)); } else if (item is Arc arc) { Points.Add(new PointF((float)arc.Center.X, (float)arc.Center.Y)); } } } else { sb.AppendLine(Lang.未选择任何图形); } TxtSelectedInfo.Text = sb.ToString(); } private string GetEntityInfo(EntityObject entity) { switch (entity) { case DxfLine line: return $"{Lang.直线} ({line.StartPoint.X:F2}, {line.StartPoint.Y:F2}) → ({line.EndPoint.X:F2}, {line.EndPoint.Y:F2})"; case Circle circle: return $"{Lang.圆中心}({circle.Center.X:F2}, {circle.Center.Y:F2}), {Lang.半径}{circle.Radius:F2}"; case Arc arc: return $"{Lang.圆弧中心}({arc.Center.X:F2}, {arc.Center.Y:F2}), {Lang.半径}{arc.Radius:F2}, {Lang.角度等于}{arc.StartAngle:F1}°~{arc.EndAngle:F1}°"; case Text text: return $"{Lang.文本} \"{text.Value}\" {Lang.位置}({text.Position.X:F2}, {text.Position.Y:F2})"; case MText mtext: return $"{Lang.多行文本} \"{mtext.Value}\" {Lang.位置}({mtext.Position.X:F2}, {mtext.Position.Y:F2})"; case DxfLwPolyline lwPoly: return $"{Lang.轻量多段线} {lwPoly.Vertexes.Count} {Lang.个顶点}{(lwPoly.IsClosed ? Lang.闭合 : "")}"; case Spline spline: // return $"样条曲线: {spline.ControlPoints.Count} 个控制点"; default: return $"{entity.Type}: {entity.CodeName}"; } } private void BtnClearSelection_Click(object sender, RoutedEventArgs e) { Points?.Clear(); _interactionController?.ClearSelection(); } private void BtnExtractSelected_Click(object sender, RoutedEventArgs e) { var selectedEntities = _interactionController?.GetSelectedEntities(); if (selectedEntities == null || selectedEntities.Count == 0) { MessageBox.Show(Lang.请先选择要提取的图形, Lang.提示, MessageBoxButton.OK, MessageBoxImage.Information); return; } var sb = new StringBuilder(); sb.AppendLine(string.Format(Lang.提取的图形信息共0个, selectedEntities.Count) +"\n"); var grouped = selectedEntities.GroupBy(p => p.Type.ToString()); foreach (var group in grouped) { sb.AppendLine($"\n{group.Key} ({group.Count()} {Lang.个}):"); sb.AppendLine(new string('-', 50)); foreach (var entity in group) { sb.AppendLine(GetEntityInfo(entity)); } } MessageBox.Show(sb.ToString(), Lang.提取的图形信息, MessageBoxButton.OK, MessageBoxImage.Information); } private void BtnZoomIn_Click(object sender, RoutedEventArgs e) { // Smooth zoom centered at viewport center var sv = ScrollViewer; if (sv != null) { double prevScale = CanvasScaleTransform.ScaleX; double target = prevScale * _zoomFactor; target = Math.Max(0.05, Math.Min(target, 50.0)); AnimateScale(target, null); } else { Zoom(_zoomFactor); } } private void BtnZoomOut_Click(object sender, RoutedEventArgs e) { var sv = ScrollViewer; if (sv != null) { double prevScale = CanvasScaleTransform.ScaleX; double target = prevScale / _zoomFactor; target = Math.Max(0.05, Math.Min(target, 50.0)); AnimateScale(target, null); } else { Zoom(1.0 / _zoomFactor); } } private void BtnFitToScreen_Click(object sender, RoutedEventArgs e) { var sv = ScrollViewer; if (sv == null) { CanvasScaleTransform.ScaleX = 1.0; CanvasScaleTransform.ScaleY = 1.0; UpdateZoomDisplay(); return; } // Compute scale to fit the canvas into viewport with some margin double marginFactor = 0.95; double targetScaleX = (sv.ViewportWidth * marginFactor) / DrawingCanvas.Width; double targetScaleY = (sv.ViewportHeight * marginFactor) / DrawingCanvas.Height; double target = Math.Min(targetScaleX, targetScaleY); if (double.IsNaN(target) || target <= 0) target = 1.0; target = Math.Max(0.05, Math.Min(target, 50.0)); // Animate to target and center AnimateScale(target, null); } private void DrawingCanvas_MouseWheel(object sender, MouseWheelEventArgs e) { // Zoom centered at current mouse position inside the ScrollViewer viewport var sv = ScrollViewer; if (sv == null) // fallback to previous behavior { double zoom1 = e.Delta > 0 ? _zoomFactor : 1.0 / _zoomFactor; Zoom(zoom1); return; } // Mouse position relative to the ScrollViewer (viewport) System.Windows.Point mousePosInViewport = e.GetPosition(sv); // Mouse position relative to the content (unscaled content coordinates) System.Windows.Point mousePosInContent = e.GetPosition(DrawingCanvas); double prevScale = CanvasScaleTransform.ScaleX; double zoom = e.Delta > 0 ? _zoomFactor : 1.0 / _zoomFactor; double newScale = prevScale * zoom; // Clamp scale to reasonable range newScale = Math.Max(0.05, Math.Min(newScale, 50.0)); // Absolute position of the content point after scaling double absX = mousePosInContent.X * newScale; double absY = mousePosInContent.Y * newScale; // Apply scale CanvasScaleTransform.ScaleX = newScale; CanvasScaleTransform.ScaleY = newScale; // Ensure layout updated so ScrollViewer extents are refreshed sv.UpdateLayout(); // Calculate target offsets so that the content point stays under the mouse cursor double targetOffsetX = absX - mousePosInViewport.X; double targetOffsetY = absY - mousePosInViewport.Y; // Clamp offsets to valid scrollable range double maxOffsetX = Math.Max(0, sv.ExtentWidth - sv.ViewportWidth); double maxOffsetY = Math.Max(0, sv.ExtentHeight - sv.ViewportHeight); targetOffsetX = Math.Max(0, Math.Min(targetOffsetX, maxOffsetX)); targetOffsetY = Math.Max(0, Math.Min(targetOffsetY, maxOffsetY)); sv.ScrollToHorizontalOffset(targetOffsetX); sv.ScrollToVerticalOffset(targetOffsetY); UpdateZoomDisplay(); e.Handled = true; } /// /// Animate scale transform to targetScale. If anchorInContent is null, use viewport center as anchor. /// anchorInContent, when provided, is in unscaled canvas coordinates (content space). /// private void AnimateScale(double targetScale, System.Windows.Point? anchorInContent) { var sv = ScrollViewer; if (sv == null) { CanvasScaleTransform.ScaleX = targetScale; CanvasScaleTransform.ScaleY = targetScale; UpdateZoomDisplay(); return; } double prevScale = CanvasScaleTransform.ScaleX; if (Math.Abs(prevScale - targetScale) < 1e-6) return; // Determine anchor in content space and its position in viewport System.Windows.Point anchorContent; System.Windows.Point anchorViewport; if (anchorInContent.HasValue) { anchorContent = anchorInContent.Value; anchorViewport = new System.Windows.Point(anchorContent.X * prevScale - sv.HorizontalOffset, anchorContent.Y * prevScale - sv.VerticalOffset); } else { // use viewport center anchorViewport = new System.Windows.Point(sv.ViewportWidth / 2.0, sv.ViewportHeight / 2.0); anchorContent = new System.Windows.Point((sv.HorizontalOffset + anchorViewport.X) / prevScale, (sv.VerticalOffset + anchorViewport.Y) / prevScale); } // Calculate target offsets so that the anchor stays in the same viewport position after scaling double targetOffsetX = anchorContent.X * targetScale - anchorViewport.X; double targetOffsetY = anchorContent.Y * targetScale - anchorViewport.Y; double maxOffsetX = Math.Max(0, sv.ExtentWidth - sv.ViewportWidth); double maxOffsetY = Math.Max(0, sv.ExtentHeight - sv.ViewportHeight); targetOffsetX = Math.Max(0, Math.Min(targetOffsetX, maxOffsetX)); targetOffsetY = Math.Max(0, Math.Min(targetOffsetY, maxOffsetY)); var duration = TimeSpan.FromMilliseconds(200); var animX = new DoubleAnimation(prevScale, targetScale, duration, FillBehavior.Stop) { EasingFunction = new QuadraticEase() }; var animY = new DoubleAnimation(prevScale, targetScale, duration, FillBehavior.Stop) { EasingFunction = new QuadraticEase() }; int completed = 0; EventHandler whenDone = (s, e) => { completed++; if (completed >= 2) { // Ensure final values CanvasScaleTransform.ScaleX = targetScale; CanvasScaleTransform.ScaleY = targetScale; // Update layout so extents reflect final scale sv.UpdateLayout(); // Recalculate clamped offsets based on final extents double maxX = Math.Max(0, sv.ExtentWidth - sv.ViewportWidth); double maxY = Math.Max(0, sv.ExtentHeight - sv.ViewportHeight); double finalOffsetX = Math.Max(0, Math.Min(targetOffsetX, maxX)); double finalOffsetY = Math.Max(0, Math.Min(targetOffsetY, maxY)); sv.ScrollToHorizontalOffset(finalOffsetX); sv.ScrollToVerticalOffset(finalOffsetY); UpdateZoomDisplay(); } }; animX.Completed += whenDone; animY.Completed += whenDone; CanvasScaleTransform.BeginAnimation(ScaleTransform.ScaleXProperty, animX); CanvasScaleTransform.BeginAnimation(ScaleTransform.ScaleYProperty, animY); } private void Zoom(double factor) { CanvasScaleTransform.ScaleX *= factor; CanvasScaleTransform.ScaleY *= factor; UpdateZoomDisplay(); } private void UpdateZoomDisplay() { TxtZoom.Text = $"{Lang.缩放} {CanvasScaleTransform.ScaleX * 100:F0}%"; } private void TxtStartIndex_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) { StartIndex= e.NewValue; } } }