Source code for honeybee_idaice.writer_obj
"""An alternative for writing the HBJSON file to a series of obj files.
This method will only work with IDA ICE 5.0.
"""
from typing import List
from pathlib import Path
from honeybee.model import Model, Aperture
from ladybug_geometry.geometry3d import Point3D
[docs]
def hbjson_to_idm_obj(model: Model, target_folder: Path):
model.convert_to_units(units='Meters')
target_folder.mkdir(parents=True, exist_ok=True)
def get_index(vertex: Point3D, vertices: List[Point3D], tol: float = 0.001):
for c, v in enumerate(vertices):
if v.distance_to_point(vertex) < tol:
return c
raise ValueError(f'Failed to find the index for {v} with the tolerance of {tol}.')
# for each room
for room in model.rooms:
name = room.display_name
id_ = room.identifier
obj_file = target_folder.joinpath(f'{name}.obj')
with obj_file.open('w') as out_file:
out_file.write('# Honeybee OBJ Export\n')
out_file.write(f'mtlib {name}.mtl\n')
out_file.write(f'o Objects.Geometry.Mesh_--_{id_}\n')
unique_vertices = room.geometry.vertices
for ver in unique_vertices:
out_file.write(f'v {ver.x} {ver.y} {ver.z}\n')
normals = [f.normal for f in room.geometry.faces]
unique_normals = list(set(normals))
for ver in unique_normals:
out_file.write(f'vn {ver.x} {ver.y} {ver.z}\n')
out_file.write('usemtl None\n')
out_file.write('s off\n')
for face in room.faces:
face_3d = face.geometry
if len(face_3d) == 3:
v = [unique_vertices.index(ver) + 1 for ver in face_3d.vertices]
n = unique_normals.index(face_3d.normal)
out_file.write(f'f {v[0]}//{n} {v[1]}//{n} {v[2]}//{n}\n')
else:
tri_mesh = face_3d.triangulated_mesh3d
for m_fac in tri_mesh.face_vertices:
index = []
n = unique_normals.index(face_3d.normal)
for ver in m_fac:
try:
ix = unique_vertices.index(ver)
except ValueError:
ix = get_index(ver, unique_vertices)
index.append(ix + 1)
out_file.write(f'f {index[0]}//{n} {index[1]}//{n} {index[2]}//{n}\n')
opening_template = '(({center}) ({normal}) (AGGREGATE :T IFCIM_{opening_type} :N "{name}") (:PAR :N DY :V {height}) (:PAR :N DX :V {width}) (:PAR :N STYLE :V "{type}"))\n'
def _get_opening(opening):
opening_type = 'aperture' if isinstance(opening, Aperture) else 'door'
geo = opening.geometry
normal = geo.normal
min_ = geo.min
max_ = geo.max
center = (min_ + max_) / 2
ref_plane = geo._upper_oriented_plane()
min_2d = ref_plane.xyz_to_xy(min_)
max_2d = ref_plane.xyz_to_xy(max_)
height = max_2d.y - min_2d.y
width = max_2d.x - min_2d.x
content = opening_template.format(
center=' '.join(map(str, center)),
normal=' '.join(map(str, normal)),
opening_type='WINDOW' if opening_type == 'aperture' else 'DOOR',
name=apt.identifier,
height=height,
width=width,
type='Glazed' if opening_type == 'aperture' else 'Entrance door'
)
return content
# get all the apertures
with target_folder.joinpath('ImportWindows.txt').open('w') as wf:
for apt in model.apertures:
wf.write(_get_opening(apt))
# get all the doors
with target_folder.joinpath('ImportDoors.txt').open('w') as wf:
for door in model.doors:
wf.write(_get_opening(door))
# write shade objects as obj file
# write txt files
with target_folder.joinpath('ImportZones.txt').open('w') as zf, \
target_folder.joinpath('ImportBuildingBodiesAndZones.txt').open('w') as bf:
for room in model.rooms:
name = room.display_name
bf.write(f"(:call Import-Geometry (:call ice-3d-pane [@] t t) 'zone (0 0 0) 0 \"{name}.obj\")\n")
zf.write(f"(:call Import-Geometry (:call ice-3d-pane [@] t t) 'zone (0 0 0) 0 \"{name}.obj\")\n")
zf.write(f"(:UPDATE [@](:REMOVE \"{name}-s\"))\n")
return target_folder