1 Jun 2020

Publish multiple drawings to a single PDF

forge-da-autocad

A CLI utility based on .NET Core technology to print multiple drawings in to a single pdf.

It is common to receive multiple drawings with external references like image files, drawings or PDF overlays, it is important to have them printed in to single pdf file, the app creates a DSD file on the fly and pass that to PublishExecute API.

A DSD file is a text configuration file that the AutoCAD publisher functionality honors. You can create it programmatically or using the PUBLISH UI. It typically looks something like this:

[DWF6Version]
Ver=1
[DWF6MinorVersion]
MinorVer=1
[DWF6Sheet:A1 Title Block_Original-Layout1]
DWG=".\A1 Title Block_Original.dwg"
Layout=Layout1
Setup=
OriginalSheetPath="D:\Temp\DSD\A1 Title Block_Original.dwg"
Has Plot Port=0
Has3DDWF=0
[Target]
Type=6
DWF="D:\Temp\DSD\A1 Title Block_Original.pdf"
OUT="D:\Temp\DSD"
PWD=
[MRU Sheet List]
MRU=2
[PdfOptions]
IncludeHyperlinks=TRUE
CreateBookmarks=TRUE
CaptureFontsInDrawing=TRUE
ConvertTextToGeometry=FALSE
VectorResolution=1200
RasterResolution=400

The .NET Core is used prepare design automation client that prepares.

1. Activity

2 Uploading Bundle

3. Submitting Workitem

This CLI code can be easily adjusted to create many other DA clients, can be used as boiler plate, the code demonstrates how to create signed resources for the objects needed to be download and upload on  OSS.

 if (string.IsNullOrEmpty(Owner))
            {
                Console.WriteLine("Please provide non-empty Owner.");
                return;
            }

            if (string.IsNullOrEmpty(UploadUrl))
            {
                Console.WriteLine("Creating Bucket and OSS Object");


                dynamic oauth = await GetInternalAsync();

                // 1. ensure bucket exists

                BucketsApi buckets = new BucketsApi();
                buckets.Configuration.AccessToken = oauth.access_token;
                try
                {
                    PostBucketsPayload bucketPayload = new PostBucketsPayload(BucketKey, null, PostBucketsPayload.PolicyKeyEnum.Transient);
                    dynamic bucketsRes = await buckets.CreateBucketAsync(bucketPayload, "US");
                }
                catch
                {
                    // in case bucket already exists
                    Console.WriteLine($"\tBucket {BucketKey} exists");
                };
                ObjectsApi objectsApi = new ObjectsApi();
                objectsApi.Configuration.AccessToken = oauth.access_token;
                dynamic objects = await objectsApi.GetObjectsAsync(BucketKey, 10, "2020");
                bool bFound = false;

                //2. Upload input file and get signed URL
                if (!bFound)
                {
                    long fileSize = (new FileInfo(FilePaths.InputFile)).Length;
                    long chunkSize = 2 * 1024 * 1024; // 100Kb
                    int numberOfChunks = (int)Math.Round((double)(fileSize / chunkSize)) + 1;
                    var options = new ProgressBarOptions
                    {
                        ProgressCharacter = '#',
                        ProgressBarOnBottom = false,
                        ForegroundColorDone = ConsoleColor.Green,
                        ForegroundColor = ConsoleColor.White
                    };

                    using var pbar = new ProgressBar(numberOfChunks, $"Uploading input file {inputFileNameOSS} to {BucketKey}..... ", options);
                    long start = 0;
                    chunkSize = (numberOfChunks > 1 ? chunkSize : fileSize);
                    long end = chunkSize;
                    string sessionId = Guid.NewGuid().ToString();
                    // upload one chunk at a time
                    using BinaryReader reader = new BinaryReader(new FileStream(FilePaths.InputFile, FileMode.Open));
                    for (int chunkIndex = 0; chunkIndex < numberOfChunks; chunkIndex++)
                    {
                        string range = string.Format("bytes {0}-{1}/{2}", start, end, fileSize);

                        long numberOfBytes = chunkSize + 1;
                        byte[] fileBytes = new byte[numberOfBytes];
                        MemoryStream memoryStream = new MemoryStream(fileBytes);
                        reader.BaseStream.Seek((int)start, SeekOrigin.Begin);
                        int count = reader.Read(fileBytes, 0, (int)numberOfBytes);
                        memoryStream.Write(fileBytes, 0, (int)numberOfBytes);
                        memoryStream.Position = 0;
                        
                        dynamic chunkUploadResponse = await objectsApi.UploadChunkAsyncWithHttpInfo(BucketKey, inputFileNameOSS, (int)numberOfBytes, range, sessionId, memoryStream);

                        start = end + 1;
                        chunkSize = ((start + chunkSize > fileSize) ? fileSize - start - 1 : chunkSize);
                        end = start + chunkSize;
                        double size = chunkIndex == 0 ? chunkSize / 1024 : (chunkIndex * chunkSize) / 1024;
                        var CustomText = $"{(fileSize-chunkSize)/ 1024} Kb uploaded...";
                        pbar.Tick(CustomText);

                    }



                }
                else
                {
                    Console.WriteLine($"{inputFileNameOSS} found in {BucketKey}.....\n\tCreating a signed resource.... ");
                }

                try
                {
                    PostBucketsSigned bucketsSigned = new PostBucketsSigned(60);
                    dynamic signedResp = await objectsApi.CreateSignedResourceAsync(BucketKey, inputFileNameOSS, bucketsSigned, "read");
                    DownloadUrl = signedResp.signedUrl;
                    signedResp = await objectsApi.CreateSignedResourceAsync(BucketKey, outputFileNameOSS, bucketsSigned, "readwrite");
                    UploadUrl = signedResp.signedUrl; 
                    Console.WriteLine($"\tSuccess: signed resource for input.zip created!\n\t{DownloadUrl}");
                    Console.WriteLine($"\tSuccess: signed resource for result.pdf created!\n\t{UploadUrl}");
                }
                catch { }


            }

            if (!await SetupOwnerAsync())
            {
                Console.WriteLine("Exiting.");
                return;
            }

            var myApp = await SetupAppBundleAsync();
            var myActivity = await SetupActivityAsync(myApp);

            await SubmitWorkItemAsync(myActivity);

 

Typical Activity:

 Console.WriteLine("Setting up activity...");
            var myActivity = $"{Owner}.{ActivityName}+{Label}";
            var actResponse = await this.api.ActivitiesApi.GetActivityAsync(myActivity, throwOnError: false);
            var activity = new Activity()
            {
                Appbundles = new List<string>()
                    {
                        myApp
                    },
                CommandLine = new List<string>()
                    {
                        $"$(engine.path)\\accoreconsole.exe /i $(args[inputFile].path) /al $(appbundles[{PackageName}].path) /s $(settings[script].path)"
                    },
                Engine = TargetEngine,
                Settings = new Dictionary<string, ISetting>()
                    {
                        { "script", new StringSetting() { Value = "BatchPublishCmd\n" } }
                    },
                Parameters = new Dictionary<string, Parameter>()
                    {
                        { "inputFile", new Parameter() { Verb= Verb.Get, LocalName = "$(HostDwg)",  Required = true } },
                        { "inputZip", new Parameter() { Verb= Verb.Get, Zip=true, LocalName = "export", Required = true} },
                        { "Result", new Parameter() { Verb= Verb.Put,  LocalName = "result.pdf", Required= true} }
                    },
                Id = ActivityName
            };
            if (actResponse.HttpResponse.StatusCode == HttpStatusCode.NotFound)
            {
                Console.WriteLine($"Creating activity {myActivity}...");
                await api.CreateActivityAsync(activity, Label);
                return myActivity;
            }
            await actResponse.HttpResponse.EnsureSuccessStatusCodeAsync();
            Console.WriteLine("\tFound existing activity...");
            if (!Equals(activity, actResponse.Content))
            {
                Console.WriteLine($"\tUpdating activity {myActivity}...");
                await api.UpdateActivityAsync(activity, Label);
            }
            return myActivity;

            bool Equals(Autodesk.Forge.DesignAutomation.Model.Activity a, Autodesk.Forge.DesignAutomation.Model.Activity b)
            {
                Console.Write("\tComparing activities...");
                //ignore id and version
                b.Id = a.Id;
                b.Version = a.Version;
                var res = a.ToString() == b.ToString();
                Console.WriteLine(res ? "Same." : "Different");
                return res;
            }

 

WorkItem:

Console.WriteLine("Submitting up workitem...");            
            var workItemStatus = await api.CreateWorkItemAsync(new Autodesk.Forge.DesignAutomation.Model.WorkItem()
            {
                ActivityId = myActivity,
                Arguments = new Dictionary<string, IArgument>() {
                              {
                               "inputFile",
                               new XrefTreeArgument() {
                                Url = "http://download.autodesk.com/us/support/files/autocad_2015_templates/acad.dwt"
                               }
                              }, {
                               "inputZip",
                               new XrefTreeArgument() {
                                Url = DownloadUrl, Verb = Verb.Get, LocalName = "export"
                               }
                              }, {
                               "Result",
                               new XrefTreeArgument() {
                                Verb = Verb.Put, Url = UploadUrl
                               }
                              }
                             }
            });

            Console.Write("\tPolling status");
            while (!workItemStatus.Status.IsDone())
            {
                await Task.Delay(TimeSpan.FromSeconds(2));
                workItemStatus = await api.GetWorkitemStatusAsync(workItemStatus.Id);
                Console.Write(".");
            }
            Console.WriteLine($"{workItemStatus.Status}.");
            var fname = await DownloadToDocsAsync(workItemStatus.ReportUrl, "Das-report.txt");
            if (workItemStatus.Status != Status.Success)
            {
                Console.WriteLine($"{workItemStatus.Status} Please refer log {fname} further details.. exiting! ");
                return;
            }
            Console.WriteLine($"Downloaded {fname}.");         
            var result = await DownloadToDocsAsync(UploadUrl, outputFileNameOSS, true);
            Console.WriteLine($"Downloaded {result}.");

 

Batch Publishing

 

Full information on how to use is available on Github github source

In next post- I will talk about a Web application that consumes takes drawing zip file and displays PDF with in the browser without any additional plugin.

A screenshot

PublishDWGZipToPlot

Related Article