22 Nov 2024

Signaling a Heartbeat for Long-Running APIs with AutoCAD Civil 3D Design Automation

Default blog image

Long-running operations, such as exporting Civil 3D corridors to solids, can cause workflows to appear unresponsive, especially in cloud-based or asynchronous environments. To address this, AutoCAD provides a heartbeat signal mechanism to indicate the application is still alive during lengthy tasks.

This post demonstrates how to implement a heartbeat mechanism while performing a complex operation: exporting corridors to solids using Civil 3D's Design Automation (DA) environment.

Thanks for Albert from Engineering to provide this suggestion.

 

Why Use a Heartbeat?

Design Automation (DA) environments often impose timeouts to ensure system stability. Without interaction during a long-running operation, DA might terminate the process, mistaking it for a hang. By signaling a heartbeat, you can inform DA that the process is active and prevent unintended termination.

Code Walkthrough

Below is the complete implementation of a corridor export command, including heartbeat signaling.

Key Features

  1. Heartbeat Signaling: Signals activity during long-running operations using AcApHeartSendBeat().
  2. Corridor Export: Exports corridors to solids while reporting progress and writing results to a file.
  3. Error Handling: Provides robust error and transaction management.

The Heartbeat Manager

The HeartbeatManager encapsulates the logic for sending heartbeat signals at regular intervals. It uses a background task to send the signal and gracefully handles cancellation when the process ends.

[DllImport("accore.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl,
           EntryPoint = "?sendBeat@AcApHeart@@YAXXZ")]
private static extern void AcApHeartSendBeat();
private class HeartbeatManager : IDisposable
{
    private readonly CancellationTokenSource _cts;
    private readonly Task _heartbeatTask;

    public HeartbeatManager()
    {
        _cts = new CancellationTokenSource();
        _heartbeatTask = StartHeartbeat(_cts.Token);
    }

    private static Task StartHeartbeat(CancellationToken token)
    {
        return Task.Run(async () =>
        {
            while (!token.IsCancellationRequested)
            {
                AcApHeartSendBeat();
                await Task.Delay(100, token);
            }
        }, token);
    }

    public void Dispose()
    {
        _cts.Cancel();
        _heartbeatTask.Wait(1000);
        _cts.Dispose();
    }
}

Exporting Corridors to Solids

The ExportCorridor command demonstrates how to:

  1. Access Civil 3D corridors from the active document.
  2. Export these corridors to solids using ExportSolids.
  3. Write the results (handles of solids) to a text file.

Here’s the core command logic:

[CommandMethod("ExportCorridor")]
public void ExportCorridor()
{
    var doc = Application.DocumentManager.MdiActiveDocument;
    if (doc == null) return;

    using var heartbeat = new HeartbeatManager();
    try
    {
        ExportCorridorInternal(doc.Database, doc.Editor);
    }
    catch (Exception ex)
    {
        doc.Editor.WriteMessage($"\nError during corridor export: {ex.Message}");
    }
}

The Export Logic

The method ExportCorridorInternal performs the export by iterating through corridors, creating solids, and writing their handles to a file. It uses the ExportCorridorSolidsParams class to define export options.

private static void ExportCorridorInternal(Database database, Editor editor)
{
    var civilDoc = CivilApplication.ActiveDocument ?? throw new InvalidOperationException("No active Civil 3D document found.");
    var corridors = civilDoc.CorridorCollection;

    var exportOptions = CreateExportOptions();
    using (var transaction = database.TransactionManager.StartOpenCloseTransaction())
    {
        foreach (ObjectId corridorId in corridors)
        {
            var corridor = transaction.GetObject(corridorId, OpenMode.ForRead) as Corridor;
            if (corridor == null) continue;

            ObjectIdCollection solidIds = corridor.ExportSolids(exportOptions, null);
            File.Create(@"solids.txt").Close();
            using (var file = new StreamWriter(@"solids.txt", true))
            {
                foreach (var solidId in solidIds.Cast<ObjectId>())
                {
                    var solid = transaction.GetObject(solidId, OpenMode.ForRead) as Solid3d;
                    if (solid != null)
                    {
                        file.WriteLine(solidId.Handle.ToString());
                    }
                }
            }

            editor.WriteMessage($"\nExported {solidIds?.Count ?? 0} solids.");
        }
    }
}

Export Options

The CreateExportOptions method defines the parameters for the export operation, including what corridor components to export.

private static ExportCorridorSolidsParams CreateExportOptions()
{
    return new ExportCorridorSolidsParams
    {
        CreateSolidForShape = true,
        ExportShapes = true,
        ExportLinks = false,
        SweepSolidForShape = false
    };
}

Example Use Case

  1. Source Drawing: The command assumes a Civil 3D corridor exists in the drawing.
    Example file: Corridor-5c.dwg (from Civil 3D tutorials).

  2. Command Execution: Run the command ExportCorridor in the Civil 3D environment.

  3. Output: A file named solids.txt will be created, listing the handles of exported solids.

  4. Heartbeat: The system remains responsive throughout the process, signaling activity every 100ms.

Example Use Case

  1. Source Drawing: The command assumes a Civil 3D corridor exists in the drawing.
    Example file: Corridor-5c.dwg (from Civil 3D tutorials).

  2. Command Execution: Run the command ExportCorridor in the Civil 3D environment.

  3. Output: A file named solids.txt will be created, listing the handles of exported solids.

  4. Heartbeat: The system remains responsive throughout the process, signaling activity every 100ms.

The heartbeat mechanism ensures smooth execution of long-running tasks in Design Automation environments. This example demonstrates how to integrate it with Civil 3D or AutoCAD Design Automation API for corridor-to-solid export tasks.

With this approach, your applications can handle complex workflows while maintaining stability and preventing timeouts.

Try it out and let us know how it works for you!

Related Article