Floor.Create() method Revit APIs 2023 (original) (raw)
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.
ar.vxv (vuxvix) April 17, 2025, 5:49am 3
Hi!
I’m trying to create a floor using the method: from beams surrounding a selected point in the plan view in Revit. I need help finding the nearest beams that form a curves loop from their centerlines . Do you have any suggestions for algorithms or Revit API methods to achieve this? thanks for any help
GavinCrump (Gavin Crump) April 17, 2025, 7:57am 4
It wont work for all beams, but get all beam curves that are on the relevant level, pull them down to the xy plane. Create lines from the focus point to the beams, then see which of those clash with more than one beam’s curve. They should be in the same ‘bay’ as the column.
This wont handle scenarios where you have L shapes, doglegs etc. In those cases you could try generating additional points along the lines of those that were successfully found and run the same check at intervals along those curves, which would likely pick up most beams in the bay.
I havent written it in revit api but have tried similar routines in grasshopper/c# before with some success for finding parcel boundaries.
ar.vxv (vuxvix) April 17, 2025, 11:54pm 5
Basically, the implementation process has already been carried out in the way you described.
However, the suitable algorithm (or method) to identify the boundary beams of a “bay” when clicking in that area has not been found yet. I’ve already tried searching for solutions like ray casting, DFS (Depth-First Search), or BFS (Breadth-First Search).
I’m wondering if there are any other methods that would be appropriate for this requirement.
GavinCrump (Gavin Crump) April 18, 2025, 5:04am 6
Yes that would have been next thought. The other is to try and construct a curve network from all the beams through extended them and finding meeting points/nodes.
From there vector network logic could be applied but im not 100% sure how easy it is to implement. I know figma is foundated on that for finding fillable zones.
Arcol has been implementing them also:
ar.vxv (vuxvix) April 18, 2025, 6:14am 7
Currently, my script is working using a method that I simulated as a brute force approach similar to vray rendering.
I’m using raycasting, shooting rays (8 rays) in different directions on the working plane. Then I find the intersections of these rays with the projections of the centerlines of the beams.
I filter out duplicate centerline rays, create a list of intersection points, and generate a floor curve loop.
For complex intersections, I think it might be easier to draw them manually.