Can't modify multiple assembly origins via python, only one at a time

Hello,

I’m having trouble getting a script I wrote up to perform appropriately, I can forward the code to anyone capable of helping but the gist of it is that I am orienting the assembly origin of a wall assembly to the front face of the largest wall in that assembly. When I run my script on a single assembly, it works exactly as intended, but when I run it on any more than 1 assembly at the same time it only orients one of them correctly and leaves the rest of them alone.

I’ve tried to run a subtransaction to see if that was the issue, perhaps the script was not pushing out it’s changes but that doesn’t really make any sense, and didn’t correct the problem.

I’ve tried to move the for loop cycling through the assemblies outside of the module so all of the local variables would be reset, that didn’t work either.

I put in a debug list to collect before and after transform information (using transform = assembly.GetTransform()) and then setting the transform with the wall orientation) and when I look over the output from that list it looks like the script is working - the before and after transforms are different from trans.BasisX/Y/Z before I set the transform and after. So I’m really stuck trying to figure out whats going on… The transforms are all different in the debug list so it doesn’t look like any variables are getting stuck with prior values or from other assemblies(thereby orienting incorrectly).

Anyone have any thoughts? I’m going to continue troubleshooting and collect more info to see if I can determine the cause of this.

If you need to see the code to help just shoot me a message or request and I’ll pm you. I’m not quite ready to publish my work for the community, in time.

I suppose if it can’t be helped I can post the code here, however there shouldn’t be any need for an image of my graph, dynamo files or a sample file. This is solely a python node operating on wall assemblies from the python script from string node - shouldn’t take more than a moment to set up, faster than downloading anything I’m sure.

# -*- coding: utf-8 -*-
"""
Created on Thu Dec 14 14:24:43 2017

@author: JBradley
"""

import sys
rpw_path = r"C:\Users\JBradley\AppData\Roaming\Dynamo\Dynamo 
Revit\1.3\packages\RevitPythonWrapper\extra\rpw.zip"
# Or any location where rpw.zip is stored. This one ^ was installed with the package manager
# but you can also download it from github
sys.path.append(rpw_path)
OUT = 'Test'
import rpw
from rpw.ui.forms import Console

import clr

from System.Collections.Generic import *
clr.AddReference('System')

clr.AddReference("ProtoGeometry")
from Autodesk.DesignScript.Geometry import *
from Autodesk.DesignScript import Geometry as Geom

# import Document Manager
clr.AddReference("RevitServices")
import RevitServices 
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

clr.AddReference('RevitAPI')

import Autodesk
from Autodesk.Revit.DB import *
from Autodesk.Revit.DB import FilteredElementCollector
from Autodesk.Revit.DB import ElementTransformUtils

doc = DocumentManager.Instance.CurrentDBDocument
uidoc = DocumentManager.Instance.CurrentUIDocument

clr.AddReference("RevitNodes")
import Revit
clr.ImportExtensions(Revit.Elements)
clr.ImportExtensions(Revit.GeometryConversion)

clr.AddReference('RevitAPIUI')
from Autodesk.Revit.UI import *

app = DocumentManager.Instance.CurrentUIApplication.Application

###############################################################

dataEnteringNode = IN

run = IN[0]

error = 0

def pickobjects():

    elements = []

    selEle = uidoc.Selection.PickObjects(Selection.ObjectType.Element, "Select Perimeter Extrusion.")
    try:
        for i in selEle:
            elements.append(doc.GetElement(i.ElementId))
    except:
        elements = [doc.GetElement(selEle.ElementId)]
    return elements

def getActiveView(doc):
    return doc.ActiveView

def getAssembliesInView(doc, active):
    wallAssemblies = []

    collector = FilteredElementCollector(doc, active.Id)
    assemblies = collector.OfCategory(BuiltInCategory.OST_Assemblies)

    for i in assemblies:
        wallAssemblies.append(i)
    
    return wallAssemblies

def getFrontFace(wall):

    orientation = wall.Orientation
    matchOrient = orientation

    #create geometry options/compute references
    geoOptions = Options()
    geoOptions.ComputeReferences = True
    #extract geometry
    geoElement = wall.get_Geometry(geoOptions)
    geoSet = List[Solid]()
    elemIter = geoElement.GetEnumerator()
    elemIter.Reset()
    while elemIter.MoveNext():
        curElem = elemIter.Current
        geoSet.Add(curElem)
    #extract faces from solids
    for i in geoSet:
        faces = i.Faces
        for each in faces:
            if each.FaceNormal[0] == matchOrient[0] and each.FaceNormal[1] == matchOrient[1] and each.FaceNormal[2] == matchOrient[2]:
                frontFace = each
                break

    return frontFace

def isWindowWall(wall):
    filterIntersect = ElementIntersectsElementFilter(wall, False)

    collector = FilteredElementCollector(doc)

    collector.WherePasses(filterIntersect)

    for i in collector:
        if i.Id.IntegerValue != wall.Id.IntegerValue:
            if "Curtain" in str(i.CurtainGrid):
                return True
    return False

def orientAssemblies(assemblies):
    for i in assemblies:
    
        areaComp = 0
        windowWall = False
    
        for j in i.GetMemberIds():
        
            if "Wall" in str(doc.GetElement(j)):
            
                wallTry = doc.GetElement(j)
                area = wallTry.LookupParameter("Area").AsDouble()
            
                if isWindowWall(wallTry) == True:
                
                    wall = wallTry
                
                    windowWall = True
            
                elif area > areaComp and windowWall == False:
                
                    areaComp = area
                    wall = wallTry
    
        frontFace = getFrontFace(wall)
        uv = UV(.5, 0)
    
        point = frontFace.Evaluate(uv)
        yv = frontFace.YVector
        xv = frontFace.XVector
        normal = frontFace.ComputeNormal(uv)

        trans = i.GetTransform()
        trans.Origin = trans.Origin
        trans.BasisX = xv
        trans.BasisY = -normal # Orient opposite of face normal so view is looking into wall
        trans.BasisZ = yv
    
        i.SetTransform(trans)

assemblies = pickobjects()

#active = getActiveView(doc)

#assemblies = getAssembliesInView(doc, active)

####################################################
#"Start" the transaction
TransactionManager.Instance.EnsureInTransaction(doc)

orientAssemblies(assemblies)

# "End" the transaction
TransactionManager.Instance.TransactionTaskDone()
####################################################

result = assemblies

if error == 0 and run == True:
    OUT = result
else:
    OUT = error

#Console(context=locals())

That module (Revit Python Wrapper) allows you to operate within the script as it runs - you can pull out live variables and elements and operate as if you’re appending your script live, I use it to debug while I’m working on my scripts because coding inside dynamo directly is cumbersome and working solely in an IDE forgoes the ability to actively check your code until you run it.

The issue I’m having doesn’t come from RPW, when I’ve finished writing up my python modules I comment out rpw so my colleagues can use my scripts inside of dynamo player.

The issue I’m having is with modifying the assembly origin of multiple assemblies via my script and I can’t seem to find the cause.

I’ve attached a revit project with 2 wall assemblies and the python script inside a dynamo file. You’ll notice the two assemblies have assembly origins that are not in line with the walls. When you run the script with this project you should be able to select 1+ assemblies, the script then finds walls within the assembly and sets the assembly origin transformation to the wall orientation (and favors walls by size or if they host a curtaingrid). The issue is that even when selecting multiple assemblies it only modifies one wall assembly origin rather than multiple.

Project1.rvt (340 KB)
t1.dyn (6.3 KB)

After more testing I can confirm that every value being passed in to the i.SetTransform(trans) method is accurate, I can only assume at this point that i.SetTransform is behaving incorrectly or requires some transaction method to function the way I want - I’ve tried running sub transactions… maybe each SetTransform requires a unique transaction?

So, having a unique transaction for each assembly origin transformation works, they all modify correctly. But trying to use subtransactions to accomplish this with a transaction manager around the function does not work, it only modifies one assembly, the last assembly passed in.

Hi @jamesbradleym,

Have you seen this tread ?
I was very happy that @Daniel_Woodcock1 was so kind to provide a solution (Python) to the OP’s question.
https://forum.dynamobim.com/t/assembly-modify-origin/11433/7?u=mjb-online
Maybe it can be of help.

Kind regards,
Mark

That’s interesting, for me the script runs instantly. There isn’t any complex computation happening - I walked through the script in my first post. Did you comment out or delete the rpw and Console code?

I can only speculate, but even when I run the script going through 256 assemblies, with a unique transaction for each assembly, it only takes 30 seconds or so.

Hello Mark, I appreciate your comment but after looking through @Daniel_Woodcock1’s dynamo nodes and python code it’s clear that the script only works on one assembly at a time. I haven’t modified his script to see if it can take in multiple assemblies simultaneously but I’m pretty certain his script would do the same thing my own has done and would require separate transaction calls for each assembly origin modification.

That’s interesting. I’m a novice coder for sure but I didn’t anticipate anyone needing to recode to be able to get the script to work… I can record a video of the script running if necessary to show this, but you should have only needed to run the code and then you’d be in objects selection (why did you need to use the dynamo nodes for object selection when the same utility was coded in python? Also note that, as the api states, you need to select “Finish” in the upper left hand of the revit screen to finish selecting your objects - I mention this because I completely missed it when I first started working with pickObjects)

If you could go into more detail on what parts of my script were done strangely to you that would certainly help me not just on this problem but also coding in general. I’d greatly appreciate that feedback.