Area Boundary - Curves are not contiguous

Hi,

I actully have a question in regards to a Python script for use through pyRevit. I am not actually sure if this is ok in the Dynamo forum… In case it is a problem, I can recreate the issue in Dynamo :slight_smile:

So, what it’s all about: in short, the scrypt lets you select Areas from Area Plan view and creates a separate plan view for each area. In the end, it also takes the Area Bounday and creates a crop region, based on that boundary. Here’s the issue - sometimes Revit creates Areas even though boundaries are drawn incorrectly:

Once you get this boundary afterwards and try to apply it to the crop region, you will get a message of the kind: ‘curves are not contiguous … smth … smth’

My only solution so far is to tell the scrypt not to create Crop Region in this case and to return a list the problematic areas for the user to check their contours. However, it would be amazing if someone knew a workaround for this.

Here’s the Python code:

from Autodesk.Revit.DB import Transaction, Element, BuiltInCategory, BoundingBoxXYZ, View, ViewPlan, DisplayStyle, SpatialElementBoundaryOptions, ViewFamilyType, ViewDrafting, ViewFamily
from Autodesk.Revit.DB import FilteredElementCollector, CurveLoop, XYZ, ViewDetailLevel, BuiltInParameter, BoundarySegment, Curve, BoundarySegment, ElementTransformUtils, Area, Parameter

from Autodesk.Revit.DB import Curve, CurveLoop, DirectShape, ElementId, Line, BoundingBoxXYZ, ViewCropRegionShapeManager, ElementId
from Autodesk.Revit.DB import SolidOptions, GeometryCreationUtilities, Transform, ViewDuplicateOption, TemporaryViewMode

from Autodesk.Revit.DB.Analysis import VectorAtPoint

from System.Collections.Generic import List

from rpw import db, ui, doc, uidoc
from pyrevit import forms, revit

import sys
import math


# Create a parameter to point at the current document
uidoc = __revit__.ActiveUIDocument
doc = __revit__.ActiveUIDocument.Document
view = doc.ActiveView

t = Transaction(doc, "FloorPlan From Area")
t1 = Transaction(doc, "Temporary Hide / Isolate")



# Check if the active view has the neccessary parameters set
if(doc.ActiveView.LookupParameter("Phasing").AsString() == None):
    print("Please, set value for the parameter 'Phasing' of your active view and try again!")
    sys.exit()



# Isolate only Areas in the active view
area_ids = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Areas).WhereElementIsNotElementType().ToElementIds()
t1.Start()

view.IsolateElementsTemporary(area_ids)

t1.Commit()



# Make a selection for desired Areas
selection = revit.get_selection()
selection_list = revit.pick_rectangle()

elements = []

for element in selection_list:
    element_id = element.Id
    elements.append(doc.GetElement(element_id))



# Filter View Types and set the one to apply to apply to new views
viewfamily_types = FilteredElementCollector(doc).OfClass(ViewFamilyType)
viewfamily_types_list = []

for view_type in viewfamily_types:
    if("Plan" in view_type.LookupParameter("Type Name").AsString()):
        viewfamily_types_list.append(view_type.LookupParameter("Type Name").AsString())
    if("Gross" in view_type.LookupParameter("Type Name").AsString()):
         viewfamily_types_list.append(view_type.LookupParameter("Type Name").AsString())
    if("Rentable" in view_type.LookupParameter("Type Name").AsString()):
        viewfamily_types_list.append(view_type.LookupParameter("Type Name").AsString())

type = forms.SelectFromList.show(viewfamily_types_list,
                                multiselect = False,
                                title = "Select View Type to assign:",
                                button_name = "ASSIGN VIEW TYPE")

for view_type in viewfamily_types:
    if(type == view_type.LookupParameter("Type Name").AsString()):
        type_id = view_type.Id


    
# Filter View Templates and set the one to apply to apply to new views
templates_collector = FilteredElementCollector(doc).OfClass(View).ToElements()
templates_list_name = []

for template in templates_collector:
    if(template.IsTemplate == True):
        if("Plan" in template.Name):
            templates_list_name.append(template.Name)

res = forms.SelectFromList.show(templates_list_name,
                                multiselect = False,
                                title = "Select View Template to assign",
                                button_name = "ASSIGN TEMPLATE")

for template in templates_collector:
    if(template.Name == res):
        res_id = template.Id



# Input angle of rotation
rotationAngle = forms.ask_for_string(
	    default="0.00",
	    prompt="Enter angle of rotation /decimal degrees, direction: counter - clockwise/:",
	    title="Angle of rotation"
	    )
        
floatAngle = (-float(rotationAngle) * math.pi) / 180



# Input for duplicating the view with detailing
detailing = forms.CommandSwitchWindow.show(
    ["YES", "NO"],
     message="Create new floorplan? 'YES' or 'NO' (duplicate the active view with detailing):"
	)

if detailing == "YES":
    noDetailing = True
else:
    noDetailing = False



# Create floorplan of selected view type and apply selected view template
newplans = []
rotation_angles = []
areas = []
errorlist = []

t.Start()

for a in elements:
    
    Options = SpatialElementBoundaryOptions()
    s = a.GetBoundarySegments(Options)[0][1].GetCurve()
    sp1 = s.GetEndPoint(0)
    sp2 = s.GetEndPoint(1)
    vector = (sp2 - sp1).Normalize()
    
    if noDetailing:
        AreaPlan = ViewPlan.Create(doc, type_id, a.LevelId)
    else:
        AreaPlan = doc.GetElement(doc.ActiveView.Duplicate(ViewDuplicateOption.WithDetailing))
        AreaPlan.DisableTemporaryViewMode(TemporaryViewMode.TemporaryHideIsolate)
        AreaPlan.CropBoxActive = False
        AreaPlan.ViewTemplateId = ElementId(-1)
    AreaPlan.LookupParameter("IPA View Sub Group").Set("400")
    AreaPlan.LookupParameter("Phasing").Set(doc.ActiveView.LookupParameter("Phasing").AsString())
    AreaPlan.ViewTemplateId = res_id
    active_view_name = view.Name.ToString()
    
    a_crop = []
    area_segments = a.GetBoundarySegments(Options)
    
    for area_segment in area_segments[0]:
            p1 = area_segment.GetCurve().GetEndPoint(0)
            p2 = area_segment.GetCurve().GetEndPoint(1)
            a_crop.append(Line.CreateBound(p1, p2))
    
    try:
        AreaPlan.CropBoxActive = True
        AreaPlan.GetCropRegionShapeManager().SetCropShape(CurveLoop.Create(a_crop))
    except:
        errorlist.append(a.Id)
    
    newplans.append(AreaPlan)
    areas.append(a)

    try:
        AreaPlan.Name = active_view_name + "PC" + "-" + a.Number.ToString()
    except:
		AreaPlan.Name = active_view_name + "PC" + "-" + a.Number.ToString() + ".01"

t.Commit()



# rotate floorplans if rotation angle is set
if(floatAngle != math.radians(0)):
    for counter, plan in enumerate(newplans):

        area = areas[counter]        
        new_crop = []
    
        # Transactions for the purpose of obtaining and rotating the crop region
        temp1 = Transaction(doc, "Hide crop region")
        temp2 = Transaction(doc, "Unhide crop region")
        temp3 = Transaction(doc, "Rotate crop region")
        temp4 = Transaction(doc, "Create new crop region offset")
    
        temp1.Start()
        plan.CropBoxVisible = False
        temp1.Commit()
    
        all_in_view_crop_hidden = FilteredElementCollector(doc, plan.Id).ToElementIds()
    
        temp2.Start()
        plan.CropBoxVisible = True
        temp2.Commit()
    
        cropId = FilteredElementCollector(doc, plan.Id).Excluding(all_in_view_crop_hidden).ToElementIds()
    
        bbox = plan.CropBox
        center = 0.5 * (bbox.Min + bbox.Max)
        axis = Line.CreateBound(center, center + XYZ.BasisZ)
    
        temp3.Start()
        ElementTransformUtils.RotateElement(doc, cropId[0], axis, floatAngle)
        temp3.Commit()
        
        Options_new = SpatialElementBoundaryOptions()
        segments = area.GetBoundarySegments(Options_new)
        
        for segment in segments[0]:
        
            pt1 = segment.GetCurve().GetEndPoint(0)
            pt2 = segment.GetCurve().GetEndPoint(1)
            new_crop.append(Line.CreateBound(pt1, pt2))
          
        temp4.Start()
        try:
            plan.GetCropRegionShapeManager().SetCropShape(CurveLoop.Create(new_crop))
        except:
            pass
        temp4.Commit()


# disable temporary hide isolate mode of the active view
t1.Start()
view.DisableTemporaryViewMode(TemporaryViewMode.TemporaryHideIsolate)
t1.Commit()


if errorlist != []:  
    print("The following Areas were found to have problematic contours, thus their floorplan views do not have Crop Region applied. You could check those areas' contours by their ids:\n\n {}".format(errorlist))
else:
    print("All area plans were created successfully!")

Here’s also a basic sample file, where it can be tested directly:
Test_detached.rvt (2.9 MB)

And the python for a direct download:
FloorPlanFromAreaRentable_script.py (7.9 KB)

Cheers!

In this case I would likely go with something like: Polycurve.ByJoinedCurves > Polycurve.CloseWithLine

But a robust solution would be to extend adjacent curves, intersect the extension, find the distance from the intersection to the curves, and extend the curve by that distance.

1 Like

Hi, Jacob, that was fast, thank you!

What you are proposing will work in a case like the previous post. However, boundaries might also look that way:

I still don’t know how people manage to draw boundaries like that, but apparantely, they do. So a more general approach might be needed :frowning:

edit: I was just thinking… since Revit somehow creates the Area with the right boundary in any case, there might be some magic method in the API doing this, that I am not aware of. Like Rooms for instance, they would not be generated within problematic or not properly enclosed contours, while Areas would

Set the SpatialElementBoundaryOptions.SpatialElementBoundaryLocation to SpatialElementBoundaryLocation.Center.

5 Likes

I believe that areas are only bound by the area lines, which have to be manually drawn, so that may not work here.

This close, in that Revit itself will ‘fill the gap’ to close out areas to some extent, it is not complete in all cases.

I can’t tell where the ‘opening’ is in your latest setup; perhaps it’s the ‘parallel lines’ not connecting, but still defining the area? In which case adding a test for parallel directions should the unbound or extended curves not intersect would allow drawing a connecting line between the ‘near miss’ endpoints, closing the gap. You’ll find other miss situations as you dig further into this topic; I certainly don’t know them all offhand, never mind how to resolve them.

If all of that sounds like a lot of work, it’s because it is. Closing those gaps for your filled regions could be quite useful, but that depends on what the goal is. Personally I steer clear of filled regions as they add a LOT of overhead to views, and in this context appear to just be redundant information which will lead to coordination problems (why have areas if the info is in the filled regions? why have filled regions if the info is in the ares?).

The other route might be more successful. Back when I was practicing I had a check for ‘areas with us closed boundary’ process, as the resulting area from incorrect boundary lines could result in a miscalculation of rentable area, condo docs, occupancy calculations, etc… As such we figured that designers should be VERY careful when drawing these and saw a disconnected area as a design issue needing to be addressed by the original author. The process was something like adding a parameter “is closed”, and a Dynamo graph to get all areas, get the associated perimeter curves, group the curves in the sub list, and check for ‘gaps’ in the resulting groups. If there was a gap, the parameter got set to ‘needs review’; if not, the parameter value got set to”closed”. Then had a schedule and a filter I had to quickly ID areas of concern.

1 Like

@jacob.small trust me, it works.

4 Likes

Thank you, @Thomas_Mahon ! It actually works, it’s exactly what I was looking for! It even deals with all kinds of messed up contours like that one:

@jacob.small what you are saying is true, but at some point I realised people will never stop drawing area contours like that. Even if you stress the importance, new people wll come in the office and they will do these mistakes as well, at least in the beginning. I also don’t see how drawing a proper area contour is an issue, but at some point we realised we needed a more ‘idiotproof’ solution :slight_smile:

2 Likes

I hear you; which is why the ‘check areas’ was part of the larger “project review”. Glad you’re all sorted.

Great insight as always @Thomas_Mahon!

1 Like