Detail Lines over MEP Spaces/Zones

I am trying to write a script that will look at the HVAC Zone
Boundary’s curve/geometry/something and will then place a
detail line of my choosing over it. Basically tracing the HVAC
Zone with a detail line.

The current script I have places detail lines on every single
space separator and i cannot figure out how to make it look at the
zone boundary.
I have been stuck on this for a while.
Any and all help will mean so much and be greatly appreciated.

This is the furthest I have able to take this with the help of few other colleagues.

from Autodesk.Revit.DB import (
    FilteredElementCollector,
    BuiltInCategory,
    ViewPlan,
    SpatialElementBoundaryOptions,
    GraphicsStyle,
    GraphicsStyleType,
    Transaction,
    ElementId,
    SpatialElement
)
from Autodesk.Revit.UI.Selection import ObjectType, ISelectionFilter
from Autodesk.Revit.Exceptions import OperationCanceledException
from pyrevit import forms

# Custom selection filter to ensure only HVAC zones are selected
class HVACZoneSelectionFilter(ISelectionFilter):
    def AllowElement(self, element):
        # Check if the element is an HVAC zone
        if isinstance(element, SpatialElement) and element.Category.Id.IntegerValue == int(BuiltInCategory.OST_MEPSpaces):
            return True
        return False
    
    def AllowReference(self, reference, position):
        return False

# Get current document
doc = __revit__.ActiveUIDocument.Document
uidoc = __revit__.ActiveUIDocument  # Correctly get the active UIDocument

# Get current view (assuming it's a floor plan or ceiling plan)
current_view = doc.ActiveView

# Check if the view is valid for detail lines
if not isinstance(current_view, ViewPlan):
    forms.alert("Please run this script in a Floor Plan or Ceiling Plan view.", exitscript=True)

# Prompt user to select HVAC zones with the custom filter
try:
    selected_zone_refs = uidoc.Selection.PickObjects(ObjectType.Element, HVACZoneSelectionFilter(), "Select HVAC Zones")
except OperationCanceledException:
    forms.alert("Selection canceled. Exiting script.", exitscript=True)
    selected_zone_refs = None  # Ensure that selected_zone_refs is None

# Exit if the user cancels the selection
if not selected_zone_refs:
    forms.alert("No HVAC zones selected. Exiting script.", exitscript=True)

# Convert selected references to elements
selected_zones = [doc.GetElement(ref.ElementId) for ref in selected_zone_refs]

# Collect all graphic styles in the document
# Graphics styles include line styles, projection styles, etc.
collector = FilteredElementCollector(doc).OfClass(GraphicsStyle)

# Collect all line styles available in the document
line_styles = [
    style for style in collector
    if style.GraphicsStyleCategory.Parent is not None and
    style.GraphicsStyleCategory.Parent.Id.IntegerValue == int(BuiltInCategory.OST_Lines) and 
    style.GraphicsStyleType == GraphicsStyleType.Projection
]

# Create a dictionary of line style names and their corresponding ElementIds
line_style_dict = {ls.Name: ls.Id for ls in line_styles}

# Sort the line style names alphabetically
sorted_line_styles = sorted(line_style_dict.keys())

# Prompt user to select a line style from the sorted list
selected_line_style_name = forms.SelectFromList.show(sorted_line_styles, title="Select Line Style", button_name="OK")

# Exit if the user cancels the selection
if not selected_line_style_name:
    forms.alert("No line style selected. Exiting script.", exitscript=True)

# Get the selected line style ElementId
selected_line_style_id = line_style_dict[selected_line_style_name]

# Start a transaction
t = Transaction(doc, "Draw HVAC Zone Boundaries")
t.Start()

try:
    for zone in selected_zones:
        # Get the boundary curves for the zone
        boundary_options = SpatialElementBoundaryOptions()  # Instantiate the boundary options
        boundary_segments = zone.GetBoundarySegments(boundary_options)
        
        for segment in boundary_segments:
            for curve in segment:
                # Convert curve to a detail line in the current view with the selected line style
                detail_line = doc.Create.NewDetailCurve(current_view, curve.GetCurve())
                detail_line.LineStyle = doc.GetElement(selected_line_style_id)  # Set the line style
                
finally:
    # Commit the transaction
    t.Commit()

the way the code works so far is:

prompts the user to select spaces/zones

ask the user the type of detail line to use

then places the detail line chosen

Two different things you’re trying to draw. Spaces are one thing. Zones are another. Right now you are just drawing lines from the spatial elements collected from the zone. I think your GetBoundarySegments if returning all edge, when what you really want is just the outer boundaries. I think you should be able to do that with just Zone.Boundary. I think that will give you just the outer edges.
You’ll need to get the spaces (SpaceSet) associated with the zones and draw the lines for the spaces. To be thorough, you’ll probably want to do a comparison of zone lines and space lines, so you don’t draw space lines over zone lines.

I’d probably go a different route as Zones may not be one contiguous set of spaces. There could be several disconnected loops. And maybe these need different colors or linestyles or filled regions to differentiate each zone. For that, I’d work directly with the geometry.
For the Zone:
Get the Zone.
Get the geometry of the active view including non-visible objects.
Get the solid.
Get the PlanarFace
Get the EdgeArrayArray (silly programmers) - You may have one or several edge array loops.
Get the edges s curve elements (AsCurve())

And of course, your zone could have islands and donuts. Probably not. But it could.

1 Like

let me give your feedback a try. I did struggle with the zones not being spacial elements and trying to modify the class to allow selections only of HVAC zones.

Yes. A Zone isn’t a spatial element. But rather a collection of spaces which are spatial elements.