Unable to create TinVolumeSurface in code

Unable to create TinVolumeSurface in code

soonhui
Advisor Advisor
483 Views
2 Replies
Message 1 of 3

Unable to create TinVolumeSurface in code

soonhui
Advisor
Advisor

I can reproduce a bug in which Civil 3D throws me "Unable to create Surface" even when it really shouldn't. Here's how to reproduce on the attached DWG and the xml file ( the surface file). Note that the xml file has to be located at a folder above the DWG file.

 

Now try to run the below code

 

    [CommandMethod(nameof(UnableAddSurface))]
    public void UnableAddSurface()
    {
        Transact(tr =>
        {
            var civilDoc = CivilApplication.ActiveDocument;
            var typicalRoadAssemblyId = civilDoc.AssemblyCollection.First();
            var typicalRoadAssembly = tr.GetObject<Assembly>(typicalRoadAssemblyId);
            var centerAlignment = civilDoc.GetAlignmentIds().FirstOrDefaultEntity<Alignment>(tr, OpenMode.ForWrite);
            var roadProfile = centerAlignment.GetProfileIds().FirstOrDefaultEntity<Profile>(tr, OpenMode.ForWrite);
            var tinSurface = civilDoc.GetSurfaceIds().FirstOrDefaultEntity<TinSurface>(tr, OpenMode.ForWrite);
            var corridorSurface = AddCorridorSimple(tr, centerAlignment, roadProfile, typicalRoadAssembly);
            var corridorDatum = tr.GetObject<TinSurface>(corridorSurface.SurfaceId);
         

        });
        //you need to comment out this method, and call it manually from the
        //autocad command line to solve the issue
        CreateTinComparisonSurfacePost();

        //uncomment the below code WONT solve the issue
        //Application.DocumentManager.ExecuteInCommandContextAsync(async (o) =>
        //{
        //    CreateTinComparisonSurfacePost();
        //    await Task.CompletedTask;
        //}, null);  
    }

    [CommandMethod(nameof(CreateTinComparisonSurfacePost))]
    public void CreateTinComparisonSurfacePost()
    {
        Transact(CreateTinComparisonSurfacePost);

    }

    private void CreateTinComparisonSurfacePost(Transaction ts)
    {
        var surveySurface = ActiveCivil3DDocument.GetSurfaceIds().SingleEntity<TinSurface>(ts,
            ss => ss.Name.Contains("EG"));
        var datumSurface = ActiveCivil3DDocument.GetSurfaceIds().SingleEntity<TinSurface>(ts,
            ss => ss.Name.Contains("RoadDatum"));
        var surfaceName = $"{surveySurface.Name}-{datumSurface.Name}-comparison";
        var tinCompare = TinVolumeSurface.Create(surfaceName, surveySurface.ObjectId,
            datumSurface.ObjectId);
    }

private CorridorSurface AddCorridorSimple(Transaction tr, Alignment centerAlignment, 
     Profile roadProfile, Assembly typicalRoadAssembly)
 {
     var corridorId = CivilApplication.ActiveDocument.CorridorCollection.Add("Corr1");
     var corridor = tr.GetObject<Corridor>(corridorId, OpenMode.ForWrite);
     var baseLine = corridor.Baselines.Add("First Baseline", centerAlignment.ObjectId,
         roadProfile.ObjectId);
     baseLine.BaselineRegions.Add("Road 1 Full Region",
         typicalRoadAssembly.ObjectId);
     corridor.Rebuild();
     var datumSurface = corridor.CorridorSurfaces.Add("RoadDatum");
     datumSurface.AddLinkCode("Datum", true);
     datumSurface.OverhangCorrection = OverhangCorrectionType.TopLinks;
     corridor.RebuildAutomatic = false;
     corridor.Rebuild();
     return datumSurface;
 }



        private static void Transact(Action<Transaction> action)
        {
            var doc = Application.DocumentManager.MdiActiveDocument;
            using var tr = doc.TransactionManager.StartTransaction();
            action(tr);
            tr.Commit();
        }

       public static T GetObject<T>(this Transaction ts, ObjectId objectId, OpenMode openMode = OpenMode.ForRead)
       where T:DBObject
       {
           return ts.GetObject(objectId, openMode) as T;
       }

        public static T FirstOrDefaultEntity<T>(this ObjectIdCollection objectIds, Transaction ts, 
            OpenMode openMode = OpenMode.ForRead)
            where T : DBObject
        {
            return FirstOrDefaultEntity<T>(objectIds, ts, openMode, o => true);
        }
          


        public static T FirstOrDefaultEntity<T>(this ObjectIdCollection objectIds, 
            Transaction ts, OpenMode openMode, Func<T, bool> filterFunc)
            where T : DBObject
        {
            return FirstOrDefaultEntity(objectIds.Cast<ObjectId>(), ts, openMode, filterFunc);
          
        }

   public static T FirstOrDefaultEntity<T>(this IEnumerable<ObjectId> objectIds,
       Transaction ts, OpenMode openMode, Func<T, bool> filterFunc)
       where T : DBObject
   {
       foreach (ObjectId objectId in objectIds)
       {
           var theObj = ts.GetObject<T>(objectId, openMode);
           if (theObj != null && filterFunc(theObj))
               return theObj;

       }

       return null;
   }

 

You will find that it throws you a "unable to add surface" exception.

 

The solution is to comment out the CreateTinComparisonSurfacePost method, and manually call it from the command line. Then the TinVolumeSurface will be created. 

 

This seems like a bug to me?

 

You might think that this is similar to the one here,  and by adding the 

 

 Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.ExecuteInCommandContextAsync(async (o) =>
                   {
                      CreateTinComparisonSurfacePost();
                       await Task.CompletedTask;
                   }, null);

 

You can solve the issue. But no, even if you add it at the end of the code ( or uncomment the above code), it still won't work.

 

##########

Ngu Soon Hui

##########

I'm the Benevolent Dictator for Life for MiTS Software. Read more here


I also setup Civil WHIZ in order to share what I learnt about Civil 3D
0 Likes
Accepted solutions (1)
484 Views
2 Replies
Replies (2)
Message 2 of 3

moogalm
Autodesk Support
Autodesk Support
Accepted solution

It indeed works with ExecuteInCommandContext, I have tested on C3D 2025.

I don't see you are running in Session mode.

 

moogalm_0-1745565874868.png

 



Here is full code, 

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.Civil.ApplicationServices;
using Autodesk.Civil.DatabaseServices;
using DBObject = Autodesk.AutoCAD.DatabaseServices.DBObject;

namespace TST24206223
{
    public static class CivilUtilsExtensions
    {
        public static T GetObject<T>(this Transaction ts, ObjectId objectId, OpenMode mode = OpenMode.ForRead)
            where T : DBObject
        {
            return ts != null && !objectId.IsNull
                ? ts.GetObject(objectId, mode) as T
                : null;
        }

        public static T FirstOrDefaultEntity<T>(this ObjectIdCollection ids, Transaction ts, OpenMode mode = OpenMode.ForRead)
            where T : DBObject
            => ids.FirstOrDefaultEntity<T>(ts, mode, (t) => true);

        public static T FirstOrDefaultEntity<T>(this ObjectIdCollection ids, Transaction ts, OpenMode mode, Func<T, bool> filter)
            where T : DBObject
            => ids?.Cast<ObjectId>().FirstOrDefaultEntity(ts, mode, filter);

        public static T FirstOrDefaultEntity<T>(this IEnumerable<ObjectId> ids, Transaction ts, OpenMode mode, Func<T, bool> filter)
            where T : DBObject
        {
            if (ids == null || ts == null || filter == null) return null;

            foreach (var id in ids)
            {
                var obj = ts.GetObject<T>(id, mode);
                if (obj != null && filter(obj))
                    return obj;
            }
            return null;
        }

        public static T SingleEntity<T>(this ObjectIdCollection ids, Transaction ts, Func<T, bool> predicate)
            where T : DBObject
        {
            return ids?
                .Cast<ObjectId>()
                .Select(id => ts.GetObject<T>(id))
                .Where(obj => obj != null)
                .SingleOrDefault(predicate);
        }
    }

    public class Cmd
    {
        private static CivilDocument CivilDoc => CivilApplication.ActiveDocument;

        private static void Transact(Action<Transaction> action)
        {
            var doc = Application.DocumentManager.MdiActiveDocument;
            using var tr = doc.TransactionManager.StartTransaction();
            action(tr);
            tr.Commit();
        }

        [CommandMethod(nameof(UnableAddSurface), CommandFlags.Session)]
        public void UnableAddSurface()
        {
            using var docLock = Application.DocumentManager.MdiActiveDocument.LockDocument();

            // Initial transaction: Build corridor & datum surface
            Transact(tr =>
            {
                var assemblyId = CivilDoc.AssemblyCollection.First();
                var assembly = tr.GetObject<Assembly>(assemblyId);
                var alignment = CivilDoc.GetAlignmentIds().FirstOrDefaultEntity<Alignment>(tr, OpenMode.ForWrite);
                var profile = alignment.GetProfileIds().FirstOrDefaultEntity<Profile>(tr, OpenMode.ForWrite);
                var tinSurface = CivilDoc.GetSurfaceIds().FirstOrDefaultEntity<TinSurface>(tr, OpenMode.ForWrite);

                var datumSurface = AddCorridorSimple(tr, alignment, profile, assembly);
                var confirm = tr.GetObject<TinSurface>(datumSurface.SurfaceId); // Confirmation read
            });

            // For comparison surface creation, only works in application context
            var dm = Application.DocumentManager;
            if (dm.IsApplicationContext)
            {
                dm.ExecuteInCommandContextAsync(async _ =>
                {
                    Transact(CreateTinComparisonSurfacePost);
                    await Task.CompletedTask;
                }, null);
            }
            else
            {
                dm.MdiActiveDocument.Editor.WriteMessage("\nNot in application context.");
            }
        }

        [CommandMethod(nameof(CreateTinComparisonSurfacePost))]
        public void CreateTinComparisonSurfacePost()
        {
            Transact(CreateTinComparisonSurfacePost);
        }

        private void CreateTinComparisonSurfacePost(Transaction ts)
        {
            var existingSurface = CivilDoc.GetSurfaceIds()
                .SingleEntity<TinSurface>(ts, s => s.Name.Contains("EG"));

            var datumSurface = CivilDoc.GetSurfaceIds()
                .SingleEntity<TinSurface>(ts, s => s.Name.Contains("RoadDatum"));

            if (existingSurface == null || datumSurface == null)
                throw new InvalidOperationException("One or both required surfaces not found.");

            var surfaceName = $"{existingSurface.Name}-{datumSurface.Name}-comparison";

            try
            {
                var compareId = TinVolumeSurface.Create(
                    surfaceName,
                    existingSurface.ObjectId,
                    datumSurface.ObjectId
                );
                Application.ShowAlertDialog($"Created comparison surface:\n{surfaceName}");
            }
            catch (Autodesk.AutoCAD.Runtime.Exception ex)
            {
                throw new System.Exception($"Failed to create TIN volume surface: {ex.Message}", ex);
            }
        }

        private CorridorSurface AddCorridorSimple(Transaction tr, Alignment alignment,
            Profile profile, Assembly assembly)
        {
            var corridorId = CivilDoc.CorridorCollection.Add("Corr1");
            var corridor = tr.GetObject<Corridor>(corridorId, OpenMode.ForWrite);

            var baseline = corridor.Baselines.Add("First Baseline", alignment.ObjectId, profile.ObjectId);
            baseline.BaselineRegions.Add("Road 1 Full Region", assembly.ObjectId);

            corridor.Rebuild();

            var datum = corridor.CorridorSurfaces.Add("RoadDatum");
            datum.AddLinkCode("Datum", true);
            datum.OverhangCorrection = OverhangCorrectionType.TopLinks;

            corridor.RebuildAutomatic = false;
            corridor.Rebuild();

            return datum;
        }
    }
}
Message 3 of 3

soonhui
Advisor
Advisor

@moogalm , thank you for your inspiration! I've put a collection of utility methods in a (growing) post, that we can always refer to in the future. I will make sure that I point to this post all the time if I have API questions and if necessary. 

##########

Ngu Soon Hui

##########

I'm the Benevolent Dictator for Life for MiTS Software. Read more here


I also setup Civil WHIZ in order to share what I learnt about Civil 3D
0 Likes