"""Functions for getting viewport properties, creating new viewports, and editing them.
"""
import math
try:
import System
except ImportError as e: # No .NET; We are really screwed
raise ImportError("Failed to import System.\n{}".format(e))
try:
import Rhino.Geometry as rg
import Rhino.Display as rd
from Rhino import RhinoDoc as rhdoc
import Rhino.ApplicationSettings.AppearanceSettings as aps
except ImportError as e: # No RhinoCommon doc is available. This module is useless.
raise ImportError("Failed to import Rhino.\n{}".format(e))
try:
import scriptcontext as sc
except ImportError as e: # No Rhino doc is available. This module is useless.
raise ImportError("Failed to import Rhino scriptcontext.\n{}".format(e))
try:
from .text import TextGoo
except Exception: # we are outside of Grasshopper
TextGoo = None
[docs]
def camera_oriented_plane(origin):
"""Get a Rhino Plane that is oriented facing the camera.
Args:
origin: A Rhino Point for the origin of the plane.
"""
active_view = rhdoc.ActiveDoc.Views.ActiveView.ActiveViewport
camera_x = active_view.CameraX
camera_y = active_view.CameraY
return rg.Plane(origin, camera_x, camera_y)
[docs]
def orient_to_camera(geometry, position=None):
"""Orient an array of Rhino geometry objects to the camera of the active viewport.
Args:
geometry: An array of Rhino Geometry objects (or TextGoo objects) to
the camera of the active Rhino viewport.
position: A point to be used as the origin around which the the geometry
will be oriented. If None, the lower left corner of the bounding box
around the geometry will be used.
"""
# set the default position if it is None
origin = _bounding_box_origin(geometry)
pt = origin if position is None else position
# get a plane oriented to the camera
oriented_plane = camera_oriented_plane(pt)
# orient the input geometry to the plane facing the camera
base_plane = rg.Plane(origin, rg.Vector3d(0, 0, 1))
xform = rg.Transform.PlaneToPlane(base_plane, oriented_plane)
geo = []
for rh_geo in geometry:
if isinstance(rh_geo, TextGoo):
geo.append(rh_geo.Transform(xform))
elif isinstance(rh_geo, rg.Point3d):
geo.append(oriented_plane)
elif isinstance(rh_geo, rg.Plane):
geo.append(oriented_plane)
else:
new_geo = rh_geo.Duplicate()
new_geo.Transform(xform)
geo.append(new_geo)
return geo
[docs]
def viewport_by_name(view_name=None):
"""Get a Rhino Viewport object using the name of the viewport.
Args:
view_name: Text for the name of the Rhino Viewport. If None, the
current Rhino viewport will be used. If the view is a named view that
is not currently open, it will be restored to the active view of
the Rhino document.
"""
try:
return rhdoc.ActiveDoc.Views.Find(view_name, False).ActiveViewport \
if view_name is not None else rhdoc.ActiveDoc.Views.ActiveView.ActiveViewport
except Exception:
# try to find a named view and restore it
view_table = rhdoc.ActiveDoc.NamedViews
for i, viewp in enumerate(view_table):
if viewp.Name == view_name:
active_viewp = rhdoc.ActiveDoc.Views.ActiveView.ActiveViewport
view_table.Restore(i, active_viewp)
return active_viewp
else:
raise ValueError('Viewport "{}" was not found in the Rhino '
'document.'.format(view_name))
[docs]
def open_viewport(view_name, width=None, height=None):
"""Create a new Viewport in the active Rhino document at specified dimensions.
This will also set the newly-created view to be tha active Viewport.
Args:
view_name: Text for the name of the new Rhino Viewport that will be created.
width: Optional positive integer for the width of the view in pixels. If None,
the width of the currently active viewport will be used.
height: Optional positive integer for the height of the view in pixels. If
None, the height of the currently active viewport will be used.
"""
# close the view if it already exists
if rhdoc.ActiveDoc.Views.Find(view_name, False):
rhdoc.ActiveDoc.Views.Find(view_name, False).Close()
# get the width and the height if it was not specified
w = rhdoc.ActiveDoc.Views.ActiveView.ActiveViewport.Size.Width if not width else width
h = rhdoc.ActiveDoc.Views.ActiveView.ActiveViewport.Size.Height if not height else height
# compute the X,Y screen coordinates where the new viewport will be placed
x = round((System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width - w) / 2)
y = round((System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height - h) / 2)
rec = System.Drawing.Rectangle(System.Drawing.Point(x, y), System.Drawing.Size(w, h))
# add the new view to the rhino document
rhdoc.ActiveDoc.Views.Add(
view_name, rd.DefinedViewportProjection.Perspective, rec, True)
return viewport_by_name(view_name)
[docs]
def set_view_display_mode(viewport, display_mode):
"""Set the display mode of a Rhino Viewport.
Args:
viewport: A Rhino ViewPort object, which will have its display mode set.
display_mode: Text for the display mode to which the Rhino viewport will be
set. For example: Wireframe, Shaded, Rendered, etc.
"""
mode_obj = rd.DisplayModeDescription.FindByName(display_mode)
viewport.DisplayMode = mode_obj
[docs]
def set_view_direction(viewport, direction, position=None, lens_length=None):
"""Set a Rhino Viewport to face a specific direction.
Args:
viewport: A Rhino ViewPort object, which will have its direction set.
direction: A Rhino vector that will be used to set the direction of the view.
position: Optional Rhino point for the target of the camera. If no point
is provided, the Rhino origin will be used (0, 0, 0).
"""
position = position if position is not None else rg.Point3d.Origin
target = rg.Point3d.Add(position, direction)
viewport.SetCameraLocation(position, True)
viewport.SetCameraDirection(direction, False)
viewport.SetCameraTarget(target, False)
if lens_length is not None:
viewport.Camera35mmLensLength = lens_length
[docs]
def set_iso_view_direction(viewport, direction, center_point=None):
"""Set a Rhino Viewport to have an isometric view in a specific direction.
Args:
viewport: A Rhino ViewPort object, which will have its direction set.
direction: A Rhino vector that will be used to set the direction of
the isometric view.
center_point: Optional Rhino point for the target of the camera. If no point
is provided, the Rhino origin will be used (0, 0, 0).
"""
viewport.ChangeToParallelProjection(True)
center_point = center_point if center_point is not None else rg.Point3d.Origin
viewport.SetCameraLocation(rg.Point3d.Add(center_point, direction), False)
viewport.SetCameraTarget(center_point, False)
viewport.SetCameraDirection(direction, False)
[docs]
def capture_view(viewport, file_path, width=None, height=None, display_mode=None,
transparent=False):
"""Capture a Viewport to a PNG file path.
Args:
viewport: A Rhino ViewPort object, which will have its display mode set.
file_path: Full path to the file where the image will be saved.
width: Integer for the image width in pixels. If None, the width of the
active viewport will be used. (Default: None).
height: Integer for the image height in pixels. If None, the height of the
active viewport will be used. (Default: None).
display_mode: Text for the display mode to which the Rhino viewport will be
set. For example: Wireframe, Shaded, Rendered, etc. If None, it will
be the current viewport's display mode. (Default: None).
transparent: Boolean to note whether the background of the image should be
transparent or have the same color as the Rhino scene. (Default: False).
Returns:
Full path to the image file that was written.
"""
# create the view capture object
active_viewp = viewport_by_name()
img_w = active_viewp.Size.Width if width is None else width
img_h = active_viewp.Size.Height if height is None else height
img_size = System.Drawing.Size(img_w, img_h)
# capture the view
if display_mode is not None:
mode_obj = rd.DisplayModeDescription.FindByName(display_mode)
pic = viewport.ParentView.CaptureToBitmap(img_size, mode_obj)
else:
pic = viewport.ParentView.CaptureToBitmap(img_size)
# remove the background color if requested
if transparent:
back_col = aps.ViewportBackgroundColor
if (display_mode is None and viewport.DisplayMode.EnglishName == 'Rendered') \
or display_mode == 'Rendered':
back_col = rhdoc.ActiveDoc.Views.Document.RenderSettings.BackgroundColorTop
pic.MakeTransparent(back_col)
# save the bitmap to a png file
if not file_path.endswith('.png'):
file_path = '{}.png'.format(file_path)
System.Drawing.Bitmap.Save(pic, file_path)
return file_path
[docs]
def viewport_vh_vv(viewport, view_type):
"""Get the horizontal angle (vh) and the vertical angle (vv) from a viewport.
Args:
viewport: A Rhino ViewPort object for which properties will be extracted.
view_type: An integer to set the view type (-vt). Choose from the
choices below.
* 0 Perspective (v)
* 1 Hemispherical fisheye (h)
* 2 Parallel (l)
* 3 Cylindrical panorama (c)
* 4 Angular fisheye (a)
* 5 Planisphere [stereographic] projection (s)
"""
if view_type == 0: # perspective
right_vec = viewport.GetFrustumRightPlane()[1][1]
left_vec = viewport.GetFrustumLeftPlane()[1][1]
h_angle = 180 - math.degrees(rg.Vector3d.VectorAngle(right_vec, left_vec))
bottom_vec = viewport.GetFrustumBottomPlane()[1][1]
top_vec = viewport.GetFrustumTopPlane()[1][1]
v_angle = 180 - math.degrees(rg.Vector3d.VectorAngle(bottom_vec, top_vec))
return h_angle, v_angle
if view_type == 1 or view_type == 5:
return 180, 180
if view_type == 2:
v_rect = viewport.GetNearRect()
return int(v_rect[0].DistanceTo(v_rect[1])), int(v_rect[0].DistanceTo(v_rect[2]))
if view_type == 3:
return 360, 180
if view_type == 4:
return 60, 60
[docs]
def viewport_properties(viewport, view_type=None):
"""Get a dictionary of properties of a Rhino viewport.
Args:
viewport: A Rhino ViewPort object for which properties will be extracted.
view_type: An integer to set the view type (-vt). Choose from the
choices below or set to None to have it derived from the viewport.
* 0 Perspective (v)
* 1 Hemispherical fisheye (h)
* 2 Parallel (l)
* 3 Cylindrical panorama (c)
* 4 Angular fisheye (a)
* 5 Planisphere [stereographic] projection (s)
Returns:
A dictionary with the following keys: 'view_type', 'position', 'direction',
'up_vector', 'h_angle', 'v_angle'
"""
# ensure that we have an integer for the view_type
if view_type is None:
view_type = 2 if viewport.IsParallelProjection else 0
# get the position, direction and up vectors
pos = viewport.CameraLocation
direct = viewport.CameraDirection
up_vec = viewport.CameraUp
direct.Unitize()
up_vec.Unitize()
# get the h_angle and v_angle from the viewport
h_angle, v_angle = viewport_vh_vv(viewport, view_type)
return {
'view_type': view_type,
'position': (pos.X, pos.Y, pos.Z),
'direction': (direct.X, direct.Y, direct.Z),
'up_vector': (up_vec.X, up_vec.Y, up_vec.Z),
'h_angle': h_angle,
'v_angle': v_angle
}
def _bounding_box_origin(geometry):
"""Get the origin of a bounding box around a list of geometry.
Args:
geometry: A list of geometry for which the bounding box origin will
be computed.
"""
# get the first geometry
first_geo = geometry[0]
# if the geometry is a point or plane, just return the plane origin
if isinstance(first_geo, rg.Point3d):
return first_geo
elif isinstance(first_geo, rg.Plane):
return first_geo.Origin
# assume that the geometry is a bunch of breps, meshes or text objects
b_box = first_geo.GetBoundingBox(False) if not isinstance(first_geo, TextGoo) \
else first_geo.get_Boundingbox()
for geo in geometry[1:]:
if isinstance(geo, TextGoo):
b_box = rg.BoundingBox.Union(b_box, geo.get_Boundingbox())
else:
b_box = rg.BoundingBox.Union(b_box, geo.GetBoundingBox(False))
return b_box.Corner(True, True, True)