/// /// Copyright (c) 2020-2021 Sensus Slovensko a.s. /// using System; using System.Collections.Generic; using System.Drawing; using System.Windows.Forms; using Dirichlet.Numerics; using System.Numerics; using System.Diagnostics; namespace SchematicDrawing { public partial class SchematicDrawingCtrl : UserControl { public bool BenchControlMode { get; set; } public bool EditMode { get; set; } private bool supressRedraws; public bool SupressRedraws { set { if (supressRedraws != value) { supressRedraws = value; if (!supressRedraws) { JoinPipeListsEtc(); Redraw(); } } } get { return supressRedraws; } } private IDrawingItem selectedItem; public IDrawingItem SelectedItem { set { if (selectedItem != value) { selectedItem = value; Redraw(); } } get { return selectedItem; } } private Element selectedElement; public Element SelectedElement { set { if (selectedElement != value) { selectedElement = value; Redraw(); } } get { return selectedElement; } } /// /// Route /// private UInt128 route; public UInt128 Route { set { if (route != value) { route = value; foreach (var it in items) { if (it is IRouteBasedDrawingItem) { Utils.UpdateGraphEdges(route, it as IRouteBasedDrawingItem); } } graph.RunDijkstraAlgo(); Redraw(); } } get { return route; } } /// /// Measurement available flags /// private bool[] msrmntAvailableFlags; public bool[] MsrmntAvailableFlags { set { if (msrmntAvailableFlags != value) { msrmntAvailableFlags = value; Redraw(); } } get { return msrmntAvailableFlags; } } /// /// Measured values /// private double[] measuredValues; public double[] MeasuredValues { set { if (measuredValues != value) { measuredValues = value; Redraw(); } } get { return measuredValues; } } /// /// Alternative strings /// private string[] altStrings; public string[] AltStrings { set { if (altStrings != value) { altStrings = value; Redraw(); } } get { return altStrings; } } /// /// Setpoints /// private double[] setpoints; public double[] Setpoints { set { if (setpoints != value) { setpoints = value; Redraw(); } } get { return setpoints; } } /// /// Custom bitmaps /// private DrawingShape[] customBitmaps; public DrawingShape[] CustomBitmaps { set { if (customBitmaps != value) { customBitmaps = value; Redraw(); } } get { return customBitmaps; } } /// /// Pipes /// Pen[] pensDry; Pen[] pensWater; Pen[] pensVacuum; Pen[] pensDryBackgr; Pen[] pensWaterBackgr; Pen[] pensVacuumBackgr; Pen[] pensHighlighted; IList pipesS; IList pipesM; IList pipesL; IList pipesXL; IList allPipes; /// public string[] PipesS { get { string[] retv = new string[pipesS.Count]; for (int i = 0; i < pipesS.Count; i++) retv[i] = pipesS[i].ToString(); return retv; } set { pipesS.Clear(); if (value != null) foreach (var pStr in value) pipesS.Add(new Pipe(pStr, Sz.S, Id2Item)); if (!supressRedraws) { JoinPipeListsEtc(); Redraw(); } } } /// public string[] PipesM { get { string[] retv = new string[pipesM.Count]; for (int i = 0; i < pipesM.Count; i++) retv[i] = pipesM[i].ToString(); return retv; } set { pipesM.Clear(); if (value != null) foreach (var pStr in value) pipesM.Add(new Pipe(pStr, Sz.M, Id2Item)); if (!supressRedraws) { JoinPipeListsEtc(); Redraw(); } } } /// public string[] PipesL { get { string[] retv = new string[pipesL.Count]; for (int i = 0; i < pipesL.Count; i++) retv[i] = pipesL[i].ToString(); return retv; } set { pipesL.Clear(); if (value != null) foreach (var pStr in value) pipesL.Add(new Pipe(pStr, Sz.L, Id2Item)); if (!supressRedraws) { JoinPipeListsEtc(); Redraw(); } } } /// public string[] PipesXL { get { string[] retv = new string[pipesXL.Count]; for (int i = 0; i < pipesXL.Count; i++) retv[i] = pipesXL[i].ToString(); return retv; } set { pipesXL.Clear(); if (value != null) foreach (var pStr in value) pipesXL.Add(new Pipe(pStr, Sz.XL, Id2Item)); if (!supressRedraws) { JoinPipeListsEtc(); Redraw(); } } } /// /// 1. Join 4 pipe lists. /// 2. Add graph edges in case EditMode is off. /// 3. Determine intersections of pipes in case EditMode is off. /// void JoinPipeListsEtc() { allPipes.Clear(); foreach (var p in pipesS) allPipes.Add(p); foreach (var p in pipesM) allPipes.Add(p); foreach (var p in pipesL) allPipes.Add(p); foreach (var p in pipesXL) allPipes.Add(p); if ((allPipes.Count > 0) && !EditMode && !graph.Completed) { /// Do the following just once in non edit mode for (int i = 0; i < allPipes.Count; i++) { Pipe p = allPipes[i]; if (p.StartItem != null && p.StartItem.GNodes.Count > p.StartNodeId && p.EndItem != null && p.EndItem.GNodes.Count > p.EndNodeId) { int dist = p.GetCoordinates() ? (int)Math.Round(Math.Sqrt((double)((p.x2 - p.x1) * (p.x2 - p.x1) + (p.y2 - p.y1) * (p.y2 - p.y1)))) : 1; graph.AddEdge(p.StartItem.GNodes[p.StartNodeId], p.EndItem.GNodes[p.EndNodeId], dist); graph.AddEdge(p.EndItem.GNodes[p.EndNodeId], p.StartItem.GNodes[p.StartNodeId], dist); } } for (int i = 0; i < allPipes.Count; i++) { for (int j = i + 1; j < allPipes.Count; j++) { if (allPipes[i].CalculateIntersectionWith(allPipes[j])) break; } } graph.Completed = true; } } /// Support for adding new pipes IDrawingItem startItem; int startNodeId; Pipe tempPipe; /// /// Dictionary that speeds up drawing edges between nodes /// public Dictionary Name2Item; public Dictionary Id2Item; /// /// Components: Added by AddItem() /// Cleared by ClearItems() /// IList items; Graph graph; /// /// Rectangles of elements of drawn items /// IList ItemElementRectangles; /// /// Pipes and water /// Brush brushDry; Brush brushWater; Brush brushVacuum; Brush brushDryBackgr; Brush brushWaterBackgr; Brush brushVacuumBackgr; Brush brushHihglighted; /// /// Labels and values /// Font labelFont; Brush labelBrush; Brush labelBackColor; /// Font measuredValueFont; Brush measuredValueBrush; Brush measuredValueBackColor; Brush measuredValueOoRBackColor; Brush measuredValueErrorBackColor; /// Font setpointFont; Brush setpointBrush; Brush setpointBackColor; /// Pen thinBlackPen; Pen selectedRectPen; public SchematicDrawingCtrl() { SupressRedraws = true; InitializeComponent(); this.AutoScaleMode = AutoScaleMode.None; this.DoubleBuffered = true; items = new List(); Name2Item = new Dictionary(); Id2Item = new Dictionary(); SelectedItem = null; route = 0x55AA; /// Invalid value to be overwritten in the first write to Route pipesS = new List(); pipesM = new List(); pipesL = new List(); pipesXL = new List(); allPipes = new List(); graph = new Graph(); ItemElementRectangles = new List(); PrepareFontsPensBrushesEtc(); this.Paint += new System.Windows.Forms.PaintEventHandler(this.SchematicDrawingCtrl_Paint); SupressRedraws = false; } public void AddItem(IDrawingItem item) { items.Add(item); Name2Item.Add(item.Name, item); Id2Item.Add(item.Id, item); if (!EditMode) { DrawingShape dshape = DrawingShape.GetDrawingShape(item); if (dshape != null && dshape.Nodes != null && dshape.Nodes.Length > 0) { item.GNodes.Clear(); for (int nid = 0; nid < dshape.Nodes.Length; nid++) { GNode gnode = new GNode(item, nid); item.GNodes.Add(gnode); graph.AddNode(gnode, dshape.Shape == Shape.Tank, dshape.Shape == Shape.Scale); } foreach (var edge in dshape.Edges) { GEdge gedge = graph.AddEdge(item.GNodes[edge.NodeId1], item.GNodes[edge.NodeId2], edge.DefaultDist, edge.RouteCouple); if (item is IRouteBasedDrawingItem && edge.RouteCouple != RouteCouple.None) { (item as IRouteBasedDrawingItem).EdgesToSet.Add(gedge); } } } } Redraw(); } public void ClearItems() { items.Clear(); Name2Item.Clear(); Id2Item.Clear(); graph.Clear(); Redraw(); } public IList GetItems() { return items; } /// /// Support functions for adding new edges /// public void ShowStartNode(IDrawingItem item, int nodeId) { startItem = item; startNodeId = nodeId; Redraw(); } public void ClearStartNode() { startItem = null; Redraw(); } public IDrawingItem GetStartNode(out int nodeId) { nodeId = startNodeId; return startItem; } public void ShowTempPipe(Pipe pipe) { tempPipe = pipe; Redraw(); } public void ClearTempPipe() { tempPipe = null; Redraw(); } public Pipe GetTempPipe() { return tempPipe; } void PrepareFontsPensBrushesEtc() { /// Font for drawing labels labelFont = new Font("Arial", 10, FontStyle.Regular); labelBrush = Brushes.Black; labelBackColor = new SolidBrush(Color.LightYellow); /// Font for drawing measured values measuredValueFont = new Font("Arial", 10, FontStyle.Regular); measuredValueBrush = Brushes.Black; measuredValueBackColor = new SolidBrush(Color.LightGreen); measuredValueOoRBackColor = new SolidBrush(Color.Orange); measuredValueErrorBackColor = new SolidBrush(Color.Red); /// Font for drawing setpoints setpointFont = new Font("Arial", 10, FontStyle.Regular); setpointBrush = Brushes.Black; setpointBackColor = new SolidBrush(Color.LightSalmon); /// Thin black pen for rounds of labels, measured values and setpoints Brush blackBrush = new SolidBrush(Color.Black); thinBlackPen = new Pen(blackBrush); thinBlackPen.Width = 1; thinBlackPen.LineJoin = System.Drawing.Drawing2D.LineJoin.Bevel; /// Pens for drawing edges/pipes brushDry = new SolidBrush(Color.FromArgb(255, 127, 127, 127)); brushWater = new SolidBrush(Color.FromArgb(255, 0, 162, 232)); brushVacuum = new SolidBrush(Color.FromArgb(255, 64, 255, 64)); brushDryBackgr = new SolidBrush(Color.FromArgb(255, 80, 80, 80)); brushWaterBackgr = new SolidBrush(Color.FromArgb(255, 0, 108, 154)); brushVacuumBackgr = new SolidBrush(Color.FromArgb(255, 48, 196, 48)); brushHihglighted = new SolidBrush(Color.FromArgb(255, 255, 0, 0)); /// pensDry = new Pen[(int)Sz.XL + 1]; pensWater = new Pen[(int)Sz.XL + 1]; pensVacuum = new Pen[(int)Sz.XL + 1]; pensDryBackgr = new Pen[(int)Sz.XL + 1]; pensWaterBackgr = new Pen[(int)Sz.XL + 1]; pensVacuumBackgr = new Pen[(int)Sz.XL + 1]; pensHighlighted = new Pen[(int)Sz.XL + 1]; /// for (Sz sz = Sz.S; sz <= Sz.XL; sz++) { pensDry[(int)sz] = new Pen(brushDry); pensWater[(int)sz] = new Pen(brushWater); pensVacuum[(int)sz] = new Pen(brushVacuum); pensDryBackgr[(int)sz] = new Pen(brushDryBackgr); pensWaterBackgr[(int)sz] = new Pen(brushWaterBackgr); pensVacuumBackgr[(int)sz] = new Pen(brushVacuumBackgr); pensHighlighted[(int)sz] = new Pen(brushHihglighted); float width; switch (sz) { default: case Sz.S: width = 8.0f; break; case Sz.M: width = 10.0f; break; case Sz.L: width = 14.0f; break; case Sz.XL: width = 20.0f; break; } pensDry[(int)sz].Width = width; pensDry[(int)sz].LineJoin = System.Drawing.Drawing2D.LineJoin.Bevel; pensWater[(int)sz].Width = width; pensWater[(int)sz].LineJoin = System.Drawing.Drawing2D.LineJoin.Bevel; pensVacuum[(int)sz].Width = width; pensVacuum[(int)sz].LineJoin = System.Drawing.Drawing2D.LineJoin.Bevel; pensDryBackgr[(int)sz].Width = width; pensDryBackgr[(int)sz].LineJoin = System.Drawing.Drawing2D.LineJoin.Bevel; pensWaterBackgr[(int)sz].Width = width; pensWaterBackgr[(int)sz].LineJoin = System.Drawing.Drawing2D.LineJoin.Bevel; pensVacuumBackgr[(int)sz].Width = width; pensVacuumBackgr[(int)sz].LineJoin = System.Drawing.Drawing2D.LineJoin.Bevel; pensHighlighted[(int)sz].Width = width; pensHighlighted[(int)sz].LineJoin = System.Drawing.Drawing2D.LineJoin.Bevel; } /// Pen for displaying selections (rectangle around a body, a label, a measured value or a setpoint) Brush redBrush = new SolidBrush(Color.Red); selectedRectPen = new Pen(redBrush); selectedRectPen.Width = 3; selectedRectPen.LineJoin = System.Drawing.Drawing2D.LineJoin.Bevel; } public void Redraw() { if (!supressRedraws) { Invalidate(); } } /// /// Draw a complete hydraulical scheme /// /// PaintEventArgs void SchematicDrawingCtrl_Paint(object sender, PaintEventArgs e) { /// Profiling stopwatch.Reset(); stopwatch.Start(); e.Graphics.Clear(Color.FromArgb(240,240,240)); ItemElementRectangles.Clear(); if (items == null) return; /// /// Draw pictures of junctions /// foreach (var item in items) { DrawingShape dshape = DrawingShape.GetDrawingShape(item); if (dshape != null && dshape.Shape == Shape.Junction && dshape.HasImg && (!Utils.IsHidden(item) || EditMode)) { Rectangle rect = DrawItem(e, item, dshape); ItemElementRectangles.Add(new ItemElementRect(item, Element.Body, rect)); } } /// /// Draw edges /// if (EditMode) { /// Draw an empty pipe. Coordinates are always recalculated for (int j = 0; j < allPipes.Count; j++) { Pipe pipe = allPipes[j]; pipe.CoordinatesAreValid = false; DrawPipe(e, pipe, int.MaxValue); } } else { /// Draw pipes with a color determined by Dijkstra shortest path algorithm for (int j = 0; j < allPipes.Count; j++) { Pipe pipe = allPipes[j]; DrawPipe(e, pipe, pipe.StartItem != null ? pipe.StartItem.GNodes[pipe.StartNodeId].Dist : int.MaxValue); } } /// /// Draw pictures of all items except of junctions /// int customBmpIx = 0; foreach (var item in items) { DrawingShape dshape = DrawingShape.GetDrawingShape(item); if (dshape != null && dshape.Shape != Shape.Junction && (!Utils.IsHidden(item) || (EditMode && item == selectedItem))) { if (item is IDrawingItemWithCustomBmp && customBitmaps != null && customBitmaps.Length > customBmpIx) { Rectangle rect = DrawItem(e, item, customBitmaps[customBmpIx++]); ItemElementRectangles.Add(new ItemElementRect(item, Element.Body, rect)); } else if (dshape.HasImg) { Rectangle rect = DrawItem(e, item, dshape); ItemElementRectangles.Add(new ItemElementRect(item, Element.Body, rect)); } } } /// /// Draw nodes and a temporary edge (edit mode only) /// if (EditMode) { if (tempPipe != null) { DrawPipe(e, tempPipe, 0, true); /// TODO } foreach (var item in items) { DrawingShape dshape; if (item != null && !Utils.IsHidden(item) && (null != (dshape = DrawingShape.GetDrawingShape(item)))) { /// /// Draw nodes and node ID-s /// for (int nid = 0; nid < dshape.Nodes.Length; nid++) { bool isStartNode = (item == startItem) && (nid == startNodeId); Node rfNode = dshape.GetRotatedFlippedNode(item, nid); Point dir = Utils.GetVect(rfNode.Orient); Point norm = Utils.GetNormalVect(rfNode.Orient); Point[] triangle = new Point[] { new Point(item.X + SchematicDrawing.Const.GridSize * rfNode.X + dir.X, item.Y + SchematicDrawing.Const.GridSize * rfNode.Y + dir.Y), new Point(item.X + SchematicDrawing.Const.GridSize * rfNode.X + 7 * dir.X + 4 * norm.X, item.Y + SchematicDrawing.Const.GridSize * rfNode.Y + 7 * dir.Y + 4 * norm.Y), new Point(item.X + SchematicDrawing.Const.GridSize * rfNode.X + 7 * dir.X - 4 * norm.X, item.Y + SchematicDrawing.Const.GridSize * rfNode.Y + 7 * dir.Y - 4 * norm.Y) }; e.Graphics.DrawPolygon(isStartNode ? Pens.Red : Pens.LawnGreen, triangle); e.Graphics.FillPolygon(isStartNode ? Brushes.Red : Brushes.LawnGreen, triangle); e.Graphics.DrawString(nid.ToString(), labelFont, isStartNode ? Brushes.Red : Brushes.LawnGreen, item.X + SchematicDrawing.Const.GridSize * rfNode.X + 14 * dir.X - 5, item.Y + SchematicDrawing.Const.GridSize * rfNode.Y + 14 * dir.Y - 7); } } } } /// /// Draw measured values /// if (measuredValues != null) { int msrdValIx = 0; foreach (var item in items) { if (msrdValIx >= measuredValues.Length) break; if (item is IDrawingItemWithMeasuredVal) { IDrawingItemWithMeasuredVal itm = item as IDrawingItemWithMeasuredVal; bool msrmntAvailable = msrmntAvailableFlags[msrdValIx]; double msrdVal = Common.Units.ConvertTo(itm.MsrdUnit, measuredValues[msrdValIx]); string altString = altStrings[msrdValIx]; if ((msrmntAvailable || EditMode) && !Utils.IsHidden(item)) { string strVal = string.IsNullOrEmpty(itm.MsrdFormat) ? altString : string.Format(itm.MsrdFormat, msrdVal); if (!string.IsNullOrEmpty(strVal)) { /// Paint water in the tank on a scale DrawingShape dshape = DrawingShape.GetDrawingShape(item); if (dshape.Shape == Shape.Scale) { Rectangle r = dshape.GetRotatedFlippedImageRectangle(item); int level = (int)Math.Round((r.Height - 2) * Math.Max(0, Math.Min(msrdVal, itm.MsrdValLimHi)) / itm.MsrdValLimHi); e.Graphics.FillRectangle(brushWater, r.X + 2, r.Y + r.Height - 2 - level, r.Width - 4, level); } /// Draw rectangle for a measured value SizeF size = e.Graphics.MeasureString(strVal, measuredValueFont); Rectangle rect = new Rectangle(itm.X + itm.MsrdX, itm.Y + itm.MsrdY, (int)Math.Round(size.Width) + 4, (int)Math.Round(size.Height) + 2); ItemElementRectangles.Add(new ItemElementRect(item, Element.MeasuredVal, rect)); if (measuredValues[msrdValIx] >= itm.MsrdValLimLo && measuredValues[msrdValIx] <= itm.MsrdValLimHi) { e.Graphics.FillRectangle(measuredValueBackColor, rect); } else { e.Graphics.FillRectangle(measuredValueOoRBackColor, rect); } e.Graphics.DrawRectangle(thinBlackPen, rect); /// Draw the measured value e.Graphics.DrawString(strVal, measuredValueFont, measuredValueBrush, rect.X + 2, rect.Y + 2); if (EditMode && (item == selectedItem) && (selectedElement == Element.MeasuredVal)) { /// Draw red rectangle around the selected item label e.Graphics.DrawRectangle(selectedRectPen, rect.X - 1, rect.Y - 1, rect.Width + 2, rect.Height + 2); } } } msrdValIx++; } } } /// /// Draw setpoints /// if (setpoints != null) { int setpointIx = 0; foreach (var item in items) { if (setpointIx >= setpoints.Length) break; if (item is IDrawingItemWithSetpoint) { IDrawingItemWithSetpoint its = item as IDrawingItemWithSetpoint; double setpoint = Common.Units.ConvertTo(its.SetpUnit, setpoints[setpointIx++]); if (!Utils.IsHidden(item)) { string strVal = string.IsNullOrEmpty(its.SetpFormat) ? setpoint.ToString() : string.Format(its.SetpFormat, setpoint); SizeF size = (its.SetpUnit != Common.Unit.Pct) ? e.Graphics.MeasureString(strVal, setpointFont) : e.Graphics.MeasureString(string.Format(its.SetpFormat, 100.0), setpointFont); Rectangle rect = new Rectangle(its.X + its.SetpX, its.Y + its.SetpY, (int)Math.Round(size.Width) + 4, (int)Math.Round(size.Height) + 2); ItemElementRectangles.Add(new ItemElementRect(item, Element.Setpoint, rect)); int barLen = (int)Math.Round(rect.Width * Math.Max(0, Math.Min(setpoint, 100.0)) / 100.0); e.Graphics.FillRectangle(setpointBackColor, rect.X, rect.Y, barLen, rect.Height); e.Graphics.DrawRectangle(thinBlackPen, rect); e.Graphics.DrawString(strVal, setpointFont, setpointBrush, rect.X + 2, rect.Y + 2); if (EditMode && (item == selectedItem) && (selectedElement == Element.Setpoint)) { /// Draw red rectangle around the selected item label e.Graphics.DrawRectangle(selectedRectPen, rect.X - 1, rect.Y - 1, rect.Width + 2, rect.Height + 2); } } } } } /// /// Draw labels, Various.Drawing... component labels are drawn in edit mode only /// foreach (var item in items) { if ((!Utils.IsHidden(item) || (EditMode && (item == selectedItem))) && /// 1. Do not show hidden items, only if they are selected (!item.ClassName.StartsWith("Various.Drawing.") || EditMode) && /// 2. Do not show Various.Drawing... items, only in EditMode !item.ClassName.Equals("Various.Drawing.Edges")) /// 3. Never show Various.Drawing.Edges components { string strVal = item.Name; SizeF size = e.Graphics.MeasureString(strVal, labelFont); Rectangle rect = new Rectangle(item.X + item.LblX, item.Y + item.LblY, (int)Math.Round(size.Width) + 4, (int)Math.Round(size.Height) + 2); ItemElementRectangles.Add(new ItemElementRect(item, Element.Label, rect)); e.Graphics.FillRectangle(labelBackColor, rect); e.Graphics.DrawRectangle(thinBlackPen, rect); e.Graphics.DrawString(strVal, labelFont, labelBrush, rect.X + 2, rect.Y + 2); if (EditMode && (item == selectedItem) && (selectedElement == Element.Label)) { /// Draw red rectangle around the selected item label e.Graphics.DrawRectangle(selectedRectPen, rect.X - 1, rect.Y - 1, rect.Width + 2, rect.Height + 2); } } } /// Profiling stopwatch.Stop(); if (firstTime) { firstTime = false; } else if (totalTimes < long.MaxValue) { totalMs += stopwatch.ElapsedMilliseconds; totalTicks += stopwatch.ElapsedTicks; totalTimes++; Console.WriteLine("Average Paint duration is {0} ms = {1} ticks", totalMs / totalTimes, totalTicks / totalTimes); } } /// Profiling Stopwatch stopwatch = new Stopwatch(); bool firstTime = true; long totalMs = 0; long totalTicks = 0; long totalTimes = 0; /// /// Draw a pipe. /// /// PaintEventArgs /// Pipe object /// Distance from source of water for coloring /// true = highlighted (edit mode) void DrawPipe(PaintEventArgs e, Pipe p, int distance, bool highlighted = false) { if (!p.CoordinatesAreValid) p.GetCoordinates(); int eIx = Math.Max((int)Sz.S, Math.Min((int)Sz.XL, (int)p.Sz)); /// eIx is (int)p.Sz, but between Sz.S .. Sz.XL (1 .. 4) Pen pen = (distance == int.MaxValue) ? pensDry[eIx] : (distance < Const.Vacuum) ? pensWater[eIx] : pensVacuum[eIx]; if (highlighted) { e.Graphics.DrawLine(pensHighlighted[eIx], p.x1, p.y1, p.x2, p.y2); } else if (!p.HasIntersection) { e.Graphics.DrawLine(pen, p.x1, p.y1, p.x2, p.y2); } else { Pen pen2 = (distance == int.MaxValue) ? pensDryBackgr[eIx] : (distance < Const.Vacuum) ? pensWaterBackgr[eIx] : pensVacuumBackgr[eIx]; float len = (float)Math.Sqrt(Convert.ToSingle((p.x2 - p.x1) * (p.x2 - p.x1) + (p.y2 - p.y1) * (p.y2 - p.y1))); float PP = 1.0f - p.P; if (p.P * len < 20 && PP * len < 20) { /// Draw a straght line with darker color e.Graphics.DrawLine(pen2, p.x1, p.y1, p.x2, p.y2); } else { /// Draw a line having normal color with a bow having darker color float vx = 17.0f * (p.x2 - p.x1) / len; /// Fixed size vector in the direction of the line float vy = 17.0f * (p.y2 - p.y1) / len; float xm = p.x1 * PP + p.x2 * p.P; /// Intersection point float ym = p.y1 * PP + p.y2 * p.P; int x3 = Convert.ToInt32(Math.Round(xm - vx)); /// Points around the intersection point int y3 = Convert.ToInt32(Math.Round(ym - vy)); int x4 = Convert.ToInt32(Math.Round(xm - 2 * vy / 3)); int y4 = Convert.ToInt32(Math.Round(ym + 2 * vx / 3)); int x5 = Convert.ToInt32(Math.Round(xm + vx)); int y5 = Convert.ToInt32(Math.Round(ym + vy)); /// Joints between straight parts of the line and the bow DrawingShape dsh = DrawingShape.GetDrawingShape(Shape.Junction, p.Sz); Image image = (distance == int.MaxValue) ? dsh.ImgRightClosedDry : (distance < Const.Vacuum) ? dsh.ImgRight : dsh.ImgRightVacuum; e.Graphics.DrawImage(image, x3 - dsh.Wid / 2, y3 - dsh.Hgh / 2); e.Graphics.DrawImage(image, x5 - dsh.Wid / 2, y5 - dsh.Hgh / 2); e.Graphics.DrawLine(pen2, x3, y3, x4, y4); /// Bow e.Graphics.DrawLine(pen2, x4, y4, x5, y5); e.Graphics.DrawLine(pen, p.x1, p.y1, x3, y3); /// Straight parts of the line e.Graphics.DrawLine(pen, x5, y5, p.x2, p.y2); } } } /// /// Draw an item (body). /// /// PaintEventArgs /// IDrawingItem object /// DrawingShape /// Rectangle where item body is drawn Rectangle DrawItem(PaintEventArgs e, IDrawingItem item, DrawingShape dsh) { bool routeBit = false; if (item is IRouteBasedDrawingItem) { UInt128 mask = ((UInt128)1) << (item as IRouteBasedDrawingItem).BitNr; routeBit = (item as IRouteBasedDrawingItem).Inverted ? ((route & mask) == 0) : ((route & mask) != 0); } /// Draw the component image Rectangle rect = dsh.GetRotatedFlippedImageRectangle(item); if (item is IRouteBasedDrawingItem && routeBit == false /// closed && item.GNodes.Count > 0 && item.GNodes[0].Dist < Const.Vacuum /// wet && dsh.HasClosedWet) /// has appropriate image { if (item.Flip) { switch (item.Orient) { case Orient.R: e.Graphics.DrawImage(dsh.ImgRightFlippedClosedWet, rect.X, rect.Y); break; case Orient.Up: e.Graphics.DrawImage(dsh.ImgUpFlippedClosedWet, rect.X, rect.Y); break; case Orient.L: e.Graphics.DrawImage(dsh.ImgLeftFlippedClosedWet, rect.X, rect.Y); break; case Orient.Dn: e.Graphics.DrawImage(dsh.ImgDownFlippedClosedWet, rect.X, rect.Y); break; } } else { switch (item.Orient) { case Orient.R: e.Graphics.DrawImage(dsh.ImgRightClosedWet, rect.X, rect.Y); break; case Orient.Up: e.Graphics.DrawImage(dsh.ImgUpClosedWet, rect.X, rect.Y); break; case Orient.L: e.Graphics.DrawImage(dsh.ImgLeftClosedWet, rect.X, rect.Y); break; case Orient.Dn: e.Graphics.DrawImage(dsh.ImgDownClosedWet, rect.X, rect.Y); break; } } } else if (item is IRouteBasedDrawingItem && routeBit == true /// open && item.GNodes.Count > 0 && item.GNodes[0].Dist == int.MaxValue /// dry && dsh.HasOpenDry) /// has appropriate image { if (item.Flip) { switch (item.Orient) { case Orient.R: e.Graphics.DrawImage(dsh.ImgRightFlippedOpenDry, rect.X, rect.Y); break; case Orient.Up: e.Graphics.DrawImage(dsh.ImgUpFlippedOpenDry, rect.X, rect.Y); break; case Orient.L: e.Graphics.DrawImage(dsh.ImgLeftFlippedOpenDry, rect.X, rect.Y); break; case Orient.Dn: e.Graphics.DrawImage(dsh.ImgDownFlippedOpenDry, rect.X, rect.Y); break; } } else { switch (item.Orient) { case Orient.R: e.Graphics.DrawImage(dsh.ImgRightOpenDry, rect.X, rect.Y); break; case Orient.Up: e.Graphics.DrawImage(dsh.ImgUpOpenDry, rect.X, rect.Y); break; case Orient.L: e.Graphics.DrawImage(dsh.ImgLeftOpenDry, rect.X, rect.Y); break; case Orient.Dn: e.Graphics.DrawImage(dsh.ImgDownOpenDry, rect.X, rect.Y); break; } } } else if (((item is IRouteBasedDrawingItem && routeBit == false) || /// closed OR (item.GNodes.Count > 0 && item.GNodes[0].Dist == int.MaxValue)) /// dry && dsh.HasClosedDry) /// has appropriate image { if (item.Flip) { switch (item.Orient) { case Orient.R: e.Graphics.DrawImage(dsh.ImgRightFlippedClosedDry, rect.X, rect.Y); break; case Orient.Up: e.Graphics.DrawImage(dsh.ImgUpFlippedClosedDry, rect.X, rect.Y); break; case Orient.L: e.Graphics.DrawImage(dsh.ImgLeftFlippedClosedDry, rect.X, rect.Y); break; case Orient.Dn: e.Graphics.DrawImage(dsh.ImgDownFlippedClosedDry, rect.X, rect.Y); break; } } else { switch (item.Orient) { case Orient.R: e.Graphics.DrawImage(dsh.ImgRightClosedDry, rect.X, rect.Y); break; case Orient.Up: e.Graphics.DrawImage(dsh.ImgUpClosedDry, rect.X, rect.Y); break; case Orient.L: e.Graphics.DrawImage(dsh.ImgLeftClosedDry, rect.X, rect.Y); break; case Orient.Dn: e.Graphics.DrawImage(dsh.ImgDownClosedDry, rect.X, rect.Y); break; } } } else if (item.GNodes.Count > 0 && Const.Vacuum <= item.GNodes[0].Dist && item.GNodes[0].Dist < int.MaxValue /// vacuum && dsh.HasVacuum) /// has appropriate image { if (item.Flip) { switch (item.Orient) { case Orient.R: e.Graphics.DrawImage(dsh.ImgRightFlippedVacuum, rect.X, rect.Y); break; case Orient.Up: e.Graphics.DrawImage(dsh.ImgUpFlippedVacuum, rect.X, rect.Y); break; case Orient.L: e.Graphics.DrawImage(dsh.ImgLeftFlippedVacuum, rect.X, rect.Y); break; case Orient.Dn: e.Graphics.DrawImage(dsh.ImgDownFlippedVacuum, rect.X, rect.Y); break; } } else { switch (item.Orient) { case Orient.R: e.Graphics.DrawImage(dsh.ImgRightVacuum, rect.X, rect.Y); break; case Orient.Up: e.Graphics.DrawImage(dsh.ImgUpVacuum, rect.X, rect.Y); break; case Orient.L: e.Graphics.DrawImage(dsh.ImgLeftVacuum, rect.X, rect.Y); break; case Orient.Dn: e.Graphics.DrawImage(dsh.ImgDownVacuum, rect.X, rect.Y); break; } } } else if (dsh.HasImg) { if (item.Flip) { switch (item.Orient) { case Orient.R: e.Graphics.DrawImage(dsh.ImgRightFlipped, rect.X, rect.Y); break; case Orient.Up: e.Graphics.DrawImage(dsh.ImgUpFlipped, rect.X, rect.Y); break; case Orient.L: e.Graphics.DrawImage(dsh.ImgLeftFlipped, rect.X, rect.Y); break; case Orient.Dn: e.Graphics.DrawImage(dsh.ImgDownFlipped, rect.X, rect.Y); break; } } else { switch (item.Orient) { case Orient.R: e.Graphics.DrawImage(dsh.ImgRight, rect.X, rect.Y); break; case Orient.Up: e.Graphics.DrawImage(dsh.ImgUp, rect.X, rect.Y); break; case Orient.L: e.Graphics.DrawImage(dsh.ImgLeft, rect.X, rect.Y); break; case Orient.Dn: e.Graphics.DrawImage(dsh.ImgDown, rect.X, rect.Y); break; } } } if (EditMode && (item == selectedItem) && (selectedElement == Element.Body)) { /// Draw red rectangle around the selected item body e.Graphics.DrawRectangle(selectedRectPen, rect.X - 1, rect.Y - 1, rect.Width + 2, rect.Height + 2); } return rect; } /// /// Searches for an elemend out of 'elements' in the displayed schematic drawing. /// /// X-coordinate /// Y-coordinate /// Bitfield of elements to be searched for /// Found item or null (in case of body, node, label, measured value or setpoint) /// Found pipe or null (in case of a pipe) /// Found node id or 0 (in case of an item node) /// Element enum value (Element.None when nothing was found) public Element FindElement(int x, int y, Element elements, out IDrawingItem item, out Pipe pipe, out int nodeId) { const int NodeTolerance = 5; const int EdgeTolerance = 2; foreach (Element element in new Element[] { Element.Setpoint, Element.MeasuredVal, Element.Label, Element.Body }) { if (((elements & element) != 0) && (null != (item = FindBodyLabelMeasuredValOrSetpoint(x, y, element)))) { pipe = null; nodeId = 0; return element; } } if (((elements & Element.Node) != 0) && FindNode(x, y, NodeTolerance, out item, out nodeId)) { pipe = null; return Element.Node; } if (((elements & Element.Pipe) != 0) && (null != (pipe = FindPipe(x, y, EdgeTolerance)))) { item = null; nodeId = 0; return Element.Pipe; } item = null; pipe = null; nodeId = 0; return Element.None; } /// /// Searches for an item body, label, measured value or setpoint. /// /// X coordinate /// Y coordinate /// Element to find /// Found item IDrawingItem FindBodyLabelMeasuredValOrSetpoint(int x, int y, Element elementSpec) { for (int i = ItemElementRectangles.Count - 1; i >= 0; i--) { var itElmRect = ItemElementRectangles[i]; if (itElmRect.Element == elementSpec && itElmRect.Rect.Contains(x, y)) { return itElmRect.Item; } } return null; } bool FindNode(int x, int y, int tolerance, out IDrawingItem foundItem, out int nid) { foreach (var item in items) { if (Utils.IsHidden(item)) continue; DrawingShape dshape = DrawingShape.GetDrawingShape(item); if (dshape != null) { for (int i = 0; i < dshape.Nodes.Length; i++) { Node nd = dshape.GetRotatedFlippedNode(item, i); int dx = Math.Abs(item.X + Const.GridSize * nd.X - x); int dy = Math.Abs(item.Y + Const.GridSize * nd.Y - y); if ((dx <= tolerance) && (dy <= tolerance)) { nid = i; foundItem = item; return true; } } } } nid = 0; foundItem = null; return false; } Pipe FindPipe(int x, int y, int tolerance) { /// Search between pipes float maxDistSquared = (tolerance + 4.0f) * (tolerance + 4.0f); if (pipesS != null) { foreach (var pipe in pipesS) if (SquaredDistanceFromLine(x, y, pipe) < maxDistSquared) return pipe; } maxDistSquared = (tolerance + 5.0f) * (tolerance + 5.0f); if (pipesM != null) { foreach (var pipe in pipesM) if (SquaredDistanceFromLine(x, y, pipe) < maxDistSquared) return pipe; } maxDistSquared = (tolerance + 7.0f) * (tolerance + 7.0f); if (pipesL != null) { foreach (var pipe in pipesL) if (SquaredDistanceFromLine(x, y, pipe) < maxDistSquared) return pipe; } maxDistSquared = (tolerance + 10.0f) * (tolerance + 10.0f); if (pipesXL != null) { foreach (var pipe in pipesXL) if (SquaredDistanceFromLine(x, y, pipe) < maxDistSquared) return pipe; } return null; } /// /// Calculates a squared distance of a point [x,y] from a line. /// /// Point X /// Point Y /// Pipe /// Squared distance float SquaredDistanceFromLine(int x, int y, Pipe pipe) { if (!pipe.GetCoordinates() || (pipe.x1 == pipe.x2 && pipe.y1 == pipe.y2)) { return float.MaxValue; } Vector2 X = new Vector2(Convert.ToSingle(x), Convert.ToSingle(y)); Vector2 X1 = new Vector2(Convert.ToSingle(pipe.x1), Convert.ToSingle(pipe.y1)); Vector2 X2 = new Vector2(Convert.ToSingle(pipe.x2), Convert.ToSingle(pipe.y2)); Vector2 vX = X - X1; /// |X1 X| Vector2 v12 = X2 - X1; /// |X1 X2| float projFactor = Vector2.Dot(vX, v12) / Vector2.Dot(v12, v12); if (projFactor < 0) { return (X - X1).LengthSquared(); } else if (projFactor > 1.0f) { return (X - X2).LengthSquared(); } else { Vector2 projX = projFactor * v12 + X1; return (X - projX).LengthSquared(); } } } }