Difficulty Aligning Tags Using TagHeadPosition

Difficulty Aligning Tags Using TagHeadPosition

jfranzR9VWK
Contributor Contributor
419 Views
5 Replies
Message 1 of 6

Difficulty Aligning Tags Using TagHeadPosition

jfranzR9VWK
Contributor
Contributor

I'm trying to align tags along the left side of the tag. Is there a way to make TagHeadPosition use a corner instead of the middle of a tag? For simplicity, I am trying to avoid using MoveElement and bounding boxes of the tags. Is there another method that I'm missing? Even using boundingboxes it seems completely random. Do the boundingBoxes include the leader lines? If I hide the leader lines, will the bounding box still include them? If so, is there a way to exclude them?

 

Here is my code with bounding boxes

transaction.Start()
//bottomTag is the starting tag
//topTag is the tag I'm attempting to move above it with shared alignment
IndependentTag topTag = element as IndependentTag;

//Get position needed for the top tag
BoundingBoxXYZ lower = bottomTag.get_BoundingBox(doc.ActiveView);
BoundingBoxXYZ upper = topTag.get_BoundingBox(doc.ActiveView);

double height = lower.Max.Y - lower.Min.Y;
double width = lower.Max.X - lower.Min.X;
double xPos = lower.Min.X -  upper.Min.X;
double yPos = lower.Min.Y - upper.Min.Y + height;


XYZ position = new XYZ(xPos, yPos, 0);

//Move the top tag into the new position
ElementTransformUtils.MoveElement(doc, topTag.Id, position);
Console.WriteLine("Moved");
Console.WriteLine("upper.min = " + upper.Min.X + " " + upper.Min.Y + " " + upper.Min.Z);
Console.WriteLine("upper.max = " + upper.Max.X + " " + upper.Max.Y + " " + upper.Max.Z);
transaction.Commit();

 Here is my code that doesn't work because TagHeadPosition is centered

transaction.Start();
IndependentTag bottomTag;

XYZ lower = bottomTag.TagHeadPosition;

IndependentTag currentTag;

XYZ position = new XYZ(lower.X, lower.Y + 1.5, lower.Z);
currentTag.TagHeadPosition = position;
transaction.Commit()

 

0 Likes
Accepted solutions (1)
420 Views
5 Replies
Replies (5)
Message 2 of 6

jose_Miquelao
Enthusiast
Enthusiast
Accepted solution

Why TagHeadPosition Won't Give You a Corner

The Revit API does not expose a corner-based position for tag heads natively. TagHeadPosition is always the insertion point of the tag family, which in virtually all standard Revit tag families is defined at the center of the label. So there's no alternative property you're missing — it simply doesn't exist at the API level for corners.


The Bounding Box Problem — Leader Lines

Yes, bounding boxes in Revit do include leader lines in their extents. This is exactly why your bounding box approach is giving you random-feeling results — tags with leaders pointing in different directions will have wildly different Min and Max values even if the tag head itself is the same size.

Hiding leader lines in the view does not remove them from the bounding box calculation. The geometry is still there; it's just not displayed.


The Right Approach: Derive the Corner from TagHeadPosition + Half-Extents

The cleanest way to avoid MoveElement while still getting corner alignment is to compute the tag head's half-width and half-height independently of the leader, and then offset TagHeadPosition by that amount.

Here's the logic:

csharp
Copiar

 

// Get the bounding box in the active view
BoundingBoxXYZ bbox = tag.get_BoundingBox(doc.ActiveView);
XYZ center = tag.TagHeadPosition;

// Compute the bbox center (which may differ from TagHeadPosition due to leader)
XYZ bboxCenter = new XYZ(
    (bbox.Min.X + bbox.Max.X) / 2.0,
    (bbox.Min.Y + bbox.Max.Y) / 2.0,
    center.Z);

// The offset between TagHeadPosition and bbox center
// is caused by the leader. We subtract it to isolate tag head extents.
// Instead, derive half-size from the family itself (see below).

But since the bounding box is polluted by the leader, the better move is to measure the tag head size from a tag that has no leader, or use a known reference tag to calibrate.


Recommended Pattern: Calibrate from a Leader-less Tag

csharp
Copiar

 

// Temporarily suppress the leader or use a tag you know has no leader
// to get a clean bounding box representing just the tag head

BoundingBoxXYZ cleanBox = referenceTag.get_BoundingBox(doc.ActiveView);
double halfWidth  = (cleanBox.Max.X - cleanBox.Min.X) / 2.0;
double halfHeight = (cleanBox.Max.Y - cleanBox.Min.Y) / 2.0;

// Now you can compute the left edge of any tag as:
XYZ headCenter = anyTag.TagHeadPosition;
double leftEdge  = headCenter.X - halfWidth;
double bottomEdge = headCenter.Y - halfHeight;

Once you have halfWidth and halfHeight as constants (they're the same for all tags of the same family and type), you can do your alignment purely through TagHeadPosition without touching MoveElement or bounding boxes at all.


Fixing Your TagHeadPosition Code

Your second block was on the right track. Here's a corrected version that left-aligns tags using TagHeadPosition with the half-width offset:

csharp
Copiar

 

transaction.Start();

// Compute half-width once from a clean reference tag (no leader, or known size)
BoundingBoxXYZ refBox = bottomTag.get_BoundingBox(doc.ActiveView);
double halfWidth  = (refBox.Max.X - refBox.Min.X) / 2.0;
double halfHeight = (refBox.Max.Y - refBox.Min.Y) / 2.0;

// Get the LEFT edge of the bottom tag
XYZ bottomCenter = bottomTag.TagHeadPosition;
double leftEdge   = bottomCenter.X - halfWidth;

// Position topTag so its left edge aligns and it sits directly above
XYZ topCenter = topTag.TagHeadPosition;

// New center X: leftEdge + halfWidth (to keep same tag width)
// New center Y: bottom of bottomTag + height of topTag's half
double newX = leftEdge + halfWidth;
double newY = bottomCenter.Y + halfHeight + halfHeight; // stack them with no gap

topTag.TagHeadPosition = new XYZ(newX, newY, topCenter.Z);

transaction.Commit();

If you want a gap between tags, just add a padding value to newY:

csharp
Copiar

 

double padding = 0.1; // in feet, adjust as needed
double newY = bottomCenter.Y + halfHeight + halfHeight + padding;

Summary of Key Points

Issue Reality
TagHeadPosition corner accessNot available — always center
Leader lines in bounding boxYes, included — this is your main bug
Hiding leaders removes them from bboxNo, they stay in the calculation
Best workaroundCalibrate half-width/height from a leader-less reference tag, then use TagHeadPosition exclusively

 

Exportar
 

 

Copiar
0 Likes
Message 3 of 6

jfranzR9VWK
Contributor
Contributor

Just to brainstorm: since every tag can be a different length, I think I need to make a reference tag for each one that will be moved or used as a starting point. I should be able to get all of the information to make each reference tag from the members of IndependentTag or Element. I can make a reference tag, get the measurements, then delete it repeatedly. Does that work with what you were suggesting? Thanks for the help on this. I was really stuck on it.

 

Are there any good examples somewhere to read about using geometry. This doesn't need to be fast, so I wanted to look into that as well.

0 Likes
Message 4 of 6

ctm_mka
Collaborator
Collaborator

honestly i think your second approach is what you want, the reference tags tag head position, then calculate a point relative to the reference tag. But, might want to adjust teh X value vs the Y value? And the one thing your missing, is accounting for view rotation. Remember, all coordinates in the API are based on Internal Coordinates, so you need to take that into consideration.

0 Likes
Message 5 of 6

jfranzR9VWK
Contributor
Contributor

One other question I had was, how do I delete the reference tags after I have created them? Is it Element.Dispose()?

0 Likes
Message 6 of 6

ctm_mka
Collaborator
Collaborator

Document.Delete()

0 Likes