# -----------------------------------------------------------------------------
# Move Grids for a 40' x 40' Central Atrium  -  Dynamo 3.x (CPython3 / PythonNet3)
#
# Moves the existing grids (made by create_grid_from_sketch.py) so a
# column-free 40' x 40' bay sits at the center of an 80' x 80' building.
# Nothing is deleted or renamed; grids keep their element IDs.
#
#                1      2              3     4     5
#   A  y=80'  ---+------+--------------+-----+-----+---
#   B  y=70'  ---+------+--------------+-----+-----+---   10'
#   C  y=60'  ---+------+--------------+-----+-----+---   10'
#                |      |              |     |     |
#                |      |    ATRIUM    |     |     |      40'
#                |      |   40' x 40'  |     |     |
#   D  y=20'  ---+------+--------------+-----+-----+---
#   E  y=10'  ---+------+--------------+-----+-----+---   10'
#   F  y= 0'  ---+------+--------------+-----+-----+---   10'
#              x=0'   20'            60'   70'   80'
#                  20'       40'       10'   10'
#
#   Atrium: x 20'-60', y 20'-60'  (bounded by grids 2, 3, C, D)
#
# Positions are measured from the current location of grid 1 (x) and grid F (y),
# so the grid is rearranged in place wherever it sits in the project.
#
# HOW TO USE
#   1. Open a FLOOR PLAN view where the grids are visible (needed to
#      lengthen the grid lines to the new 80' width).
#   2. Open Dynamo 3.x, place a Python Script node (CPython3 or PythonNet3),
#      paste this file in and Run.
#   3. OUT lists every grid moved. Ctrl+Z in Revit undoes the whole run.
# -----------------------------------------------------------------------------

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

from Autodesk.Revit.DB import (
    XYZ, Line, Grid, FilteredElementCollector, ElementTransformUtils,
    DatumExtentType, ViewPlan,
)
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

doc = DocumentManager.Instance.CurrentDBDocument

# ---------------------------------------------------------------- settings ---
# Target positions in feet, relative to grid 1 (x) and grid F (y).
VERTICAL_TARGETS = {"1": 0.0, "2": 20.0, "3": 60.0, "4": 70.0, "5": 80.0}
HORIZONTAL_TARGETS = {"A": 80.0, "B": 70.0, "C": 60.0, "D": 20.0, "E": 10.0, "F": 0.0}
EXTENSION = 5.0   # how far grid lines run past the outermost grids, in feet
X_ANCHOR, Y_ANCHOR = "1", "F"


# ------------------------------------------------------------ find grids ----
grids = {g.Name: g for g in FilteredElementCollector(doc).OfClass(Grid)}

missing = [n for n in list(VERTICAL_TARGETS) + list(HORIZONTAL_TARGETS) if n not in grids]
if missing:
    raise Exception("Grid(s) not found: {}. Run create_grid_from_sketch.py first."
                    .format(", ".join(missing)))


def endpoints(grid):
    c = grid.Curve
    return c.GetEndPoint(0), c.GetEndPoint(1)


def is_vertical(grid):
    p0, p1 = endpoints(grid)
    return abs(p1.Y - p0.Y) > abs(p1.X - p0.X)


for n in VERTICAL_TARGETS:
    if not is_vertical(grids[n]):
        raise Exception("Grid {} is not vertical - layout doesn't match the expected grid.".format(n))
for n in HORIZONTAL_TARGETS:
    if is_vertical(grids[n]):
        raise Exception("Grid {} is not horizontal - layout doesn't match the expected grid.".format(n))

x0 = endpoints(grids[X_ANCHOR])[0].X
y0 = endpoints(grids[Y_ANCHOR])[0].Y

x_min = x0 + min(VERTICAL_TARGETS.values()) - EXTENSION
x_max = x0 + max(VERTICAL_TARGETS.values()) + EXTENSION
y_min = y0 + min(HORIZONTAL_TARGETS.values()) - EXTENSION
y_max = y0 + max(HORIZONTAL_TARGETS.values()) + EXTENSION

report = []
view = doc.ActiveView

TransactionManager.Instance.EnsureInTransaction(doc)
try:
    # 1. Move each grid perpendicular to its line.
    for name, target in VERTICAL_TARGETS.items():
        current = endpoints(grids[name])[0].X
        dx = (x0 + target) - current
        if abs(dx) > 1e-6:
            ElementTransformUtils.MoveElement(doc, grids[name].Id, XYZ(dx, 0, 0))
            report.append("Moved grid {} by {:+.2f}' to x = {:.0f}'".format(name, dx, target))
        else:
            report.append("Grid {} already at x = {:.0f}'".format(name, target))

    for name, target in HORIZONTAL_TARGETS.items():
        current = endpoints(grids[name])[0].Y
        dy = (y0 + target) - current
        if abs(dy) > 1e-6:
            ElementTransformUtils.MoveElement(doc, grids[name].Id, XYZ(0, dy, 0))
            report.append("Moved grid {} by {:+.2f}' to y = {:.0f}'".format(name, dy, target))
        else:
            report.append("Grid {} already at y = {:.0f}'".format(name, target))

    doc.Regenerate()

    # 2. Stretch every grid's model extents to the new 80' x 80' footprint.
    if isinstance(view, ViewPlan):
        stretched, not_stretched = [], []
        for name in list(VERTICAL_TARGETS) + list(HORIZONTAL_TARGETS):
            g = grids[name]
            try:
                curves = g.GetCurvesInView(DatumExtentType.Model, view)
                c = curves[0]
                p0, p1 = c.GetEndPoint(0), c.GetEndPoint(1)
                z = p0.Z
                if name in VERTICAL_TARGETS:
                    a, b = XYZ(p0.X, y_min, z), XYZ(p0.X, y_max, z)
                    if p0.Y > p1.Y:
                        a, b = b, a
                else:
                    a, b = XYZ(x_min, p0.Y, z), XYZ(x_max, p0.Y, z)
                    if p0.X > p1.X:
                        a, b = b, a
                g.SetCurveInView(DatumExtentType.Model, view, Line.CreateBound(a, b))
                stretched.append(name)
            except Exception as ex:
                not_stretched.append("{} ({})".format(name, ex))
        report.append("Extended grid lines: {}".format(", ".join(stretched) or "none"))
        if not_stretched:
            report.append("Could not extend: {} - drag their ends in plan.".format(", ".join(not_stretched)))
    else:
        report.append("Active view is not a floor plan, so grid lines were moved but not lengthened. "
                      "Open a plan view and run again (it is safe to re-run).")
finally:
    TransactionManager.Instance.TransactionTaskDone()

report.append("Atrium bay: x {:.0f}'-{:.0f}', y {:.0f}'-{:.0f}' (grids 2-3, D-C)".format(
    VERTICAL_TARGETS["2"], VERTICAL_TARGETS["3"], HORIZONTAL_TARGETS["D"], HORIZONTAL_TARGETS["C"]))

OUT = report
