from System.Collections.Generic import List
import revitron
import Autodesk.Revit.UI as ui
from revitron import _
from revitron import DB
from revitron import DOC
from qualitron import View3dCreator, SharedParamUtils
[docs]class AreaHelperManager:
"""
Manage Area Helper instances, including create, purge and
write parameter values.
"""
[docs] def __init__(self):
"""
Initialize instance of the class.
"""
self.Areas = []
self.AreaDict = {}
self.ParamDict = {}
self.updateAreaDict()
self.updateParamDict()
self._removeUnused()
paramGroup = revitron.DB.BuiltInParameterGroup\
.PG_ADSK_MODEL_PROPERTIES
self.ParamUtils = SharedParamUtils('Area Helper', paramGroup)
self.UnusedDishapeTypeIds = []
self.Dishapes = []
self.DishapeTypeIdsToPurge = []
def _removeUnused(self):
"""
Remove unused direct shapes on script startup.
"""
allDishapes = self._getAllDishapes()
usedDishapeIds = [x.GetTypeId() for x in allDishapes]
flr = revitron.Filter().byClass('DirectShapeType')
allDishapeTypeIds = flr.byCategory('Mass').getElementIds()
unusedDishapeTypeIds = list(set(allDishapeTypeIds) - set(usedDishapeIds))
if unusedDishapeTypeIds:
with revitron.Transaction():
dishapeTypeIds_icol = List[DB.ElementId](unusedDishapeTypeIds)
DOC.Delete(dishapeTypeIds_icol)
def _getAllDishapes(self):
"""
Get all existing direct shapes.
"""
return revitron.Filter().byClass('DirectShape').byCategory('Mass').getElements()
[docs] def checkStatus(self):
"""
check if any areaHelper instances exist
string rules of revitron filter sometimes dont work
so using lookup parameter.
"""
allDishapes = self._getAllDishapes()
self.Dishapes = [x for x in allDishapes
if _(x).get('Comments') != 'Baked']
self.DishapeTypeIdsToPurge = [x.GetTypeId() for x in self.Dishapes]
if self.DishapeTypeIdsToPurge:
return True
else:
return False
[docs] def updateAreaDict(self):
"""
Update area dict.
{area scheme name : {levelName : [areas]}
"""
areaSchemes = revitron.Filter().byCategory('AreaSchemes').getElements()
for arsch in areaSchemes:
flr = revitron.Filter().byCategory('Areas')
flr = flr.noTypes().getElements()
areas = [x for x in flr
if x.Area > 0
and x.AreaScheme.Id == arsch.Id]
dict = {}
if areas:
"""Conclude areas to level ALL"""
dict['- ALL -'] = areas
for area in areas:
levelName = area.Level.Name
if not levelName in dict:
dict[levelName] = []
list = dict.get(levelName)
if levelName not in list:
list.append(area)
self.AreaDict[arsch.Name] = dict
[docs] def updateParamDict(self):
"""
Update parameter dict.
{areaParamName:dishapeParamName}
"""
areas = revitron.Filter().byCategory('Areas').getElements()
if areas:
area = areas[0]
for param in area.Parameters:
name = param.Definition.Name
readOnly = param.IsReadOnly
self.ParamDict[name] = 'AreaHelper - ' + name
[docs] def updateAreas(self, schemeName, levelName):
"""
Refresh selected areas.
"""
self.Areas = self.AreaDict.get(schemeName).get(levelName)
[docs] def set3DView(self):
"""
Create 3D view and set active.
"""
mass = revitron.DB.BuiltInCategory.OST_Mass
massId = revitron.DB.ElementId(mass)
View3dCreator.create('AreaHelper_', [massId])
[docs] def toggle(self):
"""
Switch direct shapes.
"""
status = self.checkStatus()
if status:
self.removeDishapes()
#self.purgeSharedParams()
else:
self.set3DView()
self.createSharedParams()
self.createDishapes()
[docs] def removeDishapes(self):
"""
Purge dishapes.
"""
with revitron.Transaction():
dishapeTypeIds_icol = List[DB.ElementId](self.DishapeTypeIdsToPurge)
DOC.Delete(dishapeTypeIds_icol)
[docs] def bakeDishapes(self):
"""
Set comment parameter to mark a direct shape baked.
"""
with revitron.Transaction():
for ds in self.Dishapes:
_(ds).set('Comments', 'Baked')
[docs] def createDishapes(self):
"""
Create all selected direct shapes.
"""
count = len(self.Areas)
run = True
if count > 300:
decide = ui.TaskDialog.Show('AreaHelper', 'Visualize ' + str(count)
+ ' areas?\nThis may take a long time.',
ui.TaskDialogCommonButtons.Yes|
ui.TaskDialogCommonButtons.No)
if decide == ui.TaskDialogResult.No:
run = False
if run:
with revitron.Transaction():
for area in self.Areas:
if area.Area > 0:
areaHelper = _AreaHelper(area)
shape = areaHelper.createDishape()
if shape:
self.ParamUtils.writeParamstoDishape(area, shape, self.ParamDict)
[docs] def createSharedParams(self):
"""
Create shared parameters to direct shape category.
"""
massCategory = revitron.DOC.Settings.Categories.get_Item('Mass')
with revitron.Transaction():
self.ParamUtils.createParams(massCategory,
self.ParamDict.values())
[docs] def purgeSharedParams(self):
"""
purge shared parameters
WIP
"""
with revitron.Transaction():
self.ParamUtils.purgeParams()
[docs] @staticmethod
def selectAreas(selected):
"""
Select areas according to heighlited direct shgapes.
Args:
selected (obj): Heighlited direct shapes
Returns:
obj: Areas
"""
result = []
for sel in selected:
if sel.GetType().Name == 'DirectShape':
type = DOC.GetElement(sel.GetTypeId())
typeName = _(type).get('Type Name')
if typeName.startswith('AreaHelper_'):
id = int(typeName.strip('AreaHelper_'))
id = DB.ElementId(id)
result.append(id)
return result
class _AreaHelper:
"""
Used to create direct shape for one area.
"""
def __init__(self, area):
"""
Initialize an area helper instance related to an area.
Args:
area (_type_): Revit area instance.
"""
self.Area = area
self.Name = 'AreaHelper_' + str(area.Id.IntegerValue)
self.doc = revitron.DOC
self.LevelHandler = LevelHandler()
self.Height = self.LevelHandler.getHeight(area)
def createDishape(self):
"""
Loops -> solid -> direct shape.
"""
def _getCrvToAppend(basept, crv):
"""
When connecting the start point of a curve with the
end point of the last curve:
If distance between two connecting points is too small,
then try the end point.
Args:
basept (obj): Base point
crv (obj): Curve after the base point
Returns:
obj: List of lines to be append to loop
"""
added_line = None
start = crv.GetEndPoint(0)
end = crv.GetEndPoint(1)
if basept.DistanceTo(end) > 0.0025:
dist = start.DistanceTo(basept)
if dist == 0:
added_line = [crv]
elif crv.Length > 0.0025 and dist > 0.0025:
added_line = [DB.Line.CreateBound(basept, start)]
added_line.append(crv)
else:
added_line = [DB.Line.CreateBound(basept, end)]
return added_line
def _makeDishape(solid):
"""
Create a direct shape using solid.
"""
cateId = DB.ElementId(DB.BuiltInCategory.OST_Mass)
lib = DB.DirectShapeLibrary.GetDirectShapeLibrary(self.doc)
shapeType = DB.DirectShapeType.Create(self.doc, self.Name , cateId)
shapeType.SetShape(List[DB.GeometryObject ]([solid]))
lib.AddDefinitionType(self.Name, shapeType.Id)
element = DB.DirectShape.CreateElementInstance(
self.doc, shapeType.Id,
cateId,
self.Name,
DB.Transform.Identity)
element.SetTypeId(shapeType.Id)
return element
try:
if self.Area.Area > 0:
seopt = DB.SpatialElementBoundaryOptions()
segments = self.Area.GetBoundarySegments(seopt)
loops_col = List[DB.CurveLoop]([])
for segmentList in segments:
loop = DB.CurveLoop()
for seg in segmentList:
segCrv = seg.GetCurve()
if loop.NumberOfCurves() == 0:
basePt = segCrv.GetEndPoint(0)
crvs_to_append = _getCrvToAppend(basePt, segCrv)
if crvs_to_append:
if loop.NumberOfCurves() == 0:
finalPt = segCrv.GetEndPoint(0)
for c in crvs_to_append:
loop.Append(c)
backPt = segCrv.GetEndPoint(0)
basePt = segCrv.GetEndPoint(1)
if loop.IsOpen():
if finalPt.DistanceTo(basePt) > 0.0025:
line = DB.Line.CreateBound(basePt, finalPt)
else:
line = DB.Line.CreateBound(basePt, backPt)
loop.Append(line)
loops_col.Add(loop)
solid = DB.GeometryCreationUtilities.CreateExtrusionGeometry(
loops_col, DB.XYZ.BasisZ, self.Height)
dishape = _makeDishape(solid)
return dishape
except:
print('Area could not be generated, please clean up the area boundaries. Area Id: ' + str(self.Area.Id))
[docs]class LevelHandler:
"""
Functions for handling level infos.
"""
[docs] def __init__(self):
"""
Initialize a level handler instance by creating level items for all levels.
"""
self.LevelItems = [LevelItem(l) for l in
revitron.Filter().byCategory('Levels')
.noTypes().getElements()]
self.Dict = {li.Name : li for li in self.LevelItems}
[docs] def getHeight(self,area):
"""
Get height of area based on this level,
by default 3 meters.
"""
levelItem = self.Dict[area.Level.Name]
aboveLevelItem = None
if levelItem.StoryAbove:
aboveLevelItem = self.Dict[levelItem.StoryAbove]
else:
aboveLevelItem = self.getAbove(levelItem)
if aboveLevelItem:
height = aboveLevelItem.Elevation - levelItem.Elevation
else:
height = 3.28084*3
return height
[docs] def getAbove(self, levelItem):
"""
Get the name of the first story level above the given level,
if not found, return None.
"""
resultItem = None
for l in self.LevelItems:
if l.Story and l.Elevation > levelItem.Elevation:
if resultItem:
if l.Elevation < resultItem.Elevation:
resultItem = l
else:
resultItem = l
return resultItem
[docs]class LevelItem:
"""
Contains infos of a level.
"""
[docs] def __init__(self, level):
"""
Initialize a level item instance.
Args:
level (obj): Revit level instance.
"""
self.Level = level
self.Name = self.Level.Name
story = _(level).getParameter('Building Story').getInteger()
self.Story = True if story == 1 else False
self.Elevation = level.Elevation
above = _(level).getParameter('Story Above').getValueString()
self.StoryAbove = None if above == "Default" else above