Source code for ladybug_rhino.command
"""Functions for dealing assisting with Rhino plugin commands."""
from __future__ import division
import os
import sys
import json
try:
import clr
import System
except ImportError as e: # No .NET being used
print('Failed to import CLR. Cannot access Pollination DLLs.\n{}'.format(e))
try:
import Rhino
except ImportError as e:
raise ImportError("Failed to import Rhino.\n{}".format(e))
try:
import scriptcontext as sc
except ImportError: # No Rhino doc is available.
print('Failed to import Rhino scriptcontext. Unable to access sticky.')
try:
from ladybug_geometry.geometry3d import Mesh3D
from ladybug.futil import unzip_file
from ladybug.config import folders
from ladybug.ddy import DDY
from ladybug.wea import Wea
from ladybug.epw import EPW
from ladybug.stat import STAT
from ladybug_display.visualization import AnalysisGeometry
except ImportError as e:
raise ImportError('\nFailed to import ladybug:\n\t{}'.format(e))
from .config import rhino_version, conversion_to_meters
from .download import download_file
from .fromgeometry import from_mesh3d
from .bakegeometry import _get_attributes
from .bakeobjects import bake_analysis, bake_context
def _pollination_rhino_dll_dir():
"""Get the directory in which the DLLs are installed."""
install_dir = os.path.dirname(folders.ladybug_tools_folder)
rh_ver = str(rhino_version[0]) + '.0'
dll_dir = os.path.join(install_dir, 'pollination', 'plugin', rh_ver, 'Pollination')
if not os.path.isdir:
msg = 'No Pollination installation could be found for Rhino {}.'.format(rh_ver)
print(msg)
return None
return dll_dir
[docs]
def import_pollination_core():
"""Import Pollination.Core from the dll or give a message if it is not found."""
try:
import Core
except ImportError: # the dll has not yet been added
# add the Pollination.Core DLL to the Common Language Runtime (CLR)
dll_dir = _pollination_rhino_dll_dir()
if dll_dir is None:
return None
pol_dll = os.path.join(dll_dir, 'Pollination.Core.dll')
if pol_dll not in sys.path:
sys.path.append(pol_dll)
clr.AddReference("Pollination.Core")
import Core
return Core
[docs]
def import_ladybug_display_schema():
"""Import LadybugDisplaySchema from the dll or give a message if it is not found."""
try:
import LadybugDisplaySchema
except ImportError: # the dll has not yet been added
# add the LadybugDisplaySchema DLL to the Common Language Runtime (CLR)
dll_dir = _pollination_rhino_dll_dir()
if dll_dir is None:
return None
pol_dll = os.path.join(dll_dir, 'LadybugDisplaySchema.dll')
if pol_dll not in sys.path:
sys.path.append(pol_dll)
clr.AddReference("LadybugDisplaySchema")
import LadybugDisplaySchema
return LadybugDisplaySchema
[docs]
def import_honeybee_ui():
"""Import Honeybee.UI.Rhino from the dll or give a message if it is not found."""
try:
import Honeybee
except ImportError: # the dll has not yet been added
# add the Honeybee.UI.Rhino DLL to the Common Language Runtime (CLR)
dll_dir = _pollination_rhino_dll_dir()
if dll_dir is None:
return None
pol_dll = os.path.join(dll_dir, 'Honeybee.UI.Rhino.dll')
if pol_dll not in sys.path:
sys.path.append(pol_dll)
clr.AddReference("Honeybee.UI.Rhino")
import Honeybee
return Honeybee
[docs]
def is_pollination_licensed():
"""Check if the installation of Pollination has an active license."""
Core = import_pollination_core()
if not Core:
return False
# use the utility to check whether there is an active license
is_licensed, msg = Core.Utility.CheckIfLicensed()
if not is_licensed:
print(msg)
return is_licensed
[docs]
def project_information():
"""Check if the installation of Pollination has an active license."""
Core = import_pollination_core()
if not Core:
return None
return Core.ModelEntity.CurrentModel.ProjectInfo
[docs]
def current_units(lbt_data_type):
"""Get the currently-assigned units for a given data type."""
Honeybee = import_honeybee_ui()
from Honeybee.UI import Units
unit_map = {
'm': Units.UnitType.Length,
'm2': Units.UnitType.Area,
'm3': Units.UnitType.Volume,
'C': Units.UnitType.Temperature,
'dC': Units.UnitType.TemperatureDelta,
'W': Units.UnitType.Power,
'W/m2': Units.UnitType.PowerDensity,
'kWh': Units.UnitType.Energy,
'kWh/m2': Units.UnitType.EnergyIntensity,
'm3/s': Units.UnitType.AirFlowRate,
'm3/s-m2': Units.UnitType.AirFlowRateArea,
'm/s': Units.UnitType.Speed,
'kg': Units.UnitType.Mass,
'kg/s': Units.UnitType.MassFlow,
'lux': Units.UnitType.Illuminance,
'cd/m2': Units.UnitType.Luminance,
'Pa': Units.UnitType.Pressure
}
try:
dot_net_units = unit_map[lbt_data_type.units[0]]
except KeyError: # the data type does not exist in .NET
return lbt_data_type.units[0]
current_settings = Units.CustomUnitSettings
unit = Units.ToUnitsNetEnum(current_settings[dot_net_units])
unit_str = Units.GetAbbreviation(unit)
unit_str = _convert_unit_abbrev(unit_str)
return unit_str if unit_str in lbt_data_type.units else lbt_data_type.units[0]
[docs]
def local_processor_count():
"""Get an integer for the number of processors on this machine.
If, for whatever reason, the number of processors could not be sensed,
None will be returned.
"""
return System.Environment.ProcessorCount
[docs]
def recommended_processor_count():
"""Get an integer for the recommended number of processors for parallel calculation.
This should be one less than the number of processors available on this machine
unless the machine has only one processor, in which case 1 will be returned.
If, for whatever reason, the number of processors could not be sensed, a value
of 1 will be returned.
"""
cpu_count = local_processor_count()
return 1 if cpu_count is None or cpu_count <= 1 else cpu_count - 1
def _download_weather(weather_URL):
"""Download a weather URL with a check if it's already in the default folder."""
# first check wether the url is actually a local path
if not weather_URL.lower().startswith('http'):
if weather_URL.lower().endswith('.epw') and os.path.isfile(weather_URL):
return weather_URL
assert os.path.isdir(weather_URL), 'Input weather URL is not a web ' \
'address nor a local folder directory.'
for fp in os.listdir(weather_URL):
if fp.lower().endswith('.epw'): # file type found
return os.path.join(weather_URL, fp)
_def_folder = folders.default_epw_folder
if weather_URL.lower().endswith('.zip'): # one building URL type
_folder_name = weather_URL.split('/')[-1][:-4]
else: # dept of energy URL type
_folder_name = weather_URL.split('/')[-2]
epw_path = os.path.join(_def_folder, _folder_name, _folder_name + '.epw')
if not os.path.isfile(epw_path):
zip_file_path = os.path.join(
_def_folder, _folder_name, _folder_name + '.zip')
download_file(weather_URL, zip_file_path, True)
unzip_file(zip_file_path)
return epw_path
[docs]
def setup_epw_input():
"""Setup the request for an EPW input and check for any previously set EPW."""
epw_input_request = Rhino.Input.Custom.GetString()
epw_input_request.SetCommandPrompt('Select an EPW file path or URL')
epw_input_request.AcceptNothing(True)
if 'lbt_epw' in sc.sticky:
epw_input_request.SetDefaultString(sc.sticky['lbt_epw'])
else: # check if the project information has an EPW associated with it
proj_info = project_information()
if proj_info is not None and proj_info.WeatherUrls is not None \
and len(proj_info.WeatherUrls) > 0:
epw_path = _download_weather(proj_info.WeatherUrls[0])
epw_input_request.SetDefaultString(os.path.basename(epw_path))
return epw_input_request
[docs]
def retrieve_epw_input(epw_input_request, command_options, option_values):
"""Retrieve an EPW input from the command line.
Args:
epw_input_request: The Rhino.Input.Custom.GetString object that was used
to setup the EPW input request.
command_options: A list of Rhino.Input.Custom.Option objects for the
options that were included with the EPW request. The values for these
options will be retrieved along with the EPW.
option_values: A list of values for each option, which will be updated
based on the user input.
Returns:
The file path to the EPW as a text string.
"""
# separate the list options from the others
list_opt_indices = [i + 1 for i, opt in enumerate(command_options)
if isinstance(opt, (tuple, list))]
# get the weather file and all options
epw_path = None
while True:
# This will prompt the user to input an EPW and visualization options
get_epw = epw_input_request.Get()
if get_epw == Rhino.Input.GetResult.String:
epw_path = epw_input_request.StringResult()
for i, opt in enumerate(command_options):
if not isinstance(opt, (tuple, list)):
option_values[i] = opt.CurrentValue
elif get_epw == Rhino.Input.GetResult.Option:
opt_ind = epw_input_request.OptionIndex()
if opt_ind in list_opt_indices:
option_values[opt_ind - 1] = \
epw_input_request.Option().CurrentListOptionIndex
continue
elif get_epw == Rhino.Input.GetResult.Cancel:
return None
break
# process the EPW file path or URL
if not epw_path:
print('No EPW file was selected')
return None
_def_folder = folders.default_epw_folder
if epw_path.startswith('http'): # download the EPW file
epw_path = _download_weather(epw_path)
sc.sticky['lbt_epw'] = os.path.basename(epw_path)
elif not os.path.isfile(epw_path):
possible_file = os.path.basename(epw_path)[:-4] \
if epw_path.lower().endswith('.epw') else epw_path
proj_info = project_information()
if proj_info is not None and proj_info.WeatherUrls is not None \
and len(proj_info.WeatherUrls) > 0:
epw_file_path = _download_weather(proj_info.WeatherUrls[0])
if possible_file in epw_file_path:
epw_path = epw_file_path
else:
epw_path = os.path.join(_def_folder, possible_file, possible_file + '.epw')
if not os.path.isfile(epw_path):
print('Selected EPW file at does not exist at: {}'.format(epw_path))
return
sc.sticky['lbt_epw'] = possible_file + '.epw'
else:
sc.sticky['lbt_epw'] = epw_path
return epw_path
[docs]
def setup_design_day_input():
"""Setup the request for a DDY, STAT, or EPW to get a design day."""
url_input_request = Rhino.Input.Custom.GetString()
url_input_request.SetCommandPrompt(
'Select a weather URL or DDY/STAT/EPW file path '
'from which a design day will be derived.')
url_input_request.AcceptNothing(True)
if 'lbt_url' in sc.sticky:
url_input_request.SetDefaultString(sc.sticky['lbt_url'])
else: # check if the project information has an EPW associated with it
proj_info = project_information()
if proj_info is not None and proj_info.WeatherUrls is not None \
and len(proj_info.WeatherUrls) > 0:
epw_path = _download_weather(proj_info.WeatherUrls[0])
url_input_request.SetDefaultString(
os.path.basename(epw_path).replace('.epw', ''))
return url_input_request
[docs]
def retrieve_cooling_design_day_input(url_input_request, command_options, option_values):
"""Retrieve the best cooling design day from what is available from a URL.
Args:
url_input_request: The Rhino.Input.Custom.GetString object that was used
to setup the URL input request. This input can be the file path to
an DDY, STAT or EPW file in which case the design day is taken directly
from the file. When a URL is used, the STAT file will first be searched
as it typically contains values for the most accurate solar model.
If no values are found there, the design day will be pulled from the
DDY. If there is no clear best design day in the DDY, it will be
derived from the EPW data.
command_options: A list of Rhino.Input.Custom.Option objects for the
options that were included with the URL request. The values for these
options will be retrieved along with the design day.
option_values: A list of values for each option, which will be updated
based on the user input.
Returns:
A tuple with two values.
- design_day: A ladybug DesignDay object for the best cooling design
day that was determined from the input.
- wea: A Wea object with annual hourly data collection of clear sky
radiation that represents design days throughout the year.
"""
# separate the list options from the others
list_opt_indices = [i + 1 for i, opt in enumerate(command_options)
if isinstance(opt, (tuple, list))]
# get the weather file and all options
url_path = None
while True:
# This will prompt the user to input an EPW and visualization options
get_url = url_input_request.Get()
if get_url == Rhino.Input.GetResult.String:
url_path = url_input_request.StringResult()
for i, opt in enumerate(command_options):
if not isinstance(opt, (tuple, list)):
option_values[i] = opt.CurrentValue
elif get_url == Rhino.Input.GetResult.Option:
opt_ind = url_input_request.OptionIndex()
if opt_ind in list_opt_indices:
option_values[opt_ind - 1] = \
url_input_request.Option().CurrentListOptionIndex
continue
elif get_url == Rhino.Input.GetResult.Cancel:
return None, None
break
# process the file path or URL
if not url_path:
print('No URL or file was selected')
return None, None
epw_path, stat_path, ddy_path = None, None, None
_def_folder = folders.default_epw_folder
if url_path.startswith('http'): # download the EPW file
epw_path = _download_weather(url_path)
stat_path = epw_path.replace('.epw', '.stat')
ddy_path = epw_path.replace('.epw', '.ddy')
sc.sticky['lbt_url'] = os.path.basename(epw_path.replace('.epw', ''))
elif not os.path.isfile(url_path):
possible_file = os.path.basename(url_path)[:-4] \
if url_path.lower().endswith('.epw') else url_path
proj_info = project_information()
if proj_info is not None and proj_info.WeatherUrls is not None \
and len(proj_info.WeatherUrls) > 0:
epw_file_path = _download_weather(proj_info.WeatherUrls[0])
if possible_file in epw_file_path:
epw_path = epw_file_path
else:
epw_path = os.path.join(_def_folder, possible_file, possible_file + '.epw')
stat_path = epw_path.replace('.epw', '.stat')
ddy_path = epw_path.replace('.epw', '.ddy')
if not os.path.isfile(epw_path):
print('Selected EPW file at does not exist at: {}'.format(epw_path))
return
sc.sticky['lbt_url'] = url_path
elif url_path.endswith('.ddy'):
ddy_path = url_path
elif url_path.endswith('.epw'):
epw_path = url_path
elif url_path.endswith('.stat'):
stat_path = url_path
else:
return None, None
# process the possible files into the best design day
if stat_path is not None:
stat_obj = STAT(stat_path)
des_day = stat_obj.annual_cooling_design_day_004
try: # first see if we can get the values from monthly optical depths
wea = Wea.from_stat_file(stat_path)
except Exception: # no optical data was found; use the original clear sky
wea = Wea.from_ashrae_clear_sky(stat_obj.location)
return des_day, wea
if ddy_path is not None:
ddy_obj = DDY.from_ddy_file(ddy_path)
des_days = []
for dday in ddy_obj:
if '.4%' in dday.name:
des_days.append(dday)
if len(des_days) != 0:
des_days.sort(key=lambda x: x.dry_bulb_condition, reverse=True)
des_day = des_days[0]
wea = Wea.from_ashrae_clear_sky(ddy_obj.location)
return des_day, wea
if epw_path is not None:
epw_obj = EPW(epw_path)
des_day = stat_obj.annual_cooling_design_day_004
if des_day is None:
des_day = epw_obj.approximate_design_day()
wea = Wea.from_ashrae_clear_sky(epw_obj.location)
return des_day, wea
return None, None
[docs]
def add_north_option(input_request):
"""Add a North option to an input request.
Args:
input_request: A Rhino Command Input such as that obtained from the
setup_epw_input function or the Rhino.Input.Custom.GetString
constructor.
Returns:
A tuple with two values.
- north_option: The Option object for the North input.
- north_value: The value of the north.
"""
if 'lbt_north' in sc.sticky:
north_value = sc.sticky['lbt_north']
else:
proj_info = project_information()
if proj_info is not None and proj_info.North is not None:
north_value = float(proj_info.North)
else:
north_value = 0
north_option = Rhino.Input.Custom.OptionDouble(north_value, -360, 360)
description = 'North - the counterclockwise difference between true North and the ' \
'Y-axis in degrees (90:West, -90:East)'
input_request.AddOptionDouble('North', north_option, description)
return north_option, north_value
[docs]
def add_month_day_hour_options(
input_request, default_inputs=(12, 21, 0, 23), sticky_key=None):
"""Add a options for Month, Day, and Hour to an input request.
Args:
input_request: A Rhino Command Input such as that obtained from the
setup_epw_input function or the Rhino.Input.Custom.GetString
constructor.
default_inputs: The default input month, day, start_hour and end_hour.
A value of 0 for month or day denotes that all values of a given
month, day and hour are used. For the start_hour and end_hour,
the values should be between 0 and 23 where 0 denotes
midnight. (Default: (12, 21, 0, 23)).
sticky_key: An optional sticky key, which will be used to to pull
previously set values from sticky. (eg. direct_sun).
Returns:
A tuple with two values.
- mdh_options: A tuple of the Option objects for the month, day and
hour inputs.
- mdh_values: The value associated with each month, day and hour.
"""
if sticky_key is not None:
month_key = 'lbt_{}_month'.format(sticky_key)
month_i_ = sc.sticky[month_key] if month_key in sc.sticky else default_inputs[0]
day_key = 'lbt_{}_day'.format(sticky_key)
day_ = sc.sticky[day_key] if day_key in sc.sticky else default_inputs[1]
sthr_key = 'lbt_{}_start_hour'.format(sticky_key)
st_hr_ = sc.sticky[sthr_key] if sthr_key in sc.sticky else default_inputs[2]
endhr_key = 'lbt_{}_end_hour'.format(sticky_key)
end_hr_ = sc.sticky[endhr_key] if endhr_key in sc.sticky else default_inputs[3]
else:
month_i_, day_, st_hr_, end_hr_ = default_inputs
month_i_ = 0 if month_i_ < 0 else month_i_
month_option = ('All', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', 'DecMarJun')
input_request.AddOptionList('Month', month_option, month_i_)
day_option = Rhino.Input.Custom.OptionInteger(day_, 0, 31)
description = 'Day - day of the month [1-31]. Use 0 to specify all days'
input_request.AddOptionInteger('Day', day_option, description)
start_hr_option = Rhino.Input.Custom.OptionDouble(st_hr_, 0, 23)
description = 'StartHour - start hour of the day [0-23]. Decimals accepted.'
input_request.AddOptionDouble('StartHour', start_hr_option, description)
end_hr_option = Rhino.Input.Custom.OptionDouble(end_hr_, 0, 23)
description = 'EndHour - start hour of the day [0-23]. Decimals accepted.'
input_request.AddOptionDouble('EndHour', end_hr_option, description)
return [month_option, day_option, start_hr_option, end_hr_option], \
[month_i_, day_, st_hr_, end_hr_]
[docs]
def add_legend_min_max_options(input_request):
"""Add legend min and max outputs to an input request.
Args:
input_request: A Rhino Command Input such as that obtained from the
setup_epw_input function or the Rhino.Input.Custom.GetString
constructor.
Returns:
A tuple with two values.
- options: The two Option objects for the legend min and max inputs.
- values: The two values of the min and max.
"""
min_val, max_val = float('-inf'), float('+inf')
min_option = Rhino.Input.Custom.OptionDouble(min_val)
input_request.AddOptionDouble('MinLegend', min_option)
max_option = Rhino.Input.Custom.OptionDouble(max_val)
input_request.AddOptionDouble('MaxLegend', max_option)
return [min_option, max_option], [min_val, max_val]
[docs]
def retrieve_geometry_input(geo_input_request, command_options, option_values):
"""Retrieve a geometry input from the command line.
Args:
geo_input_request: The Rhino.Input.Custom.GetObject object that was used
to setup the geometry input request. Note that this input does not
need any filters set on it as this method will assign them.
command_options: A list of Rhino.Input.Custom.Option objects for the
options that were included with the geometry request. The values for
these options will be retrieved along with the geometry.
option_values: A list of values for each option, which will be updated
based on the user input.
Returns:
A list of geometry objects. Will be None if the operation was canceled.
"""
# add the filters and attributes related to geometry selection
geo_filter = Rhino.DocObjects.ObjectType.Surface | \
Rhino.DocObjects.ObjectType.PolysrfFilter | \
Rhino.DocObjects.ObjectType.Mesh
geo_input_request.GeometryFilter = geo_filter
geo_input_request.GroupSelect = True
geo_input_request.SubObjectSelect = False
geo_input_request.EnableClearObjectsOnEntry(False)
geo_input_request.EnableUnselectObjectsOnExit(False)
geo_input_request.DeselectAllBeforePostSelect = False
geo_input_request.AcceptNothing(True)
# request the analysis geometries from the user
have_preselected_objects = False
while True:
res = geo_input_request.GetMultiple(1, 0)
if res == Rhino.Input.GetResult.Option:
geo_input_request.EnablePreSelect(False, True)
continue
elif res != Rhino.Input.GetResult.Object:
if res == Rhino.Input.GetResult.Cancel:
return None
return []
if geo_input_request.ObjectsWerePreselected:
have_preselected_objects = True
geo_input_request.EnablePreSelect(False, True)
continue
for i, opt in enumerate(command_options):
option_values[i] = opt.CurrentValue
break
# process any preselected objects before the command ran
if have_preselected_objects:
# Normally, pre-selected objects will remain selected, when a
# command finishes, and post-selected objects will be unselected.
# This this way of picking, it is possible to have a combination
# of pre-selected and post-selected. So, to make sure everything
# "looks the same", lets unselect everything before finishing
# the command.
for i in range(0, geo_input_request.ObjectCount):
rhino_obj = geo_input_request.Object(i).Object()
if rhino_obj is not None:
rhino_obj.Select(False)
sc.doc.Views.Redraw()
# get the actual geometry from the selection
obj_table = Rhino.RhinoDoc.ActiveDoc.Objects
geometry = []
for get_obj in geo_input_request.Objects():
geometry.append(obj_table.Find(get_obj.ObjectId).Geometry)
return geometry
[docs]
def study_geometry_request(study_name=None):
"""Prompt the user for study geometry that requires a grid size and offset.
Args:
study_name: An optional text string for the name of the study (eg. Direct Sun).
Returns:
A tuple with three values.
- geometry: The Rhino Surfaces, Polysurfaces and/or Meshes that were selected.
- grid_size: A number for the grid size that the user selected.
- offset: A number for the offset that the user selected.
"""
# setup the request to get the analysis geometry from the scene
get_geo = Rhino.Input.Custom.GetObject()
base_msg = 'Select surfaces, polysurfaces, or meshes'
msg = '{} on which {} will be studied'.format(base_msg, study_name) \
if study_name is not None else '{} to study.'.format(base_msg)
get_geo.SetCommandPrompt(msg)
# add the options for the geometry
grid_size = sc.sticky['lbt_study_grid_size'] if 'lbt_study_grid_size' in sc.sticky \
else int(1 / conversion_to_meters())
gs_option = Rhino.Input.Custom.OptionDouble(grid_size, True, 0)
description = 'GridSize - distance value for the size of grid cells at which ' \
' geometry will be subdivided'
get_geo.AddOptionDouble('GridSize', gs_option, description)
offset_dist = sc.sticky['lbt_study_offset'] if 'lbt_study_offset' in sc.sticky \
else round((0.1 / conversion_to_meters()), 2)
off_option = Rhino.Input.Custom.OptionDouble(offset_dist, True, 0)
description = 'Offset - distance value from the input geometry at which the ' \
'analysis will occur'
get_geo.AddOptionDouble('Offset', off_option, description)
# request the geometry from the user
command_options = [gs_option, off_option]
option_values = [grid_size, offset_dist]
geometry = retrieve_geometry_input(get_geo, command_options, option_values)
grid_size, offset_dist = option_values
# update the sticky values for grid size and offset
sc.sticky['lbt_study_grid_size'] = grid_size
sc.sticky['lbt_study_offset'] = offset_dist
return geometry, grid_size, offset_dist
[docs]
def add_to_document_request(geometry_name=None):
"""Prompt the user for whether geometry should be added to the Rhino document.
Returns:
A boolean value for whether the geometry should be added to the document (True)
or not (False).
"""
gres = Rhino.Input.Custom.GetString()
study_name = geometry_name if geometry_name is not None else 'Study'
msg = '{} complete! Hit ENTER when done. Add the ' \
'geometry to the document?'.format(study_name)
gres.SetCommandPrompt(msg)
gres.SetDefaultString('Add?')
bake_result = False
result_option = Rhino.Input.Custom.OptionToggle(False, 'No', 'Yes')
gres.AddOptionToggle('AddToDoc', result_option)
while True:
get_res = gres.Get()
if get_res == Rhino.Input.GetResult.String:
bake_result = result_option.CurrentValue
elif get_res == Rhino.Input.GetResult.Cancel:
bake_result = False
break
else:
continue
break
return bake_result
[docs]
def bake_pollination_vis_set(vis_set, bake_3d_legend=False):
"""Bake a VisualizationSet using Pollination Rhino libraries for an editable legend.
"""
Core = import_pollination_core()
LadybugDisplaySchema = import_ladybug_display_schema()
if not Core or not LadybugDisplaySchema:
return
for geo in vis_set.geometry:
if isinstance(geo, AnalysisGeometry):
amsh_typs = ('faces', 'vertices')
if isinstance(geo.geometry[0], Mesh3D) and geo.matching_method in amsh_typs:
layer_name = vis_set.display_name if len(vis_set.geometry) == 1 else \
'{}::{}'.format(vis_set.display_name, geo.display_name)
for data in geo.data_sets:
# translate Mesh3D into Rhino Mesh
if len(geo.geometry) == 1:
mesh = from_mesh3d(geo.geometry[0])
else:
mesh = Rhino.Geometry.Mesh()
for mesh_i in geo.geometry:
mesh.Append(from_mesh3d(mesh_i))
# translate visualization data into .NET VisualizationData
data_json = json.dumps(data.to_dict())
vis_data = LadybugDisplaySchema.VisualizationData.FromJson(data_json)
a_mesh = Core.Objects.AnalysisMeshObject(mesh, vis_data)
# add it to the Rhino document
doc = Rhino.RhinoDoc.ActiveDoc
sub_layer_name = layer_name \
if len(geo.data_sets) == 1 or data.data_type is None else \
'{}::{}'.format(layer_name, data.data_type.name)
a_mesh.Id = doc.Objects.AddMesh(
mesh, _get_attributes(sub_layer_name))
current_model = Core.ModelEntity.CurrentModel
def do_act():
pass
def undo_act():
pass
am_list = System.Array[Core.Objects.AnalysisMeshObject]([a_mesh])
current_model.Add(doc, am_list, do_act, undo_act)
else:
bake_analysis(
geo, vis_set.display_name, bake_3d_legend,
vis_set.min_point, vis_set.max_point)
else:
bake_context(geo, vis_set.display_name)
def _convert_unit_abbrev(unit_str):
"""Replace all superscripts and other crud used in the .NET unit abbreviations."""
clean_chars = []
for c in unit_str:
c_ord = ord(c)
if c_ord < 128: # ASCII character
clean_chars.append(c)
elif c_ord == 178: # superscript 2
clean_chars.append('2')
elif c_ord == 179: # superscript 3
clean_chars.append('3')
elif c_ord == 183: # multiplication dot
clean_chars.append('-')
elif c_ord == 176: # unnecessary degree symbol
pass
elif c_ord == 8710: # delta symbol
clean_chars.append('d')
else:
print('Character "{}" with ordinal {} was not decoded.'.format(c, c_ord))
unit_str = ''.join(clean_chars)
if unit_str == 'lx':
return 'lux'
return unit_str.replace(' ', '').replace('BTU', 'Btu')