-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(model): Add method to translate model to vis set
- Loading branch information
1 parent
f40f116
commit aeb0b7e
Showing
9 changed files
with
361 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
from dragonfly_display.cli import display | ||
|
||
if __name__ == '__main__': | ||
display() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,9 @@ | ||
# coding=utf-8 | ||
# import the core dragonfly modules | ||
from dragonfly.model import Model | ||
|
||
# import the extension functions | ||
from .model import model_to_vis_set | ||
|
||
# inject the methods onto the classes | ||
Model.to_vis_set = model_to_vis_set |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
"""dragonfly-display commands.""" | ||
import click | ||
import sys | ||
import os | ||
import logging | ||
import json | ||
import pickle | ||
|
||
from honeybee_display.attr import FaceAttribute, RoomAttribute | ||
|
||
from dragonfly.model import Model | ||
from dragonfly.cli import main | ||
|
||
|
||
_logger = logging.getLogger(__name__) | ||
|
||
|
||
# command group for all display extension commands. | ||
@click.group(help='dragonfly display commands.') | ||
@click.version_option() | ||
def display(): | ||
pass | ||
|
||
|
||
@display.command('model-to-vis') | ||
@click.argument('model-file', type=click.Path( | ||
exists=True, file_okay=True, dir_okay=False, resolve_path=True)) | ||
@click.option( | ||
'--multiplier/--full-geometry', ' /-fg', help='Flag to note if the ' | ||
'multipliers on each Building story should be passed along to the ' | ||
'generated Honeybee Room objects or if full geometry objects should be ' | ||
'written for each story in the building.', default=True, show_default=True) | ||
@click.option( | ||
'--no-ceil-adjacency/--ceil-adjacency', ' /-a', help='Flag to indicate ' | ||
'whether adjacencies should be solved between interior stories when ' | ||
'Room2D floor and ceiling geometries are coplanar. This ensures ' | ||
'that Surface boundary conditions are used instead of Adiabatic ones. ' | ||
'Note that this input has no effect when the object-per-model is Story.', | ||
default=True, show_default=True) | ||
@click.option( | ||
'--color-by', '-c', help='Text for the property that dictates the colors of ' | ||
'the Model geometry. Choose from: type, boundary_condition, none. ' | ||
'If none, only a wireframe of the Model will be generated (assuming the ' | ||
'--exclude-wireframe option is not used). None is useful when the primary ' | ||
'purpose of the visualization is to display results in relation to the Model ' | ||
'geometry or display some room_attr or face_attr as an AnalysisGeometry ' | ||
'or Text labels.', type=str, default='type', show_default=True) | ||
@click.option( | ||
'--wireframe/--exclude-wireframe', ' /-xw', help='Flag to note whether a ' | ||
'ContextGeometry dedicated to the Model Wireframe (in DisplayLineSegment3D) should ' | ||
'be included in the output VisualizationSet.', default=True, show_default=True) | ||
@click.option( | ||
'--mesh/--faces', help='Flag to note whether the colored model geometries should ' | ||
'be represented with DisplayMesh3D objects instead of DisplayFace3D objects. ' | ||
'Meshes can usually be rendered faster and they scale well for large models ' | ||
'but all geometry is triangulated (meaning that their wireframe in certain ' | ||
'platforms might not appear ideal).', default=True, show_default=True) | ||
@click.option( | ||
'--show-color-by/--hide-color-by', ' /-hcb', help='Flag to note whether the ' | ||
'color-by geometry should be hidden or shown by default. Hiding the color-by ' | ||
'geometry is useful when the primary purpose of the visualization is to display ' | ||
'grid-data or room/face attributes but it is still desirable to have the option ' | ||
'to turn on the geometry.', default=True, show_default=True) | ||
@click.option( | ||
'--room-attr', '-r', help='An optional text string of an attribute that the Model ' | ||
'Rooms have, which will be used to construct a visualization of this attribute ' | ||
'in the resulting VisualizationSet. Multiple instances of this option can be passed ' | ||
'and a separate VisualizationData will be added to the AnalysisGeometry that ' | ||
'represents the attribute in the resulting VisualizationSet (or a separate ' | ||
'ContextGeometry layer if room_text_labels is True). Room attributes ' | ||
'input here can have . that separates the nested attributes from ' | ||
'one another. For example, properties.energy.program_type.', | ||
type=click.STRING, multiple=True, default=None, show_default=True) | ||
@click.option( | ||
'--face-attr', '-f', help='An optional text string of an attribute that the Model ' | ||
'Faces have, which will be used to construct a visualization of this attribute in ' | ||
'the resulting VisualizationSet. Multiple instances of this option can be passed and' | ||
' a separate VisualizationData will be added to the AnalysisGeometry that ' | ||
'represents the attribute in the resulting VisualizationSet (or a separate ' | ||
'ContextGeometry layer if face_text_labels is True). Face attributes ' | ||
'input here can have . that separates the nested attributes from ' | ||
'one another. For example, properties.energy.construction.', | ||
type=click.STRING, multiple=True, default=None, show_default=True) | ||
@click.option( | ||
'--color-attr/--text-attr', help='Flag to note whether to note whether the ' | ||
'input room-attr and face-attr should be expressed as a colored AnalysisGeometry ' | ||
'or a ContextGeometry as text labels.', default=True, show_default=True) | ||
@click.option( | ||
'--grid-display-mode', '-m', help='Text that dictates how the ContextGeometry ' | ||
'for Model SensorGrids should display in the resulting visualization. The Default ' | ||
'option will draw sensor points whenever there is no grid_data_path and will not ' | ||
'draw them at all when grid data is provided, assuming the AnalysisGeometry of ' | ||
'the grids is sufficient. Choose from: Default, Points, Wireframe, Surface, ' | ||
'SurfaceWithEdges, None.', | ||
type=str, default='Default', show_default=True) | ||
@click.option( | ||
'--hide-grid/--show-grid', ' /-sg', help='Flag to note whether the SensorGrid ' | ||
'ContextGeometry should be hidden or shown by default.', | ||
default=True, show_default=True) | ||
@click.option( | ||
'--output-format', '-of', help='Text for the output format of the resulting ' | ||
'VisualizationSet File (.vsf). Choose from: vsf, json, pkl, vtkjs, html. Note ' | ||
'that both vsf and json refer to the the JSON version of the VisualizationSet ' | ||
'file and the distinction between the two is only for help in coordinating file ' | ||
'extensions (since both .vsf and .json can be acceptable). Also note that ' | ||
'ladybug-vtk must be installed in order for the vtkjs or html options to be usable ' | ||
'and the html format refers to a web page with the vtkjs file embedded within it. ' | ||
'The vtkjs and html options also require an explicit --output-file to be specified.', | ||
type=str, default='vsf', show_default=True) | ||
@click.option( | ||
'--output-file', help='Optional file to output the JSON string of ' | ||
'the config object. By default, it will be printed out to stdout', | ||
type=click.File('w'), default='-', show_default=True) | ||
def model_to_vis_set( | ||
model_file, multiplier, no_ceil_adjacency, | ||
color_by, wireframe, mesh, show_color_by, | ||
room_attr, face_attr, color_attr, grid_display_mode, hide_grid, | ||
output_format, output_file): | ||
"""Translate a Dragonfly Model file (.dfjson) to a VisualizationSet file (.vsf). | ||
This command can also optionally translate the Dragonfly Model to a .vtkjs file, | ||
which can be visualized in the open source Visual ToolKit (VTK) platform. | ||
\b | ||
Args: | ||
model_file: Full path to a Dragonfly Model (DFJSON or DFpkl) file. | ||
""" | ||
try: | ||
model_obj = Model.from_file(model_file) | ||
room_attrs = [] if len(room_attr) == 0 or room_attr[0] == '' else room_attr | ||
face_attrs = [] if len(face_attr) == 0 or face_attr[0] == '' else face_attr | ||
text_labels = not color_attr | ||
hide_color_by = not show_color_by | ||
|
||
face_attributes = [] | ||
for fa in face_attrs: | ||
faa = FaceAttribute(name=fa, attrs=[fa], color=color_attr, text=text_labels) | ||
face_attributes.append(faa) | ||
|
||
room_attributes = [] | ||
for ra in room_attrs: | ||
raa = RoomAttribute(name=ra, attrs=[ra], color=color_attr, text=text_labels) | ||
room_attributes.append(raa) | ||
|
||
ceil_adjacency = not no_ceil_adjacency | ||
vis_set = model_obj.to_vis_set( | ||
multiplier, ceil_adjacency, color_by=color_by, include_wireframe=wireframe, | ||
use_mesh=mesh, hide_color_by=hide_color_by, room_attrs=room_attributes, | ||
face_attrs=face_attributes, grid_display_mode=grid_display_mode, | ||
hide_grid=hide_grid) | ||
output_format = output_format.lower() | ||
if output_format in ('vsf', 'json'): | ||
output_file.write(json.dumps(vis_set.to_dict())) | ||
elif output_format == 'pkl': | ||
if output_file.name != '<stdout>': | ||
out_folder, out_file = os.path.split(output_file.name) | ||
vis_set.to_pkl(out_file, out_folder) | ||
else: | ||
output_file.write(pickle.dumps(vis_set.to_dict())) | ||
elif output_format in ('vtkjs', 'html'): | ||
assert output_file.name != '<stdout>', \ | ||
'Must specify an --output-file to use --output-format vtkjs.' | ||
out_folder, out_file = os.path.split(output_file.name) | ||
try: | ||
if out_file.endswith('.vtkjs'): | ||
out_file = out_file[:-6] | ||
elif out_file.endswith('.html'): | ||
out_file = out_file[:-5] | ||
if output_format == 'vtkjs': | ||
vis_set.to_vtkjs(output_folder=out_folder, file_name=out_file) | ||
if output_format == 'html': | ||
vis_set.to_html(output_folder=out_folder, file_name=out_file) | ||
except AttributeError as ae: | ||
raise AttributeError( | ||
'Ladybug-vtk must be installed in order to use --output-format ' | ||
'vtkjs.\n{}'.format(ae)) | ||
else: | ||
raise ValueError('Unrecognized output-format "{}".'.format(output_format)) | ||
except Exception as e: | ||
_logger.exception('Failed to translate Model to VisualizationSet.\n{}'.format(e)) | ||
sys.exit(1) | ||
else: | ||
sys.exit(0) | ||
|
||
|
||
# add display sub-group to dragonfly CLI | ||
main.add_command(display) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
"""Method to translate a Dragonfly Model to a VisualizationSet.""" | ||
from honeybee_display.model import model_to_vis_set as hb_model_to_vis_set | ||
|
||
|
||
def model_to_vis_set( | ||
model, use_multiplier=True, solve_ceiling_adjacencies=False, | ||
color_by='type', include_wireframe=True, use_mesh=True, | ||
hide_color_by=False, room_attrs=None, face_attrs=None, | ||
grid_display_mode='Default', hide_grid=True): | ||
"""Translate a Dragonfly Model to a VisualizationSet. | ||
Args: | ||
model: A Dragonfly Model object to be converted to a VisualizationSet. | ||
use_multiplier: If True, the multipliers on this Model's Stories will be | ||
passed along to the generated Honeybee Room objects, indicating the | ||
simulation will be run once for each unique room and then results | ||
will be multiplied. If False, full geometry objects will be written | ||
for each and every floor in the building that are represented through | ||
multipliers and all resulting multipliers will be 1. (Default: True). | ||
solve_ceiling_adjacencies: Boolean to note whether adjacencies should be | ||
solved between interior stories when Room2D floor and ceiling | ||
geometries are coplanar. This ensures that Surface boundary | ||
conditions are used instead of Adiabatic ones. Note that this input | ||
has no effect when the object_per_model is Story. (Default: False). | ||
color_by: Text that dictates the colors of the Model geometry. | ||
If none, only a wireframe of the Model will be generated, assuming | ||
include_wireframe is True. This is useful when the primary purpose of | ||
the visualization is to display results in relation to the Model | ||
geometry or display some room_attrs or face_attrs as an AnalysisGeometry | ||
or Text labels. (Default: type). Choose from the following: | ||
* type | ||
* boundary_condition | ||
* None | ||
include_wireframe: Boolean to note whether a ContextGeometry dedicated to | ||
the Model Wireframe (in DisplayLineSegment3D) should be included | ||
in the output VisualizationSet. (Default: True). | ||
use_mesh: Boolean to note whether the colored model geometries should | ||
be represented with DisplayMesh3D objects (True) instead of DisplayFace3D | ||
objects (False). Meshes can usually be rendered faster and they scale | ||
well for large models but all geometry is triangulated (meaning that | ||
the wireframe in certain platforms might not appear ideal). (Default: True). | ||
hide_color_by: Boolean to note whether the color_by geometry should be | ||
hidden or shown by default. Hiding the color-by geometry is useful | ||
when the primary purpose of the visualization is to display grid_data | ||
or room/face attributes but it is still desirable to have the option | ||
to turn on the geometry. | ||
room_attrs: An optional list of room attribute objects from the | ||
honeybee_display.attr module. | ||
face_attrs: An optional list of face attribute objects from the | ||
honeybee_display.attr module. | ||
grid_display_mode: Text that dictates how the ContextGeometry for Model | ||
SensorGrids should display in the resulting visualization. The Default | ||
option will draw sensor points. Choose from the following: | ||
* Default | ||
* Points | ||
* Wireframe | ||
* Surface | ||
* SurfaceWithEdges | ||
* None | ||
hide_grid: Boolean to note whether the SensorGrid ContextGeometry should be | ||
hidden or shown by default. (Default: True). | ||
Returns: | ||
A VisualizationSet object that represents the model. | ||
""" | ||
# create the Honeybee Model from the Dragonfly one | ||
hb_model = model.to_honeybee( | ||
'District', use_multiplier=use_multiplier, | ||
solve_ceiling_adjacencies=solve_ceiling_adjacencies, | ||
enforce_adj=False, enforce_solid=True)[0] | ||
# convert the Honeybee Model to a VisualizationSet | ||
return hb_model_to_vis_set( | ||
hb_model, color_by, include_wireframe, use_mesh, hide_color_by, | ||
room_attrs, face_attrs, grid_display_mode, hide_grid) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
dragonfly-energy>=1.95.35 | ||
dragonfly-radiance>=1.64.118 | ||
ladybug-vtk>=0.13.9 | ||
dragonfly-energy>=1.25.28 | ||
dragonfly-radiance>=0.3.20 | ||
ladybug-vtk>=0.14.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
honeybee-display>=0.3.4 | ||
honeybee-display>=0.3.6 | ||
dragonfly-core>=1.42.18 |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
"""Test the Model to_vis_set method.""" | ||
from ladybug_display.geometry3d import DisplayMesh3D, DisplayLineSegment3D, \ | ||
DisplayText3D | ||
from ladybug_display.visualization import VisualizationSet, \ | ||
ContextGeometry, AnalysisGeometry, VisualizationData | ||
from dragonfly.model import Model | ||
from honeybee_display.attr import RoomAttribute, FaceAttribute | ||
|
||
|
||
def test_default_to_vis_set(): | ||
"""Test the default output of Model.to_vis_set().""" | ||
model_json = './tests/json/model_with_doors_skylights.dfjson' | ||
parsed_model = Model.from_dfjson(model_json) | ||
vis_set = parsed_model.to_vis_set() | ||
|
||
assert isinstance(vis_set, VisualizationSet) | ||
assert len(vis_set) == 10 | ||
for geo_obj in vis_set[:-1]: | ||
assert isinstance(geo_obj, ContextGeometry) | ||
assert isinstance(geo_obj[0], DisplayMesh3D) | ||
assert isinstance(vis_set[-1], ContextGeometry) | ||
assert vis_set[-1].display_name == 'Wireframe' | ||
assert isinstance(vis_set[-1][0], DisplayLineSegment3D) | ||
|
||
vis_set = parsed_model.to_vis_set(include_wireframe=False) | ||
assert len(vis_set) == 9 | ||
for geo_obj in vis_set: | ||
assert isinstance(geo_obj, ContextGeometry) | ||
assert isinstance(geo_obj[0], DisplayMesh3D) | ||
|
||
vis_set = parsed_model.to_vis_set(color_by='none') | ||
assert len(vis_set) == 1 | ||
for geo_obj in vis_set: | ||
assert isinstance(geo_obj, ContextGeometry) | ||
assert isinstance(geo_obj[0], DisplayLineSegment3D) | ||
|
||
vis_set = parsed_model.to_vis_set( | ||
color_by='boundary_condition', include_wireframe=False) | ||
assert len(vis_set) == 5 | ||
for geo_obj in vis_set: | ||
assert isinstance(geo_obj, ContextGeometry) | ||
assert isinstance(geo_obj[0], DisplayMesh3D) | ||
|
||
|
||
def test_room_attr_to_vis_set(): | ||
"""Test the room attribute argument of Model.to_vis_set().""" | ||
model_json = './tests/json/model_with_doors_skylights.dfjson' | ||
parsed_model = Model.from_dfjson(model_json) | ||
attr_color = RoomAttribute( | ||
name='Floor Area', attrs=['floor_area'], text=False, color=True) | ||
vis_set = parsed_model.to_vis_set(color_by='none', room_attrs=[attr_color]) | ||
|
||
assert isinstance(vis_set[0], AnalysisGeometry) | ||
assert isinstance(vis_set[0][0], VisualizationData) | ||
assert len(vis_set[0][0].values) == 15 | ||
attr_txt = RoomAttribute( | ||
name='Floor Area', attrs=['floor_area'], text=True, color=False) | ||
vis_set = parsed_model.to_vis_set(color_by='none', room_attrs=[attr_txt]) | ||
assert isinstance(vis_set[0], ContextGeometry) | ||
assert len(vis_set[0]) == 15 | ||
for item in vis_set[0]: | ||
assert isinstance(item, DisplayText3D) | ||
|
||
|
||
def test_face_attr_to_vis_set(): | ||
"""Test the face attribute argument of Model.to_vis_set().""" | ||
model_json = './tests/json/model_with_doors_skylights.dfjson' | ||
parsed_model = Model.from_dfjson(model_json) | ||
attr_color = FaceAttribute(name='Area', attrs=['area'], color=True, text=False) | ||
vis_set = parsed_model.to_vis_set(color_by='None', face_attrs=[attr_color]) | ||
assert isinstance(vis_set[0], AnalysisGeometry) | ||
assert isinstance(vis_set[0][0], VisualizationData) | ||
assert len(vis_set[0][0].values) == 324 | ||
|
||
attr_txt = FaceAttribute(name='Area', attrs=['area'], color=False, text=True) | ||
vis_set = parsed_model.to_vis_set(color_by='None', face_attrs=[attr_txt]) | ||
assert isinstance(vis_set[0], ContextGeometry) | ||
assert len(vis_set[0]) == 324 | ||
for item in vis_set[0]: | ||
assert isinstance(item, DisplayText3D) |