Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(writer): support translating pvs, trees, and translucent shades #30

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 16 additions & 78 deletions honeybee_ies/reader.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import enum
import math
import pathlib
import re
Expand All @@ -12,87 +11,22 @@
from honeybee.boundarycondition import Outdoors, Ground
from honeybee.typing import clean_string, clean_and_id_ep_string

from .types import GEM_TYPES


PI = math.pi
Z_AXIS = Vector3D(0, 0, 1)
ROOF_ANGLE_TOLERANCE = math.radians(10)
MODEL_TOLERANCE = 0.001


class GEM_TYPES(enum.Enum):
"""Enumeration for different object types in GEM.

There is no public documentation for GEM files but here is our understanding based
on the sample files.

--------------------------------------------------------
| object | CATEGORY | TYPE | SUBTYPE | KEYWORD |
--------------------------------------------------------
| Rooms/Spaces | 1 | 1 | 2001 | IES |
| UnCond Space | 1 | 1 | 2002 | IES |
| Trans Shades | 1 | 1 | 2102 | IES |
| Nghbr Bldg | 1 | 2 | 0 | IES |
| PV | 3 | 202 | 0 | PVP |
| Tree | 1 | 3 | 0 | LAN |
| Topography | 1 | 3 | 0 | IES |
| Local Shades | 1 | 4 | 0 | IES |
"""
Space = '1-001-2001-IES'
TranslucentShade = '1-001-2102-IES'
ContextBuilding = '1-002-0000-IES'
PV = '3-202-0000-PVP'
Tree = '1-003-0000-LAN'
Topography = '1-003-0000-IES'
Shade = '1-004-0000-IES'

@classmethod
def from_info(cls, category, type_, sub_type, keyword):
if category == 1 and sub_type == 2001 and type_ == 1 and keyword == 'IES':
return cls.Space
if category == 1 and sub_type == 2002 and type_ == 1 and keyword == 'IES':
# uncondition space
return cls.Space
elif category == 1 and sub_type == 2102 and type_ == 1 and keyword == 'IES':
return cls.TranslucentShade
elif category == 1 and sub_type == 0 and type_ == 2 and keyword == 'IES':
return cls.ContextBuilding
elif category == 3 and sub_type == 0 and type_ == 202 and keyword == 'PVP':
return cls.PV
elif category == 1 and sub_type == 0 and type_ == 3 and keyword == 'IES':
return cls.Topography
elif category == 1 and sub_type == 0 and type_ == 3 and keyword == 'LAN':
return cls.Tree
elif category == 1 and sub_type == 0 and type_ == 4 and keyword == 'IES':
return cls.Shade
else:
raise ValueError(
'Unknown combination of inputs in the input GEM file. Reach out to '
'us with a copy of the GEM file and the information below:\n'
f'{category}-{type_}-{sub_type}-{keyword}'
)

def _get_numeric_values(self, index):
return int(self.value.split('-')[index])

def category(self):
return self._get_numeric_values(0)

def type(self):
return self._get_numeric_values(1)

def sub_type(self):
return self._get_numeric_values(2)

def keyword(self):
return self.value.split('-')[-1]


def _gem_object_type(info: str, keyword: str = 'IES') -> GEM_TYPES:
"""Get GEM object type from info."""
type_ = int(re.findall(r'^TYPE\n(\d*)', info, re.MULTILINE)[0])
sub_type = int(re.findall(r'^SUBTYPE\n(\d*)', info, re.MULTILINE)[0])
subtype = int(re.findall(r'^SUBTYPE\n(\d*)', info, re.MULTILINE)[0])
category = int(re.findall(r'^CATEGORY\n(\d*)', info, re.MULTILINE)[0])
return GEM_TYPES.from_info(
category=category, type_=type_, sub_type=sub_type, keyword=keyword
category=category, type_=type_, subtype=subtype, keyword=keyword
)


Expand Down Expand Up @@ -124,15 +58,15 @@ def _update_name(obj: Union[Shade, Room], display_name: str, count: int = None):
"""Add group id and display name to objects."""
if not isinstance(obj, Room):
identifier = \
clean_string(display_name) if count is None \
clean_string(display_name) if count \
else clean_string(f'{display_name}-{count}')
_add_user_date(
face=obj, user_data={'__group_id__': identifier}
)

id_ = _get_id(display_name)
if id_:
obj.identifier = id_ if count is None else f'{id_}-{count}'
obj.identifier = id_ if count else f'{id_}'
obj.display_name = display_name.replace(f' [{id_}]', '')
else:
obj.display_name = display_name
Expand Down Expand Up @@ -270,14 +204,18 @@ def _create_tree(info: str, tree_type=1) -> Shade:

tree_0 = _create_shade(
geos[0].lower_left_counter_clockwise_vertices,
user_data={'__ies_type__': 'tree'}
user_data={
'__gem_type__': 'tree',
'__gem_tree_type__': tree_type,
'__ies_import__': True
}
)

tree_1 = _create_shade(
geos[1].lower_left_counter_clockwise_vertices,
user_data={
'__ies_type__': 'tree',
'__ies_tree_type__': tree_type
'__gem_type__': 'tree',
'__gem_tree_type__': tree_type
}
)

Expand All @@ -301,7 +239,7 @@ def _create_pv(info: str) -> Shade:

pv = _create_shade(
geometry.lower_left_counter_clockwise_vertices,
user_data={'__ies_type__': 'pv'}
user_data={'__gem_type__': 'pv'}
)

return pv
Expand Down Expand Up @@ -341,7 +279,7 @@ def _parse_gem_segment(segment: str):
f'{tree_type} is not currently supported.'
tree_type = int(tree_type.split()[-1])
tree_info = next(content)
faces = _create_tree(tree_info)
faces = _create_tree(tree_info, tree_type=tree_type)
for count, face in enumerate(faces):
_update_name(face, display_name, count)
return faces
Expand Down
123 changes: 123 additions & 0 deletions honeybee_ies/types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import enum
from typing import Dict


class GEM_TYPES(enum.Enum):
"""Enumeration for different object types in GEM.

There is no public documentation for GEM files but here is our understanding based
on the sample files.

-----------------------------------------------------------------------------------
| object | CATEGORY | TYPE | SUBTYPE | LAYER | COLOR | COL-RGB | KEYWORD |
-----------------------------------------------------------------------------------
| Rooms/Spaces | 1 | 1 | 2001 | 1 | 0 | 16711680 | IES |
| UnCond Space | 1 | 1 | 2002 | 1 | 0 | 16711680 | IES |
| Trans Shades | 1 | 1 | 2102 | 64 | 0 | 0 | IES |
| Nghbr Bldg | 1 | 2 | 0 | 62 | 0 | 16711935 | IES |
| PV | 3 | 202 | 0 | 1 | 0 | 32767 | PVP |
| Tree | 1 | 3 | 0 | 65 | 0 | 2399294 | LAN |
| Topography | 1 | 3 | 0 | 63 | 0 | 38400 | IES |
| Local Shades | 1 | 4 | 0 | 64 | 62 | 65280 | IES |
"""
# '{CATEGORY}-{TYPE}-{SUBTYPE}-{LAYER}-{COLOR}-{COLORRGB}-{KEYWORD}'
Space = '1-001-2001-01-00-16711680-IES'
TranslucentShade = '1-001-2102-64-00-0-IES'
ContextBuilding = '1-002-0000-62-00-16711935-IES'
PV = '3-202-0000-01-00-32767-PVP'
Tree = '1-003-0000-65-00-2399294-LAN'
Topography = '1-003-0000-63-00-38400-IES'
Shade = '1-004-0000-64-62-65280-IES'

@classmethod
def from_info(cls, category: str, type_: int, subtype: int, keyword: str):
if category == 1 and subtype == 2001 and type_ == 1 and keyword == 'IES':
return cls.Space
if category == 1 and subtype == 2002 and type_ == 1 and keyword == 'IES':
# unconditioned space
return cls.Space
elif category == 1 and subtype == 2102 and type_ == 1 and keyword == 'IES':
return cls.TranslucentShade
elif category == 1 and subtype == 0 and type_ == 2 and keyword == 'IES':
return cls.ContextBuilding
elif category == 3 and subtype == 0 and type_ == 202 and keyword == 'PVP':
return cls.PV
elif category == 1 and subtype == 0 and type_ == 3 and keyword == 'IES':
return cls.Topography
elif category == 1 and subtype == 0 and type_ == 3 and keyword == 'LAN':
return cls.Tree
elif category == 1 and subtype == 0 and type_ == 4 and keyword == 'IES':
return cls.Shade
else:
raise ValueError(
'Unknown combination of inputs in the input GEM file. Reach out to '
'us with a copy of the GEM file and the information below:\n'
f'{category}-{type_}-{subtype}-{keyword}'
)

@classmethod
def from_user_data(cls, user_data: Dict):
"""Get type from user_data."""
if not user_data:
return
gem_type = user_data.get('__gem_type__', None)
if not gem_type:
# support old versions of HBJSON files
gem_type = user_data.get('__ies_type__', None)
if gem_type == 'topography':
return cls.Topography
elif gem_type == 'translucent_shade':
return cls.TranslucentShade
elif gem_type == 'pv':
return cls.PV
elif gem_type == 'tree':
return cls.Tree

def _get_numeric_values(self, index):
return int(self.value.split('-')[index])

def category(self):
return self._get_numeric_values(0)

def type(self):
return self._get_numeric_values(1)

def subtype(self):
return self._get_numeric_values(2)

def layer(self):
return self._get_numeric_values(3)

def color(self, rgb=False):
return self._get_numeric_values(5) \
if rgb else self._get_numeric_values(4)

def keyword(self):
return self.value.split('-')[-1]

def to_gem(
self, name: str, identifier: str, vertices: str, faces: str = '',
vertices_count: int = 0, face_count: int = 0):
"""Get a formatted GEM string."""
full_name = name if not identifier else f'{name} [{identifier}]'

gem_header = f'LAYER\n{self.layer()}\n' + \
f'COLOUR\n{self.color()}\n' + \
f'CATEGORY\n{self.category()}\n' + \
f'TYPE\n{self.type()}\n' + \
f'SUBTYPE\n{self.subtype()}\n' + \
f'COLOURRGB\n{self.color(True)}\n' + \
f'{self.keyword()} {full_name}\n'

if self.name in ('Tree', 'PV'):
gem_str = gem_header + f'{vertices}'
return gem_str
else:
gem_str = gem_header + f'{vertices_count} {face_count}\n' \
f'{vertices}\n' \
f'{faces}'
if self.name == 'TranslucentShade':
gem_str = '\n'.join(gem_str.split('\n')[:-1])
gem_str += f'\n1\n{vertices_count} 0\n{vertices}'

return gem_str
Loading
Loading