Optimizing performance of automatic Layout generation


For 50-100 objects, I generate workdrawings: a Layout/page, each with 4 DetailViews on it. It works fine’ish (bar the random Rhino hickups, but maybe I’m using pointers after RunScript or something), but it’s too slow so I’m trying to optimize it a bit now.

Problem 1
For each Layout I have the following piece of code:

RhinoObject currentRhinoObject = ...
string createPage = $"_-Layout \"{currentRhinoObject.Id}\" {Constants.A4_WIDTH} {Constants.A4_HEIGHT} 0 _Enter";
            RhinoApp.RunScript(createPage, false);
PageView pageView = doc.Views.GetPageViews().Last();
doc.Views.ActiveView = pageView;

IEnumerable detailViews = create4DetailViewsForObject(currentRhinoObject);

foreach (var obj in doc.Objects)
    // Do not hide the object we just created all the details for
    if (obj.Id == currentRhinoObject.Id)
    // Hide everything else
    foreach (DetailViewObject detailViewObject in detailViews)
Console.WriteLine($"HideInDetail took: {watch.ElapsedMilliseconds}ms");

It takes 3832ms with around 50-100 items in the scene, which I find very slow. Is there a better way to do this? I’m worried about the time with so few elements, because this will scale really badly: O(n^2).

Problem 2:
Elsewhere I import the company logo onto the page:

string cmdImport = $"_-Import \"{MyLogoPath}\" _Enter";
var res = RhinoApp.RunScript(cmdImport, false);
Console.WriteLine($"HideInDetail took: {watch.ElapsedMilliseconds}ms");

which works, but is excruciatingly slow: 6117ms for an 87kb .3dm file stykka_logo.3dm (85.4 KB) with the company logo in outlines and a square, and nothing else. I have “saved as small” and “geometry only”. Am I doing something wrong?
I would be happy if I could import it once when my plugin loads, hold it in memory and slap it onto every page when I need. But I don’t really get how to go about. Is that a viable way? Or is there a better way?


Hi @aske,

It’s always best to have a full command, along with any needed 3dm files, that we can run here. Otherwise all we can is speculate on why something works or doesn’t.

For item 2, this script doesn’t seem to take all that long (< 600 ms) on my little Surface Book:

import System
import System.Threading
import Rhino

path = "c:\\users\\dale\\downloads\\stykka_logo.3dm"
script = "_-Import \"%s\" _Enter" % (path)

watch = System.Diagnostics.Stopwatch()

Rhino.RhinoApp.RunScript(script, False)


Again, having a way to repeat is most helpful.


– Dale

Hey @dale

Thanks for your reply! I extracted the core parts of the code, because I can’t upload everything on this public forum. It’s also intertwined with a lot of irrelevant code that I didn’t wanna bother other people with. Usually when I have a problem it’s because I’m doing something wrong, or don’t know about an API. I hoped that would be the case here as well.

I’ll write a working example tomorrow.

Hey @dale

I made an example and figured out something new: it’s as fast as you report with few layouts, but after there are X layouts in the document, it becomes slow:

using System;
using System.Diagnostics;
using System.Linq;
using Rhino;
using Rhino.Commands;
using Rhino.Geometry;

namespace Dev.Common
    public class DevCommand2 : Command
        public DevCommand2()
            // Rhino only creates one instance of each command class defined in a
            // plug-in, so it is safe to store a refence in a static property.
            Instance = this;

        ///<summary>The only instance of this command.</summary>
        public static DevCommand2 Instance { get; private set; }

        public override string EnglishName => "MyDevCommand2";

        protected override Result RunCommand(Rhino.RhinoDoc doc, RunMode mode)
            double iterations = 0;
            Rhino.Input.RhinoGet.GetNumber("Iterations", false, ref iterations);

            int maxIterations = (int)iterations;
            RhinoApp.WriteLine($"Doing {iterations} iterations");

            for (int i = 0; i < maxIterations; i++)
                var watch = Stopwatch.StartNew();
                // Add a page
                string createPageCmd = $"_-Layout \"Import Page\" 297 210 0 _Enter";
                RhinoApp.RunScript(createPageCmd, false);
                Console.WriteLine($"createPage took: {watch.ElapsedMilliseconds}ms");

                // Make sure the page we created is active
                var pageView = doc.Views.GetPageViews().Last();
                doc.Views.ActiveView = pageView;


            RhinoApp.WriteLine($"All done");
            return Result.Success;

        private static bool ImportFile()
            var watch = Stopwatch.StartNew();
            string cmdImport = $"_-Import \"/Users/aske/Desktop/stykka_logo.3dm\" _Enter";
            var res = RhinoApp.RunScript(cmdImport, false);
            Console.WriteLine($"Import Logo took: {watch.ElapsedMilliseconds}ms");
            RhinoApp.WriteLine($"Import Logo took: {watch.ElapsedMilliseconds}ms");
            return res;

I tried running this for 1000 iterations (this is unrealistic for our use case, but 100 is possible), and the time jumps like this at some point:

createPage took: 582ms
Import Logo took: 64ms
createPage took: 565ms
Import Logo took: 575ms

and then it stays consistently around 600ms.

Hi @aske,

Let me know if the attached works any better.

TestCreateLayouts.cs (1.8 KB)

– Dale

Hey @dale,

I upgraded to V7, and the import command is now constant time irregardless of the number of pages :tada:
Create Page is exponential in the number of pages after page 16’ish, but it’s not too bad I guess: the creation part of creating 100 pages is 50s. Not the best user experience but the other parts of the process will probably dominate it anyway.
Layout Create and Import