# -----------------------------------------------------------------------------
# Create Rectangular Grid from Sketch  -  Dynamo 3.x (CPython3 / PythonNet3)
#
# Layout (from the hand sketch):
#   Vertical grids 1-5   : 4 bays @ 15'-0"           -> 60'-0" overall
#   Horizontal grids A-F : top to bottom 30', 10', 10', 20', 20' -> 90'-0" overall
#
#        1     2     3     4     5
#   A  --+-----+-----+-----+-----+--   y = 90'
#        |           30'           |
#   B  --+-----+-----+-----+-----+--   y = 60'
#   C  --+-----+-----+-----+-----+--   y = 50'   (10')
#   D  --+-----+-----+-----+-----+--   y = 40'   (10')
#   E  --+-----+-----+-----+-----+--   y = 20'   (20')
#   F  --+-----+-----+-----+-----+--   y =  0'   (20')
#
# HOW TO USE
#   1. Open Dynamo 3.x from the Revit Manage tab.
#   2. Place a Python Script node (engine: CPython3 or PythonNet3).
#   3. Paste this file into the node (Edit...).
#   4. Optional inputs (add with the "+" on the node; leave off to use defaults):
#        IN[0]  origin X in feet  (grid 1 / F intersection)   default 0
#        IN[1]  origin Y in feet                               default 0
#        IN[2]  extension past the outer grids, in feet        default 5
#   5. Run. OUT reports what was created or skipped.
#
# All distances are in feet (Revit internal units), regardless of project units.
# -----------------------------------------------------------------------------

import clr
clr.AddReference("RevitAPI")
clr.AddReference("RevitServices")

from Autodesk.Revit.DB import XYZ, Line, Grid, FilteredElementCollector
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

doc = DocumentManager.Instance.CurrentDBDocument


# ---------------------------------------------------------------- settings ---
# Spacing between consecutive grid lines, in feet.
VERTICAL_SPACINGS = [15.0, 15.0, 15.0, 15.0]            # left -> right
HORIZONTAL_SPACINGS = [30.0, 10.0, 10.0, 20.0, 20.0]    # top  -> bottom

VERTICAL_NAMES = ["1", "2", "3", "4", "5"]              # left -> right
HORIZONTAL_NAMES = ["A", "B", "C", "D", "E", "F"]       # top  -> bottom


def read_input(index, default):
    """Return IN[index] as float, or default if not wired / empty."""
    try:
        value = IN[index]
    except (IndexError, NameError):
        return default
    if value is None:
        return default
    try:
        return float(value)
    except (TypeError, ValueError):
        return default


origin_x = read_input(0, 0.0)
origin_y = read_input(1, 0.0)
extension = read_input(2, 5.0)


# ------------------------------------------------------------- geometry -----
def cumulative(spacings):
    positions = [0.0]
    for s in spacings:
        positions.append(positions[-1] + s)
    return positions


x_positions = [origin_x + p for p in cumulative(VERTICAL_SPACINGS)]

total_height = sum(HORIZONTAL_SPACINGS)
# Horizontal spacings run top -> bottom, so A sits at origin_y + total_height.
y_positions = [origin_y + total_height - p for p in cumulative(HORIZONTAL_SPACINGS)]

x_min, x_max = min(x_positions) - extension, max(x_positions) + extension
y_min, y_max = min(y_positions) - extension, max(y_positions) + extension

if len(x_positions) != len(VERTICAL_NAMES) or len(y_positions) != len(HORIZONTAL_NAMES):
    raise ValueError("Number of grid names does not match the number of spacings + 1.")

# Vertical grids drawn bottom -> top so the default bubble lands at the top.
grid_specs = []
for name, x in zip(VERTICAL_NAMES, x_positions):
    grid_specs.append((name, XYZ(x, y_min, 0), XYZ(x, y_max, 0)))
# Horizontal grids drawn right -> left so the default bubble lands at the left.
for name, y in zip(HORIZONTAL_NAMES, y_positions):
    grid_specs.append((name, XYZ(x_max, y, 0), XYZ(x_min, y, 0)))


# ------------------------------------------------------------- create -------
existing_names = set(g.Name for g in FilteredElementCollector(doc).OfClass(Grid))

report = []
TransactionManager.Instance.EnsureInTransaction(doc)
try:
    for name, start, end in grid_specs:
        if name in existing_names:
            report.append("Skipped grid {} - a grid with that name already exists".format(name))
            continue
        grid = Grid.Create(doc, Line.CreateBound(start, end))
        grid.Name = name
        existing_names.add(name)
        report.append("Created grid {}  (Id {})".format(name, grid.Id))
finally:
    TransactionManager.Instance.TransactionTaskDone()

report.append(
    "Grid extents: {:.0f}' x {:.0f}' from origin ({:.2f}, {:.2f})".format(
        max(x_positions) - min(x_positions),
        max(y_positions) - min(y_positions),
        origin_x, origin_y,
    )
)

OUT = report
