Creating a new 3D view with section box aligned to 2D crob box and depth

Hi everyone,

I am building my first script for work and decided to build one that takes you straight to a 3D view of any 2D view that you are in at that moment.

Everything is working perfectly, except for the cropping of the section box on to the 2 view crop region.

I am using the SetSectionBox(my3Dview, boundingBoxXYZ) method, my problem is that I can’t seem to find anything that returns a boundingBoxXYZ that is usable in my script.

Please see my code below for reference:

"""Create 3D view from current view"""

from pyrevit import revit, DB, UI
from pyrevit import forms
from pyrevit import script

#Create new 3D view with name of current user, cropped to the active view
def new_view(view, user):
    if type(view) == revit.DB.View3D:
        forms.alert("Please use a 2D view")
    else:
        with script.revit.Transaction("Create View"):
            newThreeDee = revit.create.create_3d_view(user + " temp")
        revit.UI.UIDocument.RequestViewChange(revit.HOST_APP.uidoc, newThreeDee)

        revit.DB.View3D.SetSectionBox(newThreeDee, revit.DB.View.getBoundingBox(view, view))
        # print(type(revit.DB.View.GetCropRegionShapeManagerForReferenceCallout(revit.HOST_APP.doc, revit.DB.View.CropBox(newThreeDee))))

originalView = revit.active_view

#Check if active view is a 3D view
#Return false if view is 2D
get_view_type = type(originalView) == revit.DB.View3D

#Gets the name of the user currently active
get_current_user = revit.HOST_APP.username
             
new_view(originalView, get_current_user)```

Here’s a section from a similar script I’ve written.

elevation = view.GenLevel.LookupParameter('Elevation').AsDouble()
vCrop = list(view.GetCropRegionShapeManager().GetCropShape())[0]
vCropPointsX = []
vCropPointsY = []
for x in vCrop:
    vCropPointsX.append(x.GetEndPoint(0).X)
    vCropPointsY.append(x.GetEndPoint(0).Y)
    vCropPointsX.append(x.GetEndPoint(1).X)
    vCropPointsY.append(x.GetEndPoint(1).Y)
minX = min(vCropPointsX)
minY = min(vCropPointsY)
maxX = max(vCropPointsX)
maxY = max(vCropPointsY)           

new_box = BoundingBoxXYZ()
new_box.Min = XYZ(minX, minY, elevation - 10)
new_box.Max = XYZ(maxX, maxY, elevation + 10)
v_name = view.Name
new_iso = DB.View3D.CreateIsometric(doc,threeDTypes[0].Id)
new_iso.SetSectionBox(new_box)
name_param = new_iso.LookupParameter('View Name')
name_param.Set('{} ISOMETRIC'.format(v_name))

This creates a 3D view cropped to the maximum extents of the view. The section box is always orthogonal to project north in the above code, if you want to look into rotated section boxes, you can dive into BoundingBoxXYZ.Transform.
ApiDocs.co · Revit · Transform Property

2 Likes

Yes, you have to create a new section box based on the view range of the view. In the example it has a fixed height, but it is better to use the actual view range.

1 Like

Thank you very much! I will give it a shot today!

Hi, i have the same idea for a while in my head, did you manage to solve your problem? Got the same problem with the BoundingBox.

1 Like

I use UI.Selection.PickBox to have users draw a box around where they want the 3D view and create a bounding box with the 2 points returned using the bounding box constructor. Please note that if the viewplan has a tranform applied to it you will need to apply the views transform to the points to rotate and/or translate the coordinates to the correct location. Otherwise the 3D section box will not closely match what you drew. Here’s my code to get the the box and then my fuction to create the DB.BoundingBox:
from Autodesk.Revit.UI.Selection import PickBoxStyle
opt = PickBoxStyle.Crossing
try:
picked_box = HOST_APP.uidoc.Selection.PickBox(
opt, “Draw Rectangle around area to create 3D view”)
except Exception as e:
forms.alert(str(e), exitscript=True)

def create_xzybox(boundmin,boundmax, top, bottom):

min_pbox = DB.XYZ(boundmin[0], boundmin[1], bottom)
max_pbox = DB.XYZ(boundmax[0], boundmax[1], (top + 2.3))   

box = DB.BoundingBoxXYZ()
box.Min = min_pbox
box.Max = max_pbox
return box

For those still looking for it, python code for 3d view from active view, also works for sections.

#### coding: utf8

__title__   = "STR_3D depuis la vue"
__author__  = "Stéphane ROSPARS DUPIN"
__version__ = "Version: 1.0"
__doc__ = """Version = 1.0
Date    = 2024.03.26
_____________________________________________________________________
Description:
Passe la vue 3D utilisateur avec une zone de coupe alignée sur la vue active.
_____________________________________________________________________
Mode d'emploi:

_____________________________________________________________________
Last update:
- [024.03.26] - 1.0 Création
_____________________________________________________________________
Contact et support:
- Stéphane ROSPARS DUPIN - AIA ANGERS
_____________________________________________________________________
To-Do:
-> Ø
_____________________________________________________________________"""
#####################################################################
#📚📚📚📚---------------------------------------------BIBLIOTHEQUES:
#####################################################################

from Autodesk.Revit.DB import *
from pyrevit import revit, HOST_APP, script, forms
from Autodesk.Revit.DB import BoundingBoxIntersectsFilter, Outline, FilteredElementCollector


#####################################################################
#*️⃣0️⃣1️⃣2️⃣------------------------------------------------VARIABLES:
#####################################################################
uidoc           = __revit__.ActiveUIDocument
doc             = __revit__.ActiveUIDocument.Document #type:Document
app             = __revit__.Application
rvt_year        = int(app.VersionNumber)
curview         = revit.active_view
selection       = revit.get_selection()
#📺----------------------------------------------------------Sorties:
output 			= script.get_output()
output.close_others()          #Ferme la fenêtre de script précédente


#####################################################################
#🧬🧬🧬🧬------------------------------------------------FONCTIONS:
#####################################################################
def is_supported_view(view):
    return view.ViewType in [ViewType.FloorPlan, ViewType.EngineeringPlan,ViewType.CeilingPlan, ViewType.Section]
def is_viewplan(view):
    return view.ViewType in [ViewType.FloorPlan, ViewType.EngineeringPlan,ViewType.CeilingPlan]


def zoom_to_bbox(view, bbox):
    """Fait un zoom étendu sur une BoundingBox donnée dans une vue donnée."""
    outline = Outline(bbox.Min, bbox.Max)
    filter_bbox = BoundingBoxIntersectsFilter(outline)
    
    ids_in_box = FilteredElementCollector(doc, view.Id).WherePasses(filter_bbox).ToElementIds()
    
    if ids_in_box:
        revit.uidoc.ShowElements(ids_in_box)

def get_crop_box_from_view_range(view):
    """Construit une bounding box orientée à partir de la plage de vue (plans) ou fallback sur la bounding box brute."""
    
    # Récupération de la bounding box actuelle
    bbox_view = view.CropBox if view.CropBoxActive else view.get_BoundingBox(None)
    if not bbox_view:
        return None

    transform = bbox_view.Transform  # Important pour l'orientation !

    # X/Y depuis la bbox (fallback plus stable que CurveLoop)
    minX = bbox_view.Min.X
    maxX = bbox_view.Max.X
    minY = bbox_view.Min.Y
    maxY = bbox_view.Max.Y

    # Lecture de la plage de vue
    view_range = view.GetViewRange()
    bottom_id = view_range.GetLevelId(PlanViewPlane.BottomClipPlane)
    top_id = view_range.GetLevelId(PlanViewPlane.TopClipPlane)

    bottom_level = revit.doc.GetElement(bottom_id) if bottom_id != ElementId.InvalidElementId else None
    top_level = revit.doc.GetElement(top_id) if top_id != ElementId.InvalidElementId else None

    bottom_offset = view_range.GetOffset(PlanViewPlane.BottomClipPlane)
    top_offset = view_range.GetOffset(PlanViewPlane.TopClipPlane)

    bottom_elev = bottom_level.ProjectElevation + bottom_offset if bottom_level else bbox_view.Min.Z
    top_elev = top_level.ProjectElevation + top_offset if top_level else bbox_view.Max.Z

    # Création de la nouvelle bounding box
    bbox = BoundingBoxXYZ()
    bbox.Min = XYZ(minX, minY, bottom_elev)
    bbox.Max = XYZ(maxX, maxY, top_elev)
    bbox.Transform = transform  

    return bbox

# ------------------------------------------
# EXPAND / REORIENT BOUNDINGBOX
# ------------------------------------------
def reorient_bbox_to_world(bbox):
    """Transforme une BoundingBox orientée dans le repère local vers le repère monde."""
    local_min = bbox.Min
    local_max = bbox.Max
    corners_local = [XYZ(x, y, z) for x in [local_min.X, local_max.X]
                                  for y in [local_min.Y, local_max.Y]
                                  for z in [local_min.Z, local_max.Z]]
    corners_world = [bbox.Transform.OfPoint(p) for p in corners_local]
    min_x = min(p.X for p in corners_world)
    min_y = min(p.Y for p in corners_world)
    min_z = min(p.Z for p in corners_world)
    max_x = max(p.X for p in corners_world)
    max_y = max(p.Y for p in corners_world)
    max_z = max(p.Z for p in corners_world)
    new_bbox = BoundingBoxXYZ()
    new_bbox.Min = XYZ(min_x, min_y, min_z)
    new_bbox.Max = XYZ(max_x, max_y, max_z)
    new_bbox.Transform = Transform.Identity
    return new_bbox


def apply_cropbox_to_user3d(cropbox):
    user = str(HOST_APP.username)
    user_view_name = "{3D - " + user[0] + "." + user[1:] + "}"
    collector = FilteredElementCollector(revit.doc).OfCategory(BuiltInCategory.OST_Views)

    target_view = next((v for v in collector if v.Name == user_view_name and isinstance(v, View3D)), None)
    if not target_view: 
        user_view_name="{3D}"
        target_view=next((v for v in collector if v.Name == user_view_name and isinstance(v, View3D)), None)
    elif not target_view:
        #🚩🚧🚩-Alerte:
        forms.alert("❌ Vue 3D non trouvée❌",
                    title="Erreur vue utilisateur", 
                    sub_msg="**{}** non trouvée".format(user_view_name),
                    ok=True,
                    yes=False,
                    no=False,
                    exitscript=True,
                    warn_icon=False)
        return

    with Transaction(revit.doc, "Appliquer CropBox") as t:
        t.Start()
        target_view.SetSectionBox(cropbox)
        if target_view.ViewTemplateId != ElementId.InvalidElementId:
            target_view.ViewTemplateId = ElementId.InvalidElementId
        target_view.SetCategoryHidden(ElementId(BuiltInCategory.OST_Levels), True)              # Cache les niveaux dans la vue
        target_view.SetCategoryHidden(ElementId(BuiltInCategory.OST_VolumeOfInterest), True)    # Cache les zones de définition dans la vue
        t.Commit()

    revit.uidoc.ActiveView = target_view
    revit.uidoc.RefreshActiveView()


def get_crop_box_transformed(view):
    """Retourne la bounding box transformée pour une coupe."""
    bbox = view.CropBox
    if not bbox or not bbox.Enabled:
        return None

    # Appliquer la transformation au repère global
    transform = bbox.Transform
    min_pt = transform.OfPoint(bbox.Min)
    max_pt = transform.OfPoint(bbox.Max)

    transformed_box = BoundingBoxXYZ()
    transformed_box.Min = XYZ(
        min(min_pt.X, max_pt.X),
        min(min_pt.Y, max_pt.Y),
        min(min_pt.Z, max_pt.Z)
    )
    transformed_box.Max = XYZ(
        max(min_pt.X, max_pt.X),
        max(min_pt.Y, max_pt.Y),
        max(min_pt.Z, max_pt.Z)
    )
    return transformed_box
#####################################################################
#🚀🚀🚀🚀🚀----------------------------------------------LANCEMENT:
#####################################################################
def main():
    view = revit.active_view
    if not is_supported_view(view):
        #🚩🚧🚩-Alerte:
        forms.alert("Type de vue non supportée",
                    title="Erreur de format", 
                    sub_msg="lancer la commande dans un plan d'étage, de plafond, une vue en plan ou une coupe",
                    ok=True,
                    yes=False,
                    no=False,
                    exitscript=True,
                    warn_icon=False)
        return

    if is_viewplan(view):
        bbox = get_crop_box_from_view_range(view)
    else:
        bbox = get_crop_box_transformed(view)

    apply_cropbox_to_user3d(bbox)

main()