Source code for honeybee_3dm.config

"""Config file schema and validation."""

import enum
import json
import os

from pydantic import BaseModel, validator, Field
from typing import Dict, Optional

from .material import mat_to_dict


[docs]class FaceObject(str, enum.Enum): """List of acceptable string names for Honeybee Face object types.""" door = 'door' shade = 'shade' aperture = 'aperture'
[docs]class FaceType(str, enum.Enum): """List of acceptable string names for Honeybee face types.""" wall = 'wall' roof = 'roof' floor = 'floor' airwall = 'airwall'
[docs]class GridSettings(BaseModel): """Config for the grid-settings.""" grid_size: float = Field( 1.0, gt=0, description='Grid spacing to control grid density.' ) grid_offset: float = Field( 0.0, description='Offset to move grid away from the parent face by certain distance.' )
[docs]class LayerConfig(BaseModel): """Config for layer keys.""" exclude_from_rad: Optional[bool] = Field( description='Boolean to indicate if radiance material is applied to this layer.' ' This is useful for layers on which grid objects are saved.' ) include_child_layers: Optional[bool] = Field( True, description='Boolean to indicate if objects from child layers are to be imported.' ) radiance_material: Optional[str] = Field( description='The name of radiance modifier.' ) grid_settings: Optional[GridSettings] = Field( description='Grid settings for the layer.' ) honeybee_face_object: Optional[FaceObject] = Field( description='A text string for FaceObject to create either a Honeybee Shade,' ' a Honeybee Door, or a Honeybee Aperture object.' ) honeybee_face_type: Optional[FaceType] = Field( description='A text string for FaceType to create either a Honeybee Wall,' ' a Honeybee Floor, a Honeybee Roof/Ceiling, or a Honeybee Airwall.' )
[docs]class Config(BaseModel): """Config file.""" sources: Dict[str, str] = Field( None, description='Path to input sources. Currently, the config only supports' ' "radiance_material" to indicate the path to the radiance.mat file.' ) layers: Dict[str, LayerConfig] = Field( ..., description='A dictionary to indicate the config for each layer in rhino 3dm' ' file. The key must be a Rhino layer name and the values must be a' ' LayerConfig.' )
[docs] @validator('sources') def check_sources(cls, v): sources = v if sources: sources = list(v.keys()) if len(sources) > 1: raise ValueError('sources can only have one key.') if sources[0] != 'radiance_material': raise ValueError( f'invalid sources key: {sources[0]}.' 'key must be radiance_material.' ) return v
[docs] @validator('layers') def check_rad(cls, v, values): config_layers = v sources = values['sources'] radiance_material_request = False for layer in config_layers: if config_layers[layer].radiance_material: radiance_material_request = True break if radiance_material_request: if sources: os.path.isfile(sources['radiance_material']) try: modifiers_dict = mat_to_dict(sources['radiance_material']) except ImportError: mat_path = sources['radiance_material'] raise ValueError( f'The path {mat_path} is not a valid path.' ' Please try using double backslashes in the file path.' ) else: rad_mat = [config_layers[layer].radiance_material for layer in config_layers if config_layers[layer].radiance_material] rad_mat_check = [True for mat in rad_mat if mat in modifiers_dict] if len(rad_mat) != rad_mat_check.count(True): raise ValueError( 'Please make sure all the radiance materials used in' ' the config file are also found in the radiance material' ' file and names of radiance materials in the config file' ' match the radiance identifiers in the radiance material' ' file.' ) else: raise ValueError( '"radiance_material" as a key and a valid path to to the radiance' ' material file as a value to the key to be provided in "sources".' ) return v
[docs] def check_layers(self, file_3dm): """Checks if layers in the config file are layers from the rhino file. Args: file_3dm: A rhino3dm file object. config: Config dictionary. Returns: Boolean value of True if no error is raised. """ rhino_layers = [layer.Name for layer in file_3dm.Layers] layer_check = [layer for layer in self.layers if layer not in rhino_layers] if layer_check: raise KeyError( 'Only layer names from the Rhino file are allowed in the config file.' f' Found invalid layer names: {layer_check}' ) else: return True
[docs]def check_config(file_3dm, config_path): """Validates the config file and returns it in the form of a dictionary. Args: file_3dm: A rhino3dm file object. config_path: A text string for the path to the config file. Returns: Config dictionary or None if any of the checks fails. """ try: with open(config_path) as fh: config = json.load(fh) except json.decoder.JSONDecodeError: raise ValueError( 'Not a valid json file.' ) else: # Parse config.json using config schema config_obj = Config.parse_file(config_path) if config_obj.check_layers(file_3dm): return config_obj.dict(exclude_none=True)