Auto Tagging without overlap

ckepner
Explorer
Explorer

Auto Tagging without overlap

ckepner
Explorer
Explorer

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()

 

Reply
Accepted solutions (1)
5,199 Views
7 Replies
Replies (7)

jeremytammik
Autodesk
Autodesk

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!

 



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

0 Likes

ckepner
Explorer
Explorer
Accepted solution

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()
0 Likes

jeremytammik
Autodesk
Autodesk

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

 



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

0 Likes

ckepner
Explorer
Explorer

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)

download.jfif

 

 

 

 

 

 

 

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.

jeremy_tammik
Autodesk
Autodesk

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!

 

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open

jeremy_tammik
Autodesk
Autodesk

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

 

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open

aliAANV4
Explorer
Explorer

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