DxfView.xaml.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. using netDxf.Entities;
  2. using OxyPlot;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. using System.Text;
  7. using System.Threading.Tasks;
  8. using System.Windows;
  9. using System.Windows.Controls;
  10. using System.Windows.Data;
  11. using System.Windows.Documents;
  12. using System.Windows.Input;
  13. using System.Windows.Media;
  14. using System.Windows.Media.Animation;
  15. using System.Windows.Media.Imaging;
  16. using System.Windows.Navigation;
  17. using System.Windows.Shapes;
  18. using WpfLine = System.Windows.Shapes.Line;
  19. using DxfLine = netDxf.Entities.Line;
  20. using DxfLwPolyline = netDxf.Entities.Polyline2D;
  21. using Microsoft.Win32;
  22. using System.Drawing;
  23. namespace TeamAAS_VP.DxfModule
  24. {
  25. /// <summary>
  26. /// DxfView.xaml 的交互逻辑
  27. /// </summary>
  28. public partial class DxfView : UserControl
  29. {
  30. private DxfParserService _dxfParser;
  31. private InteractionController _interactionController;
  32. private double _currentScale = 1.0;
  33. private double _zoomFactor = 1.2;
  34. public DxfView()
  35. {
  36. InitializeComponent();
  37. _dxfParser = new DxfParserService();
  38. InitializeInteractionController();
  39. }
  40. /// <summary>
  41. /// 选中的点位
  42. /// </summary>
  43. public List<PointF> Points { get; private set; }=new List<PointF>();
  44. /// <summary>
  45. /// 需要导入的点位表开始点编号
  46. /// </summary>
  47. public int StartIndex { get;private set; } = 1;
  48. private void InitializeInteractionController()
  49. {
  50. _interactionController = new InteractionController(DrawingCanvas);
  51. _interactionController.SelectionChanged += OnSelectionChanged;
  52. }
  53. private void BtnLoadDxf_Click(object sender, RoutedEventArgs e)
  54. {
  55. var openFileDialog = new OpenFileDialog
  56. {
  57. Filter = "DXF文件 (*.dxf)|*.dxf|所有文件 (*.*)|*.*",
  58. Title = "选择DXF文件"
  59. };
  60. if (openFileDialog.ShowDialog() == true)
  61. {
  62. LoadDxfFile(openFileDialog.FileName);
  63. }
  64. }
  65. private void LoadDxfFile(string filePath)
  66. {
  67. TxtStatus.Text = "正在加载DXF文件...";
  68. DrawingCanvas.Children.Clear();
  69. _interactionController?.ClearSelection();
  70. if (_dxfParser.LoadDxfFile(filePath))
  71. {
  72. var entities = _dxfParser.GetAllEntities();
  73. TxtStatus.Text = $"成功加载: {System.IO.Path.GetFileName(filePath)}";
  74. TxtEntityCount.Text = $"实体数: {entities.Count}";
  75. // Calculate bounds and scale
  76. var bounds = _dxfParser.GetBounds();
  77. double boundsWidth = bounds.maxX - bounds.minX;
  78. double boundsHeight = bounds.maxY - bounds.minY;
  79. if (boundsWidth > 0 && boundsHeight > 0)
  80. {
  81. double scaleX = (DrawingCanvas.Width * 0.8) / boundsWidth;
  82. double scaleY = (DrawingCanvas.Height * 0.8) / boundsHeight;
  83. _currentScale = Math.Min(scaleX, scaleY);
  84. }
  85. // Render entities
  86. foreach (var entity in entities)
  87. {
  88. var renderedElements = GraphicsRenderer.RenderEntity(entity, _currentScale);
  89. foreach (var element in renderedElements)
  90. {
  91. DrawingCanvas.Children.Add(element);
  92. }
  93. }
  94. // Center the drawing
  95. CenterDrawing(bounds);
  96. // Update statistics
  97. UpdateStatistics();
  98. }
  99. else
  100. {
  101. TxtStatus.Text = "加载DXF文件失败";
  102. MessageBox.Show("无法加载DXF文件,请检查文件是否有效。", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
  103. }
  104. }
  105. private void CenterDrawing((double minX, double minY, double maxX, double maxY) bounds)
  106. {
  107. double centerX = (bounds.minX + bounds.maxX) / 2 * _currentScale;
  108. double centerY = -(bounds.minY + bounds.maxY) / 2 * _currentScale;
  109. double offsetX = DrawingCanvas.Width / 2 - centerX;
  110. double offsetY = DrawingCanvas.Height / 2 - centerY;
  111. // Translate all elements
  112. foreach (UIElement element in DrawingCanvas.Children)
  113. {
  114. if (element is WpfLine line)
  115. {
  116. line.X1 += offsetX;
  117. line.X2 += offsetX;
  118. line.Y1 += offsetY;
  119. line.Y2 += offsetY;
  120. }
  121. else if (element is FrameworkElement fe)
  122. {
  123. double left = Canvas.GetLeft(fe);
  124. double top = Canvas.GetTop(fe);
  125. Canvas.SetLeft(fe, left + offsetX);
  126. Canvas.SetTop(fe, top + offsetY);
  127. }
  128. }
  129. }
  130. private void UpdateStatistics()
  131. {
  132. var stats = _dxfParser.GetEntityStatistics();
  133. var sb = new StringBuilder();
  134. foreach (var stat in stats.OrderByDescending(s => s.Value))
  135. {
  136. if (stat.Value > 0)
  137. {
  138. sb.AppendLine($"{stat.Key}: {stat.Value}");
  139. }
  140. }
  141. TxtStatistics.Text = sb.ToString();
  142. }
  143. private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
  144. {
  145. TxtSelectedCount.Text = $"选中: {e.SelectedEntities.Count}";
  146. var sb = new StringBuilder();
  147. if (e.SelectedEntities.Count > 0)
  148. {
  149. sb.AppendLine($"已选择 {e.SelectedEntities.Count} 个图形实体:\n");
  150. var grouped = e.SelectedEntities.GroupBy(entity => entity.Type.ToString());
  151. foreach (var group in grouped)
  152. {
  153. sb.AppendLine($"{group.Key}: {group.Count()}");
  154. }
  155. sb.AppendLine("\n详细信息:");
  156. int index = 1;
  157. foreach (var entity in e.SelectedEntities.Take(10))
  158. {
  159. sb.AppendLine($"\n[{index}] {GetEntityInfo(entity)}");
  160. index++;
  161. }
  162. if (e.SelectedEntities.Count > 10)
  163. {
  164. sb.AppendLine($"\n... 还有 {e.SelectedEntities.Count - 10} 个实体");
  165. }
  166. //如果选中的是圆,则输出圆心坐标,如果是直线,则输出中心点坐标,如果是矩形,则输出中心点坐标,如果是点,则输出点坐标,如果是圆弧,则输出中心点坐标
  167. if (Points!=null)
  168. {
  169. Points = new List<PointF>();
  170. }
  171. Points.Clear();
  172. foreach (var item in e.SelectedEntities)
  173. {
  174. if (item is Circle circle)
  175. {
  176. Points.Add(new PointF((float)circle.Center.X, (float)circle.Center.Y));
  177. }
  178. else if (item is DxfLine line)
  179. {
  180. float centerX = (float)((line.StartPoint.X + line.EndPoint.X) / 2);
  181. float centerY = (float)((line.StartPoint.Y + line.EndPoint.Y) / 2);
  182. Points.Add(new PointF(centerX, centerY));
  183. }
  184. else if (item is DxfLwPolyline polyline && polyline.IsClosed)
  185. {
  186. //计算多边形中心点
  187. float centerX = 0;
  188. float centerY = 0;
  189. foreach (var vertex in polyline.Vertexes)
  190. {
  191. centerX += (float)vertex.Position.X;
  192. centerY += (float)vertex.Position.Y;
  193. }
  194. centerX /= polyline.Vertexes.Count;
  195. centerY /= polyline.Vertexes.Count;
  196. Points.Add(new PointF(centerX, centerY));
  197. }
  198. else if (item is netDxf.Entities.Point point)
  199. {
  200. Points.Add(new PointF((float)point.Position.X, (float)point.Position.Y));
  201. }
  202. else if (item is Arc arc)
  203. {
  204. Points.Add(new PointF((float)arc.Center.X, (float)arc.Center.Y));
  205. }
  206. }
  207. }
  208. else
  209. {
  210. sb.AppendLine("未选择任何图形");
  211. }
  212. TxtSelectedInfo.Text = sb.ToString();
  213. }
  214. private string GetEntityInfo(EntityObject entity)
  215. {
  216. switch (entity)
  217. {
  218. case DxfLine line:
  219. return $"直线: ({line.StartPoint.X:F2}, {line.StartPoint.Y:F2}) → ({line.EndPoint.X:F2}, {line.EndPoint.Y:F2})";
  220. case Circle circle:
  221. return $"圆: 中心({circle.Center.X:F2}, {circle.Center.Y:F2}), 半径={circle.Radius:F2}";
  222. case Arc arc:
  223. return $"圆弧: 中心({arc.Center.X:F2}, {arc.Center.Y:F2}), 半径={arc.Radius:F2}, 角度={arc.StartAngle:F1}°~{arc.EndAngle:F1}°";
  224. case Text text:
  225. return $"文本: \"{text.Value}\" 位置({text.Position.X:F2}, {text.Position.Y:F2})";
  226. case MText mtext:
  227. return $"多行文本: \"{mtext.Value}\" 位置({mtext.Position.X:F2}, {mtext.Position.Y:F2})";
  228. case DxfLwPolyline lwPoly:
  229. return $"轻量多段线: {lwPoly.Vertexes.Count} 个顶点{(lwPoly.IsClosed ? " (闭合)" : "")}";
  230. case Spline spline:
  231. // return $"样条曲线: {spline.ControlPoints.Count} 个控制点";
  232. default:
  233. return $"{entity.Type}: {entity.CodeName}";
  234. }
  235. }
  236. private void BtnClearSelection_Click(object sender, RoutedEventArgs e)
  237. {
  238. Points?.Clear();
  239. _interactionController?.ClearSelection();
  240. }
  241. private void BtnExtractSelected_Click(object sender, RoutedEventArgs e)
  242. {
  243. var selectedEntities = _interactionController?.GetSelectedEntities();
  244. if (selectedEntities == null || selectedEntities.Count == 0)
  245. {
  246. MessageBox.Show("请先选择要提取的图形。", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
  247. return;
  248. }
  249. var sb = new StringBuilder();
  250. sb.AppendLine($"提取的图形信息 (共 {selectedEntities.Count} 个):\n");
  251. var grouped = selectedEntities.GroupBy(p => p.Type.ToString());
  252. foreach (var group in grouped)
  253. {
  254. sb.AppendLine($"\n{group.Key} ({group.Count()} 个):");
  255. sb.AppendLine(new string('-', 50));
  256. foreach (var entity in group)
  257. {
  258. sb.AppendLine(GetEntityInfo(entity));
  259. }
  260. }
  261. MessageBox.Show(sb.ToString(), "提取的图形信息", MessageBoxButton.OK, MessageBoxImage.Information);
  262. }
  263. private void BtnZoomIn_Click(object sender, RoutedEventArgs e)
  264. {
  265. // Smooth zoom centered at viewport center
  266. var sv = ScrollViewer;
  267. if (sv != null)
  268. {
  269. double prevScale = CanvasScaleTransform.ScaleX;
  270. double target = prevScale * _zoomFactor;
  271. target = Math.Max(0.05, Math.Min(target, 50.0));
  272. AnimateScale(target, null);
  273. }
  274. else
  275. {
  276. Zoom(_zoomFactor);
  277. }
  278. }
  279. private void BtnZoomOut_Click(object sender, RoutedEventArgs e)
  280. {
  281. var sv = ScrollViewer;
  282. if (sv != null)
  283. {
  284. double prevScale = CanvasScaleTransform.ScaleX;
  285. double target = prevScale / _zoomFactor;
  286. target = Math.Max(0.05, Math.Min(target, 50.0));
  287. AnimateScale(target, null);
  288. }
  289. else
  290. {
  291. Zoom(1.0 / _zoomFactor);
  292. }
  293. }
  294. private void BtnFitToScreen_Click(object sender, RoutedEventArgs e)
  295. {
  296. var sv = ScrollViewer;
  297. if (sv == null)
  298. {
  299. CanvasScaleTransform.ScaleX = 1.0;
  300. CanvasScaleTransform.ScaleY = 1.0;
  301. UpdateZoomDisplay();
  302. return;
  303. }
  304. // Compute scale to fit the canvas into viewport with some margin
  305. double marginFactor = 0.95;
  306. double targetScaleX = (sv.ViewportWidth * marginFactor) / DrawingCanvas.Width;
  307. double targetScaleY = (sv.ViewportHeight * marginFactor) / DrawingCanvas.Height;
  308. double target = Math.Min(targetScaleX, targetScaleY);
  309. if (double.IsNaN(target) || target <= 0)
  310. target = 1.0;
  311. target = Math.Max(0.05, Math.Min(target, 50.0));
  312. // Animate to target and center
  313. AnimateScale(target, null);
  314. }
  315. private void DrawingCanvas_MouseWheel(object sender, MouseWheelEventArgs e)
  316. {
  317. // Zoom centered at current mouse position inside the ScrollViewer viewport
  318. var sv = ScrollViewer;
  319. if (sv == null) // fallback to previous behavior
  320. {
  321. double zoom1 = e.Delta > 0 ? _zoomFactor : 1.0 / _zoomFactor;
  322. Zoom(zoom1);
  323. return;
  324. }
  325. // Mouse position relative to the ScrollViewer (viewport)
  326. System.Windows.Point mousePosInViewport = e.GetPosition(sv);
  327. // Mouse position relative to the content (unscaled content coordinates)
  328. System.Windows.Point mousePosInContent = e.GetPosition(DrawingCanvas);
  329. double prevScale = CanvasScaleTransform.ScaleX;
  330. double zoom = e.Delta > 0 ? _zoomFactor : 1.0 / _zoomFactor;
  331. double newScale = prevScale * zoom;
  332. // Clamp scale to reasonable range
  333. newScale = Math.Max(0.05, Math.Min(newScale, 50.0));
  334. // Absolute position of the content point after scaling
  335. double absX = mousePosInContent.X * newScale;
  336. double absY = mousePosInContent.Y * newScale;
  337. // Apply scale
  338. CanvasScaleTransform.ScaleX = newScale;
  339. CanvasScaleTransform.ScaleY = newScale;
  340. // Ensure layout updated so ScrollViewer extents are refreshed
  341. sv.UpdateLayout();
  342. // Calculate target offsets so that the content point stays under the mouse cursor
  343. double targetOffsetX = absX - mousePosInViewport.X;
  344. double targetOffsetY = absY - mousePosInViewport.Y;
  345. // Clamp offsets to valid scrollable range
  346. double maxOffsetX = Math.Max(0, sv.ExtentWidth - sv.ViewportWidth);
  347. double maxOffsetY = Math.Max(0, sv.ExtentHeight - sv.ViewportHeight);
  348. targetOffsetX = Math.Max(0, Math.Min(targetOffsetX, maxOffsetX));
  349. targetOffsetY = Math.Max(0, Math.Min(targetOffsetY, maxOffsetY));
  350. sv.ScrollToHorizontalOffset(targetOffsetX);
  351. sv.ScrollToVerticalOffset(targetOffsetY);
  352. UpdateZoomDisplay();
  353. e.Handled = true;
  354. }
  355. /// <summary>
  356. /// Animate scale transform to targetScale. If anchorInContent is null, use viewport center as anchor.
  357. /// anchorInContent, when provided, is in unscaled canvas coordinates (content space).
  358. /// </summary>
  359. private void AnimateScale(double targetScale, System.Windows.Point? anchorInContent)
  360. {
  361. var sv = ScrollViewer;
  362. if (sv == null)
  363. {
  364. CanvasScaleTransform.ScaleX = targetScale;
  365. CanvasScaleTransform.ScaleY = targetScale;
  366. UpdateZoomDisplay();
  367. return;
  368. }
  369. double prevScale = CanvasScaleTransform.ScaleX;
  370. if (Math.Abs(prevScale - targetScale) < 1e-6)
  371. return;
  372. // Determine anchor in content space and its position in viewport
  373. System.Windows.Point anchorContent;
  374. System.Windows.Point anchorViewport;
  375. if (anchorInContent.HasValue)
  376. {
  377. anchorContent = anchorInContent.Value;
  378. anchorViewport = new System.Windows.Point(anchorContent.X * prevScale - sv.HorizontalOffset, anchorContent.Y * prevScale - sv.VerticalOffset);
  379. }
  380. else
  381. {
  382. // use viewport center
  383. anchorViewport = new System.Windows.Point(sv.ViewportWidth / 2.0, sv.ViewportHeight / 2.0);
  384. anchorContent = new System.Windows.Point((sv.HorizontalOffset + anchorViewport.X) / prevScale, (sv.VerticalOffset + anchorViewport.Y) / prevScale);
  385. }
  386. // Calculate target offsets so that the anchor stays in the same viewport position after scaling
  387. double targetOffsetX = anchorContent.X * targetScale - anchorViewport.X;
  388. double targetOffsetY = anchorContent.Y * targetScale - anchorViewport.Y;
  389. double maxOffsetX = Math.Max(0, sv.ExtentWidth - sv.ViewportWidth);
  390. double maxOffsetY = Math.Max(0, sv.ExtentHeight - sv.ViewportHeight);
  391. targetOffsetX = Math.Max(0, Math.Min(targetOffsetX, maxOffsetX));
  392. targetOffsetY = Math.Max(0, Math.Min(targetOffsetY, maxOffsetY));
  393. var duration = TimeSpan.FromMilliseconds(200);
  394. var animX = new DoubleAnimation(prevScale, targetScale, duration, FillBehavior.Stop) { EasingFunction = new QuadraticEase() };
  395. var animY = new DoubleAnimation(prevScale, targetScale, duration, FillBehavior.Stop) { EasingFunction = new QuadraticEase() };
  396. int completed = 0;
  397. EventHandler whenDone = (s, e) =>
  398. {
  399. completed++;
  400. if (completed >= 2)
  401. {
  402. // Ensure final values
  403. CanvasScaleTransform.ScaleX = targetScale;
  404. CanvasScaleTransform.ScaleY = targetScale;
  405. // Update layout so extents reflect final scale
  406. sv.UpdateLayout();
  407. // Recalculate clamped offsets based on final extents
  408. double maxX = Math.Max(0, sv.ExtentWidth - sv.ViewportWidth);
  409. double maxY = Math.Max(0, sv.ExtentHeight - sv.ViewportHeight);
  410. double finalOffsetX = Math.Max(0, Math.Min(targetOffsetX, maxX));
  411. double finalOffsetY = Math.Max(0, Math.Min(targetOffsetY, maxY));
  412. sv.ScrollToHorizontalOffset(finalOffsetX);
  413. sv.ScrollToVerticalOffset(finalOffsetY);
  414. UpdateZoomDisplay();
  415. }
  416. };
  417. animX.Completed += whenDone;
  418. animY.Completed += whenDone;
  419. CanvasScaleTransform.BeginAnimation(ScaleTransform.ScaleXProperty, animX);
  420. CanvasScaleTransform.BeginAnimation(ScaleTransform.ScaleYProperty, animY);
  421. }
  422. private void Zoom(double factor)
  423. {
  424. CanvasScaleTransform.ScaleX *= factor;
  425. CanvasScaleTransform.ScaleY *= factor;
  426. UpdateZoomDisplay();
  427. }
  428. private void UpdateZoomDisplay()
  429. {
  430. TxtZoom.Text = $"缩放: {CanvasScaleTransform.ScaleX * 100:F0}%";
  431. }
  432. private void TxtStartIndex_ValueChanged(object sender, RoutedPropertyChangedEventArgs<int> e)
  433. {
  434. StartIndex= e.NewValue;
  435. }
  436. }
  437. }