// // (C) Copyright 2003-2019 by Autodesk, Inc. // // Permission to use, copy, modify, and distribute this software in // object code form for any purpose and without fee is hereby granted, // provided that the above copyright notice appears in all copies and // that both that copyright notice and the limited warranty and // restricted rights notice below appear in all supporting // documentation. // // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE // UNINTERRUPTED OR ERROR FREE. // // Use, duplication, or disclosure by the U.S. Government is subject to // restrictions set forth in FAR 52.227-19 (Commercial Computer // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. // using System; using System.Collections.Generic; using System.Text; using System.Drawing; using System.Drawing.Drawing2D; using System.Windows.Forms; using Autodesk.Revit.DB; using Autodesk.Revit.UI; using System.Resources; using System.Reflection; namespace Revit.SDK.Samples.CurtainWallGrid.CS { /// /// Maintain the appearance of the 2D projection of the curtain grid and its behaviors /// public class GridDrawing { #region Fields //the document of the sample private MyDocument m_myDocument; // stores the reference to the parent geometry private GridGeometry m_geometry; // stores the matrix transform system used in the image drawing private GridCoordinates m_coordinates; // stores the client rectangle of the canvas of the curtain grid private System.Drawing.Rectangle m_boundary; // stores the midpoint of the client rectangle private System.Drawing.Point m_center; // all the grid lines of U ("Horizontal" in curtain wall) direction (in GridLine2D format) List m_uGridLines2D; // all the grid lines of V ("Vertical" in curtain wall) direction (in GridLine2D format) List m_vGridLines2D; // stores the boundary lines of the curtain grid of the curtain wall(in GridLine2D format) List m_boundLines2D; // stores the graphics path of all the U ("Horizontal") lines private List m_uLinePathList; // stores the graphics paths of all the segments of the U lines, // each element in the "m_uSegLinePathListList" is a list, which contains all the segments of a grid line private List> m_uSegLinePathListList; // stores the graphics path of all the V ("Vertical") lines private List m_vLinePathList; // stores the graphics paths of all the segments of the V lines, // each element in the "m_uSegLinePathListList" is a list, which contains all the segments of a grid line private List> m_vSegLinePathListList; // stores the graphics path of the boundary of the curtain grid private List m_boundPath; // stores all the assistant lines & hints DrawObject m_drawObject; // stores the boundary coordinate of the curtain grid of the curtain wall int m_minX = 0; int m_minY = 0; int m_maxX = 0; int m_maxY = 0; // stores the index of the currently selected U grid line private int m_selectedUIndex = -1; // stores the index of the currently selected V grid line private int m_selectedVIndex = -1; // stores the index of the currently selected segment of a specified U grid line private int m_selectedUSegmentIndex = -1; // stores the index of the currently selected segment of a specified V grid line private int m_selectedVSegmentIndex = -1; // indicates whether the current mouse is valid // if the mouse location is outside the boundary of the curtain grid, it's invalid // if the current operation is "Add Horizontal/Vertical grid line" and the mouse location // is on another grid line, it's invalid (it's not allowed to add a grid line overlap another) // if the current operation is "Move grid line" and the destination location to be moved (indicated by the mouse location) // is on another grid line, it's invalid (it's not allowed to move a grid line to lap over another) // except these, the mouse location is valid private bool m_mouseLocationValid = false; // specify the pen width used in different kind of lines ////////////////////////////////////////////////////////////////////////// // used in select a line/a segment // in this situation, use a Pen of width 10.0f to paint the graphics path, if the mouse location // is in the outline of the graphics path, we can say that the mouse "selects" a grid line/segment private float m_outlineSelectPenWidth = 10.0f; // the width of the pen which is used to paint the boundary lines private float m_boundaryPenWidth = 1.5f; // the width of the pen which is used to paint the locked grid lines private float m_lockedPenWidth = 2.0f; // the width of the pen which is used to paint the unlocked grid lines private float m_unlockedPenWidth = 1.0f; // the width of the pen which is used to paint the currently selected grid lines private float m_selectedLinePenWidth = 2.5f; // the width of the pen which is used to paint the currently selected segments private float m_selectedSegmentPenWidth = 3.0f; // the width of the pen which is used to paint the sketch lines private float m_sketchPenWidth = 2.5f; ////////////////////////////////////////////////////////////////////////// #endregion #region Properties /// /// stores the reference to the parent geometry /// public GridGeometry Geometry { get { return m_geometry; } } /// /// stores the matrix transform system used in the image drawing /// public GridCoordinates Coordinates { get { return m_coordinates; } set { m_coordinates = value; } } /// /// stores the client rectangle of the canvas of the curtain grid /// public System.Drawing.Rectangle Boundary { get { return m_boundary; } set { m_boundary = value; } } /// /// stores the midpoint of the client rectangle /// public System.Drawing.Point Center { get { return m_center; } set { m_center = value; } } /// /// all the grid lines of U ("Horizontal" in curtain wall) direction (in GridLine2D format) /// public List UGridLines2D { get { return m_uGridLines2D; } } /// /// all the grid lines of V ("Vertical" in curtain wall) direction (in GridLine2D format) /// public List VGridLines2D { get { return m_vGridLines2D; } } /// /// stores the boundary lines of the curtain grid of the curtain wall(in GridLine2D format) /// public List BoundLines2D { get { return m_boundLines2D; } } /// /// stores the graphics path of all the U ("Horizontal") lines /// public List ULinePathList { get { return m_uLinePathList; } } /// /// stores the graphics paths of all the segments of the U lines, /// each element in the "m_uSegLinePathListList" is a list, which contains all the segments of a grid line /// public List> USegLinePathListList { get { return m_uSegLinePathListList; } } /// /// stores the graphics path of all the V ("Vertical") lines /// public List VLinePathList { get { return m_vLinePathList; } } /// /// stores the graphics paths of all the segments of the V lines, /// each element in the "m_uSegLinePathListList" is a list, which contains all the segments of a grid line /// public List> VSegLinePathListList { get { return m_vSegLinePathListList; } } /// /// stores all the assistant lines & hints /// public DrawObject DrawObject { get { return m_drawObject; } } /// /// stores the index of the currently selected U grid line /// public int SelectedUIndex { get { return m_selectedUIndex; } } /// /// stores the index of the currently selected V grid line /// public int SelectedVIndex { get { return m_selectedVIndex; } } /// /// stores the index of the currently selected segment of a specified U grid line /// public int SelectedUSegmentIndex { get { return m_selectedUSegmentIndex; } } /// /// stores the index of the currently selected segment of a specified V grid line /// public int SelectedVSegmentIndex { get { return m_selectedVSegmentIndex; } } /// /// indicates whether the current mouse is valid /// if the mouse location is outside the boundary of the curtain grid, it's invalid /// if the current operation is "Add Horizontal/Vertical grid line" and the mouse location /// is on another grid line, it's invalid (it's not allowed to add a grid line overlap another) /// if the current operation is "Move grid line" and the destination location to be moved (indicated by the mouse location) /// is on another grid line, it's invalid (it's not allowed to move a grid line to lap over another) /// except these, the mouse location is valid /// public bool MouseLocationValid { get { return m_mouseLocationValid; } } #endregion #region Delegates & Events // occurs only when the mouse is inside the curtain grid area public delegate void MouseInGridHandler(); public event MouseInGridHandler MouseInGridEvent; // occurs only when the mouse is outside (out of or at the edge) the curtain grid area public delegate void MouseOutGridHandler(); public event MouseOutGridHandler MouseOutGridEvent; #endregion #region Constructors /// /// constructor /// /// /// the referred parent geometry of the curtain grid /// public GridDrawing(MyDocument myDoc, GridGeometry geometry) { m_myDocument = myDoc; if (null == geometry) { TaskDialog.Show("Revit", "Error! There's no grid information in the curtain wall."); } else { m_geometry = geometry; m_coordinates = new GridCoordinates(myDoc, this); m_uGridLines2D = new List(); m_vGridLines2D = new List(); m_boundLines2D = new List(); m_uLinePathList = new List(); m_uSegLinePathListList = new List>(); m_vSegLinePathListList = new List>(); m_vLinePathList = new List(); m_boundPath = new List(); m_drawObject = new DrawObject(); } } #endregion #region Public methods /// /// get the 2D data of the curtain grid /// the original data is in CurtainGridLine/XYZ/Curve format of Revit, change it to Point/GridLine2D format /// public void GetLines2D() { // clear the data container first to delete all the obsolete data m_uGridLines2D.Clear(); m_vGridLines2D.Clear(); m_boundLines2D.Clear(); m_uLinePathList.Clear(); m_uSegLinePathListList.Clear(); m_vSegLinePathListList.Clear(); m_vLinePathList.Clear(); m_boundPath.Clear(); m_drawObject.Clear(); // initialize the matrixes used in the code m_coordinates.GetMatrix(); // get the U grid lines and their segments (in GridLine2D format) GetULines2D(); // get the V grid lines and their segments (in GridLine2D format) GetVLines2D(); // get all the boundary lines (in GridLine2D format) GetBoundLines2D(); // check whether the segments are "isolated" // (the "isolated" segments will be displayed especially in the sample) // how a segment is "isolated": for a segment, at least one of its end points doesn't // have any other segments connected. UpdateIsolate(); } /// /// show the candicate new U grid line to be added. In "add horizontal grid line" operation, /// there'll be a dash line following the movement of the mouse, it's drawn by this method /// /// /// the location of the mouse cursor /// /// /// if added successfully, return true; otherwise false (if the mouse location is invalid, it will return false) /// public bool AddDashULine(System.Drawing.Point mousePosition) { bool mouseInGrid = VerifyMouseLocation(mousePosition); // mouse is outside the curtain grid boundary if (false == mouseInGrid) { m_mouseLocationValid = false; return false; } // if the mouse laps over another grid line, it's invalid bool isOverlapped = IsOverlapped(mousePosition, m_uLinePathList); if (true == isOverlapped) { m_mouseLocationValid = false; string msg = "It's not allowed to add grid line lapping over another grid line"; m_myDocument.Message = new KeyValuePair(msg, true); return false; } // there's no "overlap", it's valid else { string msg = "Specify a point within the curtain grid to locate the grid line"; m_myDocument.Message = new KeyValuePair(msg, false); } m_mouseLocationValid = true; // get a parallel U line first GridLine2D uLine2D; // for "Curtain Wall: Curtain Wall 1", there's no initial U/V grid lines, so we use the boundary // line instead (the same result) if (null == m_uGridLines2D || 0 == m_uGridLines2D.Count) { uLine2D = m_boundLines2D[0]; } else { uLine2D = m_uGridLines2D[0]; } System.Drawing.Point startPoint = uLine2D.StartPoint; System.Drawing.Point endPoint = uLine2D.EndPoint; // move the start point and the end point parallelly startPoint.Y = mousePosition.Y; endPoint.Y = mousePosition.Y; // get the dash u line GridLine2D dashULine = new GridLine2D(startPoint, endPoint); // initialize the pan Pen redPen = new Pen(System.Drawing.Color.Red, m_sketchPenWidth); Brush brush = Brushes.Red; redPen.DashCap = DashCap.Flat; redPen.DashStyle = DashStyle.Dash; // add the dash line to the assistant line list for drawing m_drawObject = new DrawObject(dashULine, redPen); return true; } /// /// draw curtain grid in the canvas /// /// /// form graphic /// public void DrawCurtainGrid(Graphics graphics) { // draw the U grid lines Pen lockBluePen = new Pen(System.Drawing.Color.Blue, m_lockedPenWidth); Pen unlockBluePen = new Pen(System.Drawing.Color.Blue, m_unlockedPenWidth); DrawULines(graphics, lockBluePen, unlockBluePen); // draw the V grid lines Pen lockbrownPen = new Pen(System.Drawing.Color.Brown, m_lockedPenWidth); Pen unlockbrownPen = new Pen(System.Drawing.Color.Brown, m_unlockedPenWidth); DrawVLines(graphics, lockbrownPen, unlockbrownPen); // draw the boundary lines Pen blackPen = new Pen(System.Drawing.Color.Black, m_boundaryPenWidth); DrawBoundLines(graphics, blackPen); // draw all the assistant line & display the hints DrawAssistLine(graphics); } /// /// show the candicate new V grid line to be added. In "add vertical grid line" operation, /// there'll be a dash line following the movement of the mouse, it's drawn by this method /// /// /// the location of the mouse cursor /// /// /// if added successfully, return true; otherwise false (if the mouse location is invalid, it will return false) /// public bool AddDashVLine(System.Drawing.Point mousePosition) { bool mouseInGrid = VerifyMouseLocation(mousePosition); // mouse is outside the curtain grid boundary if (false == mouseInGrid) { m_mouseLocationValid = false; return false; } // if the mouse laps over another grid line, it's invalid bool isOverlapped = IsOverlapped(mousePosition, m_vLinePathList); if (true == isOverlapped) { m_mouseLocationValid = false; string msg = "It's not allowed to add grid line lapping over another grid line"; m_myDocument.Message = new KeyValuePair(msg, true); return false; } // there's no "overlap", it's valid else { string msg = "Specify a point within the curtain grid to locate the grid line"; m_myDocument.Message = new KeyValuePair(msg, false); } m_mouseLocationValid = true; // get a parallel V line first GridLine2D vLine2D; // for "Curtain Wall: Curtain Wall 1", there's no initial U/V grid lines, so we use the boundary // line instead (the same result) if (null == m_vGridLines2D || 0 == m_vGridLines2D.Count) { vLine2D = m_boundLines2D[1]; } else { vLine2D = m_vGridLines2D[0]; } System.Drawing.Point startPoint = vLine2D.StartPoint; System.Drawing.Point endPoint = vLine2D.EndPoint; // move the start point and the end point parallelly startPoint.X = mousePosition.X; endPoint.X = mousePosition.X; // get the dash u line GridLine2D dashVLine = new GridLine2D(startPoint, endPoint); // initialize the pan Pen redPen = new Pen(System.Drawing.Color.Red, m_sketchPenWidth); Brush brush = Brushes.Red; redPen.DashCap = DashCap.Flat; redPen.DashStyle = DashStyle.Dash; // add the dash line to the assistant line list for drawing m_drawObject = new DrawObject(dashVLine, redPen); return true; } /// /// add the dash U/V line (used only in "Move grid line" operation) /// /// /// the location of the mouse cursor /// public void AddDashLine(System.Drawing.Point mousePosition) { bool mouseInGrid = VerifyMouseLocation(mousePosition); // mouse is outside the curtain grid boundary if (false == mouseInGrid) { return; } double offset = 0.0; // the selected grid line is a U grid line (it's the grid line to be moved) if (-1 != m_selectedUIndex) { bool succeeded = AddDashULine(mousePosition); // add failed (for example, the mouse locates on another grid line) if (false == succeeded) { return; } // add the selected grid line (the line to be moved) to the assistant line list // (it will be painted in bold and with red color) GridLine2D line = m_uGridLines2D[m_selectedUIndex]; Pen redPen = new Pen(System.Drawing.Color.Red, m_selectedLinePenWidth); m_drawObject.Lines2D.Add(new KeyValuePair(line, redPen)); m_geometry.MoveOffset = mousePosition.Y - line.StartPoint.Y; // convert the 2D data to 3D Autodesk.Revit.DB.XYZ xyz = new Autodesk.Revit.DB.XYZ(mousePosition.X, mousePosition.Y, 0); Vector4 vec = new Vector4(xyz); vec = m_coordinates.RestoreMatrix.Transform(vec); offset = vec.Z - m_geometry.LineToBeMoved.FullCurve.GetEndPoint(0).Z; offset = Unit.CovertFromAPI(m_myDocument.LengthUnit, offset); // showing the move offset m_drawObject.Text = "Offset: " + Math.Round(offset, 1) + Unit.GetUnitLabel(m_myDocument.LengthUnit); m_drawObject.TextPosition = mousePosition; m_drawObject.TextPen = redPen; return; } // the selected grid line is a V grid line (it's the grid line to be moved) else if (-1 != m_selectedVIndex) { bool succeeded = AddDashVLine(mousePosition); // add failed (for example, the mouse locates on another grid line) if (false == succeeded) { return; } // add the selected grid line (the line to be moved) to the assistant line list // (it will be painted in bold and with red color) GridLine2D line = m_vGridLines2D[m_selectedVIndex]; Pen redPen = new Pen(System.Drawing.Color.Red, m_selectedLinePenWidth); m_drawObject.Lines2D.Add(new KeyValuePair(line, redPen)); m_geometry.MoveOffset = mousePosition.X - line.StartPoint.X; // convert the 2D data to 3D Autodesk.Revit.DB.XYZ xyz = new Autodesk.Revit.DB.XYZ(mousePosition.X, mousePosition.Y, 0); Vector4 vec = new Vector4(xyz); vec = m_coordinates.RestoreMatrix.Transform(vec); offset = vec.X - m_geometry.LineToBeMoved.FullCurve.GetEndPoint(0).X; offset = Unit.CovertFromAPI(m_myDocument.LengthUnit, offset); // showing the move offset m_drawObject.Text = "Offset: " + Math.Round(offset, 1) + Unit.GetUnitLabel(m_myDocument.LengthUnit); m_drawObject.TextPosition = mousePosition; m_drawObject.TextPen = redPen; } } /// /// pick up a grid line by mouse /// /// /// the location of the mouse cursor /// /// /// will locked grid lines be picked (if verifyLock is true, won't pick up locked ones) /// /// /// whether grid line without skipped segments be picked (if verifyRemove is true, won't pick up the grid line without skipped segments) /// public void SelectLine(System.Drawing.Point mousePosition, bool verifyLock, bool verifyRemove) { bool mouseInGrid = VerifyMouseLocation(mousePosition); // mouse is outside the curtain grid boundary if (false == mouseInGrid) { return; } // select the U grid line SelectULine(mousePosition, verifyLock, verifyRemove); // necessary // supposing the mouse hovers on the cross point of a U line and a V line, just handle // the U line, skip the V line // otherwise it allows users to select "2" cross lines at one time if (-1 != m_selectedUIndex) { return; } // select the V grid line SelectVLine(mousePosition, verifyLock, verifyRemove); } /// /// pick up a U grid line by mouse /// /// /// the location of the mouse cursor /// /// /// will locked grid lines be picked (if verifyLock is true, won't pick up locked ones) /// /// /// whether grid line without skipped segments be picked (if verifyRemove is true, won't pick up the grid line without skipped segments) /// public void SelectULine(System.Drawing.Point mousePosition, bool verifyLock, bool verifyRemove) { for (int i = 0; i < m_uLinePathList.Count; i++) { GraphicsPath path = m_uLinePathList[i]; GridLine2D line2D = m_uGridLines2D[i]; // the verifyLock is true (won't pick up locked ones) and the current pointed grid line is locked // so can't select it if (true == verifyLock && true == line2D.Locked) { continue; } // the verifyRemove is true (only pick up the grid line with skipped segments) and the current pointed grid line // has no skipped segments if (true == verifyRemove && line2D.RemovedNumber == 0) { continue; } Pen redPen = new Pen(System.Drawing.Color.Red, m_outlineSelectPenWidth); // the mouse is in the outline of the graphics path if (path.IsOutlineVisible(mousePosition, redPen)) { m_selectedUIndex = i; m_drawObject = new DrawObject(line2D, new Pen(System.Drawing.Color.Red, m_selectedLinePenWidth)); // show the lock status of the grid line if (false == verifyLock && false == verifyRemove) { m_drawObject.Text = (true == line2D.Locked) ? "Locked" : "Unlocked"; m_drawObject.TextPosition = mousePosition; m_drawObject.TextPen = redPen; } return; } } m_drawObject.Clear(); m_selectedUIndex = -1; } /// /// pick up a V grid line by mouse /// /// /// the location of the mouse cursor /// /// /// will locked grid lines be picked (if verifyLock is true, won't pick up locked ones) /// /// /// whether grid line without skipped segments be picked (if verifyRemove is true, won't pick up the grid line without skipped segments) /// public void SelectVLine(System.Drawing.Point mousePosition, bool verifyLock, bool verifyRemove) { for (int i = 0; i < m_vLinePathList.Count; i++) { GraphicsPath path = m_vLinePathList[i]; GridLine2D line2D = m_vGridLines2D[i]; // the verifyLock is true (won't pick up locked ones) and the current pointed grid line is locked // so can't select it if (true == verifyLock && true == line2D.Locked) { continue; } // the verifyRemove is true (only pick up the grid line with skipped segments) and the current pointed grid line // has no skipped segments if (true == verifyRemove && line2D.RemovedNumber == 0) { continue; } Pen redPen = new Pen(System.Drawing.Color.Red, m_outlineSelectPenWidth); // the mouse is in the outline of the graphics path if (path.IsOutlineVisible(mousePosition, redPen)) { m_selectedVIndex = i; m_drawObject = new DrawObject(line2D, new Pen(System.Drawing.Color.Red, m_selectedLinePenWidth)); // show the lock status of the grid line if (false == verifyLock && false == verifyRemove) { m_drawObject.Text = (true == line2D.Locked) ? "Locked" : "Unlocked"; m_drawObject.TextPosition = mousePosition; m_drawObject.TextPen = redPen; } return; } } m_drawObject.Clear(); m_selectedVIndex = -1; } /// /// pick up a segment /// /// /// the location of the mouse cursor /// public void SelectSegment(System.Drawing.Point mousePosition) { bool mouseInGrid = VerifyMouseLocation(mousePosition); // mouse is outside the curtain grid boundary if (false == mouseInGrid) { return; } // select a segment of the U grid line SelectUSegment(mousePosition); // necessary // supposing the mouse hovers on the cross point of a U line and a V line, just handle // the U line, skip the V line // otherwise it allows users to select "2" cross lines at one time if (-1 != m_selectedUIndex) { return; } // select a segment of the V grid line SelectVSegment(mousePosition); } /// /// pick up a segment of a U grid line /// /// /// the location of the mouse cursor /// public void SelectUSegment(System.Drawing.Point mousePosition) { for (int i = 0; i < m_uSegLinePathListList.Count; i++) { GridLine2D gridLine2D = m_uGridLines2D[i]; List pathList = m_uSegLinePathListList[i]; Pen redPen = new Pen(System.Drawing.Color.Red, m_outlineSelectPenWidth); // find out which segment it's on and which grid line does the segment belong to for (int j = 0; j < pathList.Count; j++) { SegmentLine2D segLine2D = m_uGridLines2D[i].Segments[j]; GraphicsPath path = pathList[j]; if (path.IsOutlineVisible(mousePosition, redPen)) { if (LineOperationType.AddSegment == m_myDocument.ActiveOperation.OpType) { // the operation is add segment, but the selected segment hasn't been removed // so skip this segment if (false == segLine2D.Removed) { string msg = "It's only allowed to add segment on a removed segment"; KeyValuePair statusMsg = new KeyValuePair(msg, true); m_myDocument.Message = statusMsg; return; } } else if (LineOperationType.RemoveSegment == m_myDocument.ActiveOperation.OpType) { // the operation is remove segment, but the selected segment has been removed // so skip this segment if (true == segLine2D.Removed) { return; } // if there's only segment existing, forbid to delete it if (gridLine2D.RemovedNumber == gridLine2D.Segments.Count - 1) { string msg = "It's not allowed to delete the last segment"; KeyValuePair statusMsg = new KeyValuePair(msg, true); m_myDocument.Message = statusMsg; return; } } m_selectedUIndex = i; m_selectedUSegmentIndex = j; m_drawObject = new DrawObject(segLine2D, new Pen(System.Drawing.Color.Red, m_selectedSegmentPenWidth)); // update the status strip hint { string msg = "Left-click to finish the operation"; KeyValuePair statusMsg = new KeyValuePair(msg, false); m_myDocument.Message = statusMsg; } return; } } } m_drawObject.Clear(); m_selectedUIndex = -1; m_selectedUSegmentIndex = -1; // update the hints { string msg = "Select a segment"; KeyValuePair statusMsg = new KeyValuePair(msg, false); m_myDocument.Message = statusMsg; } } /// /// pick up a segment of a V grid line /// /// /// the location of the mouse cursor /// public void SelectVSegment(System.Drawing.Point mousePosition) { for (int i = 0; i < m_vSegLinePathListList.Count; i++) { GridLine2D gridLine2D = m_vGridLines2D[i]; List pathList = m_vSegLinePathListList[i]; Pen redPen = new Pen(System.Drawing.Color.Red, m_outlineSelectPenWidth); // find out which segment it's on and which grid line does the segment belong to for (int j = 0; j < pathList.Count; j++) { SegmentLine2D segLine2D = m_vGridLines2D[i].Segments[j]; GraphicsPath path = pathList[j]; if (path.IsOutlineVisible(mousePosition, redPen)) { if (LineOperationType.AddSegment == m_myDocument.ActiveOperation.OpType) { // the operation is add segment, but the selected segment hasn't been removed // so skip this segment if (false == segLine2D.Removed) { string msg = "It's only allowed to add segment on a removed segment"; KeyValuePair statusMsg = new KeyValuePair(msg, true); m_myDocument.Message = statusMsg; return; } } else if (LineOperationType.RemoveSegment == m_myDocument.ActiveOperation.OpType) { // the operation is remove segment, but the selected segment has been removed // so skip this segment if (true == segLine2D.Removed) { return; } // if there's only segment existing, forbid to delete it if (gridLine2D.RemovedNumber == gridLine2D.Segments.Count - 1) { string msg = "It's not allowed to delete the last segment"; KeyValuePair statusMsg = new KeyValuePair(msg, true); m_myDocument.Message = statusMsg; return; } } m_selectedVIndex = i; m_selectedVSegmentIndex = j; m_drawObject = new DrawObject(segLine2D, new Pen(System.Drawing.Color.Red, m_selectedSegmentPenWidth)); // update the status strip hint { string msg = "Left-click to finish the operation"; KeyValuePair statusMsg = new KeyValuePair(msg, false); m_myDocument.Message = statusMsg; } return; } } } m_drawObject.Clear(); // selection failed m_selectedVIndex = -1; m_selectedVSegmentIndex = -1; // update the status hint { string msg = "Select a segment"; KeyValuePair statusMsg = new KeyValuePair(msg, false); m_myDocument.Message = statusMsg; } } /// /// check whether the segments which have the same junction with the specified segment /// will be isolated if we delete the specified segment /// for example: 2 grid line has one junction, so there'll be 4 segments connecting to this junction /// let's delete 2 segments out of the 4 first, then if we want to delete the 3rd segment, the 4th /// segment will be a "isolated" one, so we will pick this kind of segment out /// /// /// the specified segment used to checking /// /// /// the result seg list (all the segments in this list is to-be-deleted) /// public void GetConjointSegments(SegmentLine2D segLine, List removeSegments) { System.Drawing.Point startPoint = segLine.StartPoint; System.Drawing.Point endPoint = segLine.EndPoint; // get the "isolated" segment in the location of start point SegmentLine2D startRemoveSegLine = new SegmentLine2D(); GetConjointSegment(startPoint, segLine.IsUSegment, ref startRemoveSegLine); // get the "isolated" segment in the location of end point SegmentLine2D endRemoveSegLine = new SegmentLine2D(); GetConjointSegment(endPoint, segLine.IsUSegment, ref endRemoveSegLine); if (null != startRemoveSegLine) { removeSegments.Add(startRemoveSegLine); } if (null != endRemoveSegLine) { removeSegments.Add(endRemoveSegLine); } } #endregion #region Private methods /// /// get the U ("Horizontal") grid lines and their segments in GridLine2D format /// private void GetULines2D() { int gridLineIndex = -1; foreach (CurtainGridLine line in m_geometry.UGridLines) { gridLineIndex++; // store the segment paths List segPaths = new List(); // get the line2D and its segments GridLine2D line2D = ConvertToLine2D(line, segPaths, gridLineIndex); m_uGridLines2D.Add(line2D); // convert the grid line of GridLine2D format to GraphicsPath format GraphicsPath path = new GraphicsPath(); path.AddLine(line2D.StartPoint, line2D.EndPoint); m_uLinePathList.Add(path); // store the segment paths to a list m_uSegLinePathListList.Add(segPaths); } } /// /// convert the grid line in CurtainGridLine format to GridLine2D format /// in the Canvas area, the "System.Drawing.Point" instances are directly used, it's hard /// for us to use CurtainGridLine and Autodesk.Revit.DB.XYZ directly, so convert them to 2D data first /// /// /// the grid line in CurtainGridLine format /// /// /// the grid line in GraphicsPath format (the GraphicsPath contains the grid line in GridLine2D format) /// /// /// the index of the grid line /// /// /// the converted grid line in GridLine2D format /// private GridLine2D ConvertToLine2D(CurtainGridLine line, List segPaths, int gridLineIndex) { Curve curve = line.FullCurve; Autodesk.Revit.DB.XYZ point1 = curve.GetEndPoint(0); Autodesk.Revit.DB.XYZ point2 = curve.GetEndPoint(1); Vector4 v1 = new Vector4(point1); Vector4 v2 = new Vector4(point2); // transform from 3D point to 2D point v1 = m_coordinates.TransformMatrix.Transform(v1); v2 = m_coordinates.TransformMatrix.Transform(v2); // create a new line in GridLine2D format GridLine2D line2D = new GridLine2D(); line2D.StartPoint = new System.Drawing.Point((int)v1.X, (int)v1.Y); line2D.EndPoint = new System.Drawing.Point((int)v2.X, (int)v2.Y); line2D.Locked = line.Lock; line2D.IsUGridLine = line.IsUGridLine; // get which segments are skipped List skippedSegments = ConvertCurveToSegment(line.SkippedSegmentCurves); // get all the segments for the curtain grid (and tag the skipped ones out) GetSegments(line2D, line.AllSegmentCurves, skippedSegments, segPaths, gridLineIndex); return line2D; } /// /// convert the segment lines in Curve format to SegmentLine2D format /// /// /// the skipped segments in Curve (used in RevitAPI) format /// /// /// the skipped segments in SegmentLine2D format (converted from 3D to 2D) /// private List ConvertCurveToSegment(CurveArray curveArray) { List resultList = new List(); // convert the skipped segments (in Curve format) to SegmentLine2D format foreach (Curve curve in curveArray) { Autodesk.Revit.DB.XYZ point1 = curve.GetEndPoint(0); Autodesk.Revit.DB.XYZ point2 = curve.GetEndPoint(1); Vector4 v1 = new Vector4(point1); Vector4 v2 = new Vector4(point2); // transform from 3D point to 2D point v1 = m_coordinates.TransformMatrix.Transform(v1); v2 = m_coordinates.TransformMatrix.Transform(v2); // add the segment data SegmentLine2D segLine2D = new SegmentLine2D(); segLine2D.StartPoint = new System.Drawing.Point((int)v1.X, (int)v1.Y); segLine2D.EndPoint = new System.Drawing.Point((int)v2.X, (int)v2.Y); resultList.Add(segLine2D); } return resultList; } /// /// identify whether the segment is contained in the segments of a grid line /// /// /// the grid line (may contain the specified segment) /// /// /// the segment which needs to be identified /// /// /// if the segment is contained in the grid line, return true; otherwise false /// private bool IsSegLineContained(List lines, SegmentLine2D lineB) { foreach (SegmentLine2D lineA in lines) { System.Drawing.Point lineAStartPoint = lineA.StartPoint; System.Drawing.Point lineAEndPoint = lineA.EndPoint; System.Drawing.Point lineBStartPoint = lineB.StartPoint; System.Drawing.Point lineBEndPoint = lineB.EndPoint; // the 2 lines have the same start point and the same end point if ((true == IsPointsEqual(lineAStartPoint, lineBStartPoint) && true == IsPointsEqual(lineAEndPoint, lineBEndPoint)) || (true == IsPointsEqual(lineAStartPoint, lineBEndPoint) && true == IsPointsEqual(lineAEndPoint, lineBStartPoint))) { return true; } } return false; } /// /// get all the segments of the specified grid line /// /// /// the grid line which wants to get all its segments /// /// /// all the segments (include existent ones and skipped one) /// /// /// the skipped segments /// /// /// the GraphicsPath list contains all the segments /// /// /// the index of the grid line /// private void GetSegments(GridLine2D gridLine2D, CurveArray allCurves, List skippedSegments, List segPaths, int gridLineIndex) { int segIndex = -1; // convert the segments from Curve format to SegmentLine2D format (from 3D to 2D) foreach (Curve curve in allCurves) { // store the index of the segment in the grid line segIndex++; Autodesk.Revit.DB.XYZ point1 = curve.GetEndPoint(0); Autodesk.Revit.DB.XYZ point2 = curve.GetEndPoint(1); Vector4 v1 = new Vector4(point1); Vector4 v2 = new Vector4(point2); // transform from 3D point to 2D point v1 = m_coordinates.TransformMatrix.Transform(v1); v2 = m_coordinates.TransformMatrix.Transform(v2); // add the segment data SegmentLine2D segLine2D = new SegmentLine2D(); segLine2D.StartPoint = new System.Drawing.Point((int)v1.X, (int)v1.Y); segLine2D.EndPoint = new System.Drawing.Point((int)v2.X, (int)v2.Y); // if the segment is contained in the skipped list, set the Removed flag to true; otherwise false segLine2D.Removed = IsSegLineContained(skippedSegments, segLine2D); // if the segment is in a U grid line, set it true; otherwise false segLine2D.IsUSegment = gridLine2D.IsUGridLine; // the index of the parent grid line in the grid line list segLine2D.GridLineIndex = gridLineIndex; // the index of the segment in its parent grid line segLine2D.SegmentIndex = segIndex; if (true == segLine2D.Removed) { gridLine2D.RemovedNumber++; } gridLine2D.Segments.Add(segLine2D); // store the mapped graphics path GraphicsPath path = new GraphicsPath(); path.AddLine(segLine2D.StartPoint, segLine2D.EndPoint); segPaths.Add(path); } } /// /// get the V ("Vertical") grid lines and their segments in GridLine2D format /// private void GetVLines2D() { int gridLineIndex = -1; foreach (CurtainGridLine line in m_geometry.VGridLines) { gridLineIndex++; // store the segment paths List segPaths = new List(); // get the line2D and its segments GridLine2D line2D = ConvertToLine2D(line, segPaths, gridLineIndex); m_vGridLines2D.Add(line2D); GraphicsPath path = new GraphicsPath(); path.AddLine(line2D.StartPoint, line2D.EndPoint); m_vLinePathList.Add(path); // store the segment paths to a list m_vSegLinePathListList.Add(segPaths); } } /// /// get the boundary lines of the curtain grid /// private void GetBoundLines2D() { for (int i = 0; i < m_geometry.GridVertexesXYZ.Count; i += 1) { Autodesk.Revit.DB.XYZ point1, point2; // connect the last point with the first point as a boundary line if (i == m_geometry.GridVertexesXYZ.Count - 1) { point1 = m_geometry.GridVertexesXYZ[i]; point2 = m_geometry.GridVertexesXYZ[0]; } else { point1 = m_geometry.GridVertexesXYZ[i]; point2 = m_geometry.GridVertexesXYZ[i + 1]; } Vector4 v1 = new Vector4(point1); Vector4 v2 = new Vector4(point2); // transform from 3D point to 2D point v1 = m_coordinates.TransformMatrix.Transform(v1); v2 = m_coordinates.TransformMatrix.Transform(v2); // stores the bounding coordinate int v1X = (int)v1.X; int v1Y = (int)v1.Y; int v2X = (int)v2.X; int v2Y = (int)v2.Y; // obtain the min and max point m_minX = v1X; m_minY = v1Y; if (v1X > m_maxX) { m_maxX = v1X; } else if (v1X < m_minX) { m_minX = v1X; } if (v2X > m_maxX) { m_maxX = v2X; } else if (v2X < m_minX) { m_minX = v2X; } if (v1Y > m_maxY) { m_maxY = v1Y; } if (v1Y < m_minY) { m_minY = v1Y; } if (v2Y > m_maxY) { m_maxY = v2Y; } if (v2Y < m_minY) { m_minY = v2Y; } // create the boundary line GridLine2D line2D = new GridLine2D(); line2D.StartPoint = new System.Drawing.Point((int)v1.X, (int)v1.Y); line2D.EndPoint = new System.Drawing.Point((int)v2.X, (int)v2.Y); m_boundLines2D.Add(line2D); // add the line to the mapped GraphicsPath list GraphicsPath path = new GraphicsPath(); path.AddLine(line2D.StartPoint, line2D.EndPoint); m_boundPath.Add(path); } } /// /// draw the U grid lines /// /// /// used in drawing the lines /// /// /// the pen used to draw the locked grid line /// /// /// the pen used to draw the unlocked grid line /// private void DrawULines(Graphics graphics, Pen lockPen, Pen unlockPen) { foreach (GridLine2D line2D in m_uGridLines2D) { Pen pen = (true == line2D.Locked) ? lockPen : unlockPen; Pen isolatedPen = new Pen(Brushes.Gray, pen.Width); // won't draw the grid lines at GridLine2D level, draw them at SegmentLine2D level // at the skipped segments in the grid line won't be painted to the canvas foreach (SegmentLine2D segLine2D in line2D.Segments) { // skip the removed segments, won't draw them if (true == segLine2D.Removed) { continue; } else if (true == segLine2D.Isolated) { graphics.DrawLine(isolatedPen, segLine2D.StartPoint, segLine2D.EndPoint); } else { graphics.DrawLine(pen, segLine2D.StartPoint, segLine2D.EndPoint); } } } } /// /// draw the V grid lines /// /// /// used in drawing the lines /// /// /// the pen used to draw the locked grid line /// /// /// the pen used to draw the unlocked grid line /// private void DrawVLines(Graphics graphics, Pen lockPen, Pen unlockPen) { foreach (GridLine2D line2D in m_vGridLines2D) { Pen pen = (true == line2D.Locked) ? lockPen : unlockPen; Pen isolatedPen = new Pen(Brushes.Gray, pen.Width); // won't draw the grid lines at GridLine2D level, draw them at SegmentLine2D level // at the skipped segments in the grid line won't be painted to the canvas foreach (SegmentLine2D segLine2D in line2D.Segments) { // skip the removed segments, won't draw them if (true == segLine2D.Removed) { continue; } else if (true == segLine2D.Isolated) { graphics.DrawLine(isolatedPen, segLine2D.StartPoint, segLine2D.EndPoint); } else { graphics.DrawLine(pen, segLine2D.StartPoint, segLine2D.EndPoint); } } } } /// /// draw the boundary lines of the curtain grid /// /// /// used in drawing the lines /// /// /// the pen used to draw the boundary line /// private void DrawBoundLines(Graphics graphics, Pen pen) { foreach (GridLine2D line2D in m_boundLines2D) { graphics.DrawLine(pen, line2D.StartPoint, line2D.EndPoint); } } /// /// draw the assistant lines used to highlight some special lines /// for example: in add Horizontal/Vertical grid line operations, the to-be-added lines will /// be shown in bold with red color, this assistant line is drawn in this method /// /// /// used in drawing the lines /// private void DrawAssistLine(Graphics graphics) { if (null != m_drawObject) { m_drawObject.Draw(graphics); } } /// /// if mouse insiede the curtain grid area, returns true; otherwise false /// /// /// the position of the mouse cursor /// /// /// if mouse insiede the curtain grid area, returns true; otherwise false /// private bool VerifyMouseLocation(System.Drawing.Point mousePosition) { int x = mousePosition.X; int y = mousePosition.Y; // the mouse is outside the curtain grid area or at the edge of the curtain grid, just do nothing if (x <= m_minX || y <= m_minY || x >= m_maxX || y >= m_maxY) { // set the cursor to the default cursor // indicating that the mouse is outside the curtain grid area, and disables to draw U line if (null != MouseOutGridEvent) { MouseOutGridEvent(); } return false; } // set the cursor to the cursor of "Cross" // indicating that the mouse is inside the curtain grid area, and enables to draw U line if (null != MouseInGridEvent) { MouseInGridEvent(); } return true; } /// /// check whether the point is in the outline of the paths /// /// /// the point to be checked /// /// /// the paths of the lines /// /// /// if the point is in the outline of one of the paths, return true; otherwise false /// private bool IsOverlapped(System.Drawing.Point point, List paths) { Pen pen = new Pen(System.Drawing.Color.Red, m_lockedPenWidth); foreach (GraphicsPath path in paths) { // the point is in the outline of the path, so the isOverlapped is true if (path.IsOutlineVisible(point, pen)) { return true; } } // no overlap found, so return false return false; } private void UpdateIsolate() { foreach (GridLine2D line2D in m_uGridLines2D) { foreach (SegmentLine2D seg in line2D.Segments) { System.Drawing.Point startPoint = seg.StartPoint; // if we get all the U line's start points, we can cover all the conjoints IsPointIsolate(startPoint); } } } /// /// check whether the segments which have the same junction with the specified junction /// will be isolated if we delete the specified segment /// for example: 2 grid line has one junction, so there'll be 4 segments connecting to this junction /// let's delete 2 segments out of the 4 first, then if we want to delete the 3rd segment, the 4th /// segment will be a "isolated" one, so we will pick this kind of segment out /// /// /// the junction of several segments /// private bool IsPointIsolate(System.Drawing.Point point) { int uIndex = -1; List uSegIndexes = new List(); // get which U grid line contains the point uIndex = GetOutlineIndex(m_uLinePathList, point); int vIndex = -1; List vSegIndexes = new List(); // get which V grid line contains the point vIndex = GetOutlineIndex(m_vLinePathList, point); if (-1 == uIndex || -1 == vIndex) { return false; } if (-1 != uIndex) { // get the grid line containing the point and its segments List segList = m_uGridLines2D[uIndex].Segments; // get which segments of the grid line contains the point uSegIndexes = GetOutlineIndexes(segList, point); } if (-1 != vIndex) { // get the grid line containing the point and its segments List segList = m_vGridLines2D[vIndex].Segments; // get which segments of the grid line contains the point vSegIndexes = GetOutlineIndexes(segList, point); } // TODO: improve the comments // there's only 1 v segment contains the point and no u segment, so the segment is an isolated one if (0 == uSegIndexes.Count && 1 == vSegIndexes.Count) { SegmentLine2D seg = m_vGridLines2D[vIndex].Segments[vSegIndexes[0]]; seg.Isolated = true; // recursive check IsPointIsolate(seg.StartPoint); IsPointIsolate(seg.EndPoint); } else if (1 == uSegIndexes.Count && 0 == vSegIndexes.Count) { SegmentLine2D seg = m_uGridLines2D[uIndex].Segments[uSegIndexes[0]]; seg.Isolated = true; // recursive check IsPointIsolate(seg.StartPoint); IsPointIsolate(seg.EndPoint); } return false; } /// /// get which segment contains the specified point, if the segment contains the point, /// return the index of the segment in the grid line /// /// /// the segment list to be checked with the specified point /// /// /// the point which need to be checked /// /// /// the index of the segment in the grid line /// private List GetOutlineIndexes(List segList, System.Drawing.Point point) { List resultIndexes = new List(); for (int i = 0; i < segList.Count; i++) { SegmentLine2D seg = segList[i]; if (true == seg.Removed || true == seg.Isolated) { continue; } Pen redPen = Pens.Red; // the specified point is one of the end points of the current segment System.Drawing.Point[] points = { seg.StartPoint, seg.EndPoint }; foreach (System.Drawing.Point p in points) { bool equal = IsPointsEqual(p, point); if (true == equal) { resultIndexes.Add(i); } } } return resultIndexes; } /// /// judge whether the 2 points are equal (have the same coordinate) /// (as the X & Y of the Point class are both int values, so needn't use AlmostEqual) /// /// /// the point to be checked with another for equality /// /// /// the point to be checked with another for equality /// /// /// if the 2 points have the same coordinate, return true; otherwise false /// private bool IsPointsEqual(System.Drawing.Point pa, System.Drawing.Point pb) { int ax = pa.X; int ay = pa.Y; int bx = pb.X; int by = pb.Y; float result = (ax - bx) * (ax - bx) + (ay - by) * (ay - by); // the distance of the 2 points is greater than 0, they're not equal if (/*result > 1*/result != 0) { return false; } // the distance of the 2 points is 0, they're equal else { return true; } } /// /// get which grid line (in GraphicsPath format) contains the specified point, /// if the grid line contains the point, /// return the index of the grid line /// /// /// the grid line list to be checked with the specified point /// /// /// the point which need to be checked /// /// /// the index of the grid line /// private int GetOutlineIndex(List pathList, System.Drawing.Point point) { for (int i = 0; i < pathList.Count; i++) { Pen redPen = Pens.Red; GraphicsPath path = pathList[i]; if (path.IsOutlineVisible(point, redPen)) { return i; } } return -1; } /// /// get all the segments which is connected to the end point of the segment and is in the same line as the segment /// for example: 2 grid line has one junction, so there'll be 4 segments connecting to this junction /// 2 U segments and 2 V segments, delete the 2 U segments first, then delete one of the V segment, the other V segment /// will be marked as the "ConjointSegment" and will be deleted automatically. /// this method is used to pick the "the other V segment" out /// /// /// the junction of several segments /// /// /// the U/V status of the segment /// /// /// the result segment line to be removed /// private void GetConjointSegment(System.Drawing.Point point, bool isUSegment, ref SegmentLine2D removeSegLine) { int uIndex = -1; List uSegIndexes = new List(); // get which U grid line contains the point uIndex = GetOutlineIndex(m_uLinePathList, point); int vIndex = -1; List vSegIndexes = new List(); // get which V grid line contains the point vIndex = GetOutlineIndex(m_vLinePathList, point); if (-1 != uIndex) { // get the grid line containing the point and its segments List segList = m_uGridLines2D[uIndex].Segments; // get which segments of the grid line contains the point uSegIndexes = GetOutlineIndexes(segList, point); } if (-1 != vIndex) { // get the grid line containing the point and its segments List segList = m_vGridLines2D[vIndex].Segments; // get which segments of the grid line contains the point vSegIndexes = GetOutlineIndexes(segList, point); } if ((0 == uSegIndexes.Count && 1 == vSegIndexes.Count)) { // the source segment is an V segment, and the result segment is a V segment too. // they're connected and in one line, so the result V segment should be removed, // according to the UI rule if (false == isUSegment) { removeSegLine = m_vGridLines2D[vIndex].Segments[vSegIndexes[0]]; return; } } else if (1 == uSegIndexes.Count && 0 == vSegIndexes.Count) { // the source segment is an U segment, and the result segment is a U segment too. // they're connected and in one line, so the result U segment should be removed, // according to the UI rule if (true == isUSegment) { removeSegLine = m_uGridLines2D[uIndex].Segments[uSegIndexes[0]]; return; } } } #endregion }// end of class /// /// the class is designed to help draw the hints and the assistant lines of the curtain grid /// public class DrawObject { #region Fields // the lines to be drawn with the mapping pen private List> m_lines2D; // the hint to be drawn private string m_text; // the location to draw the hint private System.Drawing.Point m_textPosition; // the pen to draw the hint private Pen m_textPen; #endregion #region Properties /// /// the lines to be drawn with the mapping pen /// public List> Lines2D { get { return m_lines2D; } set { m_lines2D = value; } } /// /// the hint to be drawn /// public string Text { get { return m_text; } set { m_text = value; } } /// /// the location to draw the hint /// public System.Drawing.Point TextPosition { get { return m_textPosition; } set { m_textPosition = value; } } /// /// the pen to draw the hint /// public Pen TextPen { get { return m_textPen; } set { m_textPen = value; } } #endregion #region Constructors /// /// default constructor /// public DrawObject() { m_lines2D = new List>(); m_text = string.Empty; m_textPosition = System.Drawing.Point.Empty; } /// /// constructor /// /// /// the line to be drawn /// /// /// the pen used to draw the line /// public DrawObject(Line2D line, Pen pen) { m_lines2D = new List>(); m_lines2D.Add(new KeyValuePair(new Line2D(line), pen)); m_text = string.Empty; m_textPosition = System.Drawing.Point.Empty; } #endregion #region Public methods /// /// clear all the to-be-drawn lines /// public void Clear() { m_lines2D.Clear(); m_text = string.Empty; m_textPosition = System.Drawing.Point.Empty; } /// /// draw the assistant lines and hint text /// /// /// the graphics used to draw the lines /// public void Draw(Graphics graphics) { // draw the assistant lines foreach (KeyValuePair pair in m_lines2D) { Line2D line2D = pair.Key; if (System.Drawing.Point.Empty == line2D.StartPoint || System.Drawing.Point.Empty == line2D.EndPoint) { continue; } Pen pen = pair.Value; graphics.DrawLine(pen, line2D.StartPoint, line2D.EndPoint); } // draw the hint text if (false == string.IsNullOrEmpty(m_text) && System.Drawing.Point.Empty != m_textPosition) { Font font = new Font("Verdana", 10, FontStyle.Regular); graphics.DrawString(m_text, font, m_textPen.Brush, new PointF(m_textPosition.X + 2, m_textPosition.Y + 2)); } } #endregion } }