Script for multiple PictureFrame imports at once

Hi, this is my first foray into Python scripting. I learn best when I have a specific task i’m trying to achieve. I’ve installed Atom editor & the Rhino-Python package. I’ve tinkered with a few simple scripts. I still have a lot to learn but I want to get a jump start by solving a particular workflow issue that would make a huge difference in my day to day work.

My work is exhibition design for a museum. One of the first steps I take to lay out a design is to use PictureFrame to import at scale thumbnails of the objects.

Goal #1: Batch import of images. I’d like to learn how to choose a folder and have the script import all of the images. I realize placement and size is a factor here. At the least, i’d be happy with equal spacing of the images in a horizontal row and I would be fine if they were all a fixed width which I can rescale later.

Ambitious Goal #2: Same as above, but instead, can I create a script that would place the image with PictureFrame (embedded), then call data from a spreadsheet or text file that provides height & width info?

Extra Ambitious Goal #3: Same as #2, but with the addition of Text (from spreadsheet) placed centered a fixed distance above each image.

This is a big deal because i’m usually dealing with 150-400 objects at a time. It takes 1-3 days to manually import the images and add the text. See images below to get an idea of what i’m doing.

If I can get this figured out then i’d love to add the next step of representing the objects in 3D with a box pulling height, width & depth data from file.

Thanks for any help you have to offer!


I’ve spent the last several hours trying to familiarize myself with the proper syntax to start doing this. I’m starting to get a foundation for how the algorithm would go using lists populated from a text file but my problem is just a lack of understanding the basics of using Python to drive RhinoScript commands.

Can someone walk me through the single task of using the PictureFrame command in a script? I’m stuck on defining the filename & path and then using proper syntax to script the PictureFrame command.

Here is where i'm at.

import rhinoscriptsyntax as rs

point = rs.GetPoint(“Pick point”)

if point: rs.Command("!_-PictureFrame")

Not sure if i’m defining the filename variable correctly and I dont have a clue how to reference it when the script executes the commend.


Hi Brandon,

i am working on Windows platform so it is not so easy to check if below will work on the mac version at all and is comprehensible at the same time :slight_smile: It basically shows how to do Goal #1:

  • Ask the user to select image files in the formats (jpg, png, tif, tga)
  • Import the images as Pictureframe objects

The objects are created along the world x axis with equal width and spacing. (1.3 KB)

Goal #2 might be possible too on the Mac if you can import PIL (python image library) to read image width and height. The embedding might be handled using the PictureFrame command options. I suggest to try this out first using a simple command macro before doing it inside a script. It is easier to grasp then.

Goal #3 would require info about the spreadsheet format and what to read from it.



Something must have gotten lost in translation from Windows to Mac. That script fizzles out after choosing the file. I’m not sure where the script gets caught up but it probably has something to do with the dialogue for choosing image files only allows for the selection of a single file.

Since PictureFrame only allows one image at a time, and ultimately I want to bypass the dialog box all together, I am hoping to figure out how to set a variable with the filename & path. In this first iteration (for testing & learnings sake) I was just going to use a fixed file & path to an image on my desktop.

I may be further in over my head than I realized. I would benefit by seeing examples of simpler Rhino specific scripts but I can’t find any on the forums or elsewhere. Even just a script that made a 10x10x10 box would be helpful right now! I’ve gone through Python tutorials but I think i’m getting caught up on not knowing when, which & how to use the Rhino specific modules.

Are there any mac users out there that started scripting from scratch that can point me to the resources they found most helpful?


Hi Brandon, try running below which does not use the dialog and works for a single file only. Make sure the file path points to an existing file, eg. on your desktop:

import Rhino
import rhinoscriptsyntax as rs
def AddPictureFrame():
    # put the path in quotes to prevent problems with spaces in it
    path = chr(34) + "C:/MyFolder/MyImage.png" + chr(34)
    # set up the 2 points to pick
    pt1 = Rhino.Geometry.Point3d(0,0,0)
    pt2 = Rhino.Geometry.Point3d(10,0,0)
    # create a command string
    cmd = "_-Pictureframe " + path + " " + str(pt1) + " " + str(pt2) + " _EnterEnd"
    # run the command
    rs.Command(cmd, True)

Does this work on your end ?

you might start here


Thank you Clement. That did work. I’ve been all up and down the What is Rhino.Python pages. Its slowly coming together for me. A couple YouTube videos made the most difference.

I finally had a few ah-ha! moments where things started to click.

Your script makes sense except i’m uncertain about a couple things.
1)Why do you need to use chr(34) instead of directly using a "?
Is it the difference between Python reading it as coding language vs. actually part of the variable definition?

2)Why do you have to designate the two point variables as strings. str(pt1) & str(pt2)?

Thank you so much for this code. I think I can use it as a springboard to move on to the next step. Even if I have to manually define a list for all of the image files, it will save a ton of time importing them into my drawing. I’m currently formatting 150 thumbnail images for the next project. It would be amazing if I could finish this script to place them in the model. I’m guessing it will finish the job in a matter of seconds vs. it taking me 2-3 hours.


I guess both will work. chr(34) is just double quotes, you could even use two single quotes within double quotes or vice versa. The extra quotes around the path are required, otherwise the _-PictureFrame command interprets the space in the image path as _Enter. You can try this by manually entering a path when using _-PictureFrame. Note the underscore and dash in front of the command. The underscore makes sure the command is understood in any language version of Rhino, the dash suppresses the dialog, so you can paste or enter the pictures file path via the commandline. This is what both scripts above use.

Because the commandline accepts strings. Remember that if you script a regular Rhino command using rs.Command, you`re basically just pasting things into Rhino’s commandline.

You might use the first script above to do so. Just provide a list of paths instead of using the dialog which actually generates a list of file paths in the pictures variable. eg. just replace that code with your own list of file paths:

# create a list of picture paths
pictures = []


1 Like

Hi folks,

Keep in mind i’m working on a Mac if it makes a difference anywhere.

I’ve had some time to work on this again. Much thanks to Clement! I’ve made progress but i’m hung up. For now i’m trying to make it work using a dictionary list. As is, the script runs the pictureframe command but nothing happens, no error message, no images being placed. My guess is that i’m not properly calling data from the dictionary as I define the filename, width & index.B Scott PictureFrame (22.1 KB)

Ultimately i’d like to populate the data in ImageFileList using a spreadsheet (example file included). That is why I am iterating a DICT. Not sure if that is the easiest way to get info from a spreadsheet into a usable format for Python to call. I feel like I can address that later once I get it working in this current form.


Hi Brandon,

print the content of the cmd variable before executing it through rs.Command(cmd, True) using:

print cmd

…and post what is printed here on discourse. My guess is that the path may be invalid since you’re enclosing only the image directory into quotes in this line of your code:

path = chr(34) + "/Users/Registration-Photographer/Desktop/Object Thumbs/Python Test/Images/" + chr(34)

Note that you have to enclose the full path, including the image filename into quotes. You’ll maybe start by using this for your path variable, without any enclosing quotes:

path = "/Users/Registration-Photographer/Desktop/Object Thumbs/Python Test/Images/"

then later do the enclosing in this line:

 cmd = "_-Pictureframe " + chr(34) + path + file['filename'] + chr(34) + " " + str(pt1) + " " + str(pt2) + " _EnterEnd"

any better ?


Here is the output from print cmd as I had it before.

_-Pictureframe "/Users/Registration-Photographer/Desktop/Object Thumbs/Python Test/Images/"image1.jpg 34,0,0 48,0,0 _EnterEnd
_-Pictureframe "/Users/Registration-Photographer/Desktop/Object Thumbs/Python Test/Images/"image2.jpg 93.25,0,0 119.875,0,0 _EnterEnd
_-Pictureframe "/Users/Registration-Photographer/Desktop/Object Thumbs/Python Test/Images/"image3.jpg 144.75,0,0 173,0,0 _EnterEnd
_-Pictureframe "/Users/Registration-Photographer/Desktop/Object Thumbs/Python Test/Images/"image4.jpg 158,0,0 177.5,0,0 _EnterEnd

After making your suggested changes we do have progress but something else is happening that I can’t make sense of. The first image does get placed, the last 3 seem to run into an error. Here is the new output.

Python Script </Users/Registration-Photographer/Desktop/Object Thumbs/Python Test/PF Test Script> ( ResetEngine )-Pictureframe
Full path to image file"/Users/Registration-Photographer/Desktop/Object Thumbs/Python Test/Images/image1.jpg"
First corner of picture frame ( Vertical SelfIllumination=Yes EmbedBitmap=Yes AutoName=Yes AlphaTransparency=No )34,0,0
Length of picture frame ( SelfIllumination=Yes EmbedBitmap=Yes )48,0,0
Length of picture frame ( SelfIllumination=Yes EmbedBitmap=Yes )EnterEnd
Length of picture frame ( SelfIllumination=Yes EmbedBitmap=Yes )
Full path to image file"/Users/Registration-Photographer/Desktop/Object Thumbs/Python Test/Images/image2.jpg"
First corner of picture frame ( Vertical SelfIllumination=Yes EmbedBitmap=Yes AutoName=Yes AlphaTransparency=No )93.25,0,0
Length of picture frame ( SelfIllumination=Yes EmbedBitmap=Yes )119.875,0,0
Length of picture frame ( SelfIllumination=Yes EmbedBitmap=Yes )
Full path to image file"/Users/Registration-Photographer/Desktop/Object Thumbs/Python Test/Images/image3.jpg"
First corner of picture frame ( Vertical SelfIllumination=Yes EmbedBitmap=Yes AutoName=Yes AlphaTransparency=No )144.75,0,0
Length of picture frame ( SelfIllumination=Yes EmbedBitmap=Yes )173,0,0
Length of picture frame ( SelfIllumination=Yes EmbedBitmap=Yes )_-Pictureframe
Full path to image file"/Users/Registration-Photographer/Desktop/Object Thumbs/Python Test/Images/image4.jpg"
First corner of picture frame ( Vertical SelfIllumination=Yes EmbedBitmap=Yes AutoName=Yes AlphaTransparency=No )158,0,0
Length of picture frame ( SelfIllumination=Yes EmbedBitmap=Yes )177.5,0,0
Unable to add bitmap to the document’s bitmap table.
Unable to add bitmap to the document’s bitmap table.
Unable to add bitmap to the document’s bitmap table.

print cmd output here…

_-Pictureframe “/Users/Registration-Photographer/Desktop/Object Thumbs/Python Test/Images/image1.jpg” 34,0,0 48,0,0 _EnterEnd
_-Pictureframe “/Users/Registration-Photographer/Desktop/Object Thumbs/Python Test/Images/image2.jpg” 93.25,0,0 119.875,0,0 _EnterEnd
_-Pictureframe “/Users/Registration-Photographer/Desktop/Object Thumbs/Python Test/Images/image3.jpg” 144.75,0,0 173,0,0 _EnterEnd
_-Pictureframe “/Users/Registration-Photographer/Desktop/Object Thumbs/Python Test/Images/image4.jpg” 158,0,0 177.5,0,0 _EnterEnd

Any ideas? Is the arrangement of data in the dictionary file confusing something? Does Rhino need some kind of clear or reset command between each image?


good it shows that there was a quote in the path before the image name which cannot work.

Probably. In my code example i am using a constant width for all images whereas you’re providing different widths for every image trough the dictionary. Apart from this, if you compare your loop with the one i´ve provided in my script there is a difference. My indexing starts at zero (i = 0) and you are starting with i = 1.

No. But you might try to set the option EmbedBitmap=No if you´re planning to import a large amount of large images, otherwise this will blow up your rhino file size. What concerns me is that you´re getting this error message 3 times:

Unable to add bitmap to the document’s bitmap table.

I’m not sure if this is mac related but it might be worth a try set EmbedBitmap=No by running the _PictureFrame command manually, it should be remembered for that session. If this works i show how to do it in your code.


@Helvetosaur, can you chime in here pls and do some testing on a mac ? Does it support _EnterEnd yet ?


More progress! So, not sure why it is causing the hangup but setting EmbedBitmap=No did indeed resolve everything. All 4 images placed into the drawing.

Ideally I would prefer to embed the images. They are less than 250x250 pixels (8-25kb) and the .3dm file will be shared and eventually archived so I’d rather keep things tidy with images embedded. I think it’s been answered here before but i’ll ask again… is it possible to embed an image after the fact? I can certainly live without it if I have to.

The spacing formula isn’t working the way I expected it. I don’t understand why the first image pt1 gets placed at 34,0,0 instead of 0,0,0. I did change i=0 for the first image in the data file. Am I misunderstanding the asterisks? Is it acting as a multiplier or doing something else? I tried changing the values for i and it didn’t seem to matter what I put for the i values, I got the exact same placement each time.

I’m going to see if I can save a new variable to carry over pt2 value from one iteration to the next and then change the formula to use that. Still doesn’t explain how the i value isn’t working the way I thought. Stay tuned!


@MuseumDesigner, re-open Rhino if you’ve changed one of the imports done through python. Otherwise it will use the stored in memory.


That was definitely the problem. Is there a way around this without re-opening each time I want to test my formula? Can I boot the file out of memory after it’s done it’s job?


You can use reload(module) where module is the name of the module you’ve imported previously.

btw. i would get rid of the index (i value) in that dictionary and just use enumerate for that as shown in my example. And, the asterisk is doing multiplication of course. To get the widths correct, you somehow need to store the sum of widths of previously added images to compute the first insertion point of the next image…


I got it! Now I just need to work on creating the data file from a spreadsheet. I’m 100% clueless where to start with this so I would appreciate a springboard if anyone has a moment.

Thank you @clement for all of your assistance!

Code below works perfectly with the attached files. I also included an example of an excel spreadsheet that I would like to use to populate or have the script read directly from the excel file. Eventually i’d like to include additional fields that allow me to place text objects in alignment with the images. After that i’d like to draw a 3D box using height, width & depth info. (screen shot example at bottom)

B Scott PictureFrame Script (60.1 KB)

import Rhino
import rhinoscriptsyntax as rs

def AddPictureFrame():

# define path to folder containing image files
#path = chr(34) + "/Users/Registration-Photographer/Desktop/Object Thumbs/Python Test/Images/" + chr(34)
path = "/Users/Registration-Photographer/Desktop/Object Thumbs/Python Test/Images/"
# define gap between images & initial width value
gap = 20
prevWidth = 0
# import image dictionary
from ImageFileData import ImageFileList
# Iterate the image file data
for file in ImageFileList:
    # PictureFrame command calls for 2 placement points (location of bottom left corner & width) then it determines height by the images proportions
    # define the 2 points to pick for placement - must consider gap and width of all previously placed images
    pt1 = Rhino.Geometry.Point3d(prevWidth, 0, 0)
    pt2 = Rhino.Geometry.Point3d(pt1.X + file['width'], pt1.Y, pt1.Z)
    prevWidth = prevWidth + file['width'] + gap
    # create a command string
    # dash before PictureFrame command suppresses a dialog box to allow command line entry of info
    #cmd = "_-Pictureframe " + path + file['filename'] + " " + str(pt1) + " " + str(pt2) + " _EnterEnd"
    cmd = "_-Pictureframe " + chr(34) + path + file['filename'] + chr(34) + " " + str(pt1) + " " + str(pt2) + " _EnterEnd"
    # run the command
    rs.Command(cmd, True)


1 Like

I’m struggling to figure out what module to import so that I can start using the spreadsheet data. I installed openpyxl (using pip) on my system but Atom doesn’t recognize it. Am I missing a step or is there a different package that Atom needs to manipulate xlsx files?


Hi Brandon,

i would recommend to work with a simple text based csv file which could be written out of excel. This way you would not have to mess with importing any librarys and have more control over the whole parsing process, as it is just text based.


Hi @MuseumDesigner , did you in the end make any progress with a link to an excel sheet? I have a working script doing kind of the same using grasshopper. But it seems like GH can’t create Picture Frmes. Hope you read this. Cheers, Friso

1 Like