Mar 20, 2024

Create Sections on the ScopeBox edges

ScopeBoxes can be very versatile, and converting their edges to sections might also create a unique workflow in your office. Let me show you how to do that. The video will show you how to create Window Secitons, but the same can be applied to other elements.

Intro

I was asked on how to Create Sections on the edges of a ScopeBox?

Let's think what we need to do to achieve that:
1️⃣ Pick ScopeBoxes
2️⃣ Get ScopeBox Bounding Box
3️⃣ Calculate Lines and Heigth based on .Min and .Max
4️⃣ Calculate ScopeBox Heigth based on Min/Max Z Value
5️⃣ Calculate Points and Vectors
6️⃣ Create Transform
🎯 Create Sections

💡 Let's code step by step and combine The final script in the end which will work like this:

1️⃣ Pick ScopeBoxes

First of all we need to select desired ScopeBoxes. There are many ways to do it, I will create ISelectionFilter so I can only select ScopeBoxes.

Also keep in mind that Selection.PickObjects() return References. So we need to convert the to actual elements.

# -*- coding: utf-8 -*-

#⬇️ Imports
from Autodesk.Revit.DB import *
from Autodesk.Revit.UI.Selection import *
from pyrevit import forms

#📦 Variables
doc   = __revit__.ActiveUIDocument.Document #type: Document
uidoc = __revit__.ActiveUIDocument


#💊 Classes
# Define a selection filter class for Scope Boxes
class ScopeBoxSelectionFilter(ISelectionFilter):
    def AllowElement(self, element):
        if element.Category.Id == ElementId(BuiltInCategory.OST_VolumeOfInterest):
            return True


#1️⃣ Pick ScopeBoxes
ref_scope_boxes = uidoc.Selection.PickObjects(ObjectType.Element, ScopeBoxSelectionFilter())
scope_boxes     = [doc.GetElement(ref) for ref in ref_scope_boxes]

#Ensure ScopeBox selected
if not scope_boxes:
    forms.alert('No Box selected. Please Try Again',exitscript=True)

💡 Also it's good to check if we even selected any ScopeBoxes before going further. forms.alert is the perfect solution.

2️⃣ Get ScopeBox Bounding Box

Once we got ScopeBoxes, we can start iterating through them. The first thing we need to get its BoundingBox.
We can use get_BoundingBox which is based on BoundingBox Property.

#2️⃣ Iterate through scope boxes
for sbox in scope_boxes:
    BB = sbox.get_BoundingBox(None)

3️⃣ Calculate Lines and Height based on .Min and .Max

Next, we need to calculate Lines and Height based on BB Min and Max points.

ScopeBox Height would be the difference between Min/Max Z Coordinate.
And to create lines, we would just use X,Y coordinates of both points to create lines between corners.

#3️⃣ Iterate through scope boxes
for sbox in scope_boxes:
    BB = sbox.get_BoundingBox(None)
  
    # Calculate Heigth
    BB_height = BB.Max.Z - BB.Min.Z
  
    # Calculate Bottom Points
    pt_1 = XYZ(BB.Min.X, BB.Min.Y, BB.Min.Z)
    pt_2 = XYZ(BB.Max.X, BB.Min.Y, BB.Min.Z)
    pt_3 = XYZ(BB.Max.X, BB.Max.Y, BB.Min.Z)
    pt_4 = XYZ(BB.Min.X, BB.Max.Y, BB.Min.Z)

    # Create Lines
    line_1 = Line.CreateBound(pt_1, pt_2)
    line_2 = Line.CreateBound(pt_2, pt_3)
    line_3 = Line.CreateBound(pt_3, pt_4)
    line_4 = Line.CreateBound(pt_4, pt_1)
    lines = [line_1, line_2, line_3, line_4]

4️⃣ Calculate Points and Vectors

Now we have everything we need, let's start iterating through lines and prepare everything we need for creating sections.

First of all we need to get line points Start, End and Middle.
Start and End points will be used for creating a vector, so our sections is rotated correctly. and Point Mid will be used for the origin point.

lines = [line_1, line_2, line_3, line_4]

#4️⃣ Create Sections from Lines
for line in lines:

    # Calculate Origin + Vector
    pt_start  = line.GetEndPoint(0)   #type: XYZ
    pt_end    = line.GetEndPoint(1)   #type: XYZ
    pt_mid    = (pt_start + pt_end)/2 #type: XYZ
    vector    = pt_end - pt_start     #type: XYZ

5️⃣ Create Transform

Now let's create Transform for our section. It looks confusing, but after you use it a few times it gets easy. (Check the video for detailed explanation). To create an instance of Transform we need to use its property Identity. It's a bit strange, but that's a common way across all 3D Softwares.

Next we will set Origin property by assigning our our Mid Point.

And we need to calculate X, Y, Z vectors so our section is placed correctly.
The X is going to be the vector we calculated earlier. We will need to Normalize it(Normalized indicates that the length of this vector equals one unit vector). And also we need to rotate it so it looks in the right direction by multiplying by -1.

Y Vector is going to be vertical Axis (XYZ.BasisZ)

Last vector can always be calculated with CrossProduct. The cross product is defined as the vector which is perpendicular to both vectors.

#5️⃣ Create Transform
        trans        = Transform.Identity
        trans.Origin = pt_mid

        vector = vector.Normalize() * -1 # Reverse Vector by *-1

        trans.BasisX = vector
        trans.BasisY = XYZ.BasisZ
        trans.BasisZ = vector.CrossProduct(XYZ.BasisZ)
        #The cross product is defined as the vector which is perpendicular to both vectors

🎯 Create Section

Lastly, We need create a SectionBox which will represent the Section itself.

We start by defining a default BoundingBox.

Then we can calculate it's Min and Max points by using line_length and BB_height values. And we will also apply ther Transform that we've just created in the previous step. This will ensure our section is located in a correct place in 3D space.

#6️⃣ Create SectionBox
section_box = BoundingBoxXYZ() # origin 0,0,0
offset = 1 #in Feet!


half            = line.Length/2
section_box.Min = XYZ(-half - offset ,  0          - offset , 0)
section_box.Max = XYZ(half + offset  ,  BB_height + offset , line.Length)
#💡               XYZ(X - Left/Right , Y - Up/Down          , Z - Forward/Backwards)

section_box.Transform = trans # Apply Transform (Origin + XYZ Vectors)

#6️⃣ Create Section View
section_type_id  = doc.GetDefaultElementTypeId(ElementTypeGroup.ViewTypeSection)
new_section      = ViewSection.CreateSection(doc, section_type_id, section_box)

Final Code:

Finally here is how it works:

Let's combine all the code together, so there are no confusing of what goes where.

# -*- coding: utf-8 -*-
__title__ = 'ScopeBox to Sections'

#⬇️ Imports
from Autodesk.Revit.DB import *
from Autodesk.Revit.UI.Selection import *
from pyrevit import forms

#📦 Variables
doc   = __revit__.ActiveUIDocument.Document #type: Document
uidoc = __revit__.ActiveUIDocument
app   = __revit__.Application


#💊 Classes
# Define a selection filter class for Scope Boxes
class ScopeBoxSelectionFilter(ISelectionFilter):
    def AllowElement(self, element):
        if element.Category.Id == ElementId(BuiltInCategory.OST_VolumeOfInterest):
            return True

#🎯 MAIN

#1️⃣ Pick ScopeBoxes
ref_scope_boxes = uidoc.Selection.PickObjects(ObjectType.Element, ScopeBoxSelectionFilter())
scope_boxes     = [doc.GetElement(ref) for ref in ref_scope_boxes]

#Ensure ScopeBox selected
if not scope_boxes:
    forms.alert('No Box selected. Please Try Again',exitscript=True)


# Transaction 🔓
t = Transaction(doc, 'BB to Sections')
t.Start()


#2️⃣ Iterate through scope boxes
for sbox in scope_boxes:
    BB = sbox.get_BoundingBox(None)

    BB_height = BB.Max.Z - BB.Min.Z

    #3️⃣ Calculate Bottom Points
    pt_1 = XYZ(BB.Min.X, BB.Min.Y, BB.Min.Z)
    pt_2 = XYZ(BB.Max.X, BB.Min.Y, BB.Min.Z)
    pt_3 = XYZ(BB.Max.X, BB.Max.Y, BB.Min.Z)
    pt_4 = XYZ(BB.Min.X, BB.Max.Y, BB.Min.Z)

    #3️⃣ Create Lines
    line_1 = Line.CreateBound(pt_1, pt_2)
    line_2 = Line.CreateBound(pt_2, pt_3)
    line_3 = Line.CreateBound(pt_3, pt_4)
    line_4 = Line.CreateBound(pt_4, pt_1)
    lines = [line_1, line_2, line_3, line_4]

    # Create Sections from Lines
    for line in lines:

        #4️⃣ Calculate Origin + Vector
        pt_start  = line.GetEndPoint(0)   #type: XYZ
        pt_end    = line.GetEndPoint(1)   #type: XYZ
        pt_mid    = (pt_start + pt_end)/2 #type: XYZ
        vector    = pt_end - pt_start     #type: XYZ

        #5️⃣ Create Transform
        trans        = Transform.Identity
        trans.Origin = pt_mid

        vector = vector.Normalize() * -1 # Reverse Vector by *-1

        trans.BasisX = vector
        trans.BasisY = XYZ.BasisZ
        trans.BasisZ = vector.CrossProduct(XYZ.BasisZ)
        #The cross product is defined as the vector which is perpendicular to both vectors

        #6️⃣ Create SectionBox
        section_box = BoundingBoxXYZ() # origin 0,0,0
        offset = 1 #in Feet!


        half            = line.Length/2
        section_box.Min = XYZ(-half - offset ,  0          - offset , 0)
        section_box.Max = XYZ(half + offset  ,  BB_height + offset , line.Length)
        #💡               XYZ(X - Left/Right , Y - Up/Down          , Z - Forward/Backwards)

        section_box.Transform = trans # Apply Transform (Origin + XYZ Vectors)

        #6️⃣ Create Section View
        section_type_id  = doc.GetDefaultElementTypeId(ElementTypeGroup.ViewTypeSection)
        new_section      = ViewSection.CreateSection(doc, section_type_id, section_box)

t.Commit() #🔒


⌨️ Happy Coding!

Want to Learn More?

🔥 Amazing Revit API Resources!

Join Revit API

Newsletter Today!

Join Us!

which is already read by 4500+ people!

Get short Revit API Lessons and Tips directly in your mail!