WISH : Billboard Images


I know this has been brought up and discussed outside this group but I thought I’d share it in case there’s a half cooked piece of code that could be tested in the WIP or included in release.

A billboard image, like a 2D RPC or a sketchup face-me component would be extremely helpful for architecture visualizations. The support would be for image transparency and constant facing camera and a proper export to render. I know that Archvision support was added in 5 and it seems Archvision claims that you can make your own RPCs for free but I don’t think they work with Rhino…

Maybe another drag-and-drop option for images to convert them to billboards…

Human Scale Figure in Rhino
2D Face Me People for Rhino
(Brian James) #2

Thanks, I filed a feature request for an option to the revised PictureFrame command which is now Picture.

(Bob McNeel) #3

Same as: http://mcneel.myjetbrains.com/youtrack/issue/RH-4548


Billboards also needs the option to be both “full on” and also to only be rotated around the Z axis to face the camera.

(Brian James) #5

Good idea… I added the comment to the feature request


A billboard option to the picture frame would be nice indeed.
In the meantime you can try this bit of python, it orients all selected surfaces to face the camera of the selected viewport with the option of full and constrained rotation.

from Rhino import Commands, DocObjects, Geometry, Input
import scriptcontext

def generate_frame(forward):
    forward.Unitize() # could fail should really check
    up = Geometry.Vector3d.ZAxis
    if forward.IsParallelTo(up) != 0:
        up = Geometry.Vector3d.YAxis
    side = Geometry.Vector3d.CrossProduct(forward, up)
    side.Unitize() # could fail should really check
    up = Geometry.Vector3d.CrossProduct(side, forward)
    return (forward, side, up)

def orient_unconstrained(surface, camera_pt):
    u = (surface.Domain(0).T0 + surface.Domain(0).T1) / 2.0
    v = (surface.Domain(1).T0 + surface.Domain(1).T1) / 2.0

    srf_pt = surface.PointAt(u, v)
    srf_fwd = surface.NormalAt(u, v)
    srf_fwd, srf_side, srf_up = generate_frame(srf_fwd)

    target_fwd = camera_pt - srf_pt
    target_fwd, target_side, target_up = generate_frame(target_fwd)

    trans = Geometry.Transform.Translation(-Geometry.Vector3d(srf_pt))
    rot = Geometry.Transform.Rotation(srf_side, srf_up, srf_fwd, target_side, target_up, target_fwd)
    inv_trans = Geometry.Transform.Translation(Geometry.Vector3d(srf_pt))

    return inv_trans * rot * trans

def orient_constrained(surface, camera_pt):
    u = (surface.Domain(0).T0 + surface.Domain(0).T1) / 2.0
    v = (surface.Domain(1).T0 + surface.Domain(1).T1) / 2.0

    srf_pt = surface.PointAt(u, v)
    srf_fwd = surface.NormalAt(u, v)
    srf_fwd.Z = 0

    target_fwd = camera_pt - srf_pt
    target_fwd.Z = 0

    return Geometry.Transform.Rotation(srf_fwd, target_fwd, srf_pt)

def orient_to_camera():
    result, obj_refs = Input.RhinoGet.GetMultipleObjects("Select objects", False, DocObjects.ObjectType.Surface)
    if result != Commands.Result.Success:

    result, constrained = Input.RhinoGet.GetBool("Constrain z-axis", True, "no", "yes", True)
    if result != Commands.Result.Success:

    result, view = Input.RhinoGet.GetView("Select view")
    if result != Commands.Result.Success:
    camera_pt = view.ActiveViewport.CameraLocation

    for obj_ref in obj_refs:
        surface = obj_ref.Surface()
        if not surface:

        xform = None
        if constrained:
            xform = orient_constrained(surface, camera_pt)
            xform = orient_unconstrained(surface, camera_pt)

        scriptcontext.doc.ActiveDoc.Objects.Transform(obj_ref, xform, True)

if( __name__ == '__main__' ):


Tobias, thanks for sharing. I do use a similar version of this script for aligning pictureframes for renders. You’re right it’s a decent alternative. The version I have flip-flops people sometimes so they’re facing the wrong direction.

(Pascal Golay) #8

Hi Jorgen - - I tweaked an ancient RhinoScript - in case it helps, for now -

Unzip, save, then drag and drop to add these aliases


FaceCamera lets you pick objects to face the camera - it tries to find a reasonable plane in non planar objects. If you have already set some objects to face the camera, Enter and the previously tagged objects will face the camera. Once an object is selected to face the camera once, running this command will update it to the current camera.

To remove the FaceCamera tag so that an object no longer updates, use ClearFaceCamera (no, it’s not an acne medication for fashion models, but you’re right, it could be).

FaceCamera.zip (1.8 KB)



Hi All,

Definitely good to have non-realtime solution for this too, but in case there was an option to make the realtime orient work, maybe it could be implemented like this:

Make it another object ‘property’ (like Material, Mapping, EdgeSoftening etc.) - a checkbox to LookAt camera. Within that have options which axis has to ‘look at’ and optionally to maintain vertical axis. I guess each object would have to have its ‘look at’ frame defined, which for planar objects by default would be their plane, for 3D objects a center of bounding box. This frame can be customized by user (similar way the Gumball frame may be adjusted) and reset to default. This could help to avoid the confusion that to do with non-planar objects, but also could open up few more options like text always facing the camera etc.

Also, I think in Display Modes we will need a setting to make it work real-time or only update when the view camera is still (so the view manipulation is not slowed down with many of billboards)



(Pascal Golay) #10

Hi Jarek - I think you’d need to specify a view to pay attention to as well.



Good point Pascal. I initially thought it will just update to any active view, but you are right, it will make sense to specify which viewport it will apply to.

The other way to think about it is to have views (viewports) have a setting to “Use Billboarding”, that is OFF by default, so user can enable it per-viewport. This way the billboarded objects UI can be simpler…