"""Functions to bake entire Ladybug objects into the Rhino scene.
The methods here are intended to help translate groups of geometry that are
commonly generated by several objects in Ladybug core (ie. legends, compasses,
visualization sets, etc.)
"""
import json
from ladybug_geometry.geometry3d import Mesh3D
from ladybug.graphic import GraphicContainer
from ladybug_display.geometry3d import DisplayText3D
from ladybug_display.visualization import AnalysisGeometry
from .config import units_system
from .color import color_to_color
from .bakegeometry import bake_point2d, bake_vector2d, bake_ray2d, \
bake_linesegment2d, bake_arc2d, bake_polygon2d, bake_polyline2d, bake_mesh2d, \
bake_point3d, bake_vector3d, bake_ray3d, bake_linesegment3d, bake_plane, \
bake_arc3d, bake_polyline3d, bake_mesh3d, bake_face3d, bake_polyface3d, \
bake_sphere, bake_cone, bake_cylinder, _get_layer, _get_attributes
from .bakedisplay import bake_display_point2d, bake_display_vector2d, \
bake_display_ray2d, bake_display_linesegment2d, bake_display_arc2d, \
bake_display_polygon2d, bake_display_polyline2d, bake_display_mesh2d, \
bake_display_text3d, bake_display_vector3d, bake_display_point3d, \
bake_display_ray3d, bake_display_linesegment3d, bake_display_arc3d, \
bake_display_polyline3d, bake_display_plane, bake_display_mesh3d, \
bake_display_face3d, bake_display_polyface3d, bake_display_sphere, \
bake_display_cone, bake_display_cylinder
try:
import System
except ImportError as e:
raise ImportError("Failed to import Windows/.NET libraries\n{}".format(e))
try:
import Rhino.DocObjects as docobj
from Rhino import RhinoDoc as rhdoc
except ImportError as e:
raise ImportError("Failed to import Rhino document attributes.\n{}".format(e))
BAKE_MAPPER = {
'Vector2D': bake_vector2d,
'Point2D': bake_point2d,
'Ray2D': bake_ray2d,
'LineSegment2D': bake_linesegment2d,
'Arc2D': bake_arc2d,
'Polyline2D': bake_polyline2d,
'Polygon2D': bake_polygon2d,
'Mesh2D': bake_mesh2d,
'Vector3D': bake_vector3d,
'Point3D': bake_point3d,
'Ray3D': bake_ray3d,
'LineSegment3D': bake_linesegment3d,
'Arc3D': bake_arc3d,
'Polyline3D': bake_polyline3d,
'Plane': bake_plane,
'Mesh3D': bake_mesh3d,
'Face3D': bake_face3d,
'Polyface3D': bake_polyface3d,
'Sphere': bake_sphere,
'Cone': bake_cone,
'Cylinder': bake_cylinder,
'DisplayVector2D': bake_display_vector2d,
'DisplayPoint2D': bake_display_point2d,
'DisplayRay2D': bake_display_ray2d,
'DisplayLineSegment2D': bake_display_linesegment2d,
'DisplayPolyline2D': bake_display_polyline2d,
'DisplayArc2D': bake_display_arc2d,
'DisplayPolygon2D': bake_display_polygon2d,
'DisplayMesh2D': bake_display_mesh2d,
'DisplayVector3D': bake_display_vector3d,
'DisplayPoint3D': bake_display_point3d,
'DisplayRay3D': bake_display_ray3d,
'DisplayPlane': bake_display_plane,
'DisplayLineSegment3D': bake_display_linesegment3d,
'DisplayPolyline3D': bake_display_polyline3d,
'DisplayArc3D': bake_display_arc3d,
'DisplayFace3D': bake_display_face3d,
'DisplayMesh3D': bake_display_mesh3d,
'DisplayPolyface3D': bake_display_polyface3d,
'DisplaySphere': bake_display_sphere,
'DisplayCone': bake_display_cone,
'DisplayCylinder': bake_display_cylinder,
'DisplayText3D': bake_display_text3d
}
[docs]
def bake_legend(legend, layer_name=None):
"""Add a Ladybug Legend object to the Rhino scene.
Args:
legend: A Ladybug Legend object to be added to the Rhino scene.
layer_name: Optional text string for the layer name on which to place the
legend. If None, text will be added to the current layer.
Returns:
A list of IDs that point to the objects in the Rhino scene in the following
order:
- legend_mesh -- A colored mesh for the legend.
- legend_title -- A text object for the legend title.
- legend_text -- Text objects for the rest of the legend text.
"""
# bake the legend mesh
legend_mesh = bake_mesh3d(legend.segment_mesh, layer_name)
# translate the legend text
_height = legend.legend_parameters.text_height
_font = legend.legend_parameters.font
if legend.legend_parameters.continuous_legend is False:
legend_text = [
DisplayText3D(txt, loc, _height, None, _font, 'Left', 'Bottom')
for txt, loc in zip(legend.segment_text, legend.segment_text_location)]
elif legend.legend_parameters.vertical is True:
legend_text = [
DisplayText3D(txt, loc, _height, None, _font, 'Left', 'Center')
for txt, loc in zip(legend.segment_text, legend.segment_text_location)]
else:
legend_text = [
DisplayText3D(txt, loc, _height, None, _font, 'Center', 'Bottom')
for txt, loc in zip(legend.segment_text, legend.segment_text_location)]
legend_title = DisplayText3D(
legend.title, legend.title_location, _height, None, _font)
legend_text.insert(0, legend_title)
# bake the text objects
legend_text_guids = []
for txt_obj in legend_text:
legend_text_guids.append(bake_display_text3d(txt_obj, layer_name))
return [legend_mesh] + legend_text_guids
[docs]
def bake_analysis(analysis, layer_name=None, bake_3d_legend=False,
min_point=None, max_point=None):
"""Add a Ladybug Display AnalysisGeometry object to the Rhino scene.
Args:
analysis: A Ladybug Display AnalysisGeometry object to be added to
the Rhino scene.
layer_name: Optional text string for the parent layer name on which to
place the AnalysisGeometry. The actual layer of the context will
always have a name that aligns with the AnalysisGeometry.display_name.
bake_3d_legend: A Boolean to note whether the AnalysisGeometry should
be baked with 3D legends for any AnalysisGeometries it
includes. (Default: False).
min_point: An optional Point3D to override the default min point
that are used to generate the legend. (Default: None).
max_point: An optional Point3D to override the default max point
that are used to generate the legend. (Default: None).
Returns:
A list of IDs that point to the objects in the Rhino scene.
"""
doc = rhdoc.ActiveDoc
# get attributes corresponding to the layer
layer_name = analysis.display_name if layer_name is None else \
'{}::{}'.format(layer_name, analysis.display_name)
min_pt = analysis.min_point if min_point is None else min_point
max_pt = analysis.max_point if max_point is None else max_point
# generate the colors that correspond to the values
obj_ids = []
for i, data in enumerate(analysis.data_sets):
# get properties used for all analysis geometries
objs_to_group = []
graphic = GraphicContainer(
data.values, min_pt, max_pt,
data.legend_parameters, data.data_type, data.unit)
colors = graphic.value_colors
sub_layer_name = layer_name if data.data_type is None else \
'{}::{}'.format(layer_name, data.data_type.name)
layer_index = _get_layer(sub_layer_name)
# translate the analysis geometry using the matching method
if analysis.matching_method == 'faces':
c_count = 0
for mesh in analysis.geometry:
mesh.colors = colors[c_count:c_count + len(mesh.faces)]
c_count += len(mesh.faces)
bake_func = bake_mesh3d if isinstance(mesh, Mesh3D) else bake_mesh2d
objs_to_group.append(bake_func(mesh, layer_name=layer_index))
elif analysis.matching_method == 'vertices':
c_count = 0
for mesh in analysis.geometry:
mesh.colors = colors[c_count:c_count + len(mesh.vertices)]
c_count += len(mesh.vertices)
bake_func = bake_mesh3d if isinstance(mesh, Mesh3D) else bake_mesh2d
objs_to_group.append(bake_func(mesh, layer_name=layer_index))
else: # one color per geometry object
bake_func = BAKE_MAPPER[analysis.geometry[0].__class__.__name__]
for geo_obj, col in zip(analysis.geometry, colors):
attrib = _get_attributes(layer_index)
attrib.ColorSource = docobj.ObjectColorSource.ColorFromObject
attrib.ObjectColor = color_to_color(col)
objs_to_group.append(bake_func(geo_obj, attributes=attrib))
# group the objects, and add JSON of values to layer user data
group_table = doc.Groups # group table
group_table.Add(sub_layer_name, objs_to_group)
layer_table = doc.Layers # layer table
layer_obj = layer_table[layer_index]
layer_obj.UserDictionary.Set('vis_data', json.dumps(data.to_dict()))
layer_obj.UserDictionary.Set('guids', System.Array[System.Guid](objs_to_group))
if i != analysis.active_data: # hide the inactive data layer
layer_obj.IsVisible = False
# add geometry to the global list and bake the legend if requested
obj_ids.extend(objs_to_group)
if bake_3d_legend:
obj_ids.extend(bake_legend(graphic.legend, layer_index))
# hide the layer if it is hidden
if analysis.hidden:
layer_table = doc.Layers # layer table
layer_index = _get_layer(layer_name)
layer_obj = layer_table[layer_index]
layer_obj.IsVisible = False
return obj_ids
[docs]
def bake_context(context, layer_name=None):
"""Add a Ladybug Display ContextGeometry object to the Rhino scene.
Args:
context: A Ladybug Display ContextGeometry object to be added to
the Rhino scene.
layer_name: Optional text string for the parent layer name on which to
place the ContextGeometry. The actual layer of the context will
always have a name that aligns with the ContextGeometry.display_name.
Returns:
A list of IDs that point to the objects in the Rhino scene.
"""
doc = rhdoc.ActiveDoc
# get attributes corresponding to the layer
layer_name = context.display_name if layer_name is None else \
'{}::{}'.format(layer_name, context.display_name)
layer_index = _get_layer(layer_name)
# hide the layer if it is hidden
if context.hidden:
layer_table = doc.Layers # layer table
layer_obj = layer_table[layer_index]
layer_obj.IsVisible = False
# loop through the objects and add them to the scene
obj_ids = []
for geo_obj in context.geometry:
bake_func = BAKE_MAPPER[geo_obj.__class__.__name__]
obj_ids.append(bake_func(geo_obj, layer_index))
return obj_ids
[docs]
def bake_visualization_set(vis_set, bake_3d_legend=False):
"""Add a Ladybug Display VisualizationSet object to the Rhino scene.
Args:
context_geometry: A Ladybug VisualizationSet object to be added to
the Rhino scene.
bake_3d_legend: A Boolean to note whether the VisualizationSet should
be baked with 3D legends for any AnalysisGeometries it
includes. (Default: False).
Returns:
A list of IDs that point to the objects in the Rhino scene.
"""
# convert the visualization set to model units if necessary
units_sys = units_system()
if vis_set.units is not None and units_sys is not None \
and vis_set.units != units_sys:
vis_set.convert_to_units(units_sys)
# bake all of the geometries
obj_ids = []
for geo in vis_set.geometry:
if isinstance(geo, AnalysisGeometry):
a_objs = bake_analysis(
geo, vis_set.display_name, bake_3d_legend,
vis_set.min_point, vis_set.max_point)
obj_ids.extend(a_objs)
else: # translate it as ContextGeometry
obj_ids.extend(bake_context(geo, vis_set.display_name))
return obj_ids