FamilyInstance.ByGeometry not working in Revit 2022

I highly recommend contacting @Karam_Baki, who has done a fair bit of research on this issue and its implications. He has done a lot of work with freeform/directshape/mesh in Dynamo/Revit and was impacted by these changes quite heavily when they first hit. My understanding is he has worked around them where possible and has also had contact with the Autodesk technical team about it.

3 Likes

Thanks for the tag @GavinCrump

I knew this issue will start affecting many people by time…

How should I start…

Well, basically a decision was made to remove an old branch of code (document.import) and replace it with (ShapeImporter) code…

Benefits & Consequences ? well, better Revit performance, consistent import units… but LIMITED range of importable geometry…

Why ? well, the old branch of code did lots of healing and fixing to the geometry prior imporintg it, but has resulted (sometimes) in an undesirable performance impact.

What is healing ? well, basically, the source of troubles in Revit is it’s small geometrical tolerance, around 0.07mm … when a geometry has a surface that has an edge, of this dimension… and that edge is stitched to another one… it cannot simply import it… (Many other complex situations as well)
So healing is basically detecting those errors and re-creating the surfaces and stitching them on the fly…
Healing has so many techniques… (Mentioned later in the post)

Now after Autodesk removed that old branch of code, we can no longer import the wide range of geometry that we could through the API as before…

*Even if you imported (many) forms in older Revit versions, and opened them in 2022, they will disappear, or behave strangely… etc

I tried a lot to convince Revit team by undo of what they did until they make the new code (healable to imported geometry) and has the ability to import same wide range of geometry… but what’s made has been made… and Autodesk is… well Autodesk…

Anyways, to solve your issue, and hopefully many others.
You may try to use the node called “K-Family Import” from Synthesize toolkit package…

I implemented ALL possible healing methods in Dynamo to help fix the geometry prior importing it to Revit…

I tried your SAT file… and it worked, but there’s a catch…
I CANNO GAURENTEE THAT IT STAYS SOLID

When geometry is healed, (Surfaces are re-created) it is not guaranteed that they actually stitch together back into a proper solid… so it depends…

Autodesk’s old code was able to stitch them back into Solids, but in the cost of making Revit somehow ignoring the issues… which of course caused performance problems.

But I really recommend using it within the package to have your inputs ready.

There’s also in extra folder dyn file called Family.SAT Sync… it uses K-Family Insert in a very user-friendly way… that allows you to do a refreshable imports…etc

Code snippets in the following posts, there is a character limit…

Here are some images during the journey of studying this behavior…
You can clearly see after healing, or “Laundering” the geometry in Dynamo… I can get it inside.

4 Likes
# Created By Karam Baki, karam@aecedx.com

def smartFreeForm(geoms,scaleup,doc,centermode=False,category="Generic Models",importinstance=False,singlefinalprocess=False,safemode=False,temp_path=System.IO.Path.GetTempPath(),bypasscheck=False,returnfailures=False):
	class WarnSwallowImport(IFailuresPreprocessor):
		def PreprocessFailures(self, failuresAccessor):
			failuresAccessor.DeleteAllWarnings()
			return FailureProcessingResult.Continue
	def finalimport(importinstance,geoms,satopt,singlefinalprocess):
		randomname = ''.join([random.choice(string.ascii_letters + string.digits) for n in xrange(5)])
		sat_path = '%s%s.sat' % (temp_path, randomname)
		dg.Geometry.ExportToSAT(geoms,sat_path)
		if importinstance:
			imported = doc.Import(sat_path,satopt,None)
			element = doc.GetElement(imported)
			element.Pinned = False
			result = [element]
		else:
			result = []
			shapeImporter = ShapeImporter()
			converted = list(shapeImporter.Convert(doc, sat_path))
			if doc.IsFamilyDocument:
				for e in converted:
					try:
						result.append(FreeFormElement.Create(doc,e))
					except:
						pass
			else:
				form = DirectShape.CreateElement(doc,doc.Settings.Categories.get_Item(category).Id)
				try:
					form.SetShape(converted)
				except:
					doc.Delete(form.Id)
					imported = doc.Import(sat_path,satopt,None)
					element = doc.GetElement(imported)
					element.Pinned = False
					form = element
				result.append(form)
			shapeImporter.Dispose()
		System.IO.File.Delete(sat_path)
		return result
	def allisone(testlist):
		for t in testlist:
			if t != 1:
				return False
		return True
	def tolist(obj1):
		if hasattr(obj1,'__iter__'): return obj1
		else: return [obj1]
	def flatten(x):
		result = []
		for el in x:
			if hasattr(el, "__iter__") and not isinstance(el, basestring):
				result.extend(flatten(el))
			else:
				result.append(el)
		return result
	def movecenter(doc,elements):
		try:
			elements = flatten(elements)
			pts = []
			for e in elements:
				try:
					bbox = e.get_BoundingBox(None)
					pts.append(bbox.Max.ToPoint())
					pts.append(bbox.Min.ToPoint())
				except:
					pass
			Xs = []
			Ys = []
			Zs = []
			for pt in pts:
				Xs.append(pt.X)
				Ys.append(pt.Y)
				Zs.append(pt.Z)
			bigbox = dg.BoundingBox.ByCorners(dg.Point.ByCoordinates(max(Xs),max(Ys),max(Zs)),dg.Point.ByCoordinates(min(Xs),min(Ys),min(Zs)))
			minp = bigbox.MinPoint
			maxp = bigbox.MaxPoint
			midpoint = dg.Curve.PointAtParameter(dg.Line.ByStartPointEndPoint(minp,dg.Point.ByCoordinates(maxp.X,maxp.Y,minp.Z)),0.5)
			mainvec = dg.Vector.ByTwoPoints(midpoint, dg.Point.Origin())
			scaledvec = mainvec.Scale(unitconverter(1,doc,False))
			for e in elements:
				ElementTransformUtils.MoveElement(doc,e.Id,scaledvec.ToXyz())
			return mainvec
		except:
			return None
	def unitconverter(value,doc,frominternal=True):
		if int(DocumentManager.Instance.CurrentUIApplication.Application.VersionNumber) < 2021:
			if frominternal:
				value = UnitUtils.ConvertFromInternalUnits(value,doc.GetUnits().GetFormatOptions(UnitType.UT_Length).DisplayUnits)
			else:
				value = UnitUtils.ConvertToInternalUnits(value,doc.GetUnits().GetFormatOptions(UnitType.UT_Length).DisplayUnits)
		else:
			if frominternal:
				value = UnitUtils.ConvertFromInternalUnits(value,doc.GetUnits().GetFormatOptions(SpecTypeId.Length).GetUnitTypeId())
			else:
				value = UnitUtils.ConvertToInternalUnits(value,doc.GetUnits().GetFormatOptions(SpecTypeId.Length).GetUnitTypeId())
		return value
	def smartrevitsolid(surfs,doc,importinstance,safemode,geometrymode,disposeinit,temp_path):
		def polysurfaceOP(surfaces,chunks=12):
			def smartpolysurface(surfaces,times,round=0):
				if len(surfaces) > chunks:
					if times == 0:
						return surfaces
					result = []
					chopped = [surfaces[x:x+chunks] for x in xrange(0, len(surfaces), chunks)]
					for chop in chopped:
						try:
							joined = dg.PolySurface.ByJoinedSurfaces(chop)
							#if round != 0:
							#	for c in chop:
							#		c.Dispose()
							result.append(joined)
						except:
							for c in chop:
								result.append(c)
					times -= 1
					round += 1
					result = smartpolysurface(result,times,round)
					return result
				else:
					if isinstance(surfaces, dg.PolySurface):
						return surfaces
					else:
						return dg.PolySurface.ByJoinedSurfaces(surfaces)
			surfaces = flatten(tolist(surfaces))
			n = len(surfaces)
			times = 0
			while n > 1:
				n //= chunks
				times += 1
			times *= 1.5
			times = int(round(times))
			joined = smartpolysurface(surfaces,times)
			if isinstance(joined, list):
				try:
					joined = dg.PolySurface.ByJoinedSurfaces(joined)
				except:
					joined = joined
			return joined
		def isActuallygood(geomorsat,converted,firstTime,geometrymode,bypasscheck):
			if firstTime:
				if bypasscheck == False:
					originalfaces = []
					if isinstance(geomorsat,dg.Solid) or isinstance(geomorsat,dg.PolySurface):
						exploded = dg.Geometry.Explode(geomorsat)
						for exp in exploded:
							originalfaces.append(exp)
							if exp.Closed:
								originalfaces.append(exp)
					elif isinstance(geomorsat,dg.Surface):
						if geometrymode == False:
							originalfaces.append(geomorsat)
							if geomorsat.Closed:
								originalfaces.append(geomorsat)
						else:
							exploded = dg.Geometry.Explode(geomorsat)
							for exp in exploded:
								originalfaces.append(exp)
								if exp.Closed:
									originalfaces.append(exp)
					elif isinstance(geomorsat,str):
						geomsorsat = dg.Geometry.ImportFromSAT(geomorsat)
						for geomorsat in geomsorsat:
							if isinstance(geomorsat,dg.Solid) or isinstance(geomorsat,dg.PolySurface):
								exploded = dg.Geometry.Explode(geomorsat)
								for exp in exploded:
									originalfaces.append(exp)
									if exp.Closed:
										originalfaces.append(exp)
								if geometrymode == False and doc.IsFamilyDocument:
									geomorsat.Dispose()
							elif isinstance(geomorsat,dg.Surface):
								if geometrymode == False:
									originalfaces.append(geomorsat)
									if geomorsat.Closed:
										originalfaces.append(geomorsat)
								else:
									exploded = dg.Geometry.Explode(geomorsat)
									for exp in exploded:
										originalfaces.append(exp)
										if exp.Closed:
											originalfaces.append(exp)
						if geometrymode:
							geomorsat = list(geomsorsat)
					originalnum = len(originalfaces)
					for org in originalfaces:
						org.Dispose()
					FFfaces = []
					for e in converted:
						getfaces = e.Faces
						for gotten in getfaces:
							FFfaces.append(gotten)
					FFfacesnum = len(FFfaces)
					if FFfacesnum == originalnum:
						samefaces = True
					else:
						samefaces = False
				else:
					samefaces = True
				freeforms = []
				if doc.IsFamilyDocument == False and singlefinalprocess and geometrymode == False and samefaces and importinstance == False:
					form = DirectShape.CreateElement(doc,doc.Settings.Categories.get_Item(category).Id)
					try:
						form.SetShape(converted)
						freeforms.append(form)
					except:
						doc.Delete(form.Id)
						satopt = SATImportOptions()
						freeforms.append(finalimport(True,geomsorsat,satopt,True)[0])
						satopt.Dispose()
				if geometrymode == False and samefaces and importinstance == False:
					for e in converted:
						if doc.IsFamilyDocument:
							form = FreeFormElement.Create(doc,e)
							freeforms.append(form)
						elif singlefinalprocess == False:
							form = DirectShape.CreateElement(doc,doc.Settings.Categories.get_Item(category).Id)
							try:
								form.SetShape([e])
								freeforms.append(form)
							except:
								doc.Delete(form.Id)
			else:
				samefaces = False
			if geometrymode:
				if isinstance(geomorsat, list):
					freeforms = geomorsat
				else:
					freeforms = [geomorsat]
			if len(freeforms) == 0:
				return [False]
			if firstTime == False:
				return freeforms
			elif samefaces and firstTime:
				return freeforms
			else:
				return [False]
		def isbellowarea(surface):
			if round(meternumber(surface.Area),2) < meternumber(0.0025):
				return True
			else:
				return False
		def isbellowlength(surface):
			edges = surface.Edges
			for e in edges:
				curvegeom = e.CurveGeometry
				length = curvegeom.Length
				curvegeom.Dispose()
				if length <= 0.0007803686370625:
					return True
			return False
		def meternumber(value):
			value = value
			return value
		def metersurfarea(surface):
			area = meternumber(surface.Area)
			return area
		def almostsame(surf1,surf2,tollerance = 2):
			if round(metersurfarea(surf1),tollerance) == round(metersurfarea(surf2),tollerance):
				return True
			else:
				return False
		def quickSATcheck(sat_path,customscale=[1,1,1],importinstance=False,firstTime=True,geometrymode=False,bypasscheck=False):
			if allisone(customscale) == False:
				return [False]
			converted = []
			shapeImporter = ShapeImporter()
			converted = list(shapeImporter.Convert(doc, sat_path))
			shapeImporter.Dispose()
			if bypasscheck:
				cleanconverted = []
				for e in converted:
					if e.ToString() == "Autodesk.Revit.DB.Mesh":
						continue
					else:
						cleanconverted.append(e)
				converted = cleanconverted
			else:
				for e in converted:
					if e.ToString() == "Autodesk.Revit.DB.Mesh":
						return [False]
			if len(converted) != 0:
				converted = isActuallygood(sat_path,converted,firstTime,geometrymode,bypasscheck)
			if (bypasscheck and importinstance) or (importinstance and converted[0] != False):
				satopt = SATImportOptions()
				imported = doc.Import(sat_path,satopt,None)
				element = doc.GetElement(imported)
				element.Pinned = False
				converted = [element]
				satopt.Dispose()
			if len(converted) == 0:
				return [False]
			return converted
		def batchattempt(temp_path,surfs,importinstance,bypasscheck,scaleup,sendmessage):
			randomname = ''.join([random.choice(string.ascii_letters + string.digits) for n in xrange(5)])
			x_sat_path = '%s%s.sat' % (temp_path, randomname)
			dg.Geometry.ExportToSAT(surfs,x_sat_path)
			if importinstance and bypasscheck:
				satopt = SATImportOptions()
				imported = doc.Import(x_sat_path,satopt,None)
				element = doc.GetElement(imported)
				element.Pinned = False
				x_finalsurfaces = [element]
				satopt.Dispose()
			else:
				x_finalsurfaces = quickSATcheck(x_sat_path,scaleup,importinstance,True,False,bypasscheck)
			System.IO.File.Delete(x_sat_path)
			if x_finalsurfaces[0] == False:
				return [False]
			else:
				x_choppedfinal = []
				for xf in x_finalsurfaces:
					x_choppedfinal.append([xf])
				return x_choppedfinal,sendmessage,True
		def SurfacesLaundry(surfs,doc,importinstance,geometrymode,temp_path):
			#scaleup = round(unitconverter(3.280839895,doc,True),8)
			def tolist(obj1):
				if hasattr(obj1,'__iter__'): return obj1
				else: return [obj1]	
			def checkcompatible(geoms,customscale=[1,1,1],firstTime=False,geometrymode=True,bypasscheck=False):
				randomname = ''.join([random.choice(string.ascii_letters + string.digits) for n in xrange(5)])
				sat_path = '%s%s.sat' % (temp_path, randomname)
				if allisone(customscale) == False:
					scaledgeoms = geoms.Scale(customscale[0],customscale[1],customscale[2])
					geoms = scaledgeoms
				dg.Geometry.ExportToSAT(geoms,sat_path)
				converted = []
				shapeImporter = ShapeImporter()
				converted = list(shapeImporter.Convert(doc, sat_path))
				shapeImporter.Dispose()
				System.IO.File.Delete(sat_path)
				for e in converted:
					if e.ToString() == "Autodesk.Revit.DB.Mesh":
						return [False]
				if len(converted) != 0:
					converted = isActuallygood(geoms,converted,firstTime,geometrymode,bypasscheck)
				if len(converted) == 0:
					return [False]
				return converted
			def revitOP(finalsub,nurbs=None,cleanexplodes=None,customscale=[1,1,1],disposeNurbs=True,geometrymode=True):
				if nurbs == None and cleanexplodes == None:
					testit = checkcompatible(finalsub,customscale,False,geometrymode)
					if testit[0] == False:
						raise Exception("Geometry Incompatible With Revit")
					else:
						if geometrymode == False:
							finalsub.Dispose()
						return testit
				cleanfinalsub = dg.Geometry.Explode(finalsub)
				revitgeom = []
				for c in cleanfinalsub:
					testit = checkcompatible(c,customscale,False,geometrymode)
					if testit[0] == False:
						c.Dispose()
						raise Exception("Geometry Incompatible With Revit")
					else:
						if geometrymode == False:
							c.Dispose()
						revitgeom.append(testit)
				revitgeom = flatten(revitgeom)
				if disposeNurbs:
					nurbs.Dispose()
				for cleanex in cleanexplodes:
					cleanex.Dispose()
				finalsub.Dispose()
				return revitgeom
			def cleanupSurface(surface):
				def subtractfunction(nurbs,cleanexplodes):
					def loopclean(nurbs,thick,counter):
						result = dg.Surface.SubtractFrom(nurbs,thick)[0]
						if counter != 0:
							nurbs.Dispose()
						thick.Dispose()
						return result
					thickforms = []
					for cleanex in cleanexplodes:
						safearea = metersurfarea(cleanex)/10
						if safearea > meternumber(0.1):
							maxthicken = meternumber(0.1)
						else:
							maxthicken = safearea
						thickforms.append(dg.Surface.Thicken(cleanex,maxthicken,True))
					counter = 0
					for thick in thickforms:
						nurbs = loopclean(nurbs,thick,counter)
						counter += 1
					return nurbs
				def tryingforall(surface,nurbs,cleanexplodes,geometrymode):
					try:
						finalsub = subtractfunction(nurbs,cleanexplodes)
						return revitOP(finalsub,nurbs,cleanexplodes,scaleup,True,geometrymode)
					except:
						try:
							finalsub = dg.Surface.Difference(nurbs,cleanexplodes)
							return revitOP(finalsub,nurbs,cleanexplodes,scaleup,True,geometrymode)
						except Exception as e:
							if str(e) == "Cannot difference an empty list" and almostsame(nurbs,surface):
								finalsub = nurbs
								try:
									return revitOP(finalsub,nurbs,cleanexplodes,scaleup,False,geometrymode)
								except:
									return None,finalsub
							else:
								return None,finalsub
				def gencleanexplodes(subtractnurbs):
					if isinstance(subtractnurbs, list):
						if len(subtractnurbs) == 0:
							return []
					else:
						explodednurbs = dg.Geometry.Explode(subtractnurbs)
						cleanexplodes = []
						if safemode:
							for ex in explodednurbs:
								if ex.Area < meternumber(0.002):
									ex.Dispose()
								else:
									cleanexplodes.append(ex)
						else:
							cleanexplodes = list(explodednurbs)
						subtractnurbs.Dispose()
						return cleanexplodes
				def easymethod(surface,geometrymode):
					try:
						nurbs = dg.Surface.ToNurbsSurface(surface)
						if almostsame(nurbs,surface):
							try:
								return True,revitOP(nurbs,None,None,scaleup,True,geometrymode)
							except:
								return None,nurbs
						else:
							return None,nurbs
					except:
						return None,None
				def isplanarsurf(s):
					norms = [0,0.333,0.666,1]
					pts = []
					for n in norms:
						for n2 in norms:
							pts.append(dg.Surface.PointAtParameter(s,n,n2))
					plane = dg.Plane.ByBestFitThroughPoints(pts)
					status = True
					for p in pts:
						try:
							inter = dg.Geometry.DoesIntersect(p,plane)
							if inter == False:
								status = False
								break
						except:
							pass
					return status
				def joincurves(crvs):
					def PointsEqual(pt1, pt2):
						tolerance = 0.00001
						if (math.fabs(pt1.X - pt2.X) < tolerance and
						    math.fabs(pt1.Y - pt2.Y) < tolerance and
						    math.fabs(pt1.Z - pt2.Z) < tolerance):
							return True
						else:
							return False
					crvs = flatten(tolist(crvs))
					crvGroup = [-1 for x in range(len(crvs))]
					crvGroup[0] = 0
					maxGroupNum = 1
					starts = []
					ends = []
					for crv in crvs:
						starts.append(crv.PointAtParameter(0))
						ends.append(crv.PointAtParameter(1))
					for i in range(len(crvs)):
						if (crvGroup[i] == -1):
							crvGroup[i] = i
						for j in range(len(crvs)):
							if (i != j and
								(PointsEqual(starts[i], starts[j]) or
								PointsEqual(starts[i], ends[j]) or
								PointsEqual(ends[i], starts[j]) or
								PointsEqual(ends[i], ends[j]))):
								if (crvGroup[j] == -1):
									crvGroup[j] = crvGroup[i]
								elif (crvGroup[i] != crvGroup[j]):
									oldNum = crvGroup[j]
									for k in range(len(crvs)):
										if (crvGroup[k] == oldNum):
											crvGroup[k] = crvGroup[i]
					groups = []
					excluded = []
					for n in crvGroup:
						innercount = 0
						tmp = []
						for n2 in crvGroup:
							if n == n2 and n2 not in excluded:
								tmp.append(crvs[innercount])
							innercount += 1
						if len(tmp) == 0:
							continue
						try:
							tmp = dg.PolyCurve.ByJoinedCurves(tmp)
							groups.append(tmp)
						except:
							for t in tmp:
								groups.append(dg.PolyCurve.ByJoinedCurves([t]))
						excluded.append(n)
					joinedcurves = groups
					return joinedcurves
				def trimloopmethod(surface,nurbs,geometrymode):
					try:
						if surface.Closed:
							return [None]
						try:
							joinedperms = [dg.PolyCurve.ByJoinedCurves(surface.PerimeterCurves())]
						except:
							joinedperms = joincurves(surface.PerimeterCurves())
						finalsub = dg.Surface.TrimWithEdgeLoops(nurbs,joinedperms)
						if almostsame(finalsub,surface):
							return revitOP(finalsub,None,None,scaleup,True,geometrymode)
						else:
							return [None]
					except:
						return [None]
				def thickenintersectMethod(surface,nurbs,geometrymode,pointOneMeter):
					try:
						tmp_thicken = dg.Surface.Thicken(surface,pointOneMeter,True)
						isplanar = isplanarsurf(surface)
						if isplanar:
							tmp_pt = dg.Surface.PointAtParameter(surface,0.5,0.5)
							tmp_norm = dg.Surface.NormalAtPoint(surface,tmp_pt)
							intersecter = dg.Plane.ByOriginNormal(tmp_pt,tmp_norm)
							finalsub = dg.Geometry.Intersect(intersecter,tmp_thicken)
							for f in finalsub:
								if isinstance(f,dg.Surface):
									finalsub = f
									break
							tmp_thicken.Dispose()
						else:
							finalsub = dg.Geometry.Intersect(nurbs,tmp_thicken)
							for f in finalsub:
								if isinstance(f,dg.Surface):
									finalsub = f
									break
							tmp_thicken.Dispose()
						try:
							return revitOP(finalsub,None,None,scaleup,True,geometrymode)
						except:
							return None,finalsub
					except:
						return [None]
				def inithickenMethod(surface,nurbs,geometrymode):
					try:
						safearea = metersurfarea(surface)/10
						if safearea > meternumber(0.1):
							maxthicken = meternumber(0.1)
						else:
							maxthicken = safearea
						thickeninit = dg.Surface.Thicken(surface,maxthicken,True)
						try:
							subtractnurbs = dg.Surface.SubtractFrom(nurbs,thickeninit)[0]
						except Exception as e:
							if "index out of range" in str(e) and almostsame(nurbs,surface):
								subtractnurbs = []
							elif "index out of range" in str(e):
								try:
									subtractnurbs = dg.Surface.Difference(nurbs,[surface])
								except Exception as e:
									if str(e) == "Unable to get FACE from BODY: no faces in BODY":
										subtractnurbs = []
						thickeninit.Dispose()
						cleanexplodes = gencleanexplodes(subtractnurbs)
						tryall = tryingforall(surface,nurbs,cleanexplodes,geometrymode)
						if tryall[0] != None:
							return tryall
						else:
							return None,tryall[1]
					except:
						return [None]
				def simplesubmethod(surface,nurbs,geometrymode):
					try:
						try:
							subtractnurbs = dg.Surface.Difference(nurbs,[surface])
						except Exception as e:
							if str(e) == "Unable to get FACE from BODY: no faces in BODY":
								try:
									try:
										return revitOP(nurbs,None,None,scaleup,True,geometrymode)
									except:
										return None,nurbs
								except:
									return [None]
						cleanexplodes = gencleanexplodes(subtractnurbs)
						tryall = tryingforall(surface,nurbs,cleanexplodes,geometrymode)
						if tryall[0] != None:
							return tryall
						else:
							return None,tryall[1]
					except:
						return [None]
				easy = easymethod(surface,geometrymode)
				pointOneMeter = unitconverter(0.3280,doc,True)
				if easy[0] != None:
					return easy[1]
				elif easy[0] == None and easy[1] == None:
					return None
				else:
					nurbs = easy[1]
				failsafesurfs = []
				trimloop = trimloopmethod(surface,nurbs,geometrymode)
				if trimloop[0] != None:
					return trimloop
				simple = simplesubmethod(surface,nurbs,geometrymode)
				if simple[0] != None:
					return simple
				else:
					try:
						failsafesurfs.append(simple[1])
					except:
						pass
				inithick = inithickenMethod(surface,nurbs,geometrymode)
				if inithick[0] != None:
					return inithick
				else:
					try:
						failsafesurfs.append(inithick[1])
					except:
						pass
				thickeninter = thickenintersectMethod(surface,nurbs,geometrymode,pointOneMeter)
				if thickeninter[0] != None:
					return thickeninter
				else:
					try:
						failsafesurfs.append(thickeninter[1])
					except:
						pass
				for failsurf in failsafesurfs:
					surface = failsurf
					easy = easymethod(surface,geometrymode)
					if easy[0] != None:
						return easy[1]
					elif easy[0] == None and easy[1] == None:
						return None
					else:
						nurbs = easy[1]
					trimloop = trimloopmethod(surface,nurbs,geometrymode)
					if trimloop[0] != None:
						return trimloop
					simple = simplesubmethod(surface,nurbs,geometrymode)
					if simple[0] != None:
						return simple
					inithick = inithickenMethod(surface,nurbs,geometrymode)
					if inithick[0] != None:
						return inithick
					thickeninter = thickenintersectMethod(surface,nurbs,geometrymode,pointOneMeter)
					if thickeninter[0] != None:
						return thickeninter
				return None
			sendmessage = False
			is_sat = False
			if isinstance(surfs,str) == False:
				surfs = flatten(tolist(surfs))
				x_scaledown = round(unitconverter(0.3048,doc,False),8)
				x_pure = []
				for xs in surfs:
					if isinstance(xs,dg.Solid) or isinstance(xs,dg.Surface) or isinstance(xs,dg.PolySurface) or isinstance(xs,dg.Cuboid):
						if x_scaledown != 1:
							x_pure.append(xs.Scale(x_scaledown))
						else:
							x_pure.append(xs)
				surfs = x_pure
				bOP = batchattempt(temp_path,surfs,importinstance,bypasscheck,scaleup,sendmessage)
				if bOP[0] == False:
					pass
				else:
					return bOP
			if isinstance(surfs,str):
				if ".sat" in surfs.lower():
					finalsurfaces = quickSATcheck(surfs,scaleup,importinstance,True,False,bypasscheck)
					if finalsurfaces[0] == False:
						surfs = dg.Geometry.ImportFromSAT(surfs)
						x_pure = []
						for xs in surfs:
							if isinstance(xs,dg.Solid) or isinstance(xs,dg.Surface) or isinstance(xs,dg.PolySurface) or isinstance(xs,dg.Cuboid):
								x_pure.append(xs)
						surfs = x_pure
						is_sat = True
					else:
						choppedfinal = []
						for f in finalsurfaces:
							choppedfinal.append([f])
						return choppedfinal,sendmessage,True
				else:
					return [],sendmessage
				scaledown = 1
			else:
				scaledown = round(unitconverter(0.3048,doc,False),8)
			surfs = flatten(tolist(surfs))
			finalsurfaces = []
			precheck = []
			for s in surfs:
				try:
					if isinstance(s,dg.Surface):
						exploded = dg.Geometry.Explode(s)
						if disposeinit:
							s.Dispose()
						if len(exploded) > 1:
							try:
								precheck.append(dg.PolySurface.ByJoinedSurfaces(exploded))
							except:
								for exp in exploded:
									precheck.append(exp)
						else:
							for exp in exploded:
								precheck.append(exp)
					else:
						precheck.append(s)
				except:
					precheck.append(s)
			surfs = precheck
			bOP = batchattempt(temp_path,surfs,importinstance,bypasscheck,scaleup,sendmessage)
			if bOP[0] == False:
				pass
			else:
				return bOP
			for s in surfs:
				surface = s
				safe = True
				if safemode:
					if isinstance(surface,dg.Solid) or isinstance(surface,dg.PolySurface):
						nicesurfaces = []
						explodedcheck = dg.Geometry.Explode(surface)
						for explocheck in explodedcheck:
							if isbellowarea(explocheck):
								safe = False
								explocheck.Dispose()
								sendmessage = True
							else:
								nicesurfaces.append(explocheck)
						if safe:
							for n in nicesurfaces:
								n.Dispose()
					else:
						if isbellowarea(surface):
							s.Dispose()
							if scaledown != 1 and geometrymode == False:
								surface.Dispose()
							sendmessage = True
							continue
				if safe == False:
					tmp = []
					for mini_s in nicesurfaces:
						testit = checkcompatible(mini_s,scaleup,False,geometrymode)
						if testit[0] == False:
							cleanedup = cleanupSurface(mini_s)
							if cleanedup != None:
								tmp.append(cleanedup)
						else:
							tmp.append(testit)
						if geometrymode == False and testit[0] != False:
							mini_s.Dispose()
					if len(tmp) != 0:
						finalsurfaces.append(flatten(tmp))
					else:
						finalsurfaces.append(["FAILED"])
					if (disposeinit or is_sat) and geometrymode == False:
						s.Dispose()
					if scaledown != 1 and geometrymode == False:
						if isinstance(surface,list):
							for surf in surface:
								surf.Dispose()
						else:
							surface.Dispose()
					continue
				testit = checkcompatible(surface,scaleup,True,geometrymode)
				if testit[0] == False:
					isexploded = False
					if isinstance(surface,dg.Solid) or isinstance(surface,dg.PolySurface):
						surface = list(dg.Geometry.Explode(surface))
						isexploded = True
					if isexploded:
						tmp = []
						for mini_s in surface:
							testit = checkcompatible(mini_s,scaleup,False,geometrymode)
							if testit[0] == False:
								cleanedup = cleanupSurface(mini_s)
								if cleanedup != None:
									tmp.append(cleanedup)
							else:
								tmp.append(testit)
							if geometrymode == False and testit[0] != False:
								mini_s.Dispose()
						if len(tmp) != 0:
							try:
								soljoin = polysurfaceOP(flatten(tmp))
								try:
									sol = dg.Solid.ByJoinedSurfaces([soljoin])
									soljoin.Dispose()
								except:
									sol = soljoin
								testit = checkcompatible(sol,scaleup,True,geometrymode)
								if testit[0] == False:
									sol.Dispose()
									finalsurfaces.append(flatten(tmp))
								else:
									finalsurfaces.append([sol])
							except:
								finalsurfaces.append(flatten(tmp))
						else:
							finalsurfaces.append(["FAILED"])
						sendmessage = True
					else:
						cleanedup = cleanupSurface(surface)
						if cleanedup != None:
							finalsurfaces.append(cleanedup)
						else:
							finalsurfaces.append(["FAILED"])
				else:
					finalsurfaces.append(testit)
				if (disposeinit or is_sat) and geometrymode == False:
					s.Dispose()
				if scaledown != 1 and geometrymode == False:
					if isinstance(surface,list):
						for surf in surface:
							surf.Dispose()
					else:
						surface.Dispose()
			return finalsurfaces,sendmessage
		forms = []
		laundryMain = SurfacesLaundry(surfs,doc,importinstance,geometrymode,temp_path)
		result = laundryMain[0]
		sendmessage = laundryMain[1]
		for rlist in result:
			tmp = []
			for r in rlist:
				tmp.append(r)
			if len(tmp) != 0:
				forms.append(tmp)
		if len(laundryMain) == 3:
			return forms,sendmessage,True
		else:
			return forms,sendmessage
	if geoms == [] or geoms == None:
		return [],None,False
	geometrymode = True
	disposeinit = False
	TransactionManager.Instance.ForceCloseTransaction()
	if importinstance:
		trans = Transaction(doc, 'Create ImportInstance')
	else:
		if doc.IsFamilyDocument:
			trans = Transaction(doc, 'Create FreeForm')
		else:
			trans = Transaction(doc, 'Create DirectShape')
	trans.Start()
	warnhandle = WarnSwallowImport()
	foptions = trans.GetFailureHandlingOptions()
	foptions.SetFailuresPreprocessor(warnhandle)
	foptions.SetClearAfterRollback(True)
	trans.SetFailureHandlingOptions(foptions)
	try:
		smartrevitsolidMain = smartrevitsolid(geoms,doc,importinstance,safemode,geometrymode,disposeinit,temp_path)
	except:
		trans.RollBack()
		return [],None,False
	if len(smartrevitsolidMain) == 3:
		trans.Commit()
	else:
		trans.RollBack()
	freeformslists = smartrevitsolidMain[0]
	sendmessage = smartrevitsolidMain[1]
	result = [] 
	singlelist = []
	TransactionManager.Instance.ForceCloseTransaction()
	if importinstance:
		if singlefinalprocess:
			trans = Transaction(doc, 'Create ImportInstance')
		else:
			trans = Transaction(doc, 'Create ImportInstances')
	else:
		if doc.IsFamilyDocument:
			trans = Transaction(doc, 'Create FreeForms')
		else:
			trans = Transaction(doc, 'Create DirectShape')
	if len(smartrevitsolidMain) == 3:
		if singlefinalprocess:
			trans = Transaction(doc, 'Move ImportInstance')
		else:
			trans = Transaction(doc, 'Move ImportInstances')
	trans.Start()
	satopt = SATImportOptions()
	for slist in freeformslists:
		tmp = []
		tmpexport = []
		for s in slist:
			if s == "FAILED":
				if returnfailures:
					tmp.append(None)
				continue
			compare = s.ToString()
			if compare == "Autodesk.Revit.DB.FreeFormElement" or compare == "Autodesk.Revit.DB.Solid" or compare == "Autodesk.Revit.DB.ImportInstance" or compare == "Autodesk.Revit.DB.DirectShape":
				tmp.append(s)
			else:
				if singlefinalprocess:
					singlelist.append(s)
				else:
					tmpexport.append(s)
		if len(tmp) != 0:
			result.append(tmp)
		if len(tmpexport) != 0:
			result.append(finalimport(importinstance,tmpexport,satopt,singlefinalprocess))
	if len(singlelist) != 0:
		result.append(finalimport(importinstance,singlelist,satopt,singlefinalprocess))
	satopt.Dispose()
	mainvec = None
	if len(result) != 0 and centermode:
		doc.Regenerate()
		mainvec = movecenter(doc,result)
	trans.Commit()
	if importinstance or (singlefinalprocess and doc.IsFamilyDocument == False):
		cleanresult = []
		for r in result:
			cleanresult.append(r[0])
		result  = cleanresult
		if (singlefinalprocess and importinstance) or (singlefinalprocess and doc.IsFamilyDocument == False):
			result = result[0]
	return result,mainvec,sendmessage
5 Likes

Okay there’s a limit for replies as well… you may check the full code from the node it self… it is several thousand lines…

If anyone from Autodesk would like to escalate this issue and perhaps give more priority than others… he’s welcome to do so…

Here’s the link of FULL report with many examples and cases and explanations …etc
https://feedback.autodesk.com/project/feedback/view.html?cap=cb0fd5af18bb49b791dfa3f5efc47a72&uf=b41ee1e3f04b4177a0823351348691e7&slsid=4a0d93f403ac4e3787cacc2530e731bf

Oh and it’s status is Closed not submitted to Jira :rofl: :rofl: :rofl: :rofl: … (I guess means ignored somehow)…

More images of the study

After Healing with Dynamo in Revit 2022:

This issue caused me and still causing lots of troubles… I hope, Autodesk listens and realize what they have caused…

9 Likes

Thanks for the great info dump @Karam_Baki. I’ve run into the character limit a few times - it’s a drag but unfortunately it’s a limit of the forum.

I believe that this is because of the source of the feedback - the beta forum is for issues around new features which stop work or fail to execute - at some point these get closed to migrate the effort to the new builds - it doesn’t mean they . I recommend creating a Dynamo for Revit issue for similar scope in the future - these stay open and are more readily reviewed by non-beta participants.

I have seen a few issues on developer channels around this issue, but the specifics are beyond my skill set. I did raise the issue with the team so hopefully we’ll hear back after the holiday.

EDIT: there is in fact an open Jira ticket (179667) and several related tasks for this issue.

2 Likes

That’s great news, I hope they really keep it as a priority, this problem will just keep getting worse with more customers facing it.

3 Likes

Thank you, @Karam_Baki, for this detailed explanation and the code. They are much appreciated.

1 Like

It seems this node works again in Revit 2023 but the geometry gets exported at a strange scale. Seems like 3:1.

perhaps a factor of 3.28084? Meters to Ft

1 Like

Ahh, very likely. Thanks. This might be the issue.

1 Like

You were right!
Here is a screenshot. I also had to scale around the lowest point of the bounding box of the geometry.

Does the Node FamilyInstance.ByGeometry work again with Revit 2022? I am still getting the error-message:

NewForm_background TypeError: expected Solid, got Mesh

@leonard.moelders - The underlying Revit API (Which this node calls) was improved for Revit 2022.1, which has caused the error you see on the FamilyInstance.ByGeometry node here.

Inside Revit 2022 and earlier, there was a legacy method used (Which is still used in the Import DWG feature) that allowed invalid geometry to be imported into Revit. “Invalid” here means that it did not pass Revit’s validation checks, which check for a whole raft of geometric conditions. If invalid geometry is detected in these validation checks, mesh will be imported instead of base geometry.

This was done to build consistency within geometrical import, for things like missing pieces of furniture in one particular view would occur. Previously, it would import everything and you as a user had to manually check if everything was correct. Now, you’ll get a solid import when the geometry is “viable” according to Revit, and a mesh when it is “not viable” as a fallback method.

We can look to improve the error message on the FamilyInstance.ByGeometry node so that it’s more human-friendly and explains what I’ve mentioned above succinctly, as well as look if it’s possible to allow it to also parse through mesh geometry, not just solids.

1 Like

“Improved” is an opinion. From the standpoint of a user that used that feature a whole lot - it sure doesn’t feel improved when I have to take the .sat down to Revit 2020 and import into that version, then turn around and bring that resultant family into Revit 2022 and see that it still works and scratch my head thinking “it obviously works in the 2022 engine - so why wont it let me do it…”. Sure feels like “broken” instead of “improved”. I understand the desired to build consistency - but it feels like some of those validity checks might be a tad bit arbitrary since that which worked in many previous versions now does not work. Instead of (or in addition to) improving the error message - why not add in a Bool option on the ImportDWG feature to allow the user to “skip” the validity check (use the old “unimproved” method") if they feel like living dangerously.

1 Like

Hi @Ben_Osborne - Definitely recognize that “improved” is an opinion here, and that this change can also be perceived as a “regression”, especially given that a lot of the geometry that came through could be effectively correct, even if not in all instances.

We can explore what options we have including what you suggested, but will be somewhat limited to what the Revit API provides and/or we could follow the same path that @Karam_Baki did above with attempted healing methods in his Synthesize Toolkit package, and in particular K-Family Import node. For the latter, note that we won’t be able to guarantee results with these and it would indeed be a “Living dangerously” situation :pray:

3 Likes

@Ben_Osborne :
Live Dangerously GIFs | Tenor

@solamour Thank you, as always, for your thoroughly explanation :+1:

1 Like

@Karam_Baki Is there any documentation on how to use your K-Family Insert - Node? As my current Code is only working up to Revit 2021 due to the changes in the Import of SATs, I am thinking of using your package. I have a family-template which I use to color deviations for a target-performance comparison. It looks like this:

the familyinstance was assembled from 5 cylindrical solids that where created in dynamo. The red and green colors are handed over via two material-names. The old code iterated through the 5 SATs and imported them as FreeFormElements into the family and assigned the material and at the end it saved the familydocument and created a new familyinstance. Can I rebuild this with your node? I tried but failed unfortunately. My Inputs would be:

5 geometries
1 family name
1 family template
1 family category
1 family subcategory
5 materials (a mix of 2 materials to paint red or green)

I would really appreciate your help :slight_smile:

Yes it can do that, but for the materials you’ll need to change an input (VARIsOneMaterial to False)

Then it will create instance (or type parameters based on another input) as “Material 1, Material 2, Material 3…etc”, so you use them in the project.

Other than this, you don’t actually have to feed any more data, just the Dynamo geometry or SAT file, it accepts both.

*Optionally you can change family category, subcategory, family name and material parameter being type or instance.

It’ll detect the appropriate family template, and all other recommended settings.

EDIT, I didn’t find the time to do proper documentation yet, probably sometime after I finish off the headache of Revit 2023

2 Likes

Thanks for your answer. Unfortunately I get this error-message:

Traceback (most recent call last):
File “”, line 2152, in
File “”, line 836, in mainOP
File “”, line 667, in geometryOP
File “”, line 145, in changemat
AttributeError: ‘list’ object has no attribute ‘lower’

This is what my material-input (strings) looks like:

image

Just input a string, not a list
It will propagate this material name to all of the geometry and then you can use the parameters to change it in Revit directly

EDIT:
I’m thinking of Supporting this, inputing a matching material list, it’s relatively easy to implement, you’ll find it working in 2023, but for now, use a single material as stated above, then change the parameters in Revit to fit your needs

You may also use @Level 1 and input the geometry list and equivalent material list, this will produce a family for each geometry though (Don’t worry about naming, it automatically increments)

2 Likes