IronPython2 to CPython3 upgrade failing

Hi

I am trying to upgrade an IronPython2 node to CPython3. The code is working on IronPython2, but if I use the migration assistant, it says there are no changes needed, but it is failing in CPython3.

The error I get is:

AttributeError : ‘list’ object has no attribute ‘Find’ [’ File “”, line 55, in \n’, ’ File “”, line 19, in GetCropBox\n’]

This is the code:

import clr
import math
clr.AddReference('RevitAPI')
import Autodesk
from Autodesk.Revit.DB import *
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Transactions import TransactionManager
clr.AddReference('RevitNodes')
import Revit
clr.ImportExtensions(Revit.GeometryConversion)

views = UnwrapElement(IN[0]) if isinstance(IN[0],list) else [UnwrapElement(IN[0])]

def GetCropBox(view):
    provider= ParameterValueProvider(ElementId(BuiltInParameter.ID_PARAM))
    rule = FilterElementIdRule(provider, FilterNumericEquals(), view.Id )
    filter= ElementParameterFilter(rule)
    return doc.GetElement(FilteredElementCollector(doc).WherePasses(filter).ToElementIds().Find(lambda x: x.Value != view.Id.Value))

elem,lines,box=[],[],[]

for view in views:
    doc=view.Document
    # CropBox
    if view.ViewType == ViewType.FloorPlan or view.ViewType == ViewType.CeilingPlan or view.ViewType == ViewType.EngineeringPlan :
        elem.append(GetCropBox(view))
        shape=view.GetCropRegionShapeManager().GetCropShape()
        if len(shape) > 0:
            lines.append([s.ToProtoType() for s in shape[0]])
        else:lines.append(None)
        # Transform required for the scopeboxes 
        bb = view.CropBox
        transform = bb.Transform
        min = transform.OfPoint(bb.Min)
        max = transform.OfPoint(bb.Max)
        viewRange = view.GetViewRange()

        topLevel = doc.GetElement(viewRange.GetLevelId(PlanViewPlane.TopClipPlane))
        topOffset = viewRange.GetOffset(PlanViewPlane.TopClipPlane)
        bttmLevel = doc.GetElement(viewRange.GetLevelId(PlanViewPlane.BottomClipPlane))
        bttmOffset = viewRange.GetOffset(PlanViewPlane.BottomClipPlane)
        cutLevel = doc.GetElement(viewRange.GetLevelId(PlanViewPlane.CutPlane))
        cutOffset = viewRange.GetOffset(PlanViewPlane.CutPlane)
        if bttmLevel : newmin = XYZ(min.X, min.Y, bttmLevel.ProjectElevation + bttmOffset)
        else: newmin = XYZ(min.X, min.Y, cutLevel.ProjectElevation + bttmOffset)
        if topLevel : newmax = XYZ(max.X, max.Y, topLevel.ProjectElevation + topOffset)
        else: newmax = XYZ(max.X, max.Y, cutLevel.ProjectElevation + cutOffset)
        newbox = BoundingBoxXYZ()
        newbox.Max = newmax
        newbox.Min = newmin
        box.append(newbox.ToProtoType())
    # viewSection Marker
    elif view.ViewType == ViewType.Section or view.ViewType == ViewType.Elevation or view.ViewType == ViewType.Detail :
        elem.append(GetCropBox(view))
        shape=view.GetCropRegionShapeManager().GetCropShape()
        if len(shape) > 0:
            lines.append([s.ToProtoType() for s in shape[0]])
        else:lines.append(None)    
        bb = view.CropBox
        transform = bb.Transform
        newmin = transform.OfPoint(bb.Min)
        newmax = transform.OfPoint(bb.Max)
        newbox = BoundingBoxXYZ()
        newbox.Max = newmax
        newbox.Min = newmin
        box.append(newbox.ToProtoType())
    # SectionBox
    elif view.ViewType == ViewType.ThreeD:
        elem.append(doc.GetElement(ElementId(view.Id.Value - 1)))
        lines.append(None)
        bb = view.GetSectionBox()
        transform = bb.Transform
        newmin = transform.OfPoint(bb.Min)
        newmax = transform.OfPoint(bb.Max)
        newbox = BoundingBoxXYZ()
        newbox.Max = newmax
        newbox.Min = newmin
        box.append(newbox.ToProtoType())

if isinstance(IN[0], list): OUT = elem, lines, box
else: OUT = elem[0], lines[0], box[0]


Any ideas as to why this might fail?

Crop box.dyn (21.6 KB)

ToElementIds() returns a .NET ICollection, not a Python list.

The .Find() being called on it is actually List.Find(Predicate) - a .NET method expecting a .NET delegate. IronPython was pretty forgiving about this and would silently prompt a Python lambda into the expected Predicate type, so it just worked.

The fix is to pull the filtering into pure Python by converting the collection to something iterable first:Iterating the .NET collection with a plain for loop works in both engines, so the filtering stays in Python-land. Should be good on both IronPython and CPython.

I had to make so many tweaks to my code when I was upgrading things across graphs :sweat_smile:

1 Like

Example

def GetCropBox(view):
provider = ParameterValueProvider(ElementId(BuiltInParameter.ID_PARAM))
rule = FilterElementIdRule(provider, FilterNumericEquals(), view.Id)
filter = ElementParameterFilter(rule)
ids = FilteredElementCollector(doc).WherePasses(filter).ToElementIds()
crop_id = next((x for x in ids if x.Value != view.Id.Value), None)
return doc.GetElement(crop_id)

2 Likes

Also look at ParameterFilterRuleFactory class CreateEqualsRule(ElementId, ElementId) Method