diff --git a/.gitignore b/.gitignore
index d852e2a..54f10c7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,4 +25,5 @@ tox.ini
#generate html model
model.html
-venv/*
\ No newline at end of file
+venv/*
+*.devcontainer
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index 6698567..2ad2f51 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,6 +1,7 @@
FROM python:3.7 as base
RUN apt-get update \
+ && apt-get -y install ffmpeg libsm6 libxext6 xvfb --no-install-recommends \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
diff --git a/README.md b/README.md
index 24138a6..5d9540b 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,6 @@
## Estidama-Daylight App
+### An app to run compliance analysis for PBRS (Pearl Building Rating System) LBi-7 Daylight & Glare credit for Abu Dhabi
![App](/images/app.png)
## To run the app locally
diff --git a/app.py b/app.py
index cff8e85..d7f93da 100644
--- a/app.py
+++ b/app.py
@@ -6,7 +6,6 @@
from pathlib import Path
from pollination_streamlit_io import get_host
-from pollination_streamlit.api.client import ApiClient
from model import sensor_grids
from introduction import introduction
@@ -79,15 +78,13 @@ def main():
st.session_state.api_client = api_client
with tab3:
- st.session_state.job_url = 'https://app.pollination.cloud/devang/projects/demo/studies/8123d1b6-71f5-414b-9601-269d5eda5c46'
- st.session_state.api_client = ApiClient(
- api_token='CC0E061B.0C4E4FA7BBA51BB7BEE453D3')
-
if 'job_url' not in st.session_state:
st.error('Go back to the Simulation tab and submit the job.')
return
- sim_dict, res_dict = visualization(st.session_state.job_url,
+ sim_dict, res_dict = visualization(st.session_state.host,
+ st.session_state.hbjson_path,
+ st.session_state.job_url,
st.session_state.api_client,
st.session_state.temp_folder)
if sim_dict and res_dict:
diff --git a/estidama.py b/estidama.py
index df8cca6..8953d13 100644
--- a/estidama.py
+++ b/estidama.py
@@ -308,11 +308,14 @@ def description(self) -> Union[None, str]:
if self._month == 9:
return 'Equinox'
elif self._month == 6:
- return 'Solstice'
+ return 'Summer Solstice'
- def as_string(self) -> str:
+ def __str__(self) -> str:
return f'{self._month}_{self._day}_{self._hour}'
+ def __repr__(self) -> str:
+ return f'{self.description()} @ {self._hour}:00'
+
SIM_TIMES = [PointInTime(9, 21, 10), PointInTime(9, 21, 12), PointInTime(9, 21, 14),
PointInTime(6, 21, 10), PointInTime(6, 21, 12), PointInTime(6, 21, 14)]
diff --git a/helper.py b/helper.py
index 7536e81..1261901 100644
--- a/helper.py
+++ b/helper.py
@@ -3,6 +3,7 @@
import json
import streamlit as st
from pathlib import Path
+from typing import Dict
from honeybee.model import Model as HBModel
from honeybee.room import Room
@@ -48,3 +49,38 @@ def hash_model(hb_model: HBModel) -> dict:
def hash_room(room: Room) -> dict:
"""Help Streamlit hash a Honeybee room object."""
return {'name': room.identifier, 'volume': room.volume, 'faces': len(room.faces)}
+
+
+def create_analytical_mesh(results_folder: Path, hb_model: HBModel) -> dict:
+ """ Generate analysis grid for sketchup and rhino
+
+ args:
+ results_folder: Path to the result folder with grids_info.json and .res files.
+ hb_model: A Honeybee model.
+
+ returns:
+ An analytical mesh object.
+ """
+ hb_model = hb_model.to_dict()
+
+ info_file = results_folder.joinpath('grids_info.json')
+ info = json.loads(info_file.read_text())
+ grids = hb_model['properties']['radiance']['sensor_grids']
+
+ geometries = []
+ merged_values = []
+ for i, grid in enumerate(info):
+ result_file = Path(results_folder, f"{grid['full_id']}.res")
+ values = [float(v) for v in result_file.read_text().splitlines()]
+ # clean dict
+ mesh = json.dumps(grids[i]['mesh'])
+
+ merged_values += values
+ geometries.append(json.loads(mesh))
+
+ analytical_mesh = {
+ "type": "AnalyticalMesh",
+ "mesh": geometries,
+ "values": merged_values
+ }
+ return analytical_mesh
diff --git a/images/app.png b/images/app.png
index 66c00d1..eaf32b6 100644
Binary files a/images/app.png and b/images/app.png differ
diff --git a/introduction.py b/introduction.py
index 2fe009a..a390c84 100644
--- a/introduction.py
+++ b/introduction.py
@@ -120,6 +120,8 @@ def introduction(host: str, target_folder: Path,
hbjson_path = write_hbjson(target_folder, hb_model)
show_model(hbjson_path, target_folder, key='model')
+ else:
+ st.success('Model linked. Move to the next tab to select occupied areas.')
else:
hb_model = None
diff --git a/model.py b/model.py
index 687508d..92ace7b 100644
--- a/model.py
+++ b/model.py
@@ -14,7 +14,7 @@
from honeybee.facetype import Floor
from honeybee_vtk.vtkjs.schema import SensorGridOptions
-from pollination_streamlit_io import send_hbjson
+from pollination_streamlit_io import send_hbjson, send_geometry
from helper import write_hbjson, hash_model, hash_room
from web import show_model
@@ -64,6 +64,9 @@ def add_sensor_grids(hb_model: HBModel, rooms: List[Room],
"""
grids = [generate_room_grid(room, grid_size, tolerance) for room in rooms]
+ if hb_model.properties.radiance.sensor_grids:
+ hb_model.properties.radiance.sensor_grids = ()
+
model = hb_model.duplicate()
model.properties.radiance.add_sensor_grids(grids)
diff --git a/requirements.txt b/requirements.txt
index 1081bf2..d16b933 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,8 +1,8 @@
-
pollination-streamlit-viewer>=0.4.5
pollination-streamlit-io>=0.31.3
pollination-streamlit>=0.3.0
streamlit>=1.11.0
streamlit-folium>=0.6.14
-honeybee-vtk >= 0.38.3
-pandas==1.3.5
+honeybee-vtk>=0.38.3
+pandas>=1.3.5
+plotly>=5.9.0
\ No newline at end of file
diff --git a/result.py b/result.py
index 1c9863b..e667eb6 100644
--- a/result.py
+++ b/result.py
@@ -172,11 +172,11 @@ def prepare_results(program: Program, occupied_rooms: List[Room],
compliant_areas = []
file_name = f'grid_{room.display_name}.res'
for sim_time in SIM_TIMES:
- sim_id = sim_dict[sim_time.as_string()]
+ sim_id = sim_dict[str(sim_time)]
res_file_path = res_file_dict[sim_id].joinpath(file_name)
compliant_area = room.floor_area*percentage_complied(
res_file_path, program.min_threshold)
- data[sim_time.as_string()].append(compliant_area)
+ data[str(sim_time)].append(compliant_area)
compliant_areas.append(compliant_area)
average_compliant_area = mean(compliant_areas)
@@ -186,7 +186,7 @@ def prepare_results(program: Program, occupied_rooms: List[Room],
data['names'].append('Total')
data['areas'].append(f'{total_area}')
for sim_time in SIM_TIMES:
- data[sim_time.as_string()].append('')
+ data[str(sim_time)].append('')
data['average'].append(f'{total_average_area}')
data_df = pd.DataFrame.from_dict(data)
@@ -230,17 +230,17 @@ def result(program: Program, occupied_rooms: List[Room],
st.plotly_chart(figure, use_container_width=True)
if compliant_area_percentage >= program.credit_2_threshold:
- st.success(f'{compliant_area_percentage*100}% area complies with the'
- ' requirements. Therefore, 2 Credit points can be claimed.')
+ st.success(f'**{compliant_area_percentage*100}%** area complies with the'
+ ' requirements. Therefore, **2 Credit points** can be claimed.')
additional_notes(program)
st.balloons()
elif compliant_area_percentage >= program.credit_1_threshold:
- st.success(f'{compliant_area_percentage*100}% area complies with the'
- ' requirements. Therefore, 1 Credit point can be claimed.')
+ st.success(f'**{compliant_area_percentage*100}%** area complies with the'
+ ' requirements. Therefore, **1 Credit point** can be claimed.')
additional_notes(program)
st.balloons()
else:
st.write(
- f'Only {compliant_area_percentage*100}% area complies with the requirements.'
- ' Hence, no credit point can be claimed.')
+ f'Only **{compliant_area_percentage*100}%** area complies with the'
+ ' requirements. Hence, no credit point can be claimed.')
diff --git a/simulation.py b/simulation.py
index 6c697a6..875d84a 100644
--- a/simulation.py
+++ b/simulation.py
@@ -71,7 +71,7 @@ def create_job(hbjson_path: Path, api_client: ApiClient, owner: str, project: st
argument['sky'] = cie_sky(latitude, longitude, point.month,
point.day, point.hour, north_angle, ground_reflectance)
arguments.append(argument)
- argument['month_day_hour'] = point.as_string()
+ argument['month_day_hour'] = str(point)
new_job.arguments = arguments
diff --git a/visualization.py b/visualization.py
index ce8f34b..0c369a5 100644
--- a/visualization.py
+++ b/visualization.py
@@ -9,12 +9,16 @@
from typing import Dict, Tuple, Union
from pathlib import Path
+from honeybee.model import Model as HBModel
+
from pollination_streamlit_viewer import viewer
from pollination_streamlit.api.client import ApiClient
from pollination_streamlit.interactors import Job
+from pollination_streamlit_io import send_hbjson, send_geometry
from queenbee.job.job import JobStatusEnum
from estidama import SIM_TIMES
+from helper import create_analytical_mesh
class SimStatus(Enum):
@@ -146,13 +150,15 @@ def generate_dicts(job: Job, target_folder: Path) -> Tuple[Dict[str, str],
return sim_dict, viz_dict, res_file_dict
-def visualization(job_url: str,
+def visualization(host: str, hbjson_path: Path, job_url: str,
api_client: ApiClient,
target_folder: Path) -> Tuple[Union[None, Dict[str, str]],
Union[None, Dict[str, Path]]]:
"""UI of visualization tab of the Estidama-daylight app.
args:
+ host: A string representing the environment the app is running inside.
+ hbjson_path: Path to the HBJSON file with grids.
job_url: Valid URL of a job on Pollination as a string.
api_client: ApiClient object containing Pollination credentials.
target_folder: Path to the target folder where outputs from the finished job
@@ -181,25 +187,41 @@ def visualization(job_url: str,
st.write('See how much daylight the occupied areas receive on selected points'
' in time during the year.')
- col0, col1 = st.columns(2)
-
- with col0:
- for sim_time in SIM_TIMES[:3]:
- id = sim_dict[sim_time.as_string()]
- viz = viz_dict[id].joinpath('point_in_time.vtkjs')
- st.write(
- f'Daylight levels on {sim_time.description()} @ {sim_time.hour}:00')
- viewer(key=f'{sim_time.as_string()}_viewer',
- content=viz.read_bytes(), style={'height': '344px'})
-
- with col1:
- for sim_time in SIM_TIMES[3:]:
- id = sim_dict[sim_time.as_string()]
- viz = viz_dict[id].joinpath('point_in_time.vtkjs')
- st.write(
- f'Daylight levels on Summer {sim_time.description()} @ {sim_time.hour}:00')
- viewer(key=f'{sim_time.as_string()}_viewer',
- content=viz.read_bytes(), style={'height': '344px'})
+ if host.lower() == 'web':
+ col0, col1 = st.columns(2)
+
+ with col0:
+ for sim_time in SIM_TIMES[:3]:
+ id = sim_dict[str(sim_time)]
+ viz = viz_dict[id].joinpath('point_in_time.vtkjs')
+ st.write(f'{sim_time.description()} @ {sim_time.hour}:00')
+ viewer(key=f'{str(sim_time)}_viewer',
+ content=viz.read_bytes(), style={'height': '344px'})
+
+ with col1:
+ for sim_time in SIM_TIMES[3:]:
+ id = sim_dict[str(sim_time)]
+ viz = viz_dict[id].joinpath('point_in_time.vtkjs')
+ st.write(f'{sim_time.description()} @ {sim_time.hour}:00')
+ viewer(key=f'{str(sim_time)}_viewer',
+ content=viz.read_bytes(), style={'height': '344px'})
+
+ elif host.lower() == 'rhino' or host.lower() == 'sketchup':
+
+ options = {
+ f'{sim_time.description()} @ {sim_time.hour}:00': sim_time
+ for sim_time in SIM_TIMES}
+
+ option = st.radio(f'Select time', list(options.keys()))
+
+ sim_time = options[option]
+ id = sim_dict[str(sim_time)]
+ res_path = res_file_dict[id]
+
+ hb_model = HBModel.from_hbjson(hbjson_path)
+ send_hbjson(key='model-results', hbjson=hb_model.to_dict())
+ analytical_mesh = create_analytical_mesh(res_path, hb_model)
+ send_geometry(key=f'{str(sim_time)}_viz', geometry=analytical_mesh)
st.write('Go to the next tab to see the results.')