/* * Copyright (c) 2022 LG Electronics Inc. * SPDX-License-Identifier: LicenseRef-LGE-Proprietary */ using Aurora; using Aurora.Api; using Aurora.View; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Globalization; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; namespace AuroraUIComponent.UserControls { public enum ePattern { Unknown = -1, Dot, Line, Rect, LWPolyLine } /// /// Interaction logic for AuroraCanvas.xaml /// public partial class AuroraCanvas : Canvas { protected static Logger Log = new Logger("AuroraCanvas"); public const double EPS7 = 1e-7; public double FrameWidth { get => (double)GetValue(FrameWidthProperty); set { if (FrameWidth != value) { SetValue(FrameWidthProperty, value); } } } public static readonly DependencyProperty FrameWidthProperty = DependencyProperty.Register( nameof(FrameWidth), typeof(double), typeof(AuroraCanvas), new PropertyMetadata(100.0, OnCanvasParamChanged)); public double FrameHeight { get => (double)GetValue(FrameHeightProperty); set { if (FrameHeight != value) { SetValue(FrameHeightProperty, value); } } } public static readonly DependencyProperty FrameHeightProperty = DependencyProperty.Register( nameof(FrameHeight), typeof(double), typeof(AuroraCanvas), new PropertyMetadata(100.0, OnCanvasParamChanged)); public bool ShowCenterLine { get => (bool)GetValue(ShowCenterLineProperty); set { if (ShowCenterLine != value) { SetValue(ShowCenterLineProperty, value); } } } public static readonly DependencyProperty ShowCenterLineProperty = DependencyProperty.Register( nameof(ShowCenterLine), typeof(bool), typeof(AuroraCanvas), new PropertyMetadata(false, OnCanvasParamChanged)); public BitmapImage BgImage { get => (BitmapImage)GetValue(BgImageProperty); set { if (BgImage != value) { SetValue(BgImageProperty, value); } } } public static readonly DependencyProperty BgImageProperty = DependencyProperty.Register( nameof(BgImage), typeof(BitmapImage), typeof(AuroraCanvas), new PropertyMetadata(null, OnCanvasParamChanged)); protected static void OnCanvasParamChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var canvasCell = d as AuroraCanvas; canvasCell.UpdateCanvas(); } protected void UpdateCanvas() { InvalidateVisual(); } #region 점/선/사각형 표시 추가 public static readonly DependencyProperty DrawDotListProperty = DependencyProperty.Register( nameof(DrawDotList), typeof(IList), typeof(AuroraCanvas), new PropertyMetadata(null, OnCanvasParamChanged)); public IList DrawDotList { get { //if (GetValue(DrawDotListProperty) == null) { // SetValue(DrawDotListProperty, new IList()); //} return (IList)GetValue(DrawDotListProperty); } set { SetValue(DrawDotListProperty, value); if (DrawDotList is ObservableCollection obcList) { obcList.CollectionChanged += Canvas_CollectionChanged; } } } public static readonly DependencyProperty DrawLineListProperty = DependencyProperty.Register( nameof(DrawLineList), typeof(IList), typeof(AuroraCanvas), new PropertyMetadata(null, OnCanvasParamChanged)); public IList DrawLineList { get { //if (GetValue(DrawLineListProperty) == null) { // SetValue(DrawLineListProperty, new IList()); //} return (IList)GetValue(DrawLineListProperty); } set { SetValue(DrawLineListProperty, value); if (DrawLineList is ObservableCollection obcList) { obcList.CollectionChanged += Canvas_CollectionChanged; } } } public static readonly DependencyProperty DrawRectListProperty = DependencyProperty.Register( nameof(DrawRectList), typeof(IList), typeof(AuroraCanvas), new PropertyMetadata(null, OnCanvasParamChanged)); public IList DrawRectList { get { //if (GetValue(DrawRectListProperty) == null) { // SetValue(DrawRectListProperty, new IList()); //} return (IList)GetValue(DrawRectListProperty); } set { SetValue(DrawRectListProperty, value); if (DrawRectList is ObservableCollection obcList) { obcList.CollectionChanged += Canvas_CollectionChanged; } } } public static readonly DependencyProperty DrawLWPolyLineProperty = DependencyProperty.Register( nameof(DrawLWPolyLine), typeof(IList), typeof(AuroraCanvas), new PropertyMetadata(null, OnCanvasParamChanged)); public IList DrawLWPolyLine { get { //if (GetValue(DrawLWPolyLineProperty) == null) { // SetValue(DrawLWPolyLineProperty, new IList()); //} return (IList)GetValue(DrawLWPolyLineProperty); } set { SetValue(DrawLWPolyLineProperty, value); if (DrawLWPolyLine is ObservableCollection obcList) { obcList.CollectionChanged += Canvas_CollectionChanged; } } } public static readonly DependencyProperty DrawCrossListProperty = DependencyProperty.Register("DrawCrossList", typeof(IList), typeof(AuroraCanvas), new PropertyMetadata(null)); public IList DrawCrossList { get { //if (GetValue(DrawCrossListProperty) == null) { // SetValue(DrawCrossListProperty, new IList()); //} return (IList)GetValue(DrawCrossListProperty); } set { SetValue(DrawCrossListProperty, value); if (DrawCrossList is ObservableCollection obcList) { obcList.CollectionChanged += Canvas_CollectionChanged; } } } protected void Canvas_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { InvalidateVisual(); } #endregion #region Flip public static readonly DependencyProperty IsFlipYProperty = DependencyProperty.Register( nameof(IsFlipY), typeof(bool), typeof(AuroraCanvas), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, (d, e) => (d as AuroraCanvas).OnFlipChanged(e) )); public bool IsFlipY { get { return (bool)GetValue(IsFlipYProperty); } set { SetValue(IsFlipYProperty, value); } } protected void OnFlipChanged(object e) { InvalidateVisual(); } public static readonly DependencyProperty IsDataChangedProperty = DependencyProperty.Register( nameof(IsDataChanged), typeof(bool), typeof(AuroraCanvas), new PropertyMetadata(false, OnIsDataChanged)); public bool IsDataChanged { get { return (bool)GetValue(IsDataChangedProperty); } set { SetValue(IsDataChangedProperty, value); } } protected static void OnIsDataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var canvasGlass = d as AuroraCanvas; canvasGlass.InvalidateVisual(); } public static readonly DependencyProperty BlockCanvasProperty = DependencyProperty.Register( nameof(BlockCanvas), typeof(bool), typeof(AuroraCanvas), new FrameworkPropertyMetadata(false, (d, e) => (d as AuroraCanvas).OnBlockCanvasChanged(e) )); public bool BlockCanvas { get { return (bool)GetValue(BlockCanvasProperty); } set { SetValue(BlockCanvasProperty, value); } } protected void OnBlockCanvasChanged(object e) { InvalidateVisual(); } #endregion #region 클릭 위치 관련 public static readonly DependencyProperty LastSavedXProperty = DependencyProperty.Register( nameof(LastSavedX), typeof(double), typeof(AuroraCanvas), new FrameworkPropertyMetadata(0.0)); public double LastSavedX { get { return (double)GetValue(LastSavedXProperty); } set { SetValue(LastSavedXProperty, value); } } public static readonly DependencyProperty LastSavedYProperty = DependencyProperty.Register( nameof(LastSavedY), typeof(double), typeof(AuroraCanvas), new FrameworkPropertyMetadata(0.0)); public double LastSavedY { get { return (double)GetValue(LastSavedYProperty); } set { SetValue(LastSavedYProperty, value); } } #endregion public static readonly Brush DUMMY_GLASS_PEN_BRUSH = Brushes.Transparent; public static readonly Brush DUMMY_GLASS_BG_BRUSH = Brushes.Black; public static readonly Brush DEFAULT_SHAPE_BRUSH = Brushes.Red; public const int FONT_HEIGHT = 10; protected readonly double PixelDip; protected double OriX, OriY; protected double CFrameW, CFrameH; protected double iViewMarginW = 20; protected double iViewMarginH = 20; protected const double MARGIN_RATIO = 10; protected double FrameW; protected double FrameH; protected double RatioX, RatioY, RatioNorm; protected double _ViewRatio = 1.0; public AuroraCanvas() { SnapsToDevicePixels = true; PixelDip = VisualTreeHelper.GetDpi(this).PixelsPerDip; MouseLeftButtonUp += Handle_MouseLeftButtonUp; // Canvas 좌표 표시 Text tbCanvasCoordPos = new TextBlock() { Foreground = Brushes.White, FontSize = 11 }; tbCanvasCoordPos.Margin = new Thickness(10, 10, 0, 0); Children.Add(tbCanvasCoordPos); MouseMove += Canvas_MouseMove; if (this.ContextMenu == null) { this.ContextMenu = new ContextMenu(); } #region MagnificationWindow : 일부 영역 확대 Window 구현 - Context Menu로 등록 // LangToken Resource Find (번역 적용을 위해) var source = TryFindResource("langTokens"); Binding bindingX5 = new Binding("Header"); bindingX5.Source = source; bindingX5.Path = new PropertyPath("[OpenMagnifyingView]"); var menuItemX5 = new MenuItem(); menuItemX5.SetBinding(MenuItem.HeaderProperty, bindingX5); menuItemX5.Click += Click_ShowMagnificationWindowX5; this.ContextMenu.Items.Add(menuItemX5); IsVisibleChanged += OnVisibleChanged; #endregion } protected void OnVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) { if (e.NewValue is bool isVisible && isVisible == false) { // 다른 메뉴로 이동하는 등, Canvas가 Visible하지 않게 되면 같이 Close if (MagWindow != null) { MagWindow.Close(); } } } protected void Handle_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) { Point mousePosition = Mouse.GetPosition(this); LastSavedX = FromCanvasX(mousePosition.X); LastSavedY = FromCanvasY(mousePosition.Y); if (MagWindow != null) { // 이미 열려있을 경우에만 갱신 (아닐 경우에는 No Action) ShowMagnificationWindow(LastSavedX, LastSavedY); } } #region Canvas 좌표 표시 Text protected TextBlock tbCanvasCoordPos; protected bool MoveLock = false; protected bool IsContextOpen = false; protected Point LastMousePosition; // Canvas좌표 protected string _CanvasXText; protected string _CanvasYText; protected double LastCanvasPositionX = 0; protected double LastCanvasPositionY = 0; // Canvas 기본좌표 그대로 protected void Canvas_MouseMove(object sender, MouseEventArgs e) { if (tbCanvasCoordPos?.Visibility != Visibility.Visible) { return; } if (MoveLock || IsContextOpen) { return; } var point = e.GetPosition(this); LastCanvasPositionX = point.X; LastCanvasPositionY = point.Y; LastMousePosition.X = FromCanvasX(point.X); LastMousePosition.Y = FromCanvasY(point.Y); _CanvasXText = LastMousePosition.X.ToString("F1"); _CanvasYText = LastMousePosition.Y.ToString("F1"); tbCanvasCoordPos.Text = $"X:{_CanvasXText} Y:{_CanvasYText}"; } #endregion protected override void OnRender(DrawingContext dc) { //Log.FuncI(); base.OnRender(dc); if (FrameWidth <= 0 || FrameHeight <= 0) { return; } dc.PushClip(new RectangleGeometry(new Rect(new Point(0, 0), RenderSize))); FrameW = FrameWidth; FrameH = FrameHeight; iViewMarginW = ActualWidth * MARGIN_RATIO / 100 / 2; iViewMarginH = ActualHeight * MARGIN_RATIO / 100 / 2; // Frame 윤곽선의 actual width & height CFrameW = ActualWidth - iViewMarginW * 2; CFrameH = ActualHeight - iViewMarginH * 2; // 가로/세로 비율 맞추기 _ViewRatio = FrameH / FrameW; // 크기/마진 재계산 if (_ViewRatio > (CFrameH / CFrameW)) { CFrameW = CFrameH / _ViewRatio; iViewMarginW = (ActualWidth - CFrameW) / 2; } else { CFrameH = CFrameW * _ViewRatio; iViewMarginH = (ActualHeight - CFrameH) / 2; } OriX = iViewMarginW; if (!IsFlipY) { OriY = iViewMarginH + CFrameH; // Origin : 좌하단 } else { OriY = iViewMarginH; // Origin : 좌상단 } // FrameW, FrameH : 실물 Frame size // Ratio 는 Canvas 크기 / 실물 Frame 크기 RatioX = (ActualWidth - (iViewMarginW * 2)) / FrameW; RatioY = (ActualHeight - (iViewMarginH * 2)) / FrameH; RatioNorm = Math.Sqrt(RatioX * RatioX + RatioY * RatioY); Render(dc); } protected virtual void Render(DrawingContext dc) { //Log.FuncI(); DrawDummyFrame(dc); // Frame 외곽선 DrawExtraShapes(dc); } protected void DrawExtraShapes(DrawingContext dc) { if (DrawLWPolyLine != null) { //foreach (var item in DrawLWPolyLine) { // DrawDot(dc, item.X, item.Y, 0, 0, 2, (item.CustomBrush != null) ? item.CustomBrush : Brushes.Red); //} //// 연결선 : 일단 직선만. (곡선까지 적용하려면 추가 코드 작업 필요) //for (int i = 1; i < DrawLWPolyLine.Count; i++) { // DrawLine(dc, DrawLWPolyLine[i - 1].X, DrawLWPolyLine[i - 1].Y, DrawLWPolyLine[i].X, DrawLWPolyLine[i].Y, 0, 0, (DrawLWPolyLine[i].CustomBrush != null) ? DrawLWPolyLine[i].CustomBrush : Brushes.Red); //} var pathList = DrawLWPolyLine; if (DrawLWPolyLine != null && DrawLWPolyLine.Count > 0) { DrawLinesAndArcFromLWPolyLine(dc, DrawLWPolyLine, (DrawLWPolyLine[0].CustomBrush != null) ? DrawLWPolyLine[0].CustomBrush : Brushes.Red, 1, false, false); } } if (DrawDotList != null) { foreach (var item in DrawDotList) { DrawDot(dc, item.ORG_X, item.ORG_Y, 0, 0, 2, (item.CustomBrush != null) ? item.CustomBrush : Brushes.Red); } } if (DrawLineList != null) { foreach (var item in DrawLineList) { DrawDot(dc, item.START_X, item.START_Y, 0, 0, 1, (item.CustomBrush != null) ? item.CustomBrush : Brushes.Red); DrawLine(dc, item.START_X, item.START_Y, item.END_X, item.END_Y, 0, 0, (item.CustomBrush != null) ? item.CustomBrush : Brushes.Red); DrawDot(dc, item.END_X, item.END_Y, 0, 0, 1, (item.CustomBrush != null) ? item.CustomBrush : Brushes.Red); } } if (DrawRectList != null) { foreach (var item in DrawRectList) { DrawRectangle(dc, item.START_X, item.START_Y, item.END_X, item.END_Y, 0, 0, (item.LineBrush != null) ? item.LineBrush : Brushes.Red, 1, item.FillBrush); if (!string.IsNullOrEmpty(item.Label)) { bool isHorizontal = Math.Abs(item.END_X - item.START_X) > Math.Abs(item.END_Y - item.START_Y); DrawText(dc, item.Label, Math.Min(item.START_X, item.END_X), Math.Min(item.START_Y, item.END_Y) - 8, 0, 0, item.LineBrush); } } } if (DrawCrossList != null) { foreach (var item in DrawCrossList) { DrawCross(dc, item.CENTER_X, item.CENTER_Y, item.LENGTH_X, item.LENGTH_Y, 0, 0, (item.CustomBrush != null) ? item.CustomBrush : Brushes.Red, item.DASH, 3); } } } protected void DrawDummyFrame(DrawingContext dc) { // Frame 외곽선 Rect frameRect = new Rect(); frameRect.X = iViewMarginW; frameRect.Y = iViewMarginH; frameRect.Width = CFrameW + 1; // +1 : Brush 영역 확보 (한쪽 면이 선에 가려서 잘림) frameRect.Height = CFrameH + 1; // +1 : Brush 영역 확보 (한쪽 면이 선에 가려서 잘림) //dc.DrawRectangle(Brushes.White, DummyFramePen, frameRect); if (BgImage != null) { ImageBrush imageBrush = new ImageBrush { //ImageSource = new BitmapImage(new Uri("pack://application:,,,/YourImage.png")), ImageSource = BgImage, Stretch = Stretch.UniformToFill }; dc.DrawRectangle(imageBrush, new Pen(DUMMY_GLASS_PEN_BRUSH, 1), frameRect); } else { dc.DrawRectangle(DUMMY_GLASS_BG_BRUSH, new Pen(DUMMY_GLASS_PEN_BRUSH, 1), frameRect); } // Flip을 확인하기 위한 Guide UI 그리기 //DrawFlipGuideImage(dc); // Origin //Point orig = new Point(); //orig.X = OriX; //orig.Y = OriY; //dc.DrawEllipse(Brushes.Yellow, new Pen(Brushes.Yellow, 1), orig, 4, 4); //if (ShowKeyRegend) { // DrawText(dc, "FrameKey(Frame)", -5, 10, 0, 0, Brushes.White, align: TextAlignment.Right); // DrawText(dc, "FrameKey(Motor)", -5, 18, 0, 0, Brushes.Yellow, align: TextAlignment.Right); // DrawText(dc, "TPKey(Frame)", -5, 30, 0, 0, Brushes.GreenYellow, align: TextAlignment.Right); // DrawText(dc, "TPKey(Motor)", -5, 38, 0, 0, Brushes.Green, align: TextAlignment.Right); //} //-- 십자선 그리기 if (ShowCenterLine) { //var dashSize = Math.Min(FrameWidth / 100, FrameHeight / 100); // NOTE: 전체 frame 중심선 Dash 크기를 바꾸고 싶으면 아래 숫자 변경 var dashSize = 0.005; DrawCross(dc, FrameWidth / 2, FrameHeight / 2, FrameWidth, FrameHeight, 0, 0, Brushes.Red, dashSize); } } #region Canvas 좌표변환 protected double ToCanvasX(double value) { return OriX + value * RatioX; } protected double ToCanvasY(double value) { if (!IsFlipY) { return OriY - value * RatioY; } else { return OriY + value * RatioY; } } protected double ToCanvasNorm(double value) { var lengthX = value * RatioX; var lengthY = value * RatioY; return Math.Sqrt(lengthX * lengthX + lengthY * lengthY) / Math.Sqrt(2); } protected double FromCanvasX(double value) { return (value - OriX) / RatioX; } protected double FromCanvasY(double value) { //return (OriY - value) / RatioY; if (!IsFlipY) { return (OriY - value) / RatioY; } else { return (value - OriY) / RatioY; } } #endregion #region Drawing Shapes public (double x, double sizeX, double y, double sizeY) GetCanvasPosAndSize(double startX, double startY, double endX, double endY, double areaStartX = 0, double areaStartY = 0) { var x1 = ToCanvasX(areaStartX + startX); var x2 = ToCanvasX(areaStartX + endX); var y1 = ToCanvasY(areaStartY + startY); var y2 = ToCanvasY(areaStartY + endY); return (x1 < x2 ? x1 : x2, Math.Abs(x1 - x2) + 1, y1 < y2 ? y1 : y2, Math.Abs(y1 - y2) + 1); } public (double x, double sizeX, double y, double sizeY) GetCanvasPosAndSize2(double orgX, double orgY, double sizeX, double sizeY, double areaStartX = 0, double areaStartY = 0) { var x1 = ToCanvasX(areaStartX + orgX - sizeX / 2); var x2 = ToCanvasX(areaStartX + orgX + sizeX / 2); var y1 = ToCanvasY(areaStartY + orgY - sizeY / 2); var y2 = ToCanvasY(areaStartY + orgY + sizeY / 2); return (x1 < x2 ? x1 : x2, Math.Abs(x1 - x2) + 1, y1 < y2 ? y1 : y2, Math.Abs(y1 - y2) + 1); } protected void DrawRectangle(DrawingContext dc, double startX, double startY, double endX, double endY, double areaStartX, double areaStartY, Brush brush = null, double brushSize = 1.0, Brush fillBrush = null) { if (dc != null) { var r2 = new Rect(); var dim = GetCanvasPosAndSize(startX, startY, endX, endY, areaStartX, areaStartY); r2.X = dim.x; r2.Width = dim.sizeX; r2.Y = dim.y; r2.Height = dim.sizeY; if (brush == null) { brush = Brushes.White; } dc.DrawRectangle(fillBrush, new Pen(brush, brushSize), r2); } } protected void DrawCross(DrawingContext dc, double origX, double origY, double sizeX, double sizeY, double areaStartX, double areaStartY, Brush brush = null, double dash = -1, double brushSize = 1.0) // areaStart : 미지정 시 frame 원점 { if (dc != null) { var x1 = ToCanvasX(areaStartX + origX - sizeX / 2); var x2 = ToCanvasX(areaStartX + origX + sizeX / 2); var y1 = ToCanvasY(areaStartY + origY - sizeY / 2); var y2 = ToCanvasY(areaStartY + origY + sizeY / 2); var startPoint = new Point(); var endPoint = new Point(); startPoint.X = x1 < x2 ? x1 : x2; startPoint.Y = (y1 + y2) / 2; endPoint.X = x1 < x2 ? x2 : x1; endPoint.Y = (y1 + y2) / 2; if (brush == null) { brush = Brushes.White; } Pen pen = new Pen(brush, brushSize); if (dash > 0) { pen.DashStyle = new DashStyle(new double[] { dash * FrameW, dash * FrameH }, 0); } dc.DrawLine(pen, startPoint, endPoint); startPoint = new Point(); endPoint = new Point(); startPoint.X = (x1 + x2) / 2; startPoint.Y = y1 < y2 ? y1 : y2; endPoint.X = (x1 + x2) / 2; endPoint.Y = y1 < y2 ? y2 : y1; dc.DrawLine(pen, startPoint, endPoint); } } protected void DrawLine(DrawingContext dc, double startX, double startY, double endX, double endY, double areaStartX, double areaStartY, Brush brush = null, double brushSize = 1.0) { if (dc != null) { var startPoint = new Point(); var endPoint = new Point(); startPoint.X = ToCanvasX(areaStartX + startX); startPoint.Y = ToCanvasY(areaStartY + startY); endPoint.X = ToCanvasX(areaStartX + endX); endPoint.Y = ToCanvasY(areaStartY + endY); if (brush == null) { brush = Brushes.White; } dc.DrawLine(new Pen(brush, brushSize), startPoint, endPoint); } } protected void DrawDot(DrawingContext dc, double originX, double originY, double areaStartX, double areaStartY, double dotSize, Brush brush = null, double brushSize = 1.0) { if (dc != null) { if (Math.Abs(dotSize) < 0.001) { dotSize = 2; } var r2 = new Point(); r2.X = ToCanvasX(areaStartX + originX); r2.Y = ToCanvasY(areaStartY + originY); var dotSizeCanvas = ToCanvasNorm(dotSize); if (brush == null) { brush = Brushes.White; } dc.DrawEllipse(brush, new Pen(brush, brushSize), r2, dotSizeCanvas, dotSizeCanvas); } } protected void DrawArcFromLWPolyLine(DrawingContext dc, double startX, double startY, double endX, double endY, double bulge, double areaStartX, double areaStartY, Brush brush = null, double brushSize = 1.0) { if (dc != null) { if (brush == null) { brush = Brushes.White; } Pen pen = new Pen(brush, brushSize); startX = ToCanvasX(areaStartX + startX); startY = ToCanvasY(areaStartY + startY); endX = ToCanvasX(areaStartX + endX); endY = ToCanvasY(areaStartY + endY); /* // C++ 원본코드 dA와 dB 가 복잡한데, 어차피 필요없으니 굳이 분석하지 말자. Best 로직도 아닌것 같음 double dT = 4 * Math.Atan(bulge); double dA = Math.Sin(dT) * Math.Sin(dT) / (1 - Math.Cos(dT)) - Math.Cos(dT) + 1; double dB = endY - startX * Math.Sin(dT) + Math.Sin(dT) * (endX - startX * Math.Cos(dT) + startY * Math.Sin(dT)) / (1 - Math.Cos(dT)) - startY * Math.Cos(dT); // arc 의 센터 위치인 것 같음 double dYc = dB / dA; double dXc = (endX - startX * Math.Cos(dT) + (startY - dYc) * Math.Sin(dT)) / (1 - Math.Cos(dT)); double dDX = endX - startX; // Bx - Ax double dDY = endY - startY; // By - Ay double dD = Math.Sqrt(dDX * dDX + dDY * dDY) / 2.0; // chord 길이의 절반 double dR = Math.Abs(dD / Math.Sin(dT / 2.0)); // arc 의 반지름 double dAngleS = Math.Atan2(startY - dYc, startX - dXc) * 180.0 / (Math.Atan(1.0) * 4.0); double dAngleE = dAngleS + dT * 180.0 / (Math.Atan(1.0) * 4.0); if (dT < 0) { dAngleS = dAngleE; dAngleE = dAngleS - dT * 180.0 / (Math.Atan(1.0) * 4.0); } */ // WPF 의 ArcSegment 를 사용하면 간단하게 LWPolyline 의 bulge 처리가 가능함 // 필요한 정보는 반지름과 중심각임.. // (주의) : 원호는 4가지 case가 존재할 수 있는데, 다양한 Bulge 에 대한 검증은 필요함 double absBulge = Math.Abs(bulge); double chordLength = Math.Sqrt(Math.Pow(endX - startX, 2) + Math.Pow(endY - startY, 2)); //double subtendedAngle = 4 * Math.Atan(absBulge); double radius = chordLength * ((1.0 + Math.Pow(absBulge, 2)) / (4.0 * absBulge)); PathGeometry pathGeometry = new PathGeometry(); PathFigure pathFigure = new PathFigure(); pathFigure.StartPoint = new Point(startX, startY); // 시작점 ArcSegment arcSegment = new ArcSegment(); arcSegment.Point = new Point(endX, endY); // 끝점 arcSegment.Size = new Size(radius, radius); // arc 의 반지름 arcSegment.SweepDirection = bulge > 0 ? SweepDirection.Counterclockwise : SweepDirection.Clockwise; //arcSegment.IsLargeArc = Math.Abs(dAngleE - dAngleS) > 180; pathFigure.Segments.Add(arcSegment); pathGeometry.Figures.Add(pathFigure); dc.DrawGeometry(null, pen, pathGeometry); } } protected void DrawLinesAndArcFromLWPolyLine(DrawingContext dc, IList pathList, Brush brush = null, double brushSize = 1.0, bool showLabel = false, bool showPos = false) { if (dc != null) { if (brush == null) { brush = DEFAULT_SHAPE_BRUSH; } Pen pen = new Pen(brush, brushSize); PathGeometry pathGeometry = new PathGeometry(); for (int i = 0; i < pathList.Count - 1; i++) { DrawDot(dc, pathList[i].X, pathList[i].Y, 0, 0, 2, brush, brushSize); if (showLabel) { DrawText(dc, $"#{i}", pathList[i].X + 5, pathList[i].Y + 5, 0, 0, brush); } if (showPos) { DrawText(dc, $"#{i}[{pathList[i].X.ToString("N2")},{pathList[i].Y.ToString("N2")}],Bulge[{pathList[i].Bulge.ToString("N2")}]", 50, i * 10 + 5, 0, 0, brush); } var startX = ToCanvasX(pathList[i].X); var startY = ToCanvasY(pathList[i].Y); var endX = ToCanvasX(pathList[i + 1].X); var endY = ToCanvasY(pathList[i + 1].Y); var bulge = pathList[i + 1].Bulge; PathFigure pathFigure = new PathFigure(); if (Math.Abs(bulge) < EPS7) { pathFigure.StartPoint = new Point(startX, startY); LineSegment lineSegment = new LineSegment() { Point = new Point(endX, endY) }; pathFigure.Segments.Add(lineSegment); } else { /* double dT = 4 * Math.Atan(pathList[i + 1].Bulge); double dA = Math.Sin(dT) * Math.Sin(dT) / (1 - Math.Cos(dT)) - Math.Cos(dT) + 1; double dB = endY - startX * Math.Sin(dT) + Math.Sin(dT) * (endX - startX * Math.Cos(dT) + startY * Math.Sin(dT)) / (1 - Math.Cos(dT)) - startY * Math.Cos(dT); double dYc = dB / dA; double dXc = (endX - startX * Math.Cos(dT) + (startY - dYc) * Math.Sin(dT)) / (1 - Math.Cos(dT)); double dDX = endX - startX; double dDY = endY - startY; double dD = Math.Sqrt(dDX * dDX + dDY * dDY) / 2.0; double dR = Math.Abs(dD / Math.Sin(dT / 2.0)); double dAngleS = Math.Atan2(startY - dYc, startX - dXc) * 180.0 / (Math.Atan(1.0) * 4.0); double dAngleE = dAngleS + dT * 180.0 / (Math.Atan(1.0) * 4.0); if (dT < 0) { dAngleS = dAngleE; dAngleE = dAngleS - dT * 180.0 / (Math.Atan(1.0) * 4.0); } */ // WPF 의 ArcSegment 를 사용하면 간단하게 LWPolyline 의 bulge 처리가 가능함 // 필요한 정보는 반지름과 중심각인데, 중심각도 필요없어 보임. // (주의) : 원호는 4가지 case가 존재할 수 있는데, 다양한 Bulge 에 대한 검증은 필요함 double absBulge = Math.Abs(bulge); double chordLength = Math.Sqrt(Math.Pow(endX - startX, 2) + Math.Pow(endY - startY, 2)); //double subtendedAngle = 4 * Math.Atan(absBulge); double radius = chordLength * ((1.0 + Math.Pow(absBulge, 2)) / (4.0 * absBulge)); pathFigure.StartPoint = new Point(startX, startY); // 시작점 ArcSegment arcSegment = new ArcSegment(); arcSegment.Point = new Point(endX, endY); // 끝점 arcSegment.Size = new Size(radius, radius); // circular arc 의 반지름 arcSegment.SweepDirection = bulge > 0 ? SweepDirection.Counterclockwise : SweepDirection.Clockwise; //arcSegment.IsLargeArc = Math.Abs(dAngleE - dAngleS) > 180; pathFigure.Segments.Add(arcSegment); /* // 레이저 스캐너용 연산함수 테스트...나중에 지우자... PolyLinePattern.CalArcCenterAndAngle(pathList[i].X, pathList[i].Y, pathList[i + 1].X, pathList[i + 1].Y, bulge, out double centerX, out double centerY, out double centralAngle); Debug.WriteLine($"Bulge: i: {i}, bulge: {bulge}, centerX: {centerX}, centerY: {centerY}, centralAng: {centralAngle*180.0/Math.PI}"); */ } pathGeometry.Figures.Add(pathFigure); } dc.DrawGeometry(null, pen, pathGeometry); } } protected void DrawText(DrawingContext dc, string text, double origX, double origY, double areaStartX, double areaStartY, Brush brush = null, int fontHeight = FONT_HEIGHT, TextAlignment align = TextAlignment.Center) { if (dc != null) { if (brush == null) { brush = DEFAULT_SHAPE_BRUSH; } var formattedText = new FormattedText( text, CultureInfo.GetCultureInfo("ko-KR"), FlowDirection.RightToLeft, new Typeface("Arial"), fontHeight, brush, PixelDip); formattedText.TextAlignment = align; var x = ToCanvasX(areaStartX + origX) - formattedText.Width / 8; var y = ToCanvasY(areaStartY + origY) - formattedText.Height / 2; var textPos = new Point(x, y); dc.DrawText(formattedText, textPos); } } protected void DrawText(DrawingContext dc, List texts, double origX, double origY, double areaStartX, double areaStartY, Brush brush = null) { if (dc != null) { for (int index = 0; index < texts.Count; index++) { if (brush == null) { brush = DEFAULT_SHAPE_BRUSH; } var formattedText = new FormattedText( texts[index], CultureInfo.GetCultureInfo("ko-KR"), FlowDirection.LeftToRight, new Typeface("Arial"), FONT_HEIGHT, brush, PixelDip); formattedText.TextAlignment = TextAlignment.Center; var x = ToCanvasX(areaStartX + origX); var y = ToCanvasY(areaStartY + origY) + formattedText.Height * (index - texts.Count / 2); var textPos = new Point(x, y); dc.DrawText(formattedText, textPos); } } } #endregion #region MagnificationWindow : 일부 영역 확대 Window 구현 protected void Click_ShowMagnificationWindowX5(object sender, RoutedEventArgs e) { ShowMagnificationWindow(LastMousePosition.X, LastMousePosition.Y, 5); // MouseMove 위치 기준으로 띄움 //ShowMagnificationWindow(LastSavedX, LastSavedY, 5); // 마지막 마우스 좌클릭 위치 기준으로 띄움 // => Context Menu 누르기 전에 눌러놓고 띄워야 한다는 걸 알고 쓰면 정확한 좌표를 쓸수있는 장점은 있지만 // 마우스 우클릭 Context Menu를 직관적으로 쓰는 것이 나을 수도 있어서 MouseMove 위치 기준으로 띄우게 해 둡니다. } protected const string TITLE_MAGNIFYING_VIEW = "Magnification"; protected double MagnifyRatio = 5; protected Window MagWindow; protected UCMagnification MagContent; protected void ShowMagnificationWindow(double centerX, double centerY, double magnifyRatio = 0) // 확대하고싶은 배율 (이에 맞는 영역이 capture됨) { if (magnifyRatio > 0) MagnifyRatio = magnifyRatio; if (MagnifyRatio <= 0) MagnifyRatio = 1; Log.I($"Center[{centerX}, {centerY}], Ratio[{MagnifyRatio}]"); // [2024.03.29. ym.park] Close하고 다시 여는 대신 Window Content만 업데이트하는 것으로 변경 //if (MagWindow != null) { // MagWindow.Close(); //} double captureSizeRef = 500; double windowSize = captureSizeRef * 1; double captureSize = windowSize / MagnifyRatio; if (MagContent == null) { MagContent = new UCMagnification(); MagContent.Ratio = MagnifyRatio; MagContent.CenterX = centerX; MagContent.CenterY = centerY; MagContent.CmdRatioChanged = new DelegateCommand(ProcessRatioChanged); MagWindow = new Window() { Content = MagContent }; MagWindow.Title = TITLE_MAGNIFYING_VIEW; //MagWindow.Width = windowSizeRef * MagnifyRatio; //MagWindow.Height = windowSizeRef * MagnifyRatio; MagWindow.Width = windowSize; MagWindow.Height = windowSize; MagWindow.Left = 0; MagWindow.Top = 0; MagWindow.Topmost = true; MagWindow.Closed += OnMagWindowClosed; } else { MagContent.Ratio = MagnifyRatio; MagContent.CenterX = centerX; MagContent.CenterY = centerY; } MagContent.RectangleBrush = GetCaptureVisualBrush( ToCanvasX(centerX) - captureSize / 2, ToCanvasY(centerY) - captureSize / 2, captureSize, captureSize); MagContent.Caption = $"DateTime : {DateTime.Now.ToString("yyyyMMdd_HH:mm:ss")}\nCenter (X:{centerX.ToString("N3")} Y:{centerY.ToString("N3")})"; // Show Window MagWindow.Show(); MagWindow.Focus(); } protected void ProcessRatioChanged(object obj) { if (obj == null) return; if (obj is Tuple item) { ShowMagnificationWindow(item.Item1, item.Item2, item.Item3); // centerX, centerY, ratio } } protected void OnMagWindowClosed(object sender, EventArgs e) { MagWindow = null; MagContent = null; } protected VisualBrush GetCaptureVisualBrush(double startPosX, double startPosY, double width, double height) { // AuroraCanvas를 VisualBrush로 저장 VisualBrush brush = new VisualBrush(); brush.Visual = this; // To show a specific part of the Canvas, set the Viewbox property of the VisualBrush brush.Viewbox = new Rect(startPosX, startPosY, width, height); brush.ViewboxUnits = BrushMappingMode.Absolute; return brush; } #endregion } }