Skip to content

Commit

Permalink
feat(writer): add support for shades
Browse files Browse the repository at this point in the history
  • Loading branch information
mostaphaRoudsari committed May 29, 2023
1 parent 6d79f29 commit 97c5c2f
Showing 1 changed file with 138 additions and 6 deletions.
144 changes: 138 additions & 6 deletions honeybee_idaice/writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,141 @@
import math
from typing import List, Union

from honeybee.model import Model, Room, Face, Aperture, Door
from honeybee.model import Model, Room, Face, Aperture, Door, Shade
from honeybee.facetype import RoofCeiling, Wall, Floor
from ladybug_geometry.geometry3d import Point3D, Vector3D, Plane, Face3D
from ladybug_geometry.geometry3d import Point3D, Vector3D, Plane, Face3D, Polyface3D, \
Mesh3D
from ladybug_geometry.bounding import bounding_box

from .archive import create_idm
from .geometry_utils import get_floor_boundary, get_ceiling_boundary


def _vertices_to_idm(vertices: List[Point3D]) -> str:
"""Get a string for vertices in IDM format."""
vertices = ' '.join(f'({v.x} {v.y} {v.z})' for v in vertices)
return vertices


def _shade_geometry_to_idm(geometry: Union[Face3D, Polyface3D], name: str):
"""Create an IDM shade block from a Ladybug geometry.
Here is an exampel:
((AGGREGATE :N "shade1" :T PICT3D)
(:PAR :N FILE :V "")
(:PAR :N POS :V #(0 0 0.0))
(:PAR :N SHADOWING :V :TRUE)
((AGGREGATE :N "geom1" :T GEOM3D)
(:PAR :N NPOINTS :V 4)
(:PAR :N POINTS :DIM (4 3) :V #2A((-0.874454021453857 -0.59070497751236 -0.941487014293671) (1.0536140203476 -0.0591499991714954 -0.941487014293671) (-1.0536140203476 0.0591499991714954 0.941487014293671) (0.874454021453857 0.59070497751236 0.941487014293671)))
(:PAR :N CELLTYPE :V 1)
(:PAR :N NCELLS :V 2)
(:PAR :N NVERTICES :DIM (2) :V #(3 3))
(:PAR :N TOTNVERTS :V 6)
(:PAR :N VERTICES :DIM (6) :V #(0 1 2 2 1 3))
(:PAR :N PROPERTY :V #(1.0 1.0 1.0 0.699999988079071 1.0 1.0 1.0 0.5 1.0 1.0 1.0 0.0 1.0 1.0 1.0 0.0 0.0))))
"""

if isinstance(geometry, Face3D):
mesh_3d = geometry.triangulated_mesh3d
else:
# it is a Polyface3D
meshes = [face.triangulated_mesh3d for face in geometry.faces]
mesh_3d = Mesh3D.join_meshes(meshes=meshes)

vertices = mesh_3d.vertices
faces = mesh_3d.faces
vertices_count = len(vertices)
face_count = len(faces)
face_length = [len(face) for face in faces]
total_vertices = sum(face_length)
faces_count = ' '.join(str(f) for f in face_length)
joined_faces = ' '.join(' '.join(str(f) for f in ff) for ff in faces)

shade = f' ((AGGREGATE :N "{name}" :T PICT3D)\n' \
' (:PAR :N FILE :V "")\n' \
' (:PAR :N SHADOWING :V :TRUE)\n' \
' ((AGGREGATE :N "geom1" :T GEOM3D)\n' \
f' (:PAR :N NPOINTS :V {vertices_count})\n' \
f' (:PAR :N POINTS :DIM ({vertices_count} 3) :V #2A({_vertices_to_idm(vertices)}))\n' \
' (:PAR :N CELLTYPE :V 1)\n' \
f' (:PAR :N NCELLS :V {face_count})\n' \
f' (:PAR :N NVERTICES :DIM ({face_count}) :V #({faces_count}))\n' \
f' (:PAR :N TOTNVERTS :V {total_vertices})\n' \
f' (:PAR :N VERTICES :DIM ({total_vertices}) :V #({joined_faces}))\n' \
' (:PAR :N PROPERTY :V #(1.0 1.0 1.0 0.699999988079071 1.0 1.0 1.0 0.5 1.0 1.0 1.0 0.0 1.0 1.0 1.0 0.0 0.0))))'

return shade


def _shade_group_to_idm(shades: List[Shade]) -> str:
"""Convert a group of shades into a IDM string.
The files in the shade group should create a closed volume. The translator uses
the name of the first shade as the name of the group.
"""
group_geometry = Polyface3D.from_faces(
[shade.geometry for shade in shades], tolerance=0.001
)
shade = shades[0]
# remove new lines from the name
name = '_'.join(
(' '.join(shade.display_name.split()), shade.identifier.replace('Shade_', ''))
)
return _shade_geometry_to_idm(group_geometry, name)


def _shade_to_idm(shade: Shade):
shade_geo = shade.geometry
name = '_'.join(
(' '.join(shade.display_name.split()), shade.identifier.replace('Shade_', ''))
)
return _shade_geometry_to_idm(shade_geo, name)


def shades_to_idm(shades: List[Shade]):
"""Convert a list of Shades to a IDM string.
Args:
shades: A list of Shade faces.
Returns:
A formatted string that represents this shade in IDM format.
"""
if not shades:
return ''

shade_groups = {}
no_groups = []
for shade in shades:
try:
group_id = shade.user_data['__group_id__']
except (TypeError, KeyError):
no_groups.append(shade)
continue
else:
if group_id not in shade_groups:
shade_groups[group_id] = [shade]
else:
shade_groups[group_id].append(shade)

filtered_groups = {}
for k, v in shade_groups.items():
if len(v) == 1:
no_groups.extend(v)
else:
filtered_groups[k] = v

single_shades = '\n'.join([_shade_to_idm(shade) for shade in no_groups])
group_shades = '\n'.join(
[_shade_group_to_idm(shades) for shades in filtered_groups.values()]
)

return f'((AGGREGATE :N ARCDATA)\n{single_shades}\n{group_shades})'


def opening_to_idm(opening: Union[Aperture, Door], is_aperture=True) -> str:
"""Translate a HBJSON aperture to an IDM Window."""
# name = opening.display_name
Expand Down Expand Up @@ -340,7 +466,8 @@ def section_to_idm(rooms: List[Room], name: str):
return ''.join(sections) + '\n'


def model_to_idm(model: Model, out_folder: pathlib.Path, name: str = None):
def model_to_idm(model: Model, out_folder: pathlib.Path, name: str = None,
debug: bool = True):
"""Translate a Honeybee model to an IDM file."""
model.convert_to_units(units='Meters')
__here__ = pathlib.Path(__file__).parent
Expand Down Expand Up @@ -391,7 +518,11 @@ def model_to_idm(model: Model, out_folder: pathlib.Path, name: str = None):
# add rooms as zones
for room in model.rooms:
bldg.write(f'((CE-ZONE :N "{room.display_name}" :T ZONE))\n')
bldg.write(f';[end of {bldg_name}.idm]\n')

# collect all the shades from room
shades_idm = shades_to_idm(model.shades)
bldg.write(shades_idm)
bldg.write(f'\n;[end of {bldg_name}.idm]\n')

# copy template files
templates = ['plant.idm', 'ahu.idc', 'ahu.idm', 'plant.idc']
Expand All @@ -416,7 +547,8 @@ def model_to_idm(model: Model, out_folder: pathlib.Path, name: str = None):
idm_file = base_folder.joinpath(f'{bldg_name}.idm')
create_idm(model_folder, idm_file)

# clean up the folder - leave it for now for debugging purposes
# shutil.rmtree(model_folder)
# clean up the folder
if not debug:
shutil.rmtree(model_folder)

return idm_file

0 comments on commit 97c5c2f

Please sign in to comment.