Align Surface Patterns to Wall Edge

Hi All,

I have a lot of brick hatch which I need to align to the wall edges, so I’m trying to rewrite this in Python & make it work for walls…

https://forums.autodesk.com/t5/revit-api-forum/use-of-align-function-programatically-to-change-the-alignment-of/m-p/7350967/highlight/true#M24949

I’m getting a problem where the reference is not aligned to the reference plane… Any assistance gratefully received!

On a side note… If anyone had some tips on making the C# into an addin, that would also be really helpful, I’m sure it would be easy for someone more capable than me! I’m not sure how to connect the variables from the helper method to the variables in the main class.

Thanks,

Mark

Dimension Align Pattern-6.dyn (17.0 KB) align.rvt (1.5 MB)

#thanks to Genius Loci and Fair59
import clr
import sys

pyt_path = r'C:\Program Files (x86)\IronPython 2.7\Lib'
sys.path.append(pyt_path)

import string

# Import Element wrapper extension methods
clr.AddReference('RevitNodes')
import Revit
clr.ImportExtensions(Revit.Elements)
# toProtoType etc.
clr.ImportExtensions(Revit.GeometryConversion)

# Import DocumentManager and TransactionManager
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument

# Import RevitAPI
clr.AddReference('RevitAPI')
import Autodesk
from Autodesk.Revit.DB import *
import System

wall = UnwrapElement(IN[0])
references = UnwrapElement(IN[1])
line = Line.CreateBound(XYZ.Zero, XYZ(10, 0, 0))

elementsRef = ReferenceArray()

for reference in references:
	elementsRef.Append(reference)
	
TransactionManager.Instance.EnsureInTransaction(doc)

dimension = doc.Create.NewDimension(doc.ActiveView, line, elementsRef)

TransactionManager.Instance.TransactionTaskDone()

for w in wall:
	try:
		extSide = HostObjectUtils.GetSideFaces(w,ShellLayerType.Exterior)	
		for eS in extSide:
			wallFace = w.GetGeometryObjectFromReference(eS)
	except:
		wallFace = 'no external face found'
#corner = start point of the 'first or default' edge loop of the face we're interested in
cvList = []
for cv in wallFace.GetEdgesAsCurveLoops()[0]:
	cvList.append(cv.GetEndPoint(0))
	cvList.append(cv.GetEndPoint(1))
corner = cvList[0]


#not sure exactly what this is doing...
dimDirection = dimension.Curve.Direction

dimOrigin = dimension.Origin.Subtract(dimDirection.Multiply(dimension.Value/2))

hatchDirection = dimDirection.CrossProduct(wallFace.FaceNormal).Normalize()

r1 = dimension.References.get_Item(0)

translation = dimOrigin.Subtract(corner)


#we need to move the reference or it's 'element'

#start a transaction
TransactionManager.Instance.EnsureInTransaction(doc)

#we need a reference plane
#the reference plane is made of the origin vector moved the hatch direction added, the origin, the wall face normal (out vector)

pl = doc.Create.NewReferencePlane(dimOrigin.Add(hatchDirection),dimOrigin,wallFace.FaceNormal,doc.ActiveView)

format = "{0}:{0}:{1}"
uid = pl.UniqueId
arg = "SURFACE"
stableRef = str.Format(format,uid,arg)
#
#stableRef = string.Formatter("{0}:0:{1}",pl.UniqueId,"SURFACE")
ref2Plane = Reference.ParseFromStableRepresentation(doc,stableRef)
#we need a reference plane & the first reference item... 
doc.Create.NewAlignment(doc.ActiveView,ref2Plane,r1)
#finish transaction
TransactionManager.Instance.TransactionTaskDone()

#start a transaction
TransactionManager.Instance.EnsureInTransaction(doc)
#we are moving the the reference plane to the origin
ElementTransformUtils.MoveElement(doc,pl.Id,translation)
#finish transaction
TransactionManager.Instance.TransactionTaskDone()

Update… Needs some more refinement…

Dimension Align Pattern-8.dyn (29.5 KB)

Hi Mark,

I was on the rigth track yesterday evening.
I had just made a mistake in the direction of translation. Now it works.

Before:


After:

Dimension Align Pattern-9.dyn (12.4 KB)

3 Likes

Thanks so much for your help!

1 Like

I’ve done a bit more work to get it running with patterns that have more than 1 reference… It now works for lists of walls & aligns both vertically and horizontally…

Wall Pattern Align Bricks Ele View.dyn (28.1 KB)

5 Likes

Thank you for the solution. When running in Revit 2020.3 and the “align.rvt” test file, rule 35 of “Create Ref Plane” runs into an error;

"IronPythonEvaluator.EvaluateIronPythonScript operation failed.
Traceback (most recent call last):
File “”, line 35, in
AttributeError: ‘Material’ object has no attribute ‘SurfacePatternId’

Might this be API dependent and how to solve this issue?

Hi,

Try with the Align Pattern To Edges node in Genius Loci package.
I have made some improvements since those first attempts;

2 Likes

Thank you. This node is not working well, I made some changes in the Python code to cope with the Revit 2020 API. For me this works well with the earlier example file.
Though, when run on stacked walls it will not work well, do you have a suggestion for this?

Adaptation:
Line 43: patternType = doc.GetElement(mat.SurfacePatternId)
Changed to: patternType = doc.GetElement(mat.SurfaceForegroundPatternId)

Thanks for telling me about this necessary node update.

You may be able to use the Align Pattern node in conjonction with the Get Wall Member node.

No problem. Thank you for the suggestion! Will try it monday and get back to you about this workaround

Hi all,

The node “Align Pattern To Edges” from Genius Loci is great - Just so you know, it doesn’t work in Revit 2020. Is this something that is going to be updated shortly?

On another hand, what I am after is the following: I want to select an X number of walls and set the same origin of the pattern for all of them. Would that be possible?

Hi,

Please update the Genius Loci package. It is working with Revit 2021 for me.
align patterns

Yes the custom node works with several walls.

The custom node aligns to the lower right corner so if you want the lower left corner you have to modify the python script.

2 Likes

Hello Alban,

First of all thank you very much for sharing your package, it is amazing!

It would be great to have control over the pattern setting out globally in the selected number of objects instead of using the bottom corner of each wall segment.

Brick setting out is consistent across entire facades so been able to specify a common origin through a point would be amazing!!!

We have tried to modify the python node inside your custom node but we are not very familiar with python, has this an easy fix?

You have to change the variable corner inside the script.

I don’t really have time at the moment but I add it to my todo list to try to add an input point.

Amazing,

Many thanks!

Hi @Alban_de_Chasteigner!
I’ve changed a little bit your code as you sugested. Base point of the pattern comes from curves intersection (@BIMadmin). It works well for “normal” walls but for stucked walls an error shows up. I have no clue what is the reason. Maybe you could figure it out. I’ve attached dynamo graph below:

and there is an error:

Blockquote
Warning: IronPythonEvaluator.EvaluateIronPythonScript operation failed.
Traceback (most recent call last):
File “”, line 89, in
Exception: Input geometry is not correct to lay out the reference plane.
Parameter name: multiple

or

Blockquote
Warning: IronPythonEvaluator.EvaluateIronPythonScript operation failed.
Traceback (most recent call last):
File “”, line 85, in
EnvironmentError: System.Runtime.InteropServices.SEHException (0x80004005): External component has thrown an exception.
at __RTDynamicCast(Void* , Int32 , Void* , Void* , Int32 )
at Autodesk.Revit.Proxy.DB.FaceProxy.getGFace()
at Autodesk.Revit.Proxy.DB.FaceProxy.getSurface()
at Autodesk.Revit.Proxy.DB.PlanarFaceProxy.getPlane()
at Autodesk.Revit.DB.PlanarFace.getVector(Int32 index, Boolean adjustForFaceFlip)
at Microsoft.Scripting.Interpreter.FuncCallInstruction2.Invoke(Object arg0) at IronPython.Runtime.Binding.PythonGetMemberBinder.FastPropertyGet1.GetProperty(CallSite site, TSelfType target, CodeContext context)
at Microsoft.Scripting.Interpreter.DynamicInstruction`3.Run(InterpretedFrame frame)
at Microsoft.Scripting.Interpreter.Interpreter.Run(InterpretedFrame frame)
at Microsoft.Scripting.Interpreter.LightLambda.Run2[T0,T1,TRet](T0 arg0, T1 arg1)
at IronPython.Compiler.PythonScriptCode.RunWorker(CodeContext ctx)
at Microsoft.Scripting.Hosting.ScriptSource.Execute(ScriptScope scope)
at DSIronPython.IronPythonEvaluator.EvaluateIronPythonScript(String code, IList bindingNames, IList bindingValues)

and the modified python script:

#Inspired by Fair59 https://forums.autodesk.com/t5/revit-api-forum/use-of-align-function-programatically-to-change-the-alignment-of/td-p/6008184
#Suggestion by Mark Ackerley

#Alban de Chasteigner 2020
#twitter : @geniusloci_bim
#geniusloci.bim@gmail.com

import clr
import System
clr.AddReference("RevitAPI")
import Autodesk
from Autodesk.Revit.DB import *
#import System
#from System import Guid

clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument

# Import ToDSType(bool) extension method
clr.AddReference("RevitNodes")
import Revit
clr.ImportExtensions(Revit.Elements)
# Import GeometryConversion
clr.ImportExtensions(Revit.GeometryConversion)

elements = UnwrapElement(IN[0]) if isinstance(IN[0],list) else [UnwrapElement(IN[0])]
alignpoint = UnwrapElement(IN[1]) if isinstance(IN[1],list) else [UnwrapElement(IN[1])]

# Use point from input
Dynpoints=[]
for i in alignpoint:
	Dynpoints.append(i.ToXyz(True))

for item in elements:
	if item.ToString() == 'Autodesk.Revit.DB.Floor' or item.ToString() == 'Autodesk.Revit.DB.RoofBase' or item.ToString() == 'Autodesk.Revit.DB.FootPrintRoof':
		ext = HostObjectUtils.GetTopFaces(item)
		end=0
	elif item.ToString() == 'Autodesk.Revit.DB.Wall':
		#get the external face reference of the item as an array
		ext = HostObjectUtils.GetSideFaces(item,ShellLayerType.Exterior)
		end=1
	else :
		ext = HostObjectUtils.GetBottomFaces(item)
		end=1
	#for (the array containing) the external face reference, get it's geometry
	for e in ext:
		extFace = item.GetGeometryObjectFromReference(e)
		#get the external face surface edges as lines, get the first line (the bottom)
		for	cv in extFace.GetEdgesAsCurveLoops()[0]:
			#get the finish point of the line
			corner = cv.GetEndPoint(end)
		#Check for model surfacepattern
		mat=doc.GetElement(extFace.MaterialElementId)
		patternType = doc.GetElement(mat.SurfaceForegroundPatternId)
		pattern = patternType.GetFillPattern()
		#Get number of gridLines in pattern                
		gridCount = pattern.GridCount
		#Construct StableRepresentation to top face reference
		StableRef = e.ConvertToStableRepresentation(doc)
		
		#we want horizontal and vertical refs so we want 2 arrays
		for hatchindex in range(0, gridCount):
			refAr = ReferenceArray()
			ip=0
			while ip<2:
				#generate an index for each hatch reference
				index = (hatchindex+1) + (ip * gridCount *2)
				#create a string for each hatch reference using the face reference and hatch index
				StableHatchString = StableRef + str.Format("/{0}", index)
				#generate a new reference for each hatch reference using the string
				HatchRef = Reference.ParseFromStableRepresentation(doc, StableHatchString)
				refAr.Append(HatchRef)
				ip += 1

			TransactionManager.Instance.EnsureInTransaction(doc)
			#create dimension to use it's references and length
			#this uses 2 references to the hatch pattern for each orientation
			_dimension = doc.Create.NewDimension(doc.ActiveView, Line.CreateBound(XYZ.Zero, XYZ(10, 0, 0)), refAr)
			ElementTransformUtils.MoveElement(doc, _dimension.Id, XYZ(.1, 0, 0))
			r1 = _dimension.References.get_Item(0)
			direction =_dimension.Curve.Direction
			hatchDirection = direction.CrossProduct(extFace.FaceNormal).Normalize()
			#move back by half because origin is always centered on the dimension
			origin = _dimension.Origin.Subtract(direction.Multiply(_dimension.Value/2))
			#create reference plane at the pattern location
			pl = doc.Create.NewReferencePlane(origin.Add(hatchDirection.Multiply(3)),origin,extFace.FaceNormal.Multiply(3),doc.ActiveView)
			#generate a unique name for the reference plane using it's Guid
			#pl.Name = str.Format("{0}_{1}","ref", Guid.NewGuid())
			TransactionManager.Instance.TransactionTaskDone()

			stableRef = str.Format("{0}:0:{1}",pl.UniqueId,"SURFACE")
			ref2Plane = Reference.ParseFromStableRepresentation(doc,stableRef)
	
			TransactionManager.Instance.ForceCloseTransaction()
			TransactionManager.Instance.EnsureInTransaction(doc)
			doc.Create.NewAlignment(doc.ActiveView,ref2Plane,r1)
			#translation = origin.Subtract(corner) <- changed
			translation = origin.Subtract(Dynpoints[0])
			ElementTransformUtils.MoveElement(doc,pl.Id,-translation)
			
			#we don't need the dimensions or reference planes any more
			doc.Delete(_dimension.Id)
			doc.Delete(pl.Id)
			TransactionManager.Instance.TransactionTaskDone()

OUT="Success"

I hope your suggestion will help me to complete this script.

Regards
Piotr

I need to shift our brick surface pattern in elevation views for a lot of walls. This Genius Loci node is well, genius! Problem is, I’m not able to get the results I’m looking for in Revit 2023. I’m in an elevation view, using the “Select Model Elements” node and connecting to the “Align Pattern to Edges” node. No change when I run the script. I’ve seen images where it usually says “Success” at the end, but now I’m seeing “null”.

Does it not work with Revit 2023? Or am I not running it correctly? Thanks!

@piotr.spyra
Did you manage to solve this?
I tried to use this and sometimes it works and sometimes doesn’t, never worked with multiple walls,
and it only works when my 3D view is activated