Well, I lied. The final final solution was actually to go back to corridor creation, since we needed full IFC 4.3 compliance. Achieving that with solids would have been either impossible or would have required third-party software. In the end, I had to rebuild everything using corridor creation — 8 baselines and around 1,800 regions — with targeting and parameter overriding applied at every 5 meters along a 100 km corridor, all fully automated with Dynamo.
Surprisingly, this approach was still faster than the solid-based method. The full process took about two hours to run, mostly due to rebuilding the corridor twice; the rest of the operations were almost instantaneous thanks to Dynamo automation.
That said, the solid-based method was a working concept — the entire model was built that way at one point. However, beyond the lack of IFC 4.3 compliance, it also produced significantly larger file sizes compared to the corridor-based IFC.
This is the code I used for the solid method. Please note that while it works like charm, this is not finished product and I also tried to optimize RAM usage, so you can find Dispose() and additional garbage collection here and there (probably totally unnecessary as the transaction ideally does that, but I had to try):
# Load the Python Standard and DesignScript Libraries
import sys
import clr
import gc
# Add Assemblies for AutoCAD and Civil3D
clr.AddReference('AcMgd')
clr.AddReference('AcCoreMgd')
clr.AddReference('AcDbMgd')
clr.AddReference('AecBaseMgd')
clr.AddReference('AecPropDataMgd')
clr.AddReference('AeccDbMgd')
# Import references from AutoCAD
from Autodesk.AutoCAD.Runtime import *
from Autodesk.AutoCAD.ApplicationServices import *
from Autodesk.AutoCAD.EditorInput import *
from Autodesk.AutoCAD.DatabaseServices import *
from Autodesk.AutoCAD.Geometry import *
# Import references from Civil3D
from Autodesk.Civil.ApplicationServices import *
from Autodesk.Civil.DatabaseServices import *
import Autodesk.Civil as civil
# The inputs to this node will be stored as a list in the IN variables.
dataEnteringNode = IN
adoc = Application.DocumentManager.MdiActiveDocument
editor = adoc.Editor
zones = IN[0]
structure = IN[1]
solidlistout = []
propertiesout = []
effectivewidthlist = []
zonenumbers = IN[2]
with adoc.LockDocument():
with adoc.Database as db:
for zone, thickness, zonenumber in zip(zones,structure,zonenumbers):
with db.TransactionManager.StartTransaction() as t:
solidsforlist = []
propertyforlist = []
plnumber = 1
surfaceId = TinSurface.Create(db, "temp")
for pline,layerthickness in zip(zone,thickness):
if pline:
# Place your code below
plineid = pline.InternalObjectId
plineobj = t.GetObject(plineid, OpenMode.ForWrite)
point_collection = Point3dCollection()
# Loop through vertices and add to collection
for vertex_id in plineobj:
vertex = t.GetObject(vertex_id, OpenMode.ForWrite)
point_collection.Add(vertex.Position)
# Create TIN Surface
#surfaceId = TinSurface.Create(db, "temp")
surface = t.GetObject(surfaceId, OpenMode.ForWrite)
# Add breakline
surface.BreaklinesDefinition.AddStandardBreaklines(point_collection, 1.0, 1.0, 1.0, 0.0)
triangles = surface.Triangles
# check if surface has triangles
if triangles.Count == 0:
hastriangles = False
editor.WriteMessage(f"\nsurface has no area, invalid boundary")
# check if surface size and shape to exclude invalid zone definitions
else:
area = surface.GetTerrainProperties().SurfaceArea2D
perimeter = plineobj.Length
effectivewidth = area / (perimeter/2)
effectivewidthlist.append(effectivewidth)
hastriangles = True
# Add as boundary
if hastriangles and area > 5 and (effectivewidth < 5 and effectivewidth > 2):
surface.BoundariesDefinition.AddBoundaries(point_collection, 1.0, civil.SurfaceBoundaryType.Outer, False)
layername = "zone_" + zonenumber
# Extract as solid
solids = surface.CreateSolidsAtDepth(layerthickness, layername, 10)
editor.WriteMessage(f"\nlayer {plnumber} of zone {zonenumber} done")
#solidsforlist.append(solids)
if solids:
for solid in solids:
solidsforlist.append(str(t.GetObject(solid,OpenMode.ForWrite).Handle))
propertyforlist.append(zonenumber)
else:
# If boundary fails, try alternative approach
editor.WriteMessage(f"\nsurface has no area, invalid boundary")
pass
surface.BreaklinesDefinition.RemoveAt(0)
surface.Rebuild()
# Delete surface
plineobj.Erase(True)
triangles.Dispose()
point_collection.Clear()
point_collection.Dispose()
plnumber += 1
surface.Erase(True)
t.Commit()
solidlistout.append(solidsforlist)
propertiesout.append(propertyforlist)
gc.collect()
OUT = solidlistout, propertiesout