"I dare you" - Take 3 : Fix the tool "set crop region to selected shape" to work with Elevations

Third edition of the “I dare you” for you mighty :ninja:

The “Set Crop Region to Selected Shape” does not work with Elevations viewports.

Most likely reason is that it does not transform Elevation viewport coordinates properly.

I created the following issue for documentation purposes

The tool code is there: pyRevit/extensions/pyRevitTools.extension/pyRevit.tab/Drawing Set.panel/Sheets.pulldown/Set Crop Region To Selected Shape.pushbutton/script.py at develop · eirannejad/pyRevit · GitHub

The request is related to:

If know how to fix the code and fixed it locally and don’t know how to bring it into the github repository, let me know.



I fixed the issues with Elevation viewports and created a pull request for this issue. I messed up a little bit, because It’s my first time creating one.

I will try it and merge next week if all goes well.
Thanks @Nicholas.Miles

Checked it this morning.
It kind of work.
Maybe the whole tool needs refactoring or rebuilding from the ground up as the result is inconsistent.

In the meantime, I got a pointer from Cyril Poupin Creating a Crop Box for Room in Elevation View - #6 by c.poupin - Dynamo
That looks like a better strategy:

import clr
import sys
import System
from System.Collections.Generic import List
from Autodesk.DesignScript.Geometry import *
import Autodesk.DesignScript.Geometry as DS

#import Revit API
import Autodesk
from Autodesk.Revit.DB import *
import Autodesk.Revit.DB as DB

from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument

import Revit

from System.Reflection import BindingFlags

debug = []
view = UnwrapElement(IN[0])
space = UnwrapElement(IN[1])
margin = IN[2] # in feet
crsm = view.GetCropRegionShapeManager()
cpbbx = view.CropBox
tfBBx = cpbbx.Transform 
pt1 = cpbbx.Min
## compute solid from section View BoundingBox with transform
pt2 = XYZ(cpbbx.Max.X, cpbbx.Min.Y, cpbbx.Min.Z)
pt3 = XYZ(cpbbx.Max.X, cpbbx.Max.Y, cpbbx.Min.Z)
pt4 = XYZ(cpbbx.Min.X, cpbbx.Max.Y, cpbbx.Min.Z)
lstLoopPoints = [pt1, pt2, pt3, pt4, pt1]
lstCurveLoop = List[CurveLoop]()
curveLoop = DB.CurveLoop()
for idx, pt in enumerate(lstLoopPoints):
    if idx > 0:
        curveLoop.Append(DB.Line.CreateBound(lstLoopPoints[idx - 1], pt))

solidSection = DB.GeometryCreationUtilities.CreateExtrusionGeometry(lstCurveLoop, XYZ.BasisZ, cpbbx.Max.Z - cpbbx.Min.Z)
solidSection = DB.SolidUtils.CreateTransformed(solidSection,  tfBBx)
## get solid of Space or Room
calculator = SpatialElementGeometryCalculator(doc)
if calculator.CanCalculateGeometry(space):
    results = calculator.CalculateSpatialElementGeometry(space)
    spaceSolid = results.GetGeometry()
    # compute solid intersection 
    interSolid = BooleanOperationsUtils.ExecuteBooleanOperation(solidSection, spaceSolid, BooleanOperationsType.Intersect)
    # get the parallel face to the view orientation
    face = max((f for f in interSolid.Faces if isinstance(f, DB.PlanarFace) and f.FaceNormal.IsAlmostEqualTo( view.ViewDirection)), key = lambda x : x.Area)
    # get the curloop
    curveloop_shape = max(face.GetEdgesAsCurveLoops(), key=lambda c : c.GetExactLength())
    # add margin
    curveloop_shape_offset = DB.CurveLoop.CreateViaOffset(curveloop_shape, margin, face.FaceNormal)
    OUT =  face.ToProtoType()


Thank for sharing the suggestion, but I don’t think it is entirely applicable.

I re-examined the original code and rewrote the logic for transforming the points between sheet space and model space. I got it mostly working, however consecutive executions of the command will cause the viewport to drift. I think this is because the center of the viewport’s “Outline” in sheet space is slightly offset from the center of the view’s crop box in model space which may be causing the alignment to be slightly off if you execute it multiple times.

Regardless, it is much more consistent with the new logic outside of the issue outlined above. I don’t have much time to look at it ATM, but I’ll try to fix it as soon as I have the time.

Here’s the code for the transforming logic for reference. The rest of the code is similar to the original except the detail lines will be manipulated using the Transform instead.

def get_transformation(view_port, view, view_crop_shape):
    def _min_mix(_shape):
        _max, _min = (lambda x: ([-x] * 3, [x-1] * 3))(2**63) # (2) 1x3 vectors with max/min 64 bit numbers
        _cl = _shape
        for _l in _cl:
            _p_zero = _l.GetEndPoint(0)
            _p_one = _l.GetEndPoint(1)
            for i in range(3):
                _min[i] = min(_min[i], _p_zero[i], _p_one[i])
                _max[i] = max(_max[i], _p_zero[i], _p_one[i])
        return DB.XYZ(*_max), DB.XYZ(*_min)

    view_crop_max, view_crop_min = _min_mix(view_crop_shape)
    sheet_max, sheet_min = view_port.GetBoxOutline().MaximumPoint, view_port.GetBoxOutline().MinimumPoint

    view_crop_center = (view_crop_max + view_crop_min) * 0.5
    sheet_center = (sheet_max + sheet_min) * 0.5
    # remove z component of sheet coordinate
    sheet_center = DB.XYZ(sheet_center.X, sheet_center.Y, 0.0)

    # transforms all points on the sheet to be relative to center of sheet viewport
    sheet_transform = DB.Transform.CreateTranslation(-sheet_center)

    # transforms all sheet points to be relative to the model views crop center
    basis_transform = DB.Transform.CreateTranslation(view_crop_center)
    basis_transform.BasisX = view.RightDirection * view.Scale
    basis_transform.BasisY = view.UpDirection * view.Scale
    basis_transform.BasisZ = view.ViewDirection * view.Scale

    # return composition of transforms going from right to left (sheet transform first, then basis transform)
    return  basis_transform * sheet_transform
1 Like

@Nicholas.Miles :pray:
Congrats and nice job!

Fix here

And available in the WIP installer