DxfView.xaml.cs 20 KB

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