Hello There,
I want to create bounding boxes at grid/curve intersections.
So far I have collected grids and all the intersecting points but I am unable to find a method where I can use these points to create bounding boxes.
I have tried to explain the problem in the snap where A, B, C and 1, 2, 3 are the grids and 4 bounding box must be created from these grids.
No need for a quadtree (or octtree really) here, as bounding boxes can be defined readily from the grids, and no not was made around segmenting inside the boxes.
Group the grids into a list of Y axis grids and a list of X axis grids.
Sort the groups of grids by their grid name.
Extract the curve of each grid.
Intersect each of the sorted X grid curves with each of the sorted Y grid curves (cross product lacing should keep the order, but you may want to verify). You should now have a list of lists of points, where each sublist is a sequenced order of points following an X grid.
Since bounding boxes are defined by a minimum point and a maximum point, you now need to drop a sublist and one item from each sublist to find the min point and max point, but we don’t know which yet.
If the vector from the first point in the first subgroup to the second point in the second subgroup has a negative X value, the min point sublists will be obtained by dropping the first item from the sublists (1 value). Otherwise you’ll need to drop the last (-1 value). This value can be pulled by taking the sign of the X value (Math.Sign node) and multiplying by -1.
If the vector from the first point of the first sublist to the first point of the second sublist has a negative Y value, the min point lists will be defined by dropping the first sublist (1). Otherwise you’ll need to drop the end sublist (-1). Again taking the sign and multiplying by -1 will get you the value to drop by.
Now that you have the drop values it’s time to remove some points. Take the sublists and drop the item from either end of the list using a List.DropItems node, where the drop value is the multiplied sign value for the sublists (step 5.1) as the amount, and the list of sublists (step 4) as the list. Be sure to set the list input to @L2 so that you are removing from the start list.
Now drop the first or last sublist to get only the min points by using another List.DropItems node with the list input as the lists of sublists with one point removed (step 6) as the list input, and the inverted sign of the Y value as the amount (step 5.2).
To get the maximum points repeat step 6 and 7 using sign values which are inverted again.
hese values will then need to be translated on the Z axis to the elevation of the level you want to split the box at (assuming you want the highest level, find that)
Draw your bounding boxes using the list of min points (step 7) as the min point, and the repeated version (final part of step 8) as the max point.
To get the names you can extract the grid names from the groups of grids (step 2) and join them with cross product lacing, add them to each other using cross product lacing, drop items from the sublists and lists as was done with the points (the added strings replace the points lists for steps 6, 7, and 8), and then add the min point string to the max point string. Or you can test the min and max point of each bounding box against the X and Y grids to find the closest.
If you can you post a Revit file and your current dyn I can try and have a look later. Explaining further than above is going to be a bit longer to build.
Out of curiosity, will you ever have grids in a non-orthogonal direction?
I have both grids orthogonal and Inclined. But 95% of the time I will deal with orthogonal grids. GridIntersectionsToSolids.dyn (35.7 KB) Project1.rvt (4.9 MB)
I don’t know how to process inclined grids. But I have added some inclined grids just for the sake of testing.
Bounding boxes will not work with anything on an angle, even with the addition of off axis bounding boxes in 2024; you’ll get overlap no matter what you do, as the bounding edges formed by the grids will not be perpendicular for at least one row of cells. You’ll want to consider solids which can be used similarly in such a case.
I’ll look into both for you quickly, but knowing what the intended use of the bounding box is will help outline the process.
import clr
import sys
pyEngineName = sys.implementation.name
import System
#
clr.AddReference('ProtoGeometry')
import Autodesk.DesignScript.Geometry as DS
#import Revit API
clr.AddReference('RevitAPI')
import Autodesk
import Autodesk.Revit.DB as DB
#import net library
from System import Array
from System.Collections.Generic import List, IList, Dictionary
clr.AddReference('RevitNodes')
import Revit
clr.ImportExtensions(Revit.GeometryConversion)
#import transactionManager and DocumentManager (RevitServices is specific to Dynamo)
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument
import itertools
import random
#Preparing input from dynamo to revit
lstGrids = UnwrapElement(IN[0])
lstPtToLine = []
lstGridCurves = []
lst_ds_bbx = []
# found points intersections
for gd_a, gd_b in itertools.combinations_with_replacement(lstGrids, 2):
curveA = gd_a.Curve
curveB = gd_b.Curve
vectA = curveA.ComputeDerivatives(0.5, True).BasisX
vectB = curveB.ComputeDerivatives(0.5, True).BasisX
# check if lines are parallals
if abs(vectA.CrossProduct(vectB).Z) < 0.01:
# if true go to the next iteration
continue
lstGridCurves.append(curveA.ToProtoType())
lstGridCurves.append(curveA.ToProtoType())
if pyEngineName == "ironpython":
outInterR = clr.Reference[DB.IntersectionResultArray]()
result = curveA.Intersect(curveB, outInterR)
if result == DB.SetComparisonResult.Overlap:
interResult = outInterR.Value
lstPtToLine.append(interResult[0].XYZPoint)
else:
dummy_out = DB.IntersectionResultArray()
result, interResult = curveA.Intersect(curveB, dummy_out)
if result == DB.SetComparisonResult.Overlap:
lstPtToLine.append(interResult[0].XYZPoint)
lstPtToLine.sort(key= lambda p : (p.Y, p.X))
# create an array
array = []
for key_, group in itertools.groupby(lstPtToLine, key= lambda p : round(p.Y, 2)):
lst = sorted(group, key= lambda p : p.X)
array.append(lst)
# create bbx
for i in range(len(array)): # nbr rows
for j in range(len(array[0])): # nbr columns
ptA = array[i][j]
try:
# get the point on the next row and the next column
ptB = array[i+1][j+1]
# add a random Z value
ptB = ptB.Add(DB.XYZ(0,0,random.randint(2,6)))
ds_bbx = DS.BoundingBox.ByCorners(ptA.ToPoint(), ptB.ToPoint())
lst_ds_bbx.append(ds_bbx)
except (IndexError, ValueError):
pass
OUT = lst_ds_bbx, lstGridCurves
Feels like you could add a ‘sort by name starting with a number’ to the mix in order to reduce the number of intersection tests… certainly performative enough as is though.
I will be using these Bounding boxes to do two things:
1.Adding the Grid Reference to the Elements.
2.And the second is to create a unique sequence for the elements that will be grouped by these grid references.
I will give a try to Cyril’s method.
For that use Cyril’s method is ideal for orthogonal grids, but you will have overlapping bounding boxes when grids are at inconsistent angles (check the right side of the image). For the 5% of projects with non-orthogonal grids I recommend running it in a clean model that has the grids copy monitored into it (or is the source for the grids) and writing a solid into the model. You can then use that solid and a point containment test in the Revit API (really a very small line intersection test) to identify any point’s grid section.
Thanks Cyril,
I have used your code as reference to obtain what I needed.
I also wanted grid names with the box so I used my original code and added your logic to create box from sorted points.
And instead of sorting grids by name I had to sort them by their location as in some of the models grids names were not in the sequence as sorting will produce.