Model BoundingBox as Elevation CropBox

Ive been working on automizing creating elevations and CropBox from the model. I have a problem with BoundingBoxes and Transform Class.
Here is my Bounding Box getting method with the first problem: No matter which view I input, the BoundingBox is the same. Is it a bug that is common? I understood from RevitAPIdocs that it should give me the view`s own boundingbox of those elements.

def get_model_bounding_box(view):
  doc  = (I have my integrated method to do doc, which i skipped here, so dont mind this)
  categories = [BuiltInCategory.OST_Columns, BuiltInCategory.OST_GenericModel, 
                BuiltInCategory.OST_StructuralFraming, BuiltInCategory.OST_Floors]
  bounding_box = None
  elements = []
  for category in categories:
  for element in elements:
    bbox = element.get_BoundingBox(view)
    if bbox is not None:
      if bounding_box is None:
        bounding_box = bbox
        bounding_box.Min = XYZ(
          min(bounding_box.Min.X, bbox.Min.X),
          min(bounding_box.Min.Y, bbox.Min.Y),
          min(bounding_box.Min.Z, bbox.Min.Z)
        bounding_box.Max = XYZ(
          max(bounding_box.Max.X, bbox.Max.X),
          max(bounding_box.Max.Y, bbox.Max.Y),
          max(bounding_box.Max.Z, bbox.Max.Z)
  return bounding_box

And here is the main code:

  bounding_box = get_model_bounding_box(None)
  print(bounding_box.Max.X - bounding_box.Min.X)
  print(bounding_box.Max.Y - bounding_box.Min.Y)
  print(bounding_box.Max.Z - bounding_box.Min.Z)
  transform = elevation_north.CropBox.Transform
  transformed_bounding_box = BoundingBoxXYZ()
  transformed_bounding_box.Transform = transform

  min_point = transform.OfPoint(bounding_box.Min)
  max_point = transform.OfPoint(bounding_box.Max)
  transformed_bounding_box.Min = XYZ(min(min_point.X, max_point.X), min(min_point.Y, max_point.Y), min(min_point.Z, max_point.Z))
  transformed_bounding_box.Max = XYZ(max(min_point.X, max_point.X), max(min_point.Y, max_point.Y), max(min_point.Z, max_point.Z))
  print("After transformation North:")
  print(transformed_bounding_box.Max.X - transformed_bounding_box.Min.X)
  print(transformed_bounding_box.Max.Y - transformed_bounding_box.Min.Y)
  print(transformed_bounding_box.Max.Z - transformed_bounding_box.Min.Z)
  t = Transaction(doc, 'Set Crop Box')
  elevation_north.CropBoxActive = True
  elevation_north.CropBoxVisible = False
  elevation_north.CropBox = transformed_bounding_box

After I run this the newly created elevation has a Cropbox like so:
Snapshot - 2024-14-4-20-24-43
Applying the elevation Transform to the BoundingBox changes Z to Y (ca. +79), Y to Z (without change) and X to X (ca. +27). The CropBox in ElevationView appears around 79 units too high (as it was transformed). Why the addition?

Also here is how i create elevations, maybe its relevant:

def create_elevation(view_name, view_direction):
  doc = 
  view_types = DB.FilteredElementCollector(doc).OfClass(ViewFamilyType).ToElements()
  view_types_elevations = [vt for vt in view_types if vt.ViewFamily == ViewFamily.Elevation]
  elevation_type = view_types_elevations[0]
  if view_direction.lower() == "north":
        angle = 0.5 * math.pi
  elif view_direction.lower() == "south":
        angle = 1.5 * math.pi
  elif view_direction.lower() == "west":
        angle = math.pi
  elif view_direction.lower() == "east":
        angle = 0
  floor_plan = create_floor_plan(view_name)
  bounding_box = get_grid_lines_bounding_box(floor_plan)
  min_point = bounding_box.Min
  max_point = bounding_box.Max
  corners = {
    "north": [XYZ(min_point.X, max_point.Y, 0), XYZ(max_point.X, max_point.Y, 0)],
    "south": [XYZ(min_point.X, min_point.Y, 0), XYZ(max_point.X, min_point.Y, 0)],
    "west": [XYZ(min_point.X, min_point.Y, 0), XYZ(min_point.X, max_point.Y, 0)],
    "east": [XYZ(max_point.X, min_point.Y, 0), XYZ(max_point.X, max_point.Y, 0)]}
  midpoints = {
    "north": XYZ((min_point.X + max_point.X) / 2, max_point.Y, 0),
    "south": XYZ((min_point.X + max_point.X) / 2, min_point.Y, 0),
    "west": XYZ(min_point.X, (min_point.Y + max_point.Y) / 2, 0),
    "east": XYZ(max_point.X, (min_point.Y + max_point.Y) / 2, 0)}
  depth = max(max_point.X - min_point.X, max_point.Y - min_point.Y)
  marker_XYZ = midpoints[view_direction.lower()]
  t = Transaction(doc, 'Create View')
  marker = ElevationMarker.CreateElevationMarker(doc, elevation_type.Id, marker_XYZ, 50)
  elevation_view = marker.CreateElevation(doc, floor_plan.Id, 0)
  # Set the bound offset
  p = elevation_view.get_Parameter(BuiltInParameter.VIEWER_BOUND_OFFSET_FAR)
  # Rotate the marker
  rotation_axis = Line.CreateBound(marker_XYZ, marker_XYZ + XYZ.BasisZ)
  ElementTransformUtils.RotateElement(doc, marker.Id, rotation_axis, angle)
  elevation_view.Name = view_name
  return elevation_view, marker_XYZ, floor_plan

Any help is greatly appreciated!

Found that transform.Origin (RevitAPI: Origin property defines the origin of the old coordinate system in the new coordinate system) prints out (124.519613414, 79.000000000, 0.000000000) which sort of adds up when you look at the numbers. This does not help me understand the issue though.

(124.519613414, 79.000000000, 0.000000000)
(-1.000000000, 0.000000000, 0.000000000)
(0.000000000, 0.000000000, 1.000000000)
(0.000000000, 1.000000000, 0.000000000)
(84.542553467, 77.500000000, 44.168333333)
(67.838363414, 89.041666667, 70.831666667)
(-39.977059947, -1.500000000, 44.168333333)
(-56.681250000, 10.041666667, 70.831666667)

Have you checked @ErikFrits video:

That explains coordinates and transforms a bit.

He also have a previous video about elevation creation

1 Like

Thanks for the recommendation! Checked both the video you’ve sent and the one on section views which was helpful to an extent:

I got to a similar point using the author’s methods. My elevation cropbox was on a right height, and had right depth, but (!) still the X was separated from the building by Y value of the given elevation marker X.
Below is a snippet of the code that worked on north and south elevations. This is the code that worked for south elevation for example:
section_box.Min = XYZ(-width/2 + marker_XYZ_south.Y, min(levels_elevations), -CROPBOX_OFFSET)
section_box.Max = XYZ(width/2 + marker_XYZ_south.Y, height, CROPBOX_OFFSET)
This is the final section box used as cropbox in the end. X is taken from width number given by cumulative bounding box of all the grids + the offset from the elevation marker’s Y value, Y is hieght based on projects levels, Z is set as .Depth separately after creating elevation view and here its widened by the offset.
I have a suspicion that CropBox for elevation might be calculated from the bottom left corner of the geometry available in the view.

I will try to do exact same for east and west elevations to get more cases with different rotations.

  # Get height of the building by levels
  levels = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Levels).WhereElementIsNotElementType().ToElements()
  levels_elevations = []
  for level in levels:
  height = max(levels_elevations) - min(levels_elevations)
  # Get width and lenght of the building by grid lines
  grid_bounding_box = blueprint_utils.get_grid_lines_bounding_box(floor_plan)
  min_point = grid_bounding_box.Min
  max_point = grid_bounding_box.Max
  marker_XYZ = XYZ((min_point.X + max_point.X) / 2, max_point.Y, 0)
  lenght = grid_bounding_box.Max.X - grid_bounding_box.Min.X
  width = grid_bounding_box.Max.Y - grid_bounding_box.Min.Y
  width = UnitUtils.ConvertFromInternalUnits(width, UnitTypeId.Feet)
  # Create Transform
  vector = XYZ(grid_bounding_box.Min.X,0,0) - XYZ(grid_bounding_box.Max.X,0,0)
  vector = vector.Normalize()
  trans = Transform.Identity
  trans.Origin = XYZ(UnitUtils.ConvertFromInternalUnits(marker_XYZ.X , UnitTypeId.Feet),
                    UnitUtils.ConvertFromInternalUnits(marker_XYZ.Y , UnitTypeId.Feet),
                    UnitUtils.ConvertFromInternalUnits(marker_XYZ.Z , UnitTypeId.Feet))
  trans.BasisX = vector
  trans.BasisY = XYZ.BasisZ
  trans.BasisZ = vector.CrossProduct(XYZ.BasisZ)
  #Create Section Box
  section_box = BoundingBoxXYZ()
  section_box.Min = XYZ(-width/2 + marker_XYZ.Y, min(levels_elevations), -CROPBOX_OFFSET)
  section_box.Max = XYZ(width/2 + marker_XYZ.Y, height, CROPBOX_OFFSET)
  section_box.Transform = trans
  blueprint_utils.set_crop_box(elevation_north, section_box)

  # Create Transform
  vector = XYZ(grid_bounding_box.Max.X,0,0) - XYZ(grid_bounding_box.Min.X,0,0)
  vector = vector.Normalize()
  trans = Transform.Identity
  trans.Origin = XYZ(UnitUtils.ConvertFromInternalUnits(marker_XYZ_south.X , UnitTypeId.Feet),
                    UnitUtils.ConvertFromInternalUnits(marker_XYZ_south.Y , UnitTypeId.Feet),
                    UnitUtils.ConvertFromInternalUnits(marker_XYZ_south.Z , UnitTypeId.Feet))
  trans.BasisX = vector
  trans.BasisY = XYZ.BasisZ
  trans.BasisZ = vector.CrossProduct(XYZ.BasisZ)
  #Create Section Box
  section_box = BoundingBoxXYZ()
  section_box.Min = XYZ(-width/2 + marker_XYZ_south.Y, min(levels_elevations), -CROPBOX_OFFSET)
  section_box.Max = XYZ(width/2 + marker_XYZ_south.Y, height, CROPBOX_OFFSET)
  section_box.Transform = trans
  blueprint_utils.set_crop_box(elevation_south, section_box)

You are getting
Not in front of my computer but I can point you to the pychilizer extension.
Look at their code.
Here is their lib with defs to get there eventually.

You should also get a look at their tools by activating the extension in Revit. It is listed in the extension directory of pyrevit