I found an old script that creates and crops Floor plans based on Room Boundaries, and I have been trying to adapt it to work with Areas in the latest versions of python, dynamo, and revit. Here is the original script from archi-lab. I searched through the forum and the two relevant instances of this problem were never solved: 1 and 2
With my current string, I was able to create a list area keys of the first instance of every “Suite Type”, as well as find its are boundaries. Currently, I have the revit key for the area, and the boundaries of the area, but now I just need to automate the view creation. I made the boundaries separate, as I could not figure out how to implement them in the script.
I made some edits to the archi-lab script to adapt it to areas, but majority of the functions don’t translate automatically - see attached - any help is appreciated a TON.
By my parsing of the information, the script gets caught up with BoundingBox, which I know does not apply to area boundaries, but I cant find any function that produces the area boundaries in a way that the current function can read.
Here is the errors I get if i drop the “Areas” list into the archilab FloorPlan.ByRoom function without changes:
The errors would indicate that the bbox value is none - so i.BoundingBox[doc.ActiveView] is returning none.
From the API documentation
If the view box is not known or cannot be calculated, this will return the model box; if the model box is not known, this will return null reference
So it appears that areas do not have a bounding box - are the areas placed? You could check i.ViewSpecific parameter to see if it has a view associated
You could also try changing the method to i.get_BoundingBox(doc.ActiveView) see discussion on The Building Coder here
Thanks Mike. I decided to teach myself python in the last week as there seemed to be no movement, and came up with a solution. Fundamentally, areas and boundingbox werent linked, and that was the main issue. I adapted some old code for rooms to views from around 8 years ago, and made it work. the dynamo script + python looks convoluted, but the results were great. See below for my code, and attached for the definition - I guess this topic is technically closed now, even tho I am still adapting my code to automate more of the process.
I had to make individual lists for the Curves, Bounding Boxes, Areas, and Names of the areas, and reassign them back to the respective Areas in python as all of the built in parameters were coming up null.
Every parameter for Areas used is built in - except for “Suite Type” which is a project parameter assigned to units within my condos. Here is a proof of concept screengrab - the list filters only the first recurring instance of a “Suite Type”, and produces it as a view. I made “Unit 2” have the same “Suite Type” as “Unit 1” to show it.
import clr
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
# Import Element wrapper extension methods
clr.AddReference("RevitNodes")
import Revit
clr.ImportExtensions(Revit.Elements)
# Import geometry conversion extension methods
clr.ImportExtensions(Revit.GeometryConversion)
# Import DocumentManager and TransactionManager
clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument
# Import RevitAPI
clr.AddReference("RevitAPI")
import Autodesk
from Autodesk.Revit.DB import *
import System
from System import Array
from System.Collections.Generic import *
import sys
pyt_path = r'C:\Program Files (x86)\IronPython 2.7\Lib'
sys.path.append(pyt_path)
#The inputs to this node will be stored as a list in the IN variable.
dataEnteringNode = IN
if isinstance(IN[0], list):
areas = UnwrapElement(IN[0])
else:
areas = [UnwrapElement(IN[0])]
if isinstance(IN[1], list):
unit = UnwrapElement(IN[1])
else:
unit = [UnwrapElement(IN[1])]
runMe = IN[2]
if isinstance(IN[3], list):
bb = UnwrapElement(IN[3])
else:
bb = [UnwrapElement(IN[3])]
if isinstance(IN[4], list):
lvl = UnwrapElement(IN[4])
else:
lvl = [UnwrapElement(IN[4])]
for i in range(len(areas)):
areas[i].BoundingBoxXYZ = bb[i].ToRevitType()
areas[i].Level.Name = lvl[i].Name
areas[i].Comments = unit[i]
try:
errorReport = None
if runMe:
viewTypes = FilteredElementCollector(doc).OfClass(ViewFamilyType)
for i in viewTypes:
if i.ViewFamily == ViewFamily.AreaPlan:
viewTypeId = i.Id
break
else:
continue
existingPlans = FilteredElementCollector(doc).OfClass(View).ToElements()
existingPlanNames, existingPlanElements = [], []
for i in existingPlans:
if not i.IsTemplate:
if i.ViewType == ViewType.AreaPlan:
existingPlanNames.append(i.ToDSType(True).Name)
existingPlanElements.append(i)
# Start Transaction
TransactionManager.Instance.EnsureInTransaction(doc)
floorPlans = []
for i in areas:
levelId = i.LevelId
bbox = i.BoundingBoxXYZ
viewName = "Suite Type " + i.Comments + " - Unit " + i.Number
if viewName in existingPlanNames:
view = existingPlanElements[existingPlanNames.index(viewName)]
view.CropBox = bbox
view.CropBoxActive = True
view.CropBoxVisible = False
floorPlans.append(view)
else:
newView = ViewPlan.Create(doc, viewTypeId, levelId)
newView.Name = viewName
newView.CropBox = bbox
newView.CropBoxActive = True
newView.CropBoxVisible = False
floorPlans.append(newView)
# End Transaction
TransactionManager.Instance.TransactionTaskDone()
else:
errorReport = "Run Me set to False"
except:
# if error accurs anywhere in the process catch it
import traceback
errorReport = traceback.format_exc()
#Assign your output to the OUT variable
if errorReport == None:
OUT = areaplans
else:
OUT = errorReport
Hi @RAW_MN can you please share your FloorPlan.ByRooom opened. mine is returning null. and the default doesnt have as many inputs so wondering if my attempt to add the inputs is incorrect. Thanks for your help.
Hey Joe - not entirely sure what you mean by “opened”. Although I did rework the script a bit recently to make it more user friendly, make it selection based (only one unit at a time), make it work with dynamo player, and require no alternate packages for install. See attached and let me know if this helps 0. Area Plan Creator - Schedule A_View and Sheet - byselection.dyn (79.2 KB)
Great work @RAW_MN!
using this ive been able to get back on track.
im trying to automate the marketing plans for all at once, but having the option to do via selection as you have done is also useful.
the area im struggling with is where i have say an apartment, with a balcony or balconies.
to get one view cropped around the area of the apartment and the balconies included.
ill spend a couple more days tinkering then create a post and upload my script once i have tidy it up. but if you have had any success let me know.
Thanks for your help.
So the easiest way you could do this if the absolute crop isnt needed is as follows (I use this method):
Get each area
Group by an apartment number in common (it is not possible to do this workflow unless you tell areas they belong together in my experience)
For each group, get the areas
Get the outline curves of each area, and get their end points. Note the minimum and maximum X/Y values of those points
Make a rectangle in the API around those 4 points, use that as the crop
This approach will put a bounded rectangle around the set of areas without needing to do any surface/solid operations and is quite programmatically light. My version of it also uses a naming syntax to skip any set of areas which already have a plan set up (by name).
An absolute crop is much harder and I have only tackled it in Grasshopper. My current method there is I get the areas, get their curves and offset outward enough that I know my balcony/apartment areas will merge. Get their combined surface, set it back in by the offset used and use that for the absolute crop. I’ve found Dynamo encounters more surface joining errors so I use Rhino Inside for that.
Why not get the balcony surface and extend the ‘inside edge’ by the distance to the area surface. PolySurface.ByJoinedSurfaces hasn’t failed me yet once that is done; and then you can pull the outter edge loop via one API call, pull perimeter curves if you don’t want to leverage Python and are in an older version, or even just pull all edges which don’t bound two faces. Build a PolyCurve from that and offset by your desired offset.
It comes down to how clean those edge(s) are really, and means the script also has to identify what constitutes an inner edge (e.g. what if a balcony hangs past the apartment vs the other way around). I’ve come across too many anomalies in how room/area separators behave when they don’t quite close properly, all it takes is one fringe case to invalidate a tool in a user’s eyes forever. I geenrally build in a curve cleanup routine at the front end as well to walk over the awkward points or cojoin colinear/parallel lines for a better offset.
Definitely solvable in Dynamo albeit more work to deal with fringe cases, it’s just not my processor of geometry by choice - hoping the algorihtm would in this case be sufficient for the user to try to implement. Above routines are 3 and 8 nodes respectively (including rounding up/grouping elements, but a fair bit of assumptive casting).
Appreciate the advice @GavinCrump, unfortunately we do want absolute crop (its for sales plans) so ive put this on the back burner for now and the team can manually edit crop region to include balcony. progress over perfection…
Ive tweaked the script to suit my needs and had great success in the first project, unfortunately the next project I tested on the area lines aren’t as clean, and I keep getting the error in the python script regarding curve loop being discontinuous…
assume its because of examples like below highlight where the area lines are slightly indented or perhaps not drawn as fully closed?
is there a way to add tolerance to this python script to avoid the error? or any ideas
i was also trying to make this fully out of the box, but couldn’t figure out how to assign view templates, is that possible? or better to just use archilab?
PS. I’m fairly new to writing dynamo scripts and this was my first go at editing python, so any tips or advice on the script is greatly appreciated.