Floor.Create() method Revit APIs 2023

Hi there! I’m currently working on developing a Revit plugin through pyRevit and I’m having some trouble creating a new Revit triangular Floor starting from three vertices defined in Python. I am new to Revit APIs and I’m currently using Revit 2023.

I am aware that there are some changes in APIs with respect to previous releases, such as the new Floor.Create() method. However, when I run my code, I get the error message “ArgumentException : The input curve loops cannot compose a valid boundary, that means: the “curveLoops” collection is empty; or some curve loops intersect with each other; or each curve loop is not closed individually; or each curve loop is not planar; or each curve loop is not in a plane parallel to the horizontal(XY) plane; or input curves contain at least one helical curve. Parameter name: profile at Autodesk.Revit.DB.Floor.Create(Document document, IList`1 profile, ElementId floorTypeId, ElementId levelId)”.

Upon further inspection, I have checked the CurveLoop() and everything seems to be in order (it’s planar, closed, and counterclockwise). I would greatly appreciate any help in resolving this issue.

#! python3

#----------------------------------------------------------------------------------------------
# IMPORT LIBRARIES
#  System library
import sys

#  Autodesk Revit API
import clr
clr.AddReference('RevitAPI')
clr.AddReference('RevitAPIUI')
from Autodesk.Revit.DB import Transaction
from Autodesk.Revit.UI import TaskDialog
from Autodesk.Revit.DB import *
from Autodesk.Revit.DB import XYZ, UV, Line, CurveLoop, Level, Floor, FloorType
from System.Collections.Generic import List

# Function to convert centimeters to feet
def centimeters_to_feet(centimeters):
    return centimeters * 0.0328084

doc = __revit__.ActiveUIDocument.Document

# Coordinates
a = 0.0
b = 10.0
c = 0.0

# Input elevation (in centimeters)
elevation_cm = c
elevation_ft = centimeters_to_feet(elevation_cm)

# Create a new level at the given elevation
new_level = None

# Start a new transaction to modify the Revit document creating a new level
transaction = Transaction(doc, 'Create New Level')
transaction.Start()

try:
    new_level = Level.Create(doc, elevation_ft)
    
    # Assign a new name to the level
    new_level_name = "elBS_BuildingStorey_{:.0f}cm".format(elevation_cm)
    new_level.Name = new_level_name

    transaction.Commit()
except Exception as e:
    # If an error occurs, roll back the transaction and show an error message
    transaction.RollBack()
    TaskDialog.Show('Error', 'Failed to create level. Error: {}'.format(e))

if new_level:
    TaskDialog.Show('Success', 'Level created at elevation: {} centimeters'.format(elevation_cm))

# Create new floor

point0 = XYZ(a, a, c)
point1 = XYZ(b, a, c)
point2 = XYZ(b, b, c)

line01 = Line.CreateBound(point0,point1).ToRevitType()
line12= Line.CreateBound(point1,point2).ToRevitType()
line23 = Line.CreateBound(point2,point0).ToRevitType()

curveloop = CurveLoop()

curveloop.Append(line01)
curveloop.Append(line12)
curveloop.Append(line23)

print("numberOfcurves: ",curveloop.NumberOfCurves())
print("IsOpen: ",curveloop.IsOpen())
print("HasPlane: ",curveloop.HasPlane())


# Collect floor types
floortypes = FilteredElementCollector(doc).OfClass(FloorType)
floortypes = [f for f in floortypes]
floortypes_id = [f.Id for f in floortypes]
floortype_id = floortypes_id[0]
print("floortype id:",floortype_id)

# Collect building storeys
el_BuildingStoreys = FilteredElementCollector(doc).OfClass(Level)
el_BuildingStoreys_id = []
for el in el_BuildingStoreys:
    el_BuildingStoreys_id.append(el.Id)
level_id = el_BuildingStoreys_id[0]
print("level id: ",level_id)

# Start transaction
t = Transaction(doc, "Create new floor")
t.Start()

# Create the floor
new_floor = Floor.Create(doc, List[CurveLoop](curveloop), floortype_id, new_level.Id)
t.Commit()

Hi @angelomassafra, welcome to the pyRevit forum and sorry for the late response!

I cannot access Revit/pyRevit right now, but is this really the script you ran?
I see that you use the ToRevitType() method on the Line objects, but that method is a Dynamo-only thing, so it should throw an error long before reaching the Floor.Create statement.

Also, I see that you reuse the new_level object between transactions; It may work, but I wouldn’t rely on that. Better to put everything into a single transaction.

Another thing to note: in the Floor.Create API documentation, there’s the following remarks:

To validate curve loop profile use BoundaryValidation . To get default floor type use GetDefaultFloorType(Document, Boolean) .

This is my completely untested version:

#! python3

# Here I see that you're using CPython, so I'll leave the clr+Autodesk imports,
# but the pyrevit.revit module and its submodules lets you avoid this boilerplate
import clr
clr.AddReference('RevitAPI')
clr.AddReference('RevitAPIUI')
from Autodesk.Revit.UI import TaskDialog
from Autodesk.Revit import DB  # just personal preference, less to import
# from xyz import * is not recommended (Google Code Style for example)


# Function to convert centimeters to feet
def centimeters_to_feet(centimeters):
    return centimeters * 0.0328084


# generalized function to create the geometry using a list of 2D coordinates (tuple or list)
def create_perimeter(coords_2d, elevation):
    points = [DB.XYZ(x, y, elevation) for x, y in coords_2d]
    if points[0] != points[-1]:
        # make sure the first and last points coincide
        points.append(points[0])
    # using zip with and the slices of the list to get the pair of endpoints
    lines = [
        DB.Line.CreateBound(p1, p2) for p1, p2 in zip(points[:-1], points[1:])
    ]
    perimeter = DB.CurveLoop()
    for line in lines:
        perimeter.Append(line)
    return perimeter


# Input elevation (in centimeters)
elevation_cm = 0
curveloop = create_perimeter([(0, 0), (10, 0), (10, 10)], elevation_cm)
print("numberOfcurves: ",curveloop.NumberOfCurves())
print("IsOpen: ",curveloop.IsOpen())
print("HasPlane: ",curveloop.HasPlane())

doc = __revit__.ActiveUIDocument.Document
# Collect floor types
# Use the ToElementIds to directly get the IDs, or ToElements() if you need to access the elements
floortype_id = DB.FilteredElementCollector(doc).OfClass(DB.FloorType).ToElemntIds()[0]
print("floortype id:", floortype_id)
# But as the remarks states, you can use
# floortype_id =GetDefaultFloorType(doc, False)

# Here I put everyting into a single transaction to be sure the new_level object can still be  used
# keep in mind that pyrevit has a handy Transaction class that simplifies things via `with Transaction():` context manager.
transaction = DB.Transaction(doc, 'Create New Level')
transaction.Start()
try:
    new_level = DB.Level.Create(doc, centimeters_to_feet(elevation_cm))    
    # Assign a new name to the level
    new_level_name = "elBS_BuildingStorey_{:.0f}cm".format(elevation_cm)
    new_level.Name = new_level_name
    TaskDialog.Show('Success', 'Level created at elevation: {} centimeters'.format(elevation_cm))
    # Create the floor
    # since the curveloop is already a CurveLoop object, it should be enough to put it in a python list, but I may be wrong! 
    if not DB.BoundaryValidation.IsValidHorizontalBoundary([curveloop]):
        raise ValueError("The coordinates form an invalid horizontal boundary")
    new_floor = DB.Floor.Create(doc, [curveloop], floortype_id, new_level.Id)
    transaction.Commit()
except Exception as e:
    # If an error occurs, roll back the transaction and show an error message
    transaction.RollBack()
    TaskDialog.Show('Error', 'Failed to create level. Error: {}'.format(e))

You can even move the validation before the transaction to entirely skip the level creation in case of errors.