Hi,
I'm trying to make a python script to auto-tag all doors and place them without clashing with other tags or doors . I'm using a custom smart tag which is much bigger than typical door tag. I hope to use this concept with other types of tagging.
Issue i'm having is that I cannot get the tag location to verify against list of points to avoid. and also to add the new tag location to the list so that the next tag does not clash with the previous.
I think the problem is the list of location to avoid is not properly updating and it's not correctly checking against it.
Any help would be appreciated.
Thanks
doorFiltered = list of doors
TagSymbols = custom smart tag
import clr
clr.AddReference('System')
clr.AddReference("System.Drawing")
clr.AddReference("System.Windows.Forms")
clr.AddReference("RevitAPI")
clr.AddReference('RevitAPIUI')
clr.AddReference("System.Collections")
import Autodesk
from Autodesk.Revit.DB import *
from Autodesk.Revit import DB
from pyrevit import forms, revit, DB, script
from itertools import compress
from operator import itemgetter, attrgetter
app = __revit__.Application
doc = __revit__.ActiveUIDocument.Document
scaleFactor = 5
def move_right(x,y,z):
n = scaleFactor
return x+n, y, z
def move_down(x,y,z):
n = scaleFactor
return x,y-n,z
def move_left(x,y,z):
n = scaleFactor
return x-n,y,z
def move_up(x,y,z):
n = scaleFactor
return x,y+n,z
moves = [move_right, move_down, move_left, move_up]
def shift(end, point):
from itertools import cycle
_moves = cycle(moves)
n = 1
pos = point
times_to_move = 1
yield pos
while True:
for _ in range(2):
move = next(_moves)
for _ in range(times_to_move):
if n >= end:
return
pos = move(*pos)
n+=1
yield pos
times_to_move+=1
avoidLoc = []
for l in doorFiltered:
avoidLoc.append(l.Location.Point)
for d in doorFiltered:
LP = d.Location
levelPoint = LP.Point
startpoint = (levelPoint.X, levelPoint.Y, levelPoint.Z)
shiftPoint = DB.XYZ(levelPoint.X, levelPoint.Y, levelPoint.Z)
R = Reference(d)
#create door tags
t = Transaction(doc)
t.Start('tag doors')
IT = IndependentTag.Create(doc, v.Id, R, False, TagMode.TM_ADDBY_CATEGORY, TagOrientation.Horizontal, shiftPoint)
IT.ChangeTypeId(TagSymbols)
#get location of tag
tagBB = IT.get_BoundingBox(doc.GetElement(IT.OwnerViewId))
globalMax = tagBB.Max
globalMin = tagBB.Min
BBloc = XYZ((globalMax.X + globalMin.X)/2, (globalMax.Y + globalMin.Y)/2, globalMax.Z)
tagSize = globalMax.DistanceTo(globalMin)
originalPoint = (BBloc.X, BBloc.Y, BBloc.Z)
#find overlap
requireDist = tagSize*2
overlap = True
counter = 1
while overlap:
spiralPoints = list(shift(counter, startpoint))
newPoint = spiralPoints[-1]
counter += 1
testPoint = DB.XYZ(newPoint[0], newPoint[1], newPoint[2])
for i in avoidLoc:
OriginalPoint = DB.XYZ(newPoint[0], newPoint[1], newPoint[2])
tagDist = OriginalPoint.DistanceTo(i)
movePoint = DB.XYZ((BBloc.X-newPoint[0]), (BBloc.Y-newPoint[1]), (BBloc.Z-newPoint[2]))
#print(tagDist)
if tagDist < requireDist:
overlap = True
#print("overlap")
else:
overlap = False
IT.Location.Move(movePoint)
NtagBB = IT.get_BoundingBox(doc.GetElement(IT.OwnerViewId))
if NtagBB == None:
break
NglobalMax = NtagBB.Max
NglobalMin = NtagBB.Min
NBBloc = XYZ((NglobalMax.X + NglobalMin.X) / 2, (NglobalMax.Y + NglobalMin.Y) / 2, NglobalMax.Z)
avoidLoc.append(NBBloc)
#print(NBBloc)
break
IT.HasLeader = True
#IT.LeaderElbow = DB.XYZ(shiftPoint.X + 10, shiftPoint.Y, shiftPoint.Z)
t.Commit()
Solved! Go to Solution.
Solved by ckepner. Go to Solution.
Thank you for sharing this interesting and simple approach.
You need to regenerate the model after each adjustment to be able to query the correct updated locations, cf., the Need to Regenerate:
https://thebuildingcoder.typepad.com/blog/about-the-author.html#5.33
If you are interested in other more complex ways to approach this task, you might want to explore various map labelling algorithms to find the optimal placements for your tags:
https://duckduckgo.com/?q=map+labelling+algorithm
I look forward to hearing how you end up solving this!
I've found a solution that seems to work.
avoidLoc = []
for l in doorFiltered:
f = l.Location.Point
fxs = DB.XYZ(f.X, f.Y, f.Z)
avoidLoc.append(fxs)
# print(elemLoc)
for d in doorFiltered:
LP = d.Location
levelPoint = LP.Point
# avoidLoc.append(levelPoint)
startpoint = (levelPoint.X, levelPoint.Y, levelPoint.Z)
shiftPoint = DB.XYZ(levelPoint.X, levelPoint.Y, levelPoint.Z)
R = Reference(d)
# create door tags
tx = Transaction(doc)
tx.Start('tag doors')
IT = IndependentTag.Create(doc, v.Id, R, False, TagMode.TM_ADDBY_CATEGORY, TagOrientation.Horizontal, shiftPoint)
IT.ChangeTypeId(TagSymbols)
tx.Commit()
# get location of tag
tagBB = IT.get_BoundingBox(doc.GetElement(IT.OwnerViewId))
globalMax = tagBB.Max
globalMin = tagBB.Min
BBloc = XYZ((globalMax.X + globalMin.X) / 2, (globalMax.Y + globalMin.Y) / 2, globalMax.Z)
tagSize = globalMax.DistanceTo(globalMin)
originalPoint = (BBloc.X, BBloc.Y, BBloc.Z)
# find overlap
requireDist = tagSize
overlap = True
counter = 1
avoidLoc1 = avoidLoc
while overlap:
spiralPoints = list(shift(counter, originalPoint))
newPoint = spiralPoints[-1]
counter += 1
testPoint = DB.XYZ(newPoint[0], newPoint[1], newPoint[2])
tagDistx = []
for s in avoidLoc1:
tDist = testPoint.DistanceTo(s)
# print(tDist)
tagDistx.append(tDist)
movePoint = DB.XYZ((newPoint[0] - levelPoint.X), (newPoint[1] - levelPoint.Y), (newPoint[2] - levelPoint.Z))
if any(x < requireDist for x in tagDistx):
continue
if movePoint == None:
print('NtagBB - empty')
continue
if all(x > requireDist for x in tagDistx):
tx = Transaction(doc)
tx.Start('move tag')
IT.Location.Move(movePoint)
NtagBB = IT.get_BoundingBox(doc.GetElement(IT.OwnerViewId))
NglobalMax = NtagBB.Max
NglobalMin = NtagBB.Min
NBBloc = XYZ((NglobalMax.X + NglobalMin.X) / 2, (NglobalMax.Y + NglobalMin.Y) / 2, NglobalMax.Z)
tx.Commit()
avoidLoc.append(NBBloc)
avoidLoc1.append(NBBloc)
print("location found")
overlap = False
break
else:
print('no match')
break
tx = Transaction(doc)
tx.Start('add leader')
IT.HasLeader = True
IT.LeaderElbow = DB.XYZ(shiftPoint.X + 10, shiftPoint.Y, shiftPoint.Z)
tx.Commit()
Dear Christopher,
Congratulations on solving this tricky task and thank you for sharing it.
Would you care to describe briefly in words how your algorithm works?
It is not obvious from me just looking at it, and seems a bit challenging to work out by reverse engineering your code.
Thank you!
Cheers,
Jeremy
Sure,
It starts with a list of doors in the variable doorFiltered.
1. Location point of the first door in the list is fed into the function below to provide a test point to see if it overlaps any location points in the list of doors.
def move_right(x,y,z):
n = scaleFactor
return x+n, y, z
def move_down(x,y,z):
n = scaleFactor
return x,y-n,z
def move_left(x,y,z):
n = scaleFactor
return x-n,y,z
def move_up(x,y,z):
n = scaleFactor
return x,y+n,z
moves = [move_right, move_down, move_left, move_up]
def shift(end, point):
from itertools import cycle
_moves = cycle(moves)
n = 1
pos = point
times_to_move = 1
yield pos
while True:
for _ in range(2):
move = next(_moves)
for _ in range(times_to_move):
if n >= end:
return
pos = move(*pos)
n+=1
yield pos
times_to_move+=1
2. If the point lands too close to any door locations in the list, the code adds a integer to the function and runs again to provide the next test point. Each time the function is re-run, the next point follows a spiral pattern from the origin (location point of the first door)
3. Once a point is found thats far enough from the list of door locations, a tag is placed and the tag location is added to the list of door locations.
4. The process loops to the next door, checking against the list of door location plus the new tag location.
Its a working concept but the output is inconsistant. Issues include:
Tags occasionally overlap with eachother.
The process takes a while. there's tons of points it tests that fail.
Tag location it finds does not work well with leaders. the tags land in every direction from the door creating overlap of leaders. It might work better with smaller tags.
> It might work better with smaller tags.
LOL. If you make the tags small enough, the problem will disappear entirely, along with the tags.
Thank you very much for the explanation. Brute force and effective, given time. I love that straightforward approach!
This other tagging conversation also mentions a couple of useful possibilities:
https://forums.autodesk.com/t5/revit-api-forum/tags-without-overlapping/m-p/7750631
As suggested by @jeremytammik it's not a trivial problem.
We have developed a solution to this problem using machine learning and AI. Our algorithms leverage fast and parallelizable techniques that we developed for calculating overlaps between rectangles (for tag/tag and tag/object collision detection) and overlaps between lines and rectangles (for leader line/tag/objection collision detection).
Using these algorithms and cloud computing, we calculate the best location for each tag and its leader line to avoid collision with other tags, objects, and their leader lines.
The solution also considers tag alignment as an objective to reach a nice outcome. You can try our method here: https://bimlogiq.com/products/smart-annotataion
Feel free to reach out if you would like to discuss it any further.
Thanks
Ali