Checking if viewport outline is fully inside drawing area outline

Checking if viewport outline is fully inside drawing area outline

joaofmoco
Advocate Advocate
1,229 Views
14 Replies
Message 1 of 15

Checking if viewport outline is fully inside drawing area outline

joaofmoco
Advocate
Advocate

Hello everyone,

 

I have been struggling with this for a while now and I seem to not found any solution.

I am developing a method where the purpose of the method is to relocate a viewport inside of a sheet's drawing area outline. This method is incorporated into an addin that I am developing (attached bellow).

The method, inserted into the class "MoveVP", is called Relocate and it's like this:

#region Namespaces
using Autodesk.Revit.ApplicationServices;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Structure;
using Autodesk.Revit.UI;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
#endregion

namespace RevitAddin
{
    class MoveVP
    {
        public Viewport Relocate(ExternalCommandData commandData, Viewport vp, Outline ol)
        {
            UIApplication uiapp = commandData.Application;
            UIDocument uidoc = uiapp.ActiveUIDocument;
            Application app = uiapp.Application;
            Document doc = uidoc.Document;

            // Filtered Element Collector list for Section Views
            FilteredElementCollector colViewports = new FilteredElementCollector(doc);
            colViewports.OfClass(typeof(Viewport));
            List<Viewport> allViewports = colViewports.Cast<Viewport>().ToList();

            for (double x = 0; x < ol.MaximumPoint.X; x += 0.005)
            {
                for (double y = 0; y < ol.MaximumPoint.Y; y += 0.005)
                {
                    vp.SetBoxCenter(new XYZ (x, y, 0));

                    // Check if the Viewport outline is inside drawing area
                    if (vp.GetBoxOutline().ContainsOtherOutline(ol, 0))
                    {
                        break;
                    }
                    else
                    {
                        // Verifying if chosen viewport contains any viewport outline
                        for (int i = 0; i < allViewports.Count; i++)
                        {
                            if (vp.GetBoxOutline().ContainsOtherOutline(allViewports[i].GetBoxOutline(), 0))
                            {
                                break;
                            }
                            else
                            {
                                // Set new location on sheet
                                vp.SetBoxCenter(new XYZ(x, y, 0));
                            }
                        }
                    }
                }
            }
            return vp;
        }
    }
}

However, when I execute my code, it returns this:

joaofmoco_0-1653040451657.png

However, I wanted the viewport to be like this:

joaofmoco_1-1653040730435.png

What am I doing wrong?

Is there another way to get this result?

Any feedback will be appreciated

 

0 Likes
Accepted solutions (2)
1,230 Views
14 Replies
Replies (14)
Message 2 of 15

RPTHOMAS108
Mentor
Mentor

Following is back to front I believe:

 if (vp.GetBoxOutline().ContainsOtherOutline(ol, 0))

i.e. you are checking the drawing area (which is larger than the viewport) is completely inside the viewport in terms of the max/min of both? You may also have to increase the Z in both directions or the tolerance.

 

You can step through and probably understand that your 'else' code is not executing because the drawing area is never completely inside the viewport.

 

Once you get to the 'else' part you'll probably encounter a similar issue with the addition of the fact you should be checking for one of the viewport max/min points being in the other rather than both or them ('or' not 'and') i.e. should be using 'Contains' individually for both max & min  not ContainsOtherOutline. Meaning that at the 'else' stage you only care that the max or min of one viewport is inside the other not both points. One corner could be inside the viewport and one outside but that is still an overlap you want to avoid.

 

You've also discovered that vp.SetBoxCenter is setting the centre not the bottom left. So actually your max drawing area limit should take account of that by deducting half the viewport size in both directions for current viewport being placed.

 

Seems more intelligent to realise you have all the sizes of the view ports and so can group them into the limits of the drawing area tiling algorithm. Although I don't know many construction professionals keen to read a drawing with a bunch of randomly tiled views.

0 Likes
Message 3 of 15

joaofmoco
Advocate
Advocate

Hello and thank you so much for your reply,

 

I have to apologise for my late reply as I had a really busy weekend.

 

I read the reply you wrote and I understand now that this:

if (vp.GetBoxOutline().ContainsOtherOutline(ol, 0))

should be something like this:

if (ol.ContainsOtherOutline(vp.GetBoxOutline(), 0.5))

 

However, I didn't quite understand this part of your answer:

 

"Once you get to the 'else' part you'll probably encounter a similar issue with the addition of the fact you should be checking for one of the viewport max/min points being in the other rather than both or them ('or' not 'and') i.e. should be using 'Contains' individually for both max & min  not ContainsOtherOutline. Meaning that at the 'else' stage you only care that the max or min of one viewport is inside the other not both points. One corner could be inside the viewport and one outside but that is still an overlap you want to avoid."

 

Is there a way where you can exeplify this by writing some pseudocode? This way, I would understand this part easily.

 

Thank you for your patience,

0 Likes
Message 4 of 15

RPTHOMAS108
Mentor
Mentor
Accepted solution

I could suggest alternative code but I think your approach is wrong to start with. Tiling algorithms are discussed in more detail elsewhere and your task is more straightforward than those in a way because you have a specific number of rectangles to place.

 

Essentially your task could be dealt with as below:

 Order view ports by heightOrder view ports by height

Place first ordered viewport and divide up remaining spacePlace first ordered viewport and divide up remaining space

Place second viewport and divide up remaining spacePlace second viewport and divide up remaining space

etc...etc...

 

Full sequenceFull sequence

 

So for the above you need the following:

The width and height of the drawing area

The width and height of each viewport to be placed

Rectangular objects that describe the empty regions available to put a viewport in (also represents the staring drawing area available)

A function to tell you when one rectangle is completely inside another

A function to split an exiting rectangle into sub rectangles after a viewport has been placed in it at top left corner.

 

The only quirk in the above is when dividing the rectangle you have to decide if it is better to divide first horizontally or vertically. Probably you could consider both and settle on the one that gives you the least even ratios of rectangular areas to ensure you get back the largest rectangle after each division. 

 

Secondly why did I decide to place the blue viewport in the top right rather than bottom left. As a human I could instantly see that doing so will give me better arrangement for further placements. So you need to consider how to convert that aspect to code i.e. what are we looking for as a result (well proportioned largest possible remaining rectangles).

 

The are multiple solutions to a problem like this that is why you need arbitrary constraints (order of placed viewport etc.) to limit those.

 

 

 

 

 

0 Likes
Message 5 of 15

joaofmoco
Advocate
Advocate

Hello and thank you for your reply,

 

Thank you for clearing this up and thank you for your proposal, I will look into it and figure out a way where I use a tiling algorithm.

 

Thank you for your patience and help.

0 Likes
Message 6 of 15

joaofmoco
Advocate
Advocate

Hello @RPTHOMAS108,

 

Thank you for the additional information added on your previous reply and I appreciate your patience with me.

 

I have been thinking about the easiest way to go about the steps mentioned.

I already have the heights and widths set up with the following code (If you notice any mistake, please let me know):

// Filtered Element Collector list for Viewports
            FilteredElementCollector colViewports = new FilteredElementCollector(doc);
            colViewports.OfClass(typeof(Viewport));
            List<Viewport> allViewports = colViewports.Cast<Viewport>().ToList();

            // Width and height of drawing area
            double olWidth = ol.MaximumPoint.X - ol.MinimumPoint.X;
            double olHeight = ol.MaximumPoint.Y - ol.MinimumPoint.Y;

            // Arrays with the widths and heights of all viewports created
            double[] widthViewports = null;
            double[] heightViewports = null;

            // Getting width and heights of viewports
            for (int i = 0; i < allViewports.Count; i++)
            {
                widthViewports.Append(allViewports[i].GetBoxOutline().MaximumPoint.X - allViewports[i].GetBoxOutline().MinimumPoint.X);
                heightViewports.Append(allViewports[i].GetBoxOutline().MaximumPoint.Y - allViewports[i].GetBoxOutline().MinimumPoint.Y);
            }

After this, I think that, to create that rectangular object mentioned, I could create a rectangle using the outline of the drawing area. Maybe like this(?):

// Create drawing area rectangle
            int left = ((int)ol.MinimumPoint.X);
            int top = ((int)ol.MaximumPoint.Y);
            int right = ((int)ol.MaximumPoint.X);
            int bottom = ((int)ol.MinimumPoint.Y);
            Rectangle drawingRectangle = new Rectangle(left, top, right, bottom);

 

Then, the viewport rectangles could be created like this, I believe:

// Create Viewport rectangles
for (int i = 0; i < allViewports.Count; i++)
            {
                int leftVP = ((int)allViewports[i].GetBoxOutline().MinimumPoint.X);
                int topVP = ((int)allViewports[i].GetBoxOutline().MaximumPoint.Y);
                int rightVP = ((int)allViewports[i].GetBoxOutline().MaximumPoint.X);
                int bottomVP = ((int)allViewports[i].GetBoxOutline().MinimumPoint.Y);
                Rectangle drawingRectangleVP = new Rectangle(leftVP, topVP, rightVP, bottomVP);
            }

 

So far, this is all I've got and I will update this reply when I get new developments.

 

Thank you for your assistance.

0 Likes
Message 7 of 15

RPTHOMAS108
Mentor
Mentor

You need an object orientated approach as sample in below post. The object named 'RT_Rectangle' in that sample can be used for available space in drawing sheet and viewport size.

 

Regarding the members of RT_Rectangle from the sample in the following post:

 

1) SplitAroundTopLeftRectangle is used to provide the remaining rectangles after one has been placed in the top left region (initial split can be vertical or horizontal).

 

2) VerticalFirstReturnsLargestRectangle tests the resulting rectangles of both options for above function to decide which way to split the area based on next rectangle to place.

 

3) CanFitWithinOther & CanOtherFitInside can be used to determine fit.

 

Check all the above functions work as expected and remember they are only appropriate under certain conditions i.e. SplitAroundTopLeftRectangle is not appropriate to use where the input size (R) is larger than the limits of the object the function is being called on (you would end up with negative numbers).

0 Likes
Message 8 of 15

RPTHOMAS108
Mentor
Mentor

Below is a complete sample I've created that fits views onto a sheet.

 

It is run as follows

  • Open an empty sheet view
  • Select the views to be placed in the project browser
  • Run command via very top function

 

This demonstrates a basic approach to adding views to a sheet you may want to also consider instances where there are many similar sized views being placed (e.g. sections drawing). Under such circumstances you may have to artificially increase the VP size being considered to make them consistent with respect to that of the largest view in order that the next view is placed along side the last view (if there is space to the right) rather than under it. Otherwise you end up with a situation where the row height is governed by the last view height and the next view, which could be slightly larger in that direction, is placed below.

 

The is an interesting aspect of the below sample in that when you place a viewport it is via the centre however when you estimate the rectangle size it is for the viewport and label. Therefore there is a correction vector from combined viewport and label centre back to the viewport centre in order to place the viewport.

 

 

 

namespace RPT_VPortTileUtil_CS
{
	public class TestEntryPoints
	{
		//Written for Revit 2023 API
		//Main entry point
		public static Result TileVPTest_EntryPoint(ExternalCommandData commandData, ref string message, ElementSet elements)
		{
			UIApplication IntUIApp = commandData.Application;
			UIDocument IntUIDoc = commandData.Application.ActiveUIDocument;
			Document IntDoc = IntUIDoc.Document;

			ViewSheet VSht = IntUIDoc.ActiveGraphicalView as ViewSheet;
			if (VSht == null)
			{
				Debug.WriteLine("Active view is not a sheet view.");
				return Result.Cancelled;
			}

			FilteredElementCollector FEC_Tb = new FilteredElementCollector(IntDoc, VSht.Id);
			IList<Element> Els = FEC_Tb.OfCategory(BuiltInCategory.OST_TitleBlocks).WhereElementIsNotElementType().ToElements();
			if (Els.Count != 1)
			{
				Debug.WriteLine("Active sheet view must have one title block instance.");
				return Result.Cancelled;
			}
			ElementId TB_Id = Els[0].GetTypeId();

			//To find outline of main drawing area for title block
			//as previous thread
			Outline OL = TitleBlockUtil.GetTitleBlockDrawingAreas(IntDoc)[TB_Id];

			RT_Rectangle MainArea = new RT_Rectangle(OL);

			List<View> ViewsList = new List<View>();
			//Views selected in project browser
			//If selected in main model then skip
			IList<ElementId> Ids = IntUIDoc.Selection.GetElementIds() as IList<ElementId>;
			List<ElementId> VPs = new List<ElementId>();
			string Msg = null;

			using (TransactionGroup TG = new TransactionGroup(IntDoc, "Add views to sheet"))
			{

				if (TG.Start() == TransactionStatus.Started)
				{

					using (Transaction tx = new Transaction(IntDoc, "Add viewports"))
					{
						if (tx.Start() == TransactionStatus.Started)
						{
							for (int i = 0; i <= Ids.Count - 1; i++)
							{
								View V = IntDoc.GetElement(Ids[i]) as View;
								if (V == null)
								{
									//Views are either selected in project browser or other types of element are selected in model via view
									break;
								}
								if (V.IsTemplate)
									continue;
								switch (V.GetPlacementOnSheetStatus())
								{
									case ViewPlacementOnSheetStatus.CompletelyPlaced:
										//can't be placed on second sheet
										continue;
									case ViewPlacementOnSheetStatus.NotApplicable:
										//can't be placed on sheet
										continue;
								}
								VPs.Add(Viewport.Create(IntDoc, VSht.Id, Ids[i], XYZ.Zero).Id);
							}
							if (VPs.Count == 0)
							{
								tx.RollBack();
								goto GroupEnd;
							}
							else
							{
								tx.Commit();
							}
						}
					}

					List<RT_Rectangle> VPSizes = new List<RT_Rectangle>();
					foreach (ElementId item in VPs)
					{
						Viewport VP = IntDoc.GetElement(item) as Viewport;
						VPSizes.Add(new RT_Rectangle(VP));
					}

					VPSizes = VPSizes.OrderBy(q => q.Area).Reverse().ToList();

					RT_Rectangle[] NotAdded = null;
					RT_Rectangle[] Res = RT_Rectangle.FitRectanglesInArea(new RT_Rectangle(OL), VPSizes, false, ref NotAdded);

					using (Transaction tx = new Transaction(IntDoc, "Relocate and delete"))
					{
						if (tx.Start() == TransactionStatus.Started)
						{
							foreach (RT_Rectangle item in Res)
							{
								//Bottom left of title block 
								//+ Centre of view port and lable + correction from centre of viewport and label to centre of viewport
								ElementTransformUtils.MoveElement(IntDoc, item.ExternalId, item.Centre + OL.MinimumPoint + item.VPCenShift);
							}
							List<ElementId> Del = new List<ElementId>();
							foreach (RT_Rectangle item in NotAdded)
							{
								Del.Add(item.ExternalId);
							}
							if (Del.Count > 0)
							{
								Msg = Del.Count.ToString() + " views could not be placed in sheet allowance.";
								IntDoc.Delete(Del);
							}

							tx.Commit();
						}
					}

					TG.Assimilate();
					goto MainEnd;
				GroupEnd:
					TG.RollBack();
				MainEnd:;
				}
			}

			if (string.IsNullOrEmpty(Msg) == false)
			{
				TaskDialog.Show("Result", Msg);
			}

			return Result.Succeeded;

		}


	}

	public class RT_Rectangle
	{
		public ElementId ExternalId { get; set; }
		private long IntId = 0;

		private static long LastId = -1;

		public long Id
		{
			get { return IntId; }
		}
		private void SetId()
		{
			LastId += 1;
			IntId = LastId;
		}

		//Overall ords including label
		private XYZ IntPosMax = null;

		private XYZ IntPosMin = null;
		//The allowance is sized for the overall of Viewport Outline + Label Outline
		//However Viewports are placed by centre of Viewport only (not including label)
		//Therefore below correction vector is stored so that VP can be placed by VP + Lavbel centre. 
		private XYZ IntVPShift = null;
		public XYZ VPCenShift
		{
			get { return IntVPShift; }
		}

		//Only used for testing on WPF canvas
		public XYZ TopLeft
		{
			get { return new XYZ(IntPosMin.X, IntPosMax.Y, 0); }
		}

		//Placement point for VP (centre of VP)
		public XYZ Centre
		{
			get { return (IntPosMin + IntPosMax) * 0.5; }
		}

		public double Area
		{
			get { return Width * Height; }
		}
		public double Width
		{
			get { return IntPosMax.X - IntPosMin.X; }
		}
		public double Height
		{
			get { return IntPosMax.Y - IntPosMin.Y; }
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="R">The top left rectangle to fill gaps around</param>
		/// <param name="VerticalFirst"></param>
		/// <returns>Two rectangles, one below and one to side</returns>
		public RT_Rectangle[] SplitAroundTopLeftRectangle(RT_Rectangle R, bool VerticalFirst, ref RT_Rectangle TL_Location)
		{
			double Right_SZX = Width - R.Width;
			double Below_SZY = Height - R.Height;
			XYZ BL0 = default(XYZ);
			XYZ TL0 = default(XYZ);
			XYZ BL1 = default(XYZ);
			XYZ TL1 = default(XYZ);

			TL_Location = new RT_Rectangle();
			TL_Location.IntPosMin = this.IntPosMin + new XYZ(0, Below_SZY, 0);
			TL_Location.IntPosMax = TL_Location.IntPosMin + (XYZ.BasisX * R.Width) + (XYZ.BasisY * R.Height);
			//Copy VP id to returned object
			TL_Location.ExternalId = R.ExternalId;
			TL_Location.IntVPShift = R.IntVPShift;

			if (VerticalFirst)
			{
				double Right_SZY = Height;
				double Below_SZX = R.Width;

				BL0 = this.IntPosMin + (XYZ.BasisX * R.Width);
				TL0 = BL0 + (XYZ.BasisX * Right_SZX) + (XYZ.BasisY * Right_SZY);

				BL1 = this.IntPosMin;
				TL1 = this.IntPosMin + (XYZ.BasisX * R.Width) + (XYZ.BasisY * Below_SZY);

			}
			else
			{
				double Right_SZY = R.Height;
				double Below_SZX = Width;

				BL0 = this.IntPosMin + (XYZ.BasisX * R.Width) + (XYZ.BasisY * Below_SZY);
				TL0 = BL0 + (XYZ.BasisX * Right_SZX) + (XYZ.BasisY * Right_SZY);

				BL1 = this.IntPosMin;
				TL1 = this.IntPosMin + (XYZ.BasisX * Width) + (XYZ.BasisY * Below_SZY);
			}
			RT_Rectangle out0 = new RT_Rectangle
			{
				IntPosMin = BL0,
				IntPosMax = TL0
			};
			RT_Rectangle out1 = new RT_Rectangle
			{
				IntPosMin = BL1,
				IntPosMax = TL1
			};

			return new RT_Rectangle[2] {
				out0,
				out1
			};

		}

		public bool CanFitWithinOther(RT_Rectangle Other)
		{
			return this.Width <= Other.Width && this.Height <= Other.Height;
		}
		public bool CanOtherInside(RT_Rectangle Other)
		{
			return this.Width >= Other.Width && this.Height >= Other.Height;
		}

		public bool VerticalFirstReturnsLargestRectangle(RT_Rectangle R)
		{
			RT_Rectangle Nu = null;
			RT_Rectangle[] Set0 = SplitAroundTopLeftRectangle(R, true, ref Nu);
			RT_Rectangle[] Set1 = SplitAroundTopLeftRectangle(R, false, ref Nu);

			double R0 = Set0.Max(j => j.Area);
			double R1 = Set1.Max(j => j.Area);

			return R0 >= R1;

		}

		private RT_Rectangle()
		{
			SetId();
		}
		public RT_Rectangle(System.Windows.Size SZ)
		{
			SetId();
			IntPosMin = new XYZ();
			IntPosMax = new XYZ(SZ.Width, SZ.Height, 0);
		}
		public RT_Rectangle(Outline OL)
		{
			SetId();
			IntPosMax = OL.MaximumPoint;
			IntPosMin = OL.MinimumPoint;
		}
		public RT_Rectangle(Viewport VP)
		{
			Outline OLMain = VP.GetBoxOutline();


			Outline Combined = new Outline(OLMain);
			dynamic OLLab = VP.GetLabelOutline();
			Combined.AddPoint(OLLab.MinimumPoint);
			Combined.AddPoint(OLLab.MaximumPoint);


			IntVPShift = Combined.MinimumPoint - OLMain.MinimumPoint;


			RT_Rectangle X = new RT_Rectangle(Combined);
			IntPosMin = X.IntPosMin;
			IntPosMax = X.IntPosMax;
			IntId = X.IntId;
			ExternalId = VP.Id;
		}


		//Main function
		public static RT_Rectangle[] FitRectanglesInArea(RT_Rectangle StartSize, List<RT_Rectangle> RectanglesToFit, bool StopWhenCannotPlace, ref RT_Rectangle[] CouldNotFit)
		{

			List<RT_Rectangle> EmptyRectangles = new List<RT_Rectangle>();
			EmptyRectangles.Add(StartSize);

			List<RT_Rectangle> Placed = new List<RT_Rectangle>();
			List<RT_Rectangle> NotPlaced = new List<RT_Rectangle>();


			for (int i = 0; i <= RectanglesToFit.Count - 1; i++)
			{
				RT_Rectangle VP_R = RectanglesToFit[i];

				List<RT_Rectangle> ResLst = EmptyRectangles.FindAll(k => k.CanOtherInside(VP_R));
				RT_Rectangle Res = ResLst.Find(u => u.Area - VP_R.Area == ResLst.Min(v => v.Area - VP_R.Area));

				if (Res == null)
				{
					if (StopWhenCannotPlace)
					{
						for (int k = i; k <= RectanglesToFit.Count - 1; k++)
						{
							NotPlaced.Add(RectanglesToFit[k]);
						}
						break; 
					}
					else
					{
						NotPlaced.Add(VP_R);
						continue;
					}
				}

				RT_Rectangle PlacementR = null;
				if (Res.VerticalFirstReturnsLargestRectangle(VP_R))
				{
					EmptyRectangles.AddRange(Res.SplitAroundTopLeftRectangle(VP_R, true, ref PlacementR));
				}
				else
				{
					EmptyRectangles.AddRange(Res.SplitAroundTopLeftRectangle(VP_R, false, ref PlacementR));
				}
				EmptyRectangles.RemoveAll(k => k.Id == Res.Id);

				Placed.Add(PlacementR);
			}
			CouldNotFit = NotPlaced.ToArray();

			return Placed.ToArray();

		}
	}

	public static class TitleBlockUtil
	{
		public static Dictionary<ElementId, Outline> GetTitleBlockDrawingAreas(Document Doc)
		{

			FilteredElementCollector FEC = new FilteredElementCollector(Doc);
			ElementCategoryFilter ECF = new ElementCategoryFilter(BuiltInCategory.OST_TitleBlocks);
			List<FamilySymbol> Els = FEC.WherePasses(ECF).WhereElementIsElementType().Cast<FamilySymbol>().ToList();

			Dictionary<ElementId, Outline> TBlockBoundingBoxes = new Dictionary<ElementId, Outline>();
			for (int i = 0; i <= Els.Count - 1; i++)
			{
				FamilySymbol FS = Els[i];
				Document DocT = Doc.EditFamily(FS.Family);

				FilteredElementCollector FEC_Lines = new FilteredElementCollector(DocT);
				ElementCategoryFilter ECF_Lines = new ElementCategoryFilter(BuiltInCategory.OST_Lines);


				List<DetailLine> Lines = FEC_Lines.WherePasses(ECF_Lines).WhereElementIsNotElementType().OfType<DetailLine>().ToList();

				Outline OL = null;

				for (int ia = 0; ia <= Lines.Count - 1; ia++)
				{
					BoundingBoxXYZ BB = Lines[ia].get_BoundingBox(null);
					if (OL == null)
					{
						OL = new Outline(BB.Min, BB.Max);
					}
					else
					{
						OL.AddPoint(BB.Min);
						OL.AddPoint(BB.Max);
					}
				}
				XYZ RoughCentre = (OL.MinimumPoint + OL.MaximumPoint) / 2;

				Line XLn = Line.CreateUnbound(RoughCentre, XYZ.BasisX);
				Line YLn = Line.CreateUnbound(RoughCentre, XYZ.BasisY);

				double YUp = double.PositiveInfinity;
				double YDown = double.NegativeInfinity;
				double XLeft = double.NegativeInfinity;
				double XRight = double.PositiveInfinity;

				int[] BoundsSet = new int[4] { 0, 0, 0, 0 };
				for (int ia = 0; ia <= Lines.Count - 1; ia++)
				{
					DetailLine LN = Lines[ia];
					Line C = (Line) LN.GeometryCurve;

					IntersectionResultArray IRA = null;

					if (Math.Abs(C.Direction.DotProduct(YLn.Direction)) > 0.99)
					{
						SetComparisonResult SCR = XLn.Intersect(C,out IRA);
						if (SCR != SetComparisonResult.Overlap)
							continue;

						for (int ic = 0; ic <= IRA.Size - 1; ic++)
						{
							IntersectionResult IR = IRA.get_Item(ic);
							XYZ Pt = IR.XYZPoint;

							if (Pt.X > XLeft && Pt.X < RoughCentre.X)
							{
								XLeft = Pt.X;
								BoundsSet[0] = 1;
							}
							else if (Pt.X < XRight && Pt.X > RoughCentre.X)
							{
								XRight = Pt.X;
								BoundsSet[1] = 1;
							}
						}

					}
					else if (Math.Abs(C.Direction.DotProduct(YLn.Direction)) < 0.01)
					{
						SetComparisonResult SCR = YLn.Intersect(C,out IRA);
						if (SCR != SetComparisonResult.Overlap)
							continue;

						for (int ic = 0; ic <= IRA.Size - 1; ic++)
						{
							IntersectionResult IR = IRA.get_Item(ic);
							XYZ Pt = IR.XYZPoint;

							if (Pt.Y > YDown && Pt.Y < RoughCentre.Y)
							{
								YDown = Pt.Y;
								BoundsSet[2] = 1;
							}
							else if (Pt.Y < YUp && Pt.Y > RoughCentre.Y)
							{
								YUp = Pt.Y;
								BoundsSet[3] = 1;
							}
						}
					}
				}
				if (!BoundsSet.Contains(0))
				{
					XYZ Min = new XYZ(XLeft, YDown, 0);
					XYZ Max = new XYZ(XRight, YUp, 0);
					TBlockBoundingBoxes.Add(FS.Id, new Outline(Min, Max));
				}

				DocT.Close(false);
			}
			return TBlockBoundingBoxes;
		}
	}

}
0 Likes
Message 9 of 15

joaofmoco
Advocate
Advocate

Hello and thank you for you patience,

 

I have implemented your code and adapted it to my needs and it works partially.

One of the viewports created couldn't be fitted. However, it has a positive area and it has space to be fitted.

I don't really understand what is happening.

Bellow is images showing what happened, the C# Visual Studio solution and the Revit file used for testing.

joaofmoco_0-1653407333276.png

 

Thank you so much for your help,

Message 10 of 15

RPTHOMAS108
Mentor
Mentor

I imagine it is doing something similar to below.

 

If the unplaced view has the third largest area then after the second division there will be no available room to fit it's width. Rather than relying on area to set placement order you could trial different orders of placements until finding one where all views are placed.

 

Alternatively you could add a step to see if merger of empty rectangles is possible after each division. The newly created rectangles from such a merger would then be available to fit the view. The algorithm is quite simple at the moment it could be made more complex to solve this.

 

220524a.png

0 Likes
Message 11 of 15

joaofmoco
Advocate
Advocate

That makes sense.

Maybe by using the

CombineElements

class into the main function and check if there are adjacent empty rectangles, this can be solved?

Message 12 of 15

joaofmoco
Advocate
Advocate

I have tried to implement the checker in the main function of "RectangleRT.cs", like this:

 

// Rest of the main function above
placed.Add(placementR);

				for (int j = 0; j < emptyRectangles.Count; j++)
				{
					for (int n = 0; n < emptyRectangles.Count; n++)
					{
						if ((emptyRectangles[j].IntPosMax.X == emptyRectangles[n].IntPosMax.X) && (emptyRectangles[j].IntPosMin.X == emptyRectangles[n].IntPosMin.X))
                        {
							RectangleRT newEmptyX = new RectangleRT();

							if (emptyRectangles[j].IntPosMax.Y >= emptyRectangles[n].IntPosMax.Y)
                            {
								newEmptyX.IntPosMax = emptyRectangles[j].IntPosMax;
							}
							else if (emptyRectangles[j].IntPosMax.Y <= emptyRectangles[n].IntPosMax.Y)
							{
								newEmptyX.IntPosMax = emptyRectangles[n].IntPosMax;
							}

							if (emptyRectangles[j].IntPosMin.Y >= emptyRectangles[n].IntPosMin.Y)
							{
								newEmptyX.IntPosMin = emptyRectangles[n].IntPosMin;
							}
							else if (emptyRectangles[j].IntPosMin.Y <= emptyRectangles[n].IntPosMin.Y)
							{
								newEmptyX.IntPosMin = emptyRectangles[j].IntPosMin;
							}

							emptyRectangles.AddRange((IEnumerable<RectangleRT>)newEmptyX);

							emptyRectangles.Remove(emptyRectangles[j]);
							emptyRectangles.Remove(emptyRectangles[n]);
						}

						if ((emptyRectangles[j].IntPosMax.Y == emptyRectangles[n].IntPosMax.Y) && (emptyRectangles[j].IntPosMin.Y == emptyRectangles[n].IntPosMin.Y))
						{
							RectangleRT newEmptyY = new RectangleRT();

							if (emptyRectangles[j].IntPosMax.X >= emptyRectangles[n].IntPosMax.X)
							{
								newEmptyY.IntPosMax = emptyRectangles[j].IntPosMax;
							}
							else if (emptyRectangles[j].IntPosMax.X <= emptyRectangles[n].IntPosMax.X)
							{
								newEmptyY.IntPosMax = emptyRectangles[n].IntPosMax;
							}

							if (emptyRectangles[j].IntPosMin.X >= emptyRectangles[n].IntPosMin.X)
							{
								newEmptyY.IntPosMin = emptyRectangles[n].IntPosMin;
							}
							else if (emptyRectangles[j].IntPosMin.X <= emptyRectangles[n].IntPosMin.X)
							{
								newEmptyY.IntPosMin = emptyRectangles[j].IntPosMin;
							}

							emptyRectangles.AddRange((IEnumerable<RectangleRT>)newEmptyY);

							emptyRectangles.Remove(emptyRectangles[j]);
							emptyRectangles.Remove(emptyRectangles[n]);
						}
					}
				}
			}
			couldNotFit = notPlaced.ToArray();

			return placed.ToArray();

 

However, it gives an error where I can't cast the "newEmptyX"/"newEmptyY" into the "emptyRectangles". Any suggestions?

Message 13 of 15

RPTHOMAS108
Mentor
Mentor

Some things to consider:

 

You need to refactor that checking process into a separate function.

You shouldn't change a collection by adding or removing elements whilst iterating over that collection.

If you compare every empty rectangle to every empty rectangle then at some point you'll be comparing a rectangle against itself, you need to avoid that (I created the Id for that purpose).

A neighbour to the right has the same MinX position as the MaxX position of the item the neighbour is neighbouring (I'm not sure that is what you are checking above).

 

Once you find a neighbour to the right you have to establish how that neighbour overlaps in the Y direction with the limits of the current rectangle:

1) No overlap

2) MinY within limits (new height is overlap between the two)

3) MaxY within limits (new height is overlap between the two)

4) MinY and MaxY both within limits (height limited to neighbour height)

5) MinY and MaxY both outside limits with overlap (height limited to current rectangle (not neighbour))

6) MinY and MaxY both outside limits, (no overlap) (N/A)

 

As noted the form of overlap will determine the final height of the rectangle i.e. if the current rectangle governs height or the neighbouring one does or if a region between the two does (between MinY of one and MaxY of the other).

 

You could do the same exercise in the other direction. I would perhaps leave the unplaced views to last then perhaps carry out a sperate exercise see if merging in either direction (vertically/horizontally) can open up new spaces for each of those.

 

The easier option would be to place the most awkward views first i.e. those with greatest width to height ratio etc.

 

 

0 Likes
Message 14 of 15

joaofmoco
Advocate
Advocate

Hello,

 

I haven't been able to figure out a solution to this issue yet.

However, I will keep updating this post until a solution is found.

 

Thank you so much for your patience

Message 15 of 15

joaofmoco
Advocate
Advocate
Accepted solution

Hello and thank you for your patience,

 

I have implemented a solution that does what I wanted.

I need to thank @RPTHOMAS108 for the precious help and the time he spent.

 

Here's the solution:

// New function
public static RectangleRT[] UniteEmptyRectangles(List<RectangleRT> eR)
        {
			List<RectangleRT> newEmptyR = new List<RectangleRT>();

			for (int i = 0; i < eR.Count; i++)
            {
				for (int j = 0; j < eR.Count; j++)
				{
					if ((eR[i].IntPosMax.X == eR[j].IntPosMax.X) && (eR[i].IntPosMin.X == eR[j].IntPosMin.X) && (eR[i].IntId != eR[j].IntId))
					{
						RectangleRT newERX = new RectangleRT();

						if (eR[j].IntPosMax.Y >= eR[i].IntPosMax.Y)
						{
							newERX.IntPosMax = eR[j].IntPosMax;
						}
						else if (eR[j].IntPosMax.Y <= eR[i].IntPosMax.Y)
						{
							newERX.IntPosMax = eR[i].IntPosMax;
						}

						if (eR[j].IntPosMin.Y >= eR[i].IntPosMin.Y)
						{
							newERX.IntPosMin = eR[i].IntPosMin;
						}
						else if (eR[j].IntPosMin.Y <= eR[i].IntPosMin.Y)
						{
							newERX.IntPosMin = eR[j].IntPosMin;
						}

						newEmptyR.Add(newERX);
					}

					if ((eR[j].IntPosMax.Y == eR[i].IntPosMax.Y) && (eR[j].IntPosMin.Y == eR[i].IntPosMin.Y) && (eR[i].IntId != eR[j].IntId))
					{
						RectangleRT newERY = new RectangleRT();

						if (eR[j].IntPosMax.X >= eR[i].IntPosMax.X)
						{
							newERY.IntPosMax = eR[j].IntPosMax;
						}
						else if (eR[j].IntPosMax.X <= eR[i].IntPosMax.X)
						{
							newERY.IntPosMax = eR[i].IntPosMax;
						}

						if (eR[j].IntPosMin.X >= eR[i].IntPosMin.X)
						{
							newERY.IntPosMin = eR[i].IntPosMin;
						}
						else if (eR[j].IntPosMin.X <= eR[i].IntPosMin.X)
						{
							newERY.IntPosMin = eR[j].IntPosMin;
						}

						newEmptyR.Add(newERY);
					}
				}
			}

			RectangleRT[] emptyRs = newEmptyR.ToArray();

			return emptyRs;
        }

//Main function
		public static RectangleRT[] FitRectanglesInArea(RectangleRT startSize, List<RectangleRT> rectanglesToFit, bool stopWhenCannotPlace, ref RectangleRT[] couldNotFit)
		{

			List<RectangleRT> emptyRectangles = new List<RectangleRT>();
			emptyRectangles.Add(startSize);

			List<RectangleRT> placed = new List<RectangleRT>();
			List<RectangleRT> notPlaced = new List<RectangleRT>();


			for (int i = 0; i < rectanglesToFit.Count; i++)
			{
				RectangleRT vp_R = rectanglesToFit[i];

				List<RectangleRT> resLst = emptyRectangles.FindAll(k => k.CanOtherInside(vp_R));
				RectangleRT res = resLst.Find(u => u.Area - vp_R.Area == resLst.Min(v => v.Area - vp_R.Area));

				if (res == null)
				{
					if (stopWhenCannotPlace)
					{
						for (int k = i; k < rectanglesToFit.Count; k++)
						{
							notPlaced.Add(rectanglesToFit[k]);
						}
						break; // TODO: might not be correct. Was : Exit For
					}
					else
					{
						notPlaced.Add(vp_R);
						continue;
					}
				}

				RectangleRT placementR = null;
				if (res.VerticalFirstReturnsLargestRectangle(vp_R))
				{
					emptyRectangles.AddRange(res.SplitAroundTopLeftRectangle(vp_R, true, ref placementR));
				}
				else
				{
					emptyRectangles.AddRange(res.SplitAroundTopLeftRectangle(vp_R, false, ref placementR));
				}
				emptyRectangles.RemoveAll(k => k.Id == res.Id);

				placed.Add(placementR);
			}

			couldNotFit = notPlaced.ToArray();

			List<RectangleRT> newEmptyRectangleList = new List<RectangleRT>(UniteEmptyRectangles(emptyRectangles));

			for (int j = 0; j < couldNotFit.Length; j++)
            {
				RectangleRT vp_New = couldNotFit[j];

				List<RectangleRT> resLst2 = newEmptyRectangleList.FindAll(a => a.CanOtherInside(vp_New));
				RectangleRT res2 = resLst2.Find(u => u.Area - vp_New.Area == resLst2.Min(v => v.Area - vp_New.Area));

				RectangleRT placingR = null;
				if(res2.VerticalFirstReturnsLargestRectangle(vp_New))
                {
					newEmptyRectangleList.AddRange(res2.SplitAroundTopLeftRectangle(vp_New, true, ref placingR));
				}
				else
                {
					newEmptyRectangleList.AddRange(res2.SplitAroundTopLeftRectangle(vp_New, false, ref placingR));
				}
				newEmptyRectangleList.RemoveAll(k => k.Id == res2.Id);

				placed.Add(placingR);
			}

			return placed.ToArray();

		}

 

The rest of the code in the add-in is unaltered.

 

Kind regards

0 Likes