The level does not exist in the given document. Create openings from linked file with Python

Hello everyone. I’m not a programmer, but I’m trying to automize floor and wall openings in revit.
So basically I took a script but it didnt work for me.The script works in communication model with architectural model linked.I found that it doesn’t look right, like it doesn’t take all the stuff from a link. So I used python script to get the linked document (in[10]) It was able to pick up walls and floors. However I couldn’t identify further issues. The current Error I get: The level does not exist in the given document. (in createOpenings function). Thats my current code
I’m pretty sure its due to mess with a linked document.

#-------------------------------------------------------------------------------------------------------------------------
DOWNLOAD LIBRARIES
#-------------------------------------------------------------------------------------------------------------------------
import clr
import math
from System.Collections.Generic import *

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

# Define options Revit API
options = Options()
intersectionOptions = SolidCurveIntersectionOptions()						# find intersection
nonStructural = Autodesk.Revit.DB.Structure.StructuralType.NonStructural	# for pasting a family to a host

#Communication categories
pipeCategory = BuiltInCategory.OST_PipeCurves
ductCategory = BuiltInCategory.OST_DuctCurves
conduitCategory = BuiltInCategory.OST_Conduit
cableTrayCategory = BuiltInCategory.OST_CableTray

clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

#-------------------------------------------------------------------------------------------------------------------------
# INPUT
#-------------------------------------------------------------------------------------------------------------------------

# Openings families
rectangularFamilyForWall = UnwrapElement(IN[0])		# rectangular for walls
roundFamilyForWall = UnwrapElement(IN[1])			# round for walls
rectangularFamilyForFloor = UnwrapElement(IN[2])	# rectangular for floors
roundFamilyForFloor = UnwrapElement(IN[3])			# round for floors

# Selected construction elements
selectElements = UnwrapElement(IN[4])
try:
	selectElements.Count
except:
	if selectElements is not None:
		selectElements = [UnwrapElement(IN[4])]

# Openings margin
isReservByCoefficient = IN[5]	# ByCoefficient?
if isReservByCoefficient:
	reserv = IN[6]				# margin by sides proportion
else:
	reserv = IN[6] / 304.8		# margin in mm

# Condition for round openings
maxAspectRatio = IN[7]			# max comminication dimension ratio
maxDiameter = IN[8] / 304.8		# max diameter

# Linked files
isLinkDocument = IN[9] 			# constructions in linked file?
nameLinkDocument = IN[10]		# a part of a linked file name

# Constructions processing
includeWalls = IN[11]			# walls ?
includeFloors = IN[12]			# floors?

# Parameter values for openings
comment = IN[13]				# date or other comment
disciplineName = IN[14]			# opening discipline

# Opening parameters names
disciplineParameter = IN[15]
diameterParameter = IN[16]
widthParameter = IN[17]
heightParameter = IN[18]
commentParameter = IN[19]



#-------------------------------------------------------------------------------------------------------------------------
# EXTRACTING DATA
#-------------------------------------------------------------------------------------------------------------------------

# Current file
currentDoc = DocumentManager.Instance.CurrentDBDocument

# File with construction
docWithConstruction = UnwrapElement(IN[10])

#-------------------------------------------------------------------------------------------------------------------------
# CREATING CLASSES AND FUNCTIONS
#-------------------------------------------------------------------------------------------------------------------------

# Wall class
class WallObject:
	# Properties
	def __init__(self, instance, width, A, B, solid, level):
		self.instance = instance
		self.width = width
		self.A = A
		self.B = B
		self.solid = solid
		self.level = level

# Floors
class FloorObject:
	def __init__(self, instance, solid, level):
		self.instance = instance
		self.solid = solid
		self.level = level

# Communications
class CommunicationObject:
	def __init__(self, width, height, A, B, C, direction, centerPoint):
		self.width = width
		self.height = height
		self.A = A
		self.B = B
		self.C = C
		self.direction = direction
		self.centerPoint = centerPoint

# Getting coordinates of starting and ending point of a line
def getLineCoordinates(line):
	endPoint0 = line.GetEndPoint(0)
	endPoint1 = line.GetEndPoint(1)
	x0 = endPoint0.X; y0 = endPoint0.Y; z0 = endPoint0.Z
	x1 = endPoint1.X; y1 = endPoint1.Y; z1 = endPoint1.Z
	coordinates = x0, y0, z0, x1, y1, z1
	return coordinates

# Getting a construction volume geometry
def getSolid(construction):
	geometry = construction.get_Geometry(options)
	for object in geometry:
		solid = object
	return solid

# Creating Wall class object
def createWallObject(wall):
	width = wall.Width
	x0, y0, z0, x1, y1, z1 = getLineCoordinates(wall.Location.Curve)
	A = y0 - y1
	B = x1 - x0
	solid = getSolid(wall)
	level = docWithConstruction.GetElement(wall.LevelId)
	wallObject = WallObject(wall, width, A, B, solid, level)
	return wallObject

# # Creating Floor class object
def createFloorObject(floor):
	solid = getSolid(floor)
	level = docWithConstruction.GetElement(floor.LevelId)
	floorObject = FloorObject(floor, solid, level)
	return floorObject

# Getting model Instanses of different classes
def getInstances(revitClass):
	instances = FilteredElementCollector(docWithConstruction).OfClass(revitClass).WhereElementIsNotElementType().ToElements()
	return instances

# Create openings in construction element
def createOpenings(category, constructionObject):

	# Create Communication
	def createCommunication(communication):

		# Getting communication section measurements
		def getCommunicationSize():
			# For pipes
			if category == pipeCategory:
				diameter = communication.get_Parameter(BuiltInParameter.RBS_PIPE_OUTER_DIAMETER).AsDouble()
				width = diameter
				height = diameter
			# For ducts
			elif category == ductCategory:
				try:
					diameter = communication.get_Parameter(BuiltInParameter.RBS_CURVE_DIAMETER_PARAM).AsDouble()
					width = diameter
					height = diameter
				except:
					width = communication.get_Parameter(BuiltInParameter.RBS_CURVE_WIDTH_PARAM).AsDouble()
					height = communication.get_Parameter(BuiltInParameter.RBS_CURVE_HEIGHT_PARAM).AsDouble()
			# For Conduit
			elif category == conduitCategory:
				diameter = communication.get_Parameter(BuiltInParameter.RBS_CONDUIT_DIAMETER_PARAM).AsDouble()
				width = diameter
				height = diameter
			# For cables
			else:
				width = communication.get_Parameter(BuiltInParameter.RBS_CABLETRAY_WIDTH_PARAM).AsDouble()
				height = communication.get_Parameter(BuiltInParameter.RBS_CABLETRAY_HEIGHT_PARAM).AsDouble()
			return width, height

		# Getting coordinates of starting and ending point of a communication line
		def getCommunicationLineCoordinates():
			curve = communication.Location.Curve
			lines = constructionObject.solid.IntersectWithCurve(curve, intersectionOptions)
			try:
				line = lines.GetCurveSegment(0)
			except:
				line = curve
			coordinates = getLineCoordinates(line)
			return coordinates

		# Defining communication direction
		def getCommunicationDirection():

			# Defining the normal to a communication line for floor openings
			def getFaceNormal():
				geometry = communication.get_Geometry(options)
				for object in geometry:
					solid = object
				faces = solid.Faces
				for face in faces:
					_faceNormal = face.FaceNormal

					if _faceNormal.Z == 0:
						faceNormal = _faceNormal
				return faceNormal


			if constructionObject.__class__.__name__ == 'FloorObject':

				if category == ductCategory:
					try:
						diameter = communication.get_Parameter(BuiltInParameter.RBS_CURVE_DIAMETER_PARAM).AsDouble()
						direction = XYZ(0, 0, 0)
					except:

						direction = getFaceNormal()

				elif category == cableTrayCategory:

					faceNormal = getFaceNormal()
					direction = XYZ(faceNormal.Y, -faceNormal.X, faceNormal.Z)

				else:
					direction = XYZ(0, 0, 0)
				return direction

		width, height = getCommunicationSize()
		x0, y0, z0, x1, y1, z1 = getCommunicationLineCoordinates()
		A = y0 - y1
		B = x1 - x0
		C = z1 - z0
		direction = getCommunicationDirection()
		centerPoint = XYZ((x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2)
		# Communication create
		communicationObject = CommunicationObject(width, height, A, B, C, direction, centerPoint)
		return communicationObject

	# Opening create
	def createOpening(rectangularFamily, roundFamily, minWidth, minHeight, centerPoint, direction):

		# Create opnings element and set parameters
		def _createOpening(maxSide, minSide, openingWidth, openingHeight):
			# Set diameter
			if maxSide / minSide <= maxAspectRatio and maxSide <= maxDiameter:
				if constructionObject.__class__.__name__ == 'WallObject':
					_opening = currentDoc.Create.NewFamilyInstance(centerPoint, roundFamily, constructionObject.instance, constructionObject.level, nonStructural)
				else:
					_opening = currentDoc.Create.NewFamilyInstance(centerPoint, roundFamily, direction, constructionObject.instance, nonStructural)
					_opening.get_Parameter(BuiltInParameter.FAMILY_LEVEL_PARAM).Set(constructionObject.level.Id)
				_opening.LookupParameter(diameterParameter).Set(maxSide)
			# Set height width parameters
			else:
				if constructionObject.__class__.__name__ == 'WallObject':
					_opening = currentDoc.Create.NewFamilyInstance(centerPoint, rectangularFamily, constructionObject.instance, constructionObject.level, nonStructural)
				else:
					_opening = currentDoc.Create.NewFamilyInstance(centerPoint, rectangularFamily, direction, constructionObject.instance, nonStructural)
					_opening.get_Parameter(BuiltInParameter.FAMILY_LEVEL_PARAM).Set(constructionObject.level.Id)
				_opening.LookupParameter(widthParameter).Set(openingWidth)
				_opening.LookupParameter(heightParameter).Set(openingHeight)
			return _opening

		# Round opening check
		if minWidth is not None and minHeight is not None:
			# Margin account
			if isReservByCoefficient:									# если допуск задан коэффициентом
				openingWidth = minWidth * reserv
				openingHeight = minHeight * reserv
			else:														# если допуск задан в миллиметрах
				openingWidth = minWidth + reserv
				openingHeight = minHeight + reserv
			# Round to cm
			openingWidth = round(openingWidth * 304.8, -1) / 304.8
			openingHeight = round(openingHeight * 304.8, -1) / 304.8
			# Create opening
			if openingHeight > openingWidth:
				opening = _createOpening(openingHeight, openingWidth, openingWidth, openingHeight)	# случай, если высота больше ширины
			else:
				opening = _createOpening(openingWidth, openingHeight, openingWidth, openingHeight)	# случай, если ширина больше высоты
			# Set other parameters
			opening.LookupParameter(disciplineParameter).Set(disciplineName)
			opening.LookupParameter(commentParameter).Set(comment)
			return opening
		# If could not create
		else:
			message = 'Communication is inside the construction'
			return message

	# Create opening in walls
	def createWallOpening(communicationObject):

		# Calculating min opening side
		def getMinSide(cosin, communicationSide):
			angle = round(math.degrees(math.acos(cosin)))
			if angle > 90:
				angle = 180 - angle
			angleRad = math.radians(angle)
			if angle != 180 and angle != 0:
				#
				minSide = constructionObject.width / math.tan(angleRad) + communicationSide / math.sin(angleRad)
				return minSide

		# Getting cosin of construction and communication intersection
		try:
			cosin = (constructionObject.A * communicationObject.A + constructionObject.B * communicationObject.B) / ((constructionObject.A**2 + constructionObject.B**2)**0.5 * (communicationObject.A**2 + communicationObject.B**2)**0.5)
		except:
			cosin = 1
		minWidth = getMinSide(cosin, communicationObject.width)
		# Getting intersection angle (between the pipe and horizontal plane)
		cosin = communicationObject.C / ((communicationObject.A**2 + communicationObject.B**2 + communicationObject.C**2)**0.5)
		minHeight = getMinSide(cosin, communicationObject.height)	# min opening height
		rectangularFamily = rectangularFamilyForWall				# opening family
		roundFamily = roundFamilyForWall							# round opening family
		# Create opening
		wallOpening = createOpening(rectangularFamily, roundFamily, minWidth, minHeight, communicationObject.centerPoint, None)
		return wallOpening

	# Create floor openings
	def createFloorOpening(communicationObject):
		minWidth = communicationObject.width
		minHeight = communicationObject.height
		direction = communicationObject.direction
		rectangularFamily = rectangularFamilyForFloor
		roundFamily = roundFamilyForFloor
		# Создание проёма
		floorOpening = createOpening(rectangularFamily, roundFamily, minWidth, minHeight, communicationObject.centerPoint, direction)
		return floorOpening

	# Getting model communications
	communications = FilteredElementCollector(currentDoc).OfCategory(category).WhereElementIsNotElementType()
	#  Gettting only intersected comminication
	intersectingCommunications = communications.WherePasses(ElementIntersectsSolidFilter(constructionObject.solid)).ToElements()
	# Forming openings list
	openings = []

	for communication in intersectingCommunications:

		communicationObject = createCommunication(communication)
		# For walls
		if constructionObject.__class__.__name__ == 'WallObject':
			opening = createWallOpening(communicationObject)
		# For floors
		else:
			opening = createFloorOpening(communicationObject)
		openings.append([communication, opening])
	return openings

#-------------------------------------------------------------------------------------------------------------------------
# TRANSACTION
#-------------------------------------------------------------------------------------------------------------------------

# Getting constructions
walls = getInstances(Wall)
floors = getInstances(Floor)


TransactionManager.Instance.EnsureInTransaction(currentDoc)


if includeWalls:

	rectangularFamilyForWall.Activate()
	roundFamilyForWall.Activate()

	wallOpenings = []
	for wall in walls:
		wallObject = createWallObject(wall)
		for category in [pipeCategory, ductCategory, conduitCategory, cableTrayCategory]:
			openings = createOpenings(category, wallObject)
			wallOpenings.append(openings)
else:
	wallOpenings = 'Walls processing disabled'


if includeFloors:
	rectangularFamilyForFloor.Activate()
	roundFamilyForFloor.Activate()
	floorOpenings = []
	for floor in floors:
		floorObject = createFloorObject(floor)
		for category in [pipeCategory, ductCategory, conduitCategory, cableTrayCategory]:
			openings = createOpenings(category, floorObject)
			wallOpenings.append(openings)
else:
	floorOpenings = 'Floors processing disabled


TransactionManager.Instance.TransactionTaskDone()

#-------------------------------------------------------------------------------------------------------------------------
# OUT
#-------------------------------------------------------------------------------------------------------------------------

OUT = wallOpenings, floorOpenings

@Misa ,

I am not sure regarding the error, but you have not so much libraries loaded f.e. import sys
i use always this framing
https://primer.dynamobim.org/10_Custom-Nodes/10-6_Python-Templates.html

It didn’t help. But thanks for the advice anyway

I believe you will need to add some code to get the actual level in your live model with same name if a linked object is used to provide it’s level for placing the openings.

Currently the coding issues probably occur within the createWallObject and createFloorObject functions. If this level of coding is beyond your abilities currently, I would suggest trying to build this workflow with nodes instead as a learning exercise. It looks like this is probably a company specific script due to the level of function/class setup in it - this is not common for users unless they are building tools in a shared coding environment where this is a more user friendly approach.

Here’s an example of a helper function to switch to a live level from a linked one if it has a matching name:

# Boilerplate text
import clr

clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager 

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

# Current doc/app/ui
doc = DocumentManager.Instance.CurrentDBDocument

# Define list/unwrap list functions
def uwlist(input):
    result = input if isinstance(input, list) else [input]
    return UnwrapElement(result)

# Get input linked levels and their names
linkedLevels = uwlist(IN[0])

# Get host levels and their names
liveLevels = FilteredElementCollector(doc).OfClass(Level).ToElements()
liveNames = [l.Name for l in liveLevels]

# Function to get live level with same name
def liveLevel(f_lvl,f_names,f_levels):
	# Get the input levels name
	f_name = f_lvl.Name
	try:
		# Try to get level with same name
		live_ind = f_names.index(f_name)
		return f_levels[live_ind]
	except:
		# Otherwise return null
		return None

# Get the live level of same name
OUT = [liveLevel(l,liveNames,liveLevels) for l in linkedLevels]