Calculate usable length of sheet

hi everyone,
Actually, i am creating one sheet and placed the viewport in center. but the center point coming on the base of entire sheet size. instead of entire sheet i want to calculate one usable length &
width and base on that i want to set my viewport center point. Kindly tell me some solution.


Regards,
BR

Change the position of your Viewport after placement is one way.
Substract half of the width of ‘text block’. Something like :point_down:.

A simple way is to draw two lines that cross in the center of your open space and then get that line intersection point, then when you place the view you can use that point x and y co ordinates.

Its not a super smart way but it will work :slight_smile:

Thank you for taking time to reply.

those thinks are ok by using coordinate point but i want to know how to find that usable length & Width from this entire sheet.

Thanks & Regards,
BR

Use a specific line type for the interior border of your title block. Then you can use Dynamo to get those lines and/or a bounding box to define your useable length x width.

1 Like

What do you by Sheet now. Do you mean Title Block by that? If so don’t you use Height and Width Parameter the Title Block Family?

I also like your @Nick_Boyts idea for getting the ‘usable’ part / center for the Sheet

From what i can see you can’t pull the geometry directly though to dynamo from a linkinstance titleblock.
(You can do it inside the family enviroment) So that said there are a few different ways of doing this depending on what you are trying to do but they all envolve a little bit of manual math (ie: known dimensions).

An example here;

Hi @Biplab probably some BG open could work…

Revit_sHrnY1u5Lw

EDIT you dont need BG open, just me there play around
:wink:

3 Likes

Oh yooo! Smart!
Where the familyinstance 2D geometry node come from?

Yeps good old Genius Loci again :wink: :wink:

Using this i was trying to find the technically third biggest rectangle but using a list of line intersection points to create all the possible rectangle positions it works out something O(n^4) number of possible rectangle, which is too many to push through dynamo :frowning:

1 Like

A more straightforward approach to assessing the dimensions of usable space could be creating parameters within the family to dimension the desired area.

Then you can just extract the parameter values from the sheet families without the need for any geometry.

4 Likes

great idea :wink:

My first approach was as simple, draw a line intersection in the center of the usable space and get a point from that and with a know point location you can just shove everything there. I suppose what he is looking for is something that will work on many titleblocks that arn’t always placed the same place like they are suppose to or facing many different types of titleblocks in a single project or over multiple projects, Hence the need some some degree of automation.

Unfortunatly, i got about this far. and its hitting midnight here so maybe someone can pick this up where i left off (Or with luck it might just work for you)

class Point:
    def __init__(self, x, y):
        self.X = x
        self.Y = y

class Line:
    def __init__(self, start_point, end_point):
        self.StartPoint = start_point
        self.EndPoint = end_point

def calculate_distance(point1, point2):
    return ((point1.X - point2.X) ** 2 + (point1.Y - point2.Y) ** 2) ** 0.5

def find_largest_rectangle(lines):
    def is_inside(p, rect):
        # Check if a point is inside a rectangle
        x, y = p.X, p.Y
        xs = sorted([rect[i].X for i in range(4)])
        ys = sorted([rect[i].Y for i in range(4)])
        return xs[0] < x < xs[-1] and ys[0] < y < ys[-1]

    def has_line_inside(rect, lines):
        # Check if there is any line inside the rectangle
        for line in lines:
            if is_inside(line.StartPoint, rect) or is_inside(line.EndPoint, rect):
                return True
        return False

    max_area = 0
    largest_rectangle = None

    # Generate all combinations of four points
    for i in range(len(lines)):
        for j in range(i + 1, len(lines)):
            for k in range(j + 1, len(lines)):
                for l in range(k + 1, len(lines)):
                    rect_points = [lines[i].StartPoint, lines[j].StartPoint, lines[k].StartPoint, lines[l].StartPoint]
                    if len(set(rect_points)) == 4:
                        # Calculate area of the rectangle
                        width = calculate_distance(lines[i].StartPoint, lines[j].StartPoint)
                        height = calculate_distance(lines[k].StartPoint, lines[l].StartPoint)
                        area = width * height

                        # Check if there are any lines inside the rectangle
                        if area > max_area and not has_line_inside(rect_points, lines):
                            max_area = area
                            largest_rectangle = rect_points

    return largest_rectangle

lines = IN[0]

largest_rect = find_largest_rectangle(lines)
OUT = largest_rect
2 Likes

Another solution using vertical and horizontal rays (intersections with unbound curves)

Python Code (all engines)

import clr
import sys
import System
pyEngineName = sys.implementation.name 
#
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
import Autodesk.DesignScript.Geometry as DS

#import Revit API
clr.AddReference('RevitAPI')
import Autodesk
from Autodesk.Revit.DB import *
import Autodesk.Revit.DB as DB

clr.AddReference('RevitNodes')
import Revit
clr.ImportExtensions(Revit.GeometryConversion)

#import transactionManager and DocumentManager (RevitServices is specific to Dynamo)
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument

def drange(start, end, step):
    i = start
    while round(i, 10) < end:
        yield i
        i += step
  
def flattenGen(*args):
    for x in args:
        if hasattr(x, "__iter__") and not isinstance(x, str):
            for y in flattenGen(*x) : yield y
        else: yield x
  
def get_geomCurves(elem):
    """
    Retrieves the geometric curves from a given Revit element.
    Args:
        elem (Autodesk.Revit.DB.Element): The Revit element from which to extract curves.
    Returns:
        list: List of geometric curves extracted from the element.
    """
    out = []
    opt = Options()
    opt.DetailLevel = ViewDetailLevel.Fine
    instGeo = elem.GetOriginalGeometry(opt)
    tf = elem.GetTransform()
    for ig in instGeo:
        if isinstance(ig, DB.Curve):
            out.append(ig.CreateTransformed(tf))
                
        elif isinstance(ig, DB.GeometryInstance):
            for g in flattenGen(ig.GetInstanceGeometry()):
                if isinstance(g, DB.Curve):
                    out.append(g.CreateTransformed(tf))
    return out 

def toList(x):
    if isinstance(x, (list, dict)) or \
            (hasattr(x, "GetType") and x.GetType().GetInterface("ICollection") is not None):
        return x
    else : return [x]
 
def search_minPts_by_vector(minInt, maxInt, vectorRay, midPt, titlebk_curves):
    """
    Search for minimum points along a vector within a range.

    Args:
        minInt (float): The minimum value of the range.from BoundingBox
        maxInt (float): The maximum value of the range.from BoundingBox
        vectorRay (object): The vector along which to search for minimum points.
        midPt (object): The midpoint of the bounding box.
        titlebk_curves (list): The list of curves representing the title block geometry
    Returns:
        tuple: Tuple containing two minimum points (usable length sheet)
    """
    global debug
    results = []
    for d in drange(minInt, maxInt, 0.05):
        if vectorRay.IsAlmostEqualTo(XYZ.BasisX):
            ptRay = XYZ(midPt.X, d, midPt.Z)
        else:
            ptRay = XYZ(d, midPt.Y, midPt.Z)
        raybound = DB.Line.CreateUnbound(ptRay, vectorRay)
        for c in titlebk_curves:
            result = None
            if pyEngineName == "ironpython":
                out_resultArray = clr.Reference[IntersectionResultArray]()
                result = raybound.Intersect(c, out_resultArray)
            else:
                dummy_out = DB.IntersectionResultArray()
                result, interResult = raybound.Intersect(c, dummy_out)
            #
            if result == SetComparisonResult.Overlap:
                interResult = out_resultArray.Value[0] if pyEngineName == "ironpython" else interResult[0]
                pt = interResult.XYZPoint
                distance = ptRay.DistanceTo(pt)
                vector = DB.Line.CreateBound(ptRay, pt).Direction 
                results.append([pt, distance, vector])
                #debug.append(pt.ToPoint())
        raybound.Dispose()
    results.sort(key = lambda x : x[1])
    dataA = results.pop(0)
    ptA, distanceA, vectA = dataA
    dataB = next(x for x in results if x[2].DotProduct(vectA) < 0)
    ptB = dataB[0]
    return ptA, ptB

lst_titleBk = toList(UnwrapElement(IN[0]))
out = []
debug = []
for tb in lst_titleBk:

    bbx = tb.get_BoundingBox(doc.GetElement(tb.OwnerViewId))
    midPt = (bbx.Min.Add(bbx.Max)).Multiply(0.5)
    titlebk_curves = get_geomCurves(tb)
    
    ptX1, ptX2 = search_minPts_by_vector(bbx.Min.Y, bbx.Max.Y, XYZ.BasisX, midPt, titlebk_curves)
    if ptX1.X >  ptX2.X:
        ptX1, ptX2 = ptX2, ptX1
    ptY1, ptY2 = search_minPts_by_vector(ptX1.X, ptX2.X, XYZ.BasisY, midPt, titlebk_curves)
    out.append([tb, [ptX1.ToPoint(), ptX2.ToPoint()], [ptY1.ToPoint(), ptY2.ToPoint()], [c.ToProtoType() for c in titlebk_curves]])
OUT = out
1 Like

Thank you everyone for taking time to reply.

I got my solution.

Regards,
BR

FYI, when i tried this, it gave me the outermost rectangle of the sheet (Maybe i typed something wrong)

1 Like

Actually, in family itself i created two parameters. after that using get parameter i extract those value and divided with 2. like that i got 2 midpoints.

2 Likes

ïf we have foldermarks as i have in the titleblock, then its pretty easy i guess

Revit_6k1calAJoa

1 Like

I know this topic has a solution, but i was too deep working on this to stop. so…

A solution with no titleblock modification (Still seems to have some quirks i can’t quite work out or certain types of titleblocks :))

Python - ℗ | LargestRectangleCenter.FromLines
import clr
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
from math import sqrt
from operator import itemgetter

# Assuming 'curves' is your list of Curve elements
curves = IN[0]

def is_almost_equal(pt1, pt2, tolerance):
    return pt1.DistanceTo(pt2) < tolerance

points = []
for i in range(len(curves)):
    for j in range(i+1, len(curves)):
        intersection = Geometry.Intersect(curves[i], curves[j])
        if intersection:
            points.extend([pt for pt in intersection if isinstance(pt, Point)])

# Removing duplicates
tolerance = 0.01  # Tolerance for considering points as duplicates
unique_points = []
for pt in points:
    if not any(is_almost_equal(pt, p, tolerance) for p in unique_points):
        unique_points.append(pt)

_pts = unique_points

def hull(_pts):
    def pCrs(o, a, b):
        return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0])

    pts = sorted( (p.X, p.Y) for p in _pts)
    pLen = len(pts)
    if pLen < 4 : return pts
    else:
        lower, upper = [], []
        
        for i in range(pLen):
            j = pLen - 1 - i
            while len(lower) >= 2 and pCrs(lower[-2], lower[-1], pts[i]) <= 0.000001:
                lower.pop()
            lower.append(pts[i])
            while len(upper) >= 2 and pCrs(upper[-2], upper[-1], pts[j]) <= 0.000001:
                upper.pop()
            upper.append(pts[j])
        
        lower.pop()
        upper.pop()
        lower.extend(upper)
        return lower

def scalar(k, i, j):
    return k[0] * (i[1] - j[1]) + k[1] * (j[0] - i[0]) + j[1] * i[0] - i[1] * j[0]

def pDist(a, b, sqrt=sqrt):
    return sqrt( (a[0] - b[0])**2 + (a[1] - b[1])**2)

def linDist(a, b, p):
    line_dist = pDist(a, b)
    if line_dist == 0:
        return a, pDist(p, a)
    
    t = ( (p[0] - a[0]) * (b[0] - a[0]) + (p[1] - a[1]) * (b[1] - a[1]) ) / line_dist**2
    
    cp = ( (b[0] - a[0]) * t + a[0], (b[1] - a[1]) * t + a[1])
    d1 = pDist(p, cp)
    return cp, d1

def indexer(end, position=0, start=0, step=1):
    i = position - step
    while True:
        i += step
        if i >= end:
            i = start
        yield i

def highSearch(pts, iPt, jPt, j, it1=None, k=None, m=None,
               indexer=indexer, scalar=scalar):
    if it1 is None:
        it1 = indexer(len(pts), j)
        k, m = next(it1), next(it1)
    Sa = scalar(pts[k], iPt, jPt)
    Sb = scalar(pts[m], iPt, jPt)
    while Sa < Sb:
        k, m = m, next(it1)
        Sa = scalar(pts[k], iPt, jPt)
        Sb = scalar(pts[m], iPt, jPt)
    return k, m

pts = hull(_pts)

OUT = []
highPts, closePts, areas, dists = [], [], [], []
lnPts = len(pts)

it1 = indexer(lnPts, 2)
k, m = next(it1), next(it1)

for i in range(lnPts):
    j = (i + 1) % lnPts

    k, m = highSearch(pts, pts[i], pts[j], j+1, it1, k, m)
    
    highPts.append(pts[k])
    cp, l1 = linDist(pts[i], pts[j], pts[k])
    closePts.append(cp)
    
    n, _ = highSearch(pts, pts[k], cp, j)
    _, l2a = linDist(pts[k], cp, pts[n])
        
    q, _ = highSearch(pts, cp, pts[k], m)
    _, l2b = linDist(cp, pts[k], pts[q])
    
    areas.append(l1 * (l2a + l2b) )
    dists.append( (l2a, l2b) )

ix, _ = min(enumerate(areas), key=itemgetter(1) )
d1, d2 = dists[ix]
_a, _b = closePts[ix], highPts[ix]
a = Point.ByCoordinates(_a[0], _a[1], 0)
b = Point.ByCoordinates(_b[0], _b[1], 0)

_v = Vector.ByTwoPoints(a, b)
v = Vector.ByCoordinates(-_v.Y, _v.X, 0).Normalized()
p1 = a.Add(v.Scale(d2) )
p2 = a.Subtract(v.Scale(d1) )
p3 = b.Subtract(v.Scale(d1) )
p4 = b.Add(v.Scale(d2) )

# Calculate the center point of the rectangle
center_x = (p1.X + p2.X + p3.X + p4.X) / 4
center_y = (p1.Y + p2.Y + p3.Y + p4.Y) / 4

# Create the center point
center_point = Point.ByCoordinates(center_x, center_y, 0)

# Output the center point instead of the rectangle
OUT = center_point
Python - ℗ | ClosestLine +Y, -Y, +X, -X From Point
class Point:
    def __init__(self, X, Y, Z):
        self.X = X
        self.Y = Y
        self.Z = Z

class Vector:
    def __init__(self, X, Y, Z, Length):
        self.X = X
        self.Y = Y
        self.Z = Z
        self.Length = Length

class Line:
    def __init__(self, StartPoint, EndPoint, Direction):
        self.StartPoint = StartPoint
        self.EndPoint = EndPoint
        self.Direction = Direction

def closest_lines(point, lines):
    min_dist = {'+X': float('inf'), '-X': float('inf'), '+Y': float('inf'), '-Y': float('inf')}
    closest_lines = {'+X': None, '-X': None, '+Y': None, '-Y': None}

    for line in lines:
        # Calculate the intersection point of the line with the +X ray
        if line.Direction.X > 0:
            t = (point.X - line.StartPoint.X) / line.Direction.X
            if 0 <= t <= 1:  # Check if the intersection is within the line segment
                intersection = Point(line.StartPoint.X + t * line.Direction.X, line.StartPoint.Y + t * line.Direction.Y, 0)
                dist = ((intersection.X - point.X)**2 + (intersection.Y - point.Y)**2)**0.5
                if dist < min_dist['+X']:
                    min_dist['+X'] = dist
                    closest_lines['+X'] = line

        # Repeat for the other directions
        # -X
        elif line.Direction.X < 0:
            t = (point.X - line.StartPoint.X) / line.Direction.X
            if 0 <= t <= 1:  # Check if the intersection is within the line segment
                intersection = Point(line.StartPoint.X + t * line.Direction.X, line.StartPoint.Y + t * line.Direction.Y, 0)
                dist = ((intersection.X - point.X)**2 + (intersection.Y - point.Y)**2)**0.5
                if dist < min_dist['-X']:
                    min_dist['-X'] = dist
                    closest_lines['-X'] = line

        # +Y
        if line.Direction.Y > 0:
            t = (point.Y - line.StartPoint.Y) / line.Direction.Y
            if 0 <= t <= 1:  # Check if the intersection is within the line segment
                intersection = Point(line.StartPoint.X + t * line.Direction.X, line.StartPoint.Y + t * line.Direction.Y, 0)
                dist = ((intersection.X - point.X)**2 + (intersection.Y - point.Y)**2)**0.5
                if dist < min_dist['+Y']:
                    min_dist['+Y'] = dist
                    closest_lines['+Y'] = line

        # -Y
        elif line.Direction.Y < 0:
            t = (point.Y - line.StartPoint.Y) / line.Direction.Y
            if 0 <= t <= 1:  # Check if the intersection is within the line segment
                intersection = Point(line.StartPoint.X + t * line.Direction.X, line.StartPoint.Y + t * line.Direction.Y, 0)
                dist = ((intersection.X - point.X)**2 + (intersection.Y - point.Y)**2)**0.5
                if dist < min_dist['-Y']:
                    min_dist['-Y'] = dist
                    closest_lines['-Y'] = line

    # Return the results as a list
    return [closest_lines[key] for key in ['+X', '-X', '+Y', '-Y'] if closest_lines[key] is not None]

point = IN[1]
lines = IN[0]

result = closest_lines(point, lines)
OUT = result

3 Likes