Saving a specific area of the viewport as an image

Hi,

I’m looking for a way to save as a .jpg or .png file the elements that appear inside a specific area of the viewport and at a custom resolution . I was thinking about a Python script in Grasshopper that would take as inputs:

  • a closed polyline (area/contour of the canvas)
  • 2 ints (width and height in pixels)
  • 2 strings (path + file name)
  • a boolean (to trigger the saving of the image)

Is it feasible ?

Looking at the RhinoCommon API I found the following classes and functions that, I think, could be useful in this case but I can’t seem to figure out which one exactly is the most relevant and how to use it properly:

  • Rhino.Display.ViewCaptureSettings:

    • CropRectangle
    • DrawSelectedObjectsOnly
    • ViewArea
    • SetWindowRect
    • ViewAreaMapping
  • Rhino.Display.RhinoView:

    • Bounds
    • ClientRectangle
    • ScreenRectangle
    • Size

Could someone give me a hint on this ?

2 Likes

After numerous trial and errors I came up with the following script that makes use of the SetWindowRect method :

import Rhino.Geometry as rg
import Rhino.Display as rd
import scriptcontext as sc
import System, operator

# Get x and y coordinates of frame vertices
px, py = set(frame.X), set(frame.Y)

# Get horizontal and vertical lengths
dx = abs(reduce(operator.sub, px))
dy = abs(reduce(operator.sub, py))

# Construct the points that will set the print area
offx = (dx/dy) * (3/factor)
offy = (dy/dx )* (3/factor)
p1 = rg.Point3d(min(px) + offx, min(py) + offy, 0)
p2 = rg.Point3d(max(px) - offx, max(py) - offy, 0)

# Set resolution to chosen scaling factor
sx, sy = dx*factor, dy*factor


def Capture():

    view = sc.doc.Views.ActiveView
    size = System.Drawing.Size(sx, sy) 

    settings = rd.ViewCaptureSettings(view, size, 300) #view, size, dpi
    settings.SetWindowRect(p1, p2)
    settings.RasterMode = True
    settings.DrawGrid = False
    settings.DrawAxis = False
    settings.DrawWallpaper = False

    bitmap = rd.ViewCapture.CaptureToBitmap(settings)
    bitmap.Save(path+filename+".png")


if save:
    Capture()

One thing that still bothers me is the presence of small margins all around the drawing, as if it was impossible to entirely fill the selected frame (WindowRect). A workaround I found is to add a slight offset (proportional to the chosen resolution) to the points defining the print area.

While this seems to work for now I would really appreciate if someone could tell me if a less hacky / more appropriate way of saving a specific print area is possible.

Set specific print area.gh (8.1 KB)

Hi @liukov.dobriev,

i’m not sure but i think you must pass Point2d to SetWindowRect for more accuracy. Below is a quick example which seems to capture the picked area (try this from the EditPythonScript dialog):

import Rhino
import scriptcontext
import rhinoscriptsyntax as rs
import System

def TestCapture():
    
    rc, rect, view = Rhino.Input.RhinoGet.Get2dRectangle(False)
    if rc != Rhino.Commands.Result.Success: return
    
    size = System.Drawing.Size(rect.Width, rect.Height)
    p0 = Rhino.Geometry.Point2d(rect.X, rect.Y)
    p1 = Rhino.Geometry.Point2d(rect.X + rect.Width, rect.Y + rect.Height)
    settings = Rhino.Display.ViewCaptureSettings(view, size, 72) 
    settings.SetWindowRect(p0, p1)
    settings.DrawGrid = True
    settings.DrawAxis = True
    bitmap = Rhino.Display.ViewCapture.CaptureToBitmap(settings)
    if not bitmap: print "No bitmap"; return
    file = rs.SaveFileName ("Save", "PNG Files (*.png)|*.png||")
    if file: bitmap.Save(file)
    
TestCapture()

@dale, if you could please take a look here? The resolution in dpi always seems 72, regardless of the value used. I was only able to capture higher sizes my multiplying the size and it does not alias. Do i miss something ?

thanks,
c.

2 Likes

Hi, thanks for your insight @clement !

I had already tried with Point2d but the problem was that the saved image came out completely blanch (nothing drawn on it, just plain white). It might due to the fact that what I’m trying to capture is layered on the Z axis but can’t say for sure.

Hi @liukov.dobriev,

in order to get Point2d from Point3d you would need to transform the 3d points from world to screen based on the current view. Something like this might do it:

pt0 = rs.GetPoint("First point")
pt1 = rs.GetPoint("Second point")

s = Rhino.DocObjects.CoordinateSystem.Screen
w = Rhino.DocObjects.CoordinateSystem.World
xform = viewport.GetTransform(w,s)

screen_pt0 = Rhino.Geometry.Point2d(xform * pt0)
screep_pt1 = Rhino.Geometry.Point2d(xform * pt1)

I guess the z-height should not matter when used in a top view as long as you can see the objects in the viewport.

_
c.

Apologies for the late reply.

I couldn’t find how to combine the 2 snippets you provided in order to get a fully working script. The former saves an image that doesn’t match the viewport and the latter doesn’t have any saving option.

Hi @liukov.dobriev,

the first example seems to work fine here using Rhino 7 (Windows), either in views using planar or perspective viewport projection. The second is not meant as a complete script which saves an image but demonstrates how to obtain Point2d from Point3d, eg. using 2 corner points (3d) of a polyline rectangle. You can use the converted 2d points in the SetWindowRect function and just copy the rest of the first example code below it if you want to save.

One remark about your first post: If you want to define the area to capture from a rectangle, you will likely want to preserve the proportion of the rectangle. It makes no sense to enter Width and Hight as shown in your GH definition. That’s why i use the width and height from the viewport which is then cropped to the rectangle size. To get higher resolution you need to multiply width and height with the same factor. My example does capture the size (of the rectangle) in pixels 1 to 1.

_
c.

1 Like

I have an old Python script that may help you. Give me a couple of days to try and get it going again. Last time I used it, it had some dropped pixels.

Regards,
Terry.

1 Like

Liukov,

Here is the Python script. To use it you need to do 5 things:

  1. Go to bottom of the script file and change the storage location to be appropriate for your machine.
  2. Run the script and then resize the box in order for it to get the right scale factor.
  3. Move the box and resize it to enclose the area you want to capture. You can also use the mouse roller to enlarge/shrink the box size.
  4. Push Capture & then Save to store image shown in the box to the location you setup in step 1.
  5. Press Done when finished.

There are other buttons, Locate & Size, which position the form back to a previous location (if you wrote down the coordinates you can type them back in and then go there). There are details on how these work in the writeup at the top of the script.

The script has two parts:

  1. The top part contains the class definitions for the form. This is in lines 49-696.
  2. The bottom part contains the code for displaying the form and responding to the buttons. This is in lines 698 - 1400.

The script is not a simple script but it is working well for me. It took me a couple of weeks to get it working back in Jan of 2017. It uses Windows code for generating the form and capturing the image. It needs to be updated to use Eto forms which is much better for working in both Windows and Mac environments. I started this but do not have a working version yet.

The script associates the coordinates it shows and uses to the whole Windows screen. For your application it sounds like you want to work with a single Rhino viewport. In that case you will need to modify the script to work with the viewport’s X & Y limits. Also for your application you do not want the form but you want some of the functions imbedded inside the code that supports the form. I suggest you accomplish this in 2 steps: (1) modify the existing script to work with viewports coordinates. You will be able to see in the window of the form when you get this right. Then build what you need for your Grasshopper application from the code in the script. I have done something like this before where I automated a script that created a spoked wheel to display a set of options and then use pieces of this code to snap a picture of each one. I will see if I can find that old code if this will help you out.

Let me know if you can get the script working as it is now. Then I can help you with the changes needed for it to work in GH.

PictureCaptureNewer.py (62.5 KB)

Regards,
Terry.