Display Conduit, Detail Viewports and Printing. Or, RhinoViewport.GetScreenPort always returns true

Hello everyone,

TLDR; RhinoViewport.GetScreenPort always returns true - regardless of whether the viewport is actually being drawn or not. This is meaningful for detail views on a layout, especially during printing. Shouldn’t this function return false if the viewport is not presently within the area being drawn? Or can we have another function in RhinoCommon to give the same result? This seems to be the same as this old thread which doesn’t seem to have a resolution, and as I explain below, the screen port doesn’t always update with accurate tiling information.

I have an analysis plugin that I wrote that uses a display conduit to overlay the analysis results in various viewports. The analysis needs to display different data laid over multiple instances of the same view in a page layout.

Ideally the result looks like this (taken with -_ViewCaptureToFile):


Notice the six identical views, titled “Range a” to “Range f” in the upper-left-hand corner of the detail view, and the data readout in the upper-right-hand corner of the detail view.

But when it is printed (for me, always to a PDF with Acrobat; haven’t tried a physical printer), this is the result (png converted from pdf):


What happens is that each of the elements in the display conduit drawn in the DisplayConduit.DrawForeground override is drawn during the composition of each print tile, whether or not the associated detail viewport is within the bounds of the tile.

This happens whether raster or vector printing is selected, though occasionally I get lucky and Rhino manages to produce the entire image in a single, large tile with vector printing, but this is definitely happens only with unrealistically simple test models.

All of these items are drawn when viewport calling the display conduit is the main pageview viewport - i.e. override void DrawForeground(DrawEventArts e) { ... e.Viewport.ViewportType == ViewportyType.PageViewMainViewport } - this is to ensure the proper scaling of the items produced by e.Display.Draw2dText and e.Display.Draw2dRectangle, as discussed in this thread.

In order to properly line up those conduit items on the details, the following steps occur for the range identifier in the upper-left hand corner (the data in the other corner is similar but of course more complicated):

RhinoPageView page = e.Viewport.ParentView as RhinoPageView;
DetailViewObject[] details = page.GetDetailViews();
foreach (DetailViewObject detail in details)
{
  bool getPort = detail.Viewport.GetScreenPort(out int left, out int right, out bot, out top, out _ , out _);
  if (getPort) 
  {
    string Label = $"Range {detail.Viewport.Name} left:{left} right:{right} top:{top}";
    Rectangle rect = e.Display.Measure2dText(Label, new Point2d(0,0), false, 0, 10, "Arial");
    e.Display.Draw2dRectangle(new Rectangle(left, top, width, height), color.Transparent, 0, Color.Black);
    e.Display.Draw2dText(Label, Color.White, new Point2d(left, top, false, 10, "Arial");
  }
}

It’s worth noting that each of those out int values are returned in pixels - whether screen or printer pixels. They need some scaling not shown here to make it work on the printed page. Also, those are the same values available from detail.Viewport.Bounds and ViewInfo info = new ViewInfo(detail.Viewport); Rectangle port = info.Viewport.ScreenPort;

For testing, I added the left/right/top values to the label (you can see it above), to get the following images:

First, just the screen capture:


A pretty normal result, with normal paper-space coordinates.

Then, the printed version:


This is actually really hard to follow, but the second row shows “Range d left:5 right:84 top:94”. This is an odd result, since you can see the correct result “Range d left:127 right:2171 top:535” - this is actually relative to the details of the tile being printed.

So, for further sleuthing, I put a persistent counter in the plugin class, so that I can record the data from each tile, keeping track of the e.Display.IsPrinting property, in DrawForeground:

AnalysisPlugin p = AnalysisPlugin.Instance;
if (e.Display.IsPrinting)
{
  if (p.drawcount == null)
  {
    p.drawcount = 0;
    p.stringbuilder = new Stringbuilder(); // then a header line
  }
  else 
  { 
    p.drawcount++;
    p.stringbuilder.AppendLine($"{drawcount} \t {detail.Viewport.Name} \t" +
      $"{e.Display.FrameSize.Width} \t {E.Display.FrameSize.Height} \t" +
      $"{l} \t {r} \t {top} \t {bot} \t" +
      $"{detail.Viewport.Bounds.X} \t {detail.Viewport.Bounds.Y} \t" +
      $"{detail.Viewport.Bounds.Width} \t {detail.Viewport.Bounds.Height}");
  }
}
else if (p.drawcount != null) // output stringbuilder to clipboard when view conduit returns away from printing
{
  Clipboard.SetText(p.stringbuilder.ToString());
  p.stringbuilder.Clear();
  p.drawcount = null;
}

So, that gives the following output:
printing details tiling draw order bounds - buggy.csv (7.7 KB)
This shows all the draw events (left column; 0-24) for all the different detail viewports (second column; a-f). The first 4 draws (0-3) are the “print preview” dialog box, then the “print progress” dialog box, then 4-23 are the actual print tiles (note the display frame is 1600x1000 +/-), and #24 is the whole page (12764x9764 = 1100ppi +/-). (I can’t just filter the display frame sizes while printing to look for this one large tile and output the analysis there - this last one seems like a cleanup and doesn’t really do anything; I tried it.)

For instance, detail a in the upper left hand corner falls in draw pass #4, 5, 8, 9, 12 and 13 - for each of those passes, the bounds are updated. In the other passes - when the screen port is not visible - the bounds are not updated, just held over from the previous pass. I think this happens because of the way the ViewInfo is set in the OpenNurbs base - it only returns false if left==right or top==bottom.

This can be seen, perhaps more clearly, in the image below, where I’ve panned the view (of the layout) faster than the display conduit can keep up with, and the viewport bounds get stuck with viable values - even though the detail viewport is off screen.

So, if anyone has any ideas about how I should check whether a particular viewport is visible (especially when printing), I’d love to hear about it.

Some other instances where print tiling was mentioned, but not really resolved:
https://mcneel.myjetbrains.com/youtrack/issue/RH-448

https://mcneel.myjetbrains.com/youtrack/issue/RH-37103
https://mcneel.myjetbrains.com/youtrack/issue/RH-52493

I think you want to get the BoundingBox for each detail. The maximum x,y value of each bbox will be the top right corner of each detail in the layout’s units

Thanks for the suggestion; I looked at that, however, I don’t understand how it might help with deciding whether to draw an element or not, depending on the frame of the current print tile.

If I put it in my list (i.e. bounding box max/min of the detail viewport), it stays the same for every pass of the print cycle:
printing details tiling draw order bounds - detail bbox.csv (9.9 KB)

Is there a display/viewport property available somewhere that I’m missing, that shows the boundary of the print tile in the page units?

Thanks

I think I see what is going on. Try only having your conduit draw when
e.Viewport.ViewportType == ViewportType.PageMainViewport

Hi Steve-
Checking the viewport type is a critical part of the display conduit - the conduit draws differently whether it is a PageViewMainViewport or StandardModelingViewport. (It doesn’t draw for a DetailViewport - computing the math do draw the correct size was a bit impenetrable.)

Here’s a stripped down version of a complete working display conduit with all of the drawing functions that still exhibits the tiling problem, and a model with a page layout.

PrintingConduitTest.zip (103.9 KB)
blank box layout.3dm (88.0 KB)
PrintingConduitTest.rhp (43.5 KB)

Even though it is written for Rhino6, it works the same in Rhino7, especially if you pick Raster Output when printing.

Dan