From a443e8098dea6d9bab5112e830d7098123e62656 Mon Sep 17 00:00:00 2001 From: Tom Kingstone Date: Mon, 1 Jun 2026 16:11:37 +0100 Subject: [PATCH 01/15] initial conversion of important classes to subclasses of BHoMObject Wind has been tested and works as expected, but external comfort module has not been tested, need to also write tests for BHoMObject and check that existing unit tests still pass. --- .../external_comfort/_externalcomfortbase.py | 46 ++++++++---- .../external_comfort/_shelterbase.py | 45 +++++++---- .../external_comfort/_simulatebase.py | 74 ++++++++++++++++--- .../external_comfort/_typologybase.py | 40 +++++++--- .../ladybug_extension/datacollection.py | 4 + .../Python/src/ladybugtools_toolkit/wind.py | 27 +++---- 6 files changed, 171 insertions(+), 65 deletions(-) diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_externalcomfortbase.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_externalcomfortbase.py index 9bbd4897..a1ef4547 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_externalcomfortbase.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_externalcomfortbase.py @@ -19,8 +19,9 @@ from ..categorical.categories import UTCI_DEFAULT_CATEGORIES, Categorical from ..helpers import convert_keys_to_snake_case from ..ladybug_extension.analysisperiod import describe_analysis_period -from ..ladybug_extension.datacollection import collection_to_series +from ..ladybug_extension.datacollection import collection_to_series, collection_from_bhom_object from python_toolkit.plot.heatmap import heatmap +from python_toolkit.bhom.bhom_object import BHoMObject from ..plot._utci import utci_day_comfort_metrics, utci_heatmap_histogram from ..plot.colormaps import ( DBT_COLORMAP, @@ -44,8 +45,8 @@ ] -@dataclass(init=True, repr=True, eq=True) -class ExternalComfort: +@dataclass(init=False, repr=True, eq=True) +class ExternalComfort(BHoMObject): """_""" simulation_result: SimulationResult @@ -57,6 +58,35 @@ class ExternalComfort: mean_radiant_temperature: HourlyContinuousCollection = None universal_thermal_climate_index: HourlyContinuousCollection = None + def __init__( + self, + simulation_result: SimulationResult, + typology: Typology, + dry_bulb_temperature: HourlyContinuousCollection = None, + relative_humidity: HourlyContinuousCollection = None, + wind_speed: HourlyContinuousCollection = None, + mean_radiant_temperature: HourlyContinuousCollection = None, + universal_thermal_climate_index: HourlyContinuousCollection = None, + **kwargs + ) -> "ExternalComfort": + if isinstance(simulation_result, BHoMObject): + simulation_result = SimulationResult._from_bhom_object(simulation_result) + + if isinstance(typology, BHoMObject): + typology = Typology._from_bhom_object(typology) + + self.simulation_result = simulation_result + self.typology = Typology + + self.dry_bulb_temperature = collection_from_bhom_object(dry_bulb_temperature) if isinstance(dry_bulb_temperature, BHoMObject) else dry_bulb_temperature + self.relative_humidity = collection_from_bhom_object(relative_humidity) if isinstance(relative_humidity, BHoMObject) else relative_humidity + self.wind_speed = collection_from_bhom_object(wind_speed) if isinstance(wind_speed, BHoMObject) else wind_speed + self.mean_radiant_temperature = collection_from_bhom_object(mean_radiant_temperature) if isinstance(mean_radiant_temperature, BHoMObject) else mean_radiant_temperature + self.universal_thermal_climate_index = collection_from_bhom_object(universal_thermal_climate_index) if isinstance(universal_thermal_climate_index, BHoMObject) else universal_thermal_climate_index + + _t = kwargs.pop("_t", "BH.oM.LadybugTools.ExternalComfort") + super().__init__(_t, **kwargs) + def __post_init__(self): """_""" @@ -168,16 +198,6 @@ def from_dict(cls, d: dict) -> "ExternalComfort": universal_thermal_climate_index=d["universal_thermal_climate_index"], ) - def to_json(self) -> str: - """Convert this object to a JSON string.""" - return json.dumps(self.to_dict()) - - @classmethod - def from_json(cls, json_string: str) -> "SimulationResult": - """Create this object from a JSON string.""" - - return cls.from_dict(json.loads(json_string)) - def to_file(self, path: Path) -> Path: """Write this object to a JSON file.""" diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_shelterbase.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_shelterbase.py index a7c7ac37..444f6a66 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_shelterbase.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_shelterbase.py @@ -2,7 +2,7 @@ # pylint: disable=E0401 import json from pathlib import Path -from typing import Any +from typing import Any, Union from dataclasses import dataclass # pylint: enable=E0401 @@ -29,6 +29,7 @@ from python_toolkit.bhom.analytics import bhom_analytics +from python_toolkit.bhom.bhom_object import BHoMObject from ..bhom.to_bhom import point3d_to_bhom from ..ladybug_extension.epw import sun_position_list from ..helpers import convert_keys_to_snake_case @@ -36,14 +37,32 @@ SENSOR_LOCATION = Point3D(0, 0, 1.2) -@dataclass(init=True, eq=True) -class Shelter: +@dataclass(init=False, eq=True) +class Shelter(BHoMObject): """_""" - vertices: tuple[Point3D] + vertices: tuple[Union[Point3D, IObject]] wind_porosity: tuple[float] = (0,) * 8760 radiation_porosity: tuple[float] = (0,) * 8760 + def __init__(self, + vertices: tuple[Union[Point3D, IObject]], + wind_porosity: tuple[float] = None, + radiation_porosity: tuple[float] = None, + **kwargs + ) -> "Shelter": + self.vertices = vertices + + for i, item in self.vertices: + if isinstance(item, IObject): + self.vertices[i] = Point3D.from_dict(vars(item)) + + self.wind_porosity = (0,) * 8760 if wind_porosity is None else wind_porosity + self.radiation_porosity = (0,) * 8760 if radiation_porosity is None else radiation_porosity + + _t = kwargs.pop("_t", "BH.oM.LadybugTools.Shelter") + super().__init__(_t, **kwargs) + def __post_init__(self): """_""" @@ -81,8 +100,11 @@ def __post_init__(self): if len(self.vertices) < 3: raise ValueError("A shelter must have at least 3 vertices.") - if not all(isinstance(item, Point3D) for item in self.vertices): - raise ValueError("All vertices must be Point3D objects.") + for i, item in self.vertices: + if isinstance(item, IObject): #if this object has been translated from json as part of a bhom object. + self.vertices[i] = Point3D.from_dict(vars(item)) + elif not isinstance(item, Point3D): + raise ValueError("All vertices must be Point3D objects, or BHoM IObjects.") _plane = Plane.from_three_points(*self.vertices[:3]) for vertex in self.vertices[3:]: @@ -99,6 +121,7 @@ def __repr__(self) -> str: ")" ) + #TODO: maybe these methods aren't needed with the BHoMObject class implemented def to_dict(self) -> str: """Convert this object to a dictionary.""" point_dicts = [] @@ -130,16 +153,6 @@ def from_dict(cls, d: dict) -> "Shelter": radiation_porosity=d["radiation_porosity"], ) - def to_json(self) -> str: - """Convert this object to a JSON string.""" - return json.dumps(self.to_dict()) - - @classmethod - def from_json(cls, json_string: str) -> "Shelter": - """Create this object from a JSON string.""" - - return cls.from_dict(json.loads(json_string)) - def to_file(self, path: Path) -> Path: """Convert this object to a JSON file.""" diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_simulatebase.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_simulatebase.py index e7ae8483..52f8b3a3 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_simulatebase.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_simulatebase.py @@ -40,6 +40,7 @@ from ladybug_comfort.collection.solarcal import OutdoorSolarCal, SolarCalParameter from lbt_recipes.version import check_openstudio_version +from python_toolkit.bhom.bhom_object import BHoMObject from ..bhom.logger import CONSOLE_LOGGER from ..bhom.to_bhom import ( hourlycontinuouscollection_to_bhom, @@ -49,6 +50,7 @@ from ..ladybug_extension.datacollection import ( collection_from_series, collection_to_series, + collection_from_bhom_object ) from ..ladybug_extension.epw import epw_to_dataframe from ..ladybug_extension.epw import equality as epw_equality @@ -605,8 +607,8 @@ def radiant_temperature( ] -@dataclass(init=True, repr=True, eq=True) -class SimulationResult: +@dataclass(init=False, repr=True, eq=True) +class SimulationResult(BHoMObject): """_""" epw_file: Path @@ -630,6 +632,54 @@ class SimulationResult: unshaded_shortwave_mean_radiant_temperature_delta: HourlyContinuousCollection = None unshaded_mean_radiant_temperature: HourlyContinuousCollection = None + def __init__( + self, + epw_file: Path, + ground_material: EnergyMaterial | EnergyMaterialVegetation, + shade_material: EnergyMaterial | EnergyMaterialVegetation, + identifier: str = None, + + shaded_down_temperature: HourlyContinuousCollection = None, + shaded_up_temperature: HourlyContinuousCollection = None, + + unshaded_down_temperature: HourlyContinuousCollection = None, + unshaded_up_temperature: HourlyContinuousCollection = None, + + shaded_radiant_temperature: HourlyContinuousCollection = None, + shaded_longwave_mean_radiant_temperature_delta: HourlyContinuousCollection = None, + shaded_shortwave_mean_radiant_temperature_delta: HourlyContinuousCollection = None, + shaded_mean_radiant_temperature: HourlyContinuousCollection = None, + + unshaded_radiant_temperature: HourlyContinuousCollection = None, + unshaded_longwave_mean_radiant_temperature_delta: HourlyContinuousCollection = None, + unshaded_shortwave_mean_radiant_temperature_delta: HourlyContinuousCollection = None, + unshaded_mean_radiant_temperature: HourlyContinuousCollection = None, + **kwargs + ) -> "SimulationResult": + self.epw_file = epw_file + self.ground_material = ground_material + self.shade_material = shade_material + self.identifier = identifier + + self.shaded_down_temperature = collection_from_bhom_object(shaded_down_temperature) if isinstance(shaded_down_temperature, BHoMObject) else shaded_down_temperature + self.shaded_up_temperature = collection_from_bhom_object(shaded_up_temperature) if isinstance(shaded_up_temperature, BHoMObject) else shaded_up_temperature + + self.unshaded_down_temperature = collection_from_bhom_object(unshaded_down_temperature) if isinstance(unshaded_down_temperature, BHoMObject) else unshaded_down_temperature + self.unshaded_up_temperature = collection_from_bhom_object(unshaded_up_temperature) if isinstance(unshaded_up_temperature, BHoMObject) else unshaded_up_temperature + + self.shaded_radiant_temperature = collection_from_bhom_object(shaded_radiant_temperature) if isinstance(shaded_radiant_temperature, BHoMObject) else shaded_radiant_temperature + self.shaded_longwave_mean_radiant_temperature_delta = collection_from_bhom_object(shaded_longwave_mean_radiant_temperature_delta) if isinstance(shaded_longwave_mean_radiant_temperature_delta, BHoMObject) else shaded_longwave_mean_radiant_temperature_delta + self.shaded_shortwave_mean_radiant_temperature_delta = collection_from_bhom_object(shaded_shortwave_mean_radiant_temperature_delta) if isinstance(shaded_shortwave_mean_radiant_temperature_delta, BHoMObject) else shaded_shortwave_mean_radiant_temperature_delta + self.shaded_mean_radiant_temperature = collection_from_bhom_object(shaded_mean_radiant_temperature) if isinstance(shaded_mean_radiant_temperature, BHoMObject) else shaded_mean_radiant_temperature + + self.unshaded_radiant_temperature = collection_from_bhom_object(unshaded_radiant_temperature) if isinstance(unshaded_radiant_temperature, BHoMObject) else unshaded_radiant_temperature + self.unshaded_longwave_mean_radiant_temperature_delta = collection_from_bhom_object(unshaded_longwave_mean_radiant_temperature_delta) if isinstance(unshaded_longwave_mean_radiant_temperature_delta, BHoMObject) else unshaded_longwave_mean_radiant_temperature_delta + self.unshaded_shortwave_mean_radiant_temperature_delta = collection_from_bhom_object(unshaded_shortwave_mean_radiant_temperature_delta) if isinstance(unshaded_shortwave_mean_radiant_temperature_delta, BHoMObject) else unshaded_shortwave_mean_radiant_temperature_delta + self.unshaded_mean_radiant_temperature = collection_from_bhom_object(unshaded_mean_radiant_temperature) if isinstance(unshaded_mean_radiant_temperature, BHoMObject) else unshaded_mean_radiant_temperature + + _t = kwargs.pop("_t", "BH.oM.LadybugTools.SimulationResult") + super().__init__(_t, **kwargs) + def __repr__(self) -> str: return f"{self.__class__.__name__}({self.identifier})" @@ -648,6 +698,16 @@ def __post_init__(self): if isinstance(self.shade_material, Materials): self.shade_material = self.shade_material.value + if isinstance(ground_material, BHoMObject): + self.ground_material = dict_to_material(vars(self.ground_material)) + if isinstance(ground_material, dict): + self.ground_material = dict_to_material(self.ground_material) + + if isinstance(shade_material, BHoMObject): + self.shade_material = dict_to_material(vars(self.shade_material)) + if isinstance(shade_material, dict): + self.shade_material = dict_to_material(self.shade_material) + if not isinstance( self.ground_material, (EnergyMaterial, EnergyMaterialVegetation) ): @@ -822,16 +882,6 @@ def from_dict(cls, d: dict[str, Any]) -> "SimulationResult": unshaded_mean_radiant_temperature=d["unshaded_mean_radiant_temperature"], ) - def to_json(self) -> str: - """Create a JSON string from this object.""" - return json.dumps(self.to_dict()) - - @classmethod - def from_json(cls, json_string: str) -> "SimulationResult": - """Create this object from a JSON string.""" - - return cls.from_dict(json.loads(json_string)) - def to_file(self, path: Path) -> Path: """Write this object to a JSON file.""" diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_typologybase.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_typologybase.py index ba07fee2..487209ef 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_typologybase.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_typologybase.py @@ -11,6 +11,7 @@ from ladybug.epw import EPW, HourlyContinuousCollection from python_toolkit.bhom.analytics import bhom_analytics +from python_toolkit.bhom.bhom_object import BHoMObject from ..helpers import ( convert_keys_to_snake_case, decay_rate_smoother, @@ -29,8 +30,8 @@ from .simulate import SimulationResult -@dataclass(init=True, repr=True, eq=True) -class Typology: +@dataclass(init=False, repr=True, eq=True) +class Typology(BHoMObject): """_""" identifier: str @@ -40,6 +41,32 @@ class Typology: wind_speed_multiplier: float = 1 radiant_temperature_adjustment: tuple[float] = (0,) * 8760 + def __init__(self, + identifier: str, + shelters: tuple[Shelter] = (), + evaporative_cooling_effect: tuple[float] = None, + target_wind_speed: tuple[float] = None, + wind_speed_multiplier: float = 1, + radiant_temperature_adjustment: tuple[float] = None, + **kwargs + ) -> "Typology": + self.identifier = identifier + self.shelters = (None,) * len(shelters) + + for i, shelter in enumerate(shelters): + if isinstance(shelter, BHoMObject): + self.shelters[i] = Shelter._from_bhom_object(shelter) + else + self.shelters[i] = shelter + + self.evaporative_cooling_effect = (0,) * 8760 if evaporative_cooling_effect is None else evaporative_cooling_effect + self.target_wind_speed = (None,) * 8760 if target_wind_speed is None else target_wind_speed + self.wind_speed_multiplier = wind_speed_multiplier + self.radiant_temperature_adjustment = (0,) * 8760 if radiant_temperature_adjustment is None else radiant_temperature_adjustment + + _t = kwargs.pop("_t", "BH.oM.LadybugTools.Typology") + super().__init__(_t, **kwargs) + def __post_init__(self): """_""" @@ -118,15 +145,6 @@ def from_dict(cls, d: dict) -> "Shelter": radiant_temperature_adjustment=d["radiant_temperature_adjustment"], ) - def to_json(self) -> str: - """Convert this object to a JSON string.""" - return json.dumps(self.to_dict()) - - @classmethod - def from_json(cls, json_string: str) -> "Shelter": - """Create this object from a JSON string.""" - return cls.from_dict(json.loads(json_string)) - def to_file(self, path: Path) -> Path: """Convert this object to a JSON file.""" if Path(path).suffix != ".json": diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/ladybug_extension/datacollection.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/ladybug_extension/datacollection.py index b432de1d..4197a9cd 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/ladybug_extension/datacollection.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/ladybug_extension/datacollection.py @@ -24,6 +24,10 @@ from .analysisperiod import describe_analysis_period from .header import header_from_string, header_to_string +def collection_from_bhom_object(obj: BHoMObject) -> HourlyContinuousCollection: + """Convert a BHoMObject representation of an HourlyContinuousCollection to an HourlyContinuousCollection instance""" + d = obj.to_dict() + return HourlyContinuousCollection.from_dict(d) def collection_to_series(collection: BaseCollection, name: str = None) -> pd.Series: """Convert a Ladybug hourlyContinuousCollection object into a Pandas Series object. diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/wind.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/wind.py index 7d7734c8..404519ec 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/wind.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/wind.py @@ -42,13 +42,14 @@ describe_analysis_period, ) from python_toolkit.plot.timeseries import timeseries +from python_toolkit.bhom.bhom_object import BHoMObject from .plot.utilities import contrasting_color, format_polar_plot # pylint: enable=E0401 -@dataclass(init=True, eq=True, repr=True) -class Wind: +@dataclass(eq=True, repr=True) +class Wind(BHoMObject): """An object containing historic, time-indexed wind data. Args: @@ -64,13 +65,23 @@ class Wind: source (str, optional): A source string to describe where the input data comes from. Defaults to None. """ - wind_speeds: list[float] wind_directions: list[float] datetimes: list[datetime] | pd.DatetimeIndex height_above_ground: float = 10.0 source: str = None + def __init__(self, wind_speeds: list[float], wind_directions: list[float], datetimes: list[datetime] | pd.DatetimeIndex, height_above_ground: float = 10.0, source: str = None, **kwargs): + self.wind_speeds = wind_speeds + self.wind_directions = wind_directions + self.datetimes = datetimes + self.height_above_ground = height_above_ground + self.source = source + + _t = kwargs.pop("_t", "BH.oM.LadybugTools.Wind") + super().__init__(_t, **kwargs) + + def __post_init__(self): if self.height_above_ground < 0.1: raise ValueError("Height above ground must be >= 0.1m.") @@ -150,16 +161,6 @@ def from_dict(cls, d: dict) -> "Wind": source=d["source"], ) - def to_json(self) -> str: - """Convert this object to a JSON string.""" - return json.dumps(self.to_dict()) - - @classmethod - def from_json(cls, json_string: str) -> "Wind": - """Create this object from a JSON string.""" - - return cls.from_dict(json.loads(json_string)) - def to_file(self, path: Path) -> Path: """Convert this object to a JSON file.""" From e92c72f4434456d1db506448e89eacc94c8a330b Mon Sep 17 00:00:00 2001 From: Tom Kingstone Date: Tue, 2 Jun 2026 11:13:47 +0100 Subject: [PATCH 02/15] save point as changing laptops --- .../categorical/categorical.py | 23 ++++++-- .../external_comfort/_externalcomfortbase.py | 26 ++++----- .../external_comfort/_shelterbase.py | 24 +++----- .../external_comfort/_simulatebase.py | 57 +++++++++---------- .../external_comfort/_typologybase.py | 13 ++--- .../ladybug_extension/datacollection.py | 1 + .../Python/src/ladybugtools_toolkit/wind.py | 2 - 7 files changed, 74 insertions(+), 72 deletions(-) diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/categorical/categorical.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/categorical/categorical.py index e5c1fb6b..331e15b7 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/categorical/categorical.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/categorical/categorical.py @@ -22,6 +22,7 @@ from matplotlib.legend import Legend from mpl_toolkits.axes_grid1 import make_axes_locatable from python_toolkit.bhom.analytics import bhom_analytics +from python_toolkit.bhom.bhom_object import BHoMObject from python_toolkit.plot.heatmap import heatmap from python_toolkit.plot.timeseries import timeseries @@ -30,8 +31,8 @@ from ..plot.utilities import contrasting_color -@dataclass(init=True, repr=True) -class Categorical: +@dataclass(init=False, repr=True) +class Categorical(BHoMObject): """A class to hold categorical data. Args: @@ -51,7 +52,14 @@ class Categorical: colors: tuple[str] = field(default_factory=tuple, repr=True) name: str = field(default="GenericCategories") - def __post_init__(self): + def __init__(self, bins = (), bin_names = (), colors = (), name = "GenericCategories", **kwargs): + self.bins = bins + self.bin_names = bin_names + self.colors = colors + + _t = kwargs.pop("_t", "BH.oM.LadybugTools.Categorical") + super().__init__(_t, name=name, **kwargs) + # ensure colors are valid if len(self.colors) == 0: cycle = tuple(plt.rcParams["axes.prop_cycle"].by_key()["color"]) @@ -811,7 +819,7 @@ def text(self) -> str: return d[self] -@dataclass(init=True, repr=True) +@dataclass(init=False, repr=True) class CategoricalComfort(Categorical): """A class to hold categorical comfort data. @@ -822,12 +830,15 @@ class CategoricalComfort(Categorical): comfort_classes: tuple[ComfortClass] = field(default_factory=tuple, repr=False) - def __post_init__(self): + def __init__(self, comfort_classes: tuple[ComfortClass] = (), **kwargs): + self.comfort_classes = comfort_classes + + super().__init__(**kwargs) + if len(self.comfort_classes) == 0: raise ValueError("The comfort classes cannot be empty.") if len(self.comfort_classes) != len(self): raise ValueError("The number of comfort classes must match the number of bins.") - return super().__post_init__() @bhom_analytics() def simplify(self) -> "CategoricalComfort": diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_externalcomfortbase.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_externalcomfortbase.py index a1ef4547..9f8b0d2c 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_externalcomfortbase.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_externalcomfortbase.py @@ -60,23 +60,23 @@ class ExternalComfort(BHoMObject): def __init__( self, - simulation_result: SimulationResult, - typology: Typology, - dry_bulb_temperature: HourlyContinuousCollection = None, - relative_humidity: HourlyContinuousCollection = None, - wind_speed: HourlyContinuousCollection = None, - mean_radiant_temperature: HourlyContinuousCollection = None, - universal_thermal_climate_index: HourlyContinuousCollection = None, + simulation_result: SimulationResult | BHoMObject, + typology: Typology | BHoMObject, + dry_bulb_temperature: HourlyContinuousCollection | BHoMObject = None, + relative_humidity: HourlyContinuousCollection | BHoMObject = None, + wind_speed: HourlyContinuousCollection | BHoMObject = None, + mean_radiant_temperature: HourlyContinuousCollection | BHoMObject = None, + universal_thermal_climate_index: HourlyContinuousCollection | BHoMObject = None, **kwargs ) -> "ExternalComfort": - if isinstance(simulation_result, BHoMObject): + if type(simulation_result) is BHoMObject: simulation_result = SimulationResult._from_bhom_object(simulation_result) - if isinstance(typology, BHoMObject): + if type(typology) is BHoMObject: typology = Typology._from_bhom_object(typology) self.simulation_result = simulation_result - self.typology = Typology + self.typology = typology self.dry_bulb_temperature = collection_from_bhom_object(dry_bulb_temperature) if isinstance(dry_bulb_temperature, BHoMObject) else dry_bulb_temperature self.relative_humidity = collection_from_bhom_object(relative_humidity) if isinstance(relative_humidity, BHoMObject) else relative_humidity @@ -87,9 +87,6 @@ def __init__( _t = kwargs.pop("_t", "BH.oM.LadybugTools.ExternalComfort") super().__init__(_t, **kwargs) - def __post_init__(self): - """_""" - # validation if not isinstance(self.simulation_result, SimulationResult): raise ValueError( @@ -170,6 +167,9 @@ def to_dict(self) -> str: } return d + def to_json(self) -> str: + return super().to_json(default=dict) + @classmethod def from_dict(cls, d: dict) -> "ExternalComfort": """Create a dictionary from this object.""" diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_shelterbase.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_shelterbase.py index 444f6a66..2f658738 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_shelterbase.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_shelterbase.py @@ -29,7 +29,7 @@ from python_toolkit.bhom.analytics import bhom_analytics -from python_toolkit.bhom.bhom_object import BHoMObject +from python_toolkit.bhom.bhom_object import BHoMObject, IObject from ..bhom.to_bhom import point3d_to_bhom from ..ladybug_extension.epw import sun_position_list from ..helpers import convert_keys_to_snake_case @@ -41,21 +41,24 @@ class Shelter(BHoMObject): """_""" - vertices: tuple[Union[Point3D, IObject]] + vertices: tuple[Point3D] wind_porosity: tuple[float] = (0,) * 8760 radiation_porosity: tuple[float] = (0,) * 8760 def __init__(self, - vertices: tuple[Union[Point3D, IObject]], + vertices: tuple[Point3D | IObject], wind_porosity: tuple[float] = None, radiation_porosity: tuple[float] = None, **kwargs ) -> "Shelter": - self.vertices = vertices - - for i, item in self.vertices: + self.vertices = list(vertices) + for i, item in enumerate(self.vertices): if isinstance(item, IObject): self.vertices[i] = Point3D.from_dict(vars(item)) + elif not isinstance(item, Point3D): + raise ValueError("All vertices must be Point3D objects, or BHoM IObjects.") + + self.vertices = tuple(self.vertices) self.wind_porosity = (0,) * 8760 if wind_porosity is None else wind_porosity self.radiation_porosity = (0,) * 8760 if radiation_porosity is None else radiation_porosity @@ -63,9 +66,6 @@ def __init__(self, _t = kwargs.pop("_t", "BH.oM.LadybugTools.Shelter") super().__init__(_t, **kwargs) - def __post_init__(self): - """_""" - # validation if len(self.wind_porosity) != 8760: raise ValueError("wind_porosity must be 8760 items long.") @@ -100,12 +100,6 @@ def __post_init__(self): if len(self.vertices) < 3: raise ValueError("A shelter must have at least 3 vertices.") - for i, item in self.vertices: - if isinstance(item, IObject): #if this object has been translated from json as part of a bhom object. - self.vertices[i] = Point3D.from_dict(vars(item)) - elif not isinstance(item, Point3D): - raise ValueError("All vertices must be Point3D objects, or BHoM IObjects.") - _plane = Plane.from_three_points(*self.vertices[:3]) for vertex in self.vertices[3:]: if not np.isclose(a=_plane.distance_to_point(point=vertex), b=0): diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_simulatebase.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_simulatebase.py index 52f8b3a3..1e5e06b3 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_simulatebase.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_simulatebase.py @@ -635,30 +635,41 @@ class SimulationResult(BHoMObject): def __init__( self, epw_file: Path, - ground_material: EnergyMaterial | EnergyMaterialVegetation, - shade_material: EnergyMaterial | EnergyMaterialVegetation, + ground_material: EnergyMaterial | EnergyMaterialVegetation | BHoMObject, + shade_material: EnergyMaterial | EnergyMaterialVegetation | BHoMObject, identifier: str = None, - shaded_down_temperature: HourlyContinuousCollection = None, - shaded_up_temperature: HourlyContinuousCollection = None, + shaded_down_temperature: HourlyContinuousCollection | BHoMObject = None, + shaded_up_temperature: HourlyContinuousCollection | BHoMObject = None, - unshaded_down_temperature: HourlyContinuousCollection = None, - unshaded_up_temperature: HourlyContinuousCollection = None, + unshaded_down_temperature: HourlyContinuousCollection | BHoMObject = None, + unshaded_up_temperature: HourlyContinuousCollection | BHoMObject = None, - shaded_radiant_temperature: HourlyContinuousCollection = None, - shaded_longwave_mean_radiant_temperature_delta: HourlyContinuousCollection = None, - shaded_shortwave_mean_radiant_temperature_delta: HourlyContinuousCollection = None, - shaded_mean_radiant_temperature: HourlyContinuousCollection = None, + shaded_radiant_temperature: HourlyContinuousCollection | BHoMObject = None, + shaded_longwave_mean_radiant_temperature_delta: HourlyContinuousCollection | BHoMObject = None, + shaded_shortwave_mean_radiant_temperature_delta: HourlyContinuousCollection | BHoMObject = None, + shaded_mean_radiant_temperature: HourlyContinuousCollection | BHoMObject = None, - unshaded_radiant_temperature: HourlyContinuousCollection = None, - unshaded_longwave_mean_radiant_temperature_delta: HourlyContinuousCollection = None, - unshaded_shortwave_mean_radiant_temperature_delta: HourlyContinuousCollection = None, - unshaded_mean_radiant_temperature: HourlyContinuousCollection = None, + unshaded_radiant_temperature: HourlyContinuousCollection | BHoMObject = None, + unshaded_longwave_mean_radiant_temperature_delta: HourlyContinuousCollection | BHoMObject = None, + unshaded_shortwave_mean_radiant_temperature_delta: HourlyContinuousCollection | BHoMObject = None, + unshaded_mean_radiant_temperature: HourlyContinuousCollection | BHoMObject = None, **kwargs ) -> "SimulationResult": self.epw_file = epw_file + + if isinstance(ground_material, BHoMObject): + ground_material = dict_to_material(vars(self.ground_material)) + if isinstance(ground_material, dict): + ground_material = dict_to_material(self.ground_material) self.ground_material = ground_material + + if isinstance(shade_material, BHoMObject): + shade_material = dict_to_material(vars(self.shade_material)) + if isinstance(shade_material, dict): + shade_material = dict_to_material(self.shade_material) self.shade_material = shade_material + self.identifier = identifier self.shaded_down_temperature = collection_from_bhom_object(shaded_down_temperature) if isinstance(shaded_down_temperature, BHoMObject) else shaded_down_temperature @@ -680,12 +691,6 @@ def __init__( _t = kwargs.pop("_t", "BH.oM.LadybugTools.SimulationResult") super().__init__(_t, **kwargs) - def __repr__(self) -> str: - return f"{self.__class__.__name__}({self.identifier})" - - def __post_init__(self): - """_""" - # validation if not isinstance(self.epw_file, (Path, str)): raise ValueError("epw_file must be a Path or str.") @@ -698,15 +703,6 @@ def __post_init__(self): if isinstance(self.shade_material, Materials): self.shade_material = self.shade_material.value - if isinstance(ground_material, BHoMObject): - self.ground_material = dict_to_material(vars(self.ground_material)) - if isinstance(ground_material, dict): - self.ground_material = dict_to_material(self.ground_material) - - if isinstance(shade_material, BHoMObject): - self.shade_material = dict_to_material(vars(self.shade_material)) - if isinstance(shade_material, dict): - self.shade_material = dict_to_material(self.shade_material) if not isinstance( self.ground_material, (EnergyMaterial, EnergyMaterialVegetation) @@ -815,6 +811,9 @@ def __post_init__(self): # add some accessors for collections as series for attr in _ATTRIBUTES: setattr(self, f"{attr}_series", collection_to_series(getattr(self, attr))) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.identifier})" def to_dict(self) -> dict[str, Any]: """Convert this object to a dictionary.""" diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_typologybase.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_typologybase.py index 487209ef..7d4e7fc8 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_typologybase.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_typologybase.py @@ -43,7 +43,7 @@ class Typology(BHoMObject): def __init__(self, identifier: str, - shelters: tuple[Shelter] = (), + shelters: tuple[Shelter | BHoMObject] = (), evaporative_cooling_effect: tuple[float] = None, target_wind_speed: tuple[float] = None, wind_speed_multiplier: float = 1, @@ -51,14 +51,16 @@ def __init__(self, **kwargs ) -> "Typology": self.identifier = identifier - self.shelters = (None,) * len(shelters) + self.shelters = list((None,) * len(shelters)) for i, shelter in enumerate(shelters): - if isinstance(shelter, BHoMObject): + if type(shelter) is BHoMObject: self.shelters[i] = Shelter._from_bhom_object(shelter) - else + else: self.shelters[i] = shelter + self.shelters = tuple(self.shelters) + self.evaporative_cooling_effect = (0,) * 8760 if evaporative_cooling_effect is None else evaporative_cooling_effect self.target_wind_speed = (None,) * 8760 if target_wind_speed is None else target_wind_speed self.wind_speed_multiplier = wind_speed_multiplier @@ -67,9 +69,6 @@ def __init__(self, _t = kwargs.pop("_t", "BH.oM.LadybugTools.Typology") super().__init__(_t, **kwargs) - def __post_init__(self): - """_""" - # validation if len(self.shelters) > 0: if any(not isinstance(shelter, Shelter) for shelter in self.shelters): diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/ladybug_extension/datacollection.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/ladybug_extension/datacollection.py index 4197a9cd..fa4c100b 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/ladybug_extension/datacollection.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/ladybug_extension/datacollection.py @@ -19,6 +19,7 @@ from ladybug.datatype.angle import Angle from ladybug.dt import DateTime from python_toolkit.bhom.analytics import bhom_analytics +from python_toolkit.bhom.bhom_object import BHoMObject from ..helpers import circular_weighted_mean from .analysisperiod import analysis_period_to_datetimes from .analysisperiod import describe_analysis_period diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/wind.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/wind.py index 404519ec..531e1122 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/wind.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/wind.py @@ -81,8 +81,6 @@ def __init__(self, wind_speeds: list[float], wind_directions: list[float], datet _t = kwargs.pop("_t", "BH.oM.LadybugTools.Wind") super().__init__(_t, **kwargs) - - def __post_init__(self): if self.height_above_ground < 0.1: raise ValueError("Height above ground must be >= 0.1m.") From 552f95531a774af97a548c0aca6ef29d0832be0c Mon Sep 17 00:00:00 2001 From: Tom Kingstone Date: Tue, 2 Jun 2026 15:42:34 +0100 Subject: [PATCH 03/15] fix init methods and serialisation for all classes that were converted to BHoMObjects --- .../external_comfort/_externalcomfortbase.py | 13 ++++-- .../external_comfort/_shelterbase.py | 5 ++- .../external_comfort/_simulatebase.py | 41 +++++++++++-------- 3 files changed, 37 insertions(+), 22 deletions(-) diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_externalcomfortbase.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_externalcomfortbase.py index 9f8b0d2c..09caa8bd 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_externalcomfortbase.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_externalcomfortbase.py @@ -95,15 +95,23 @@ def __init__( if isinstance(self.typology, Typologies): self.typology = self.typology.value + if not isinstance(self.typology, Typology): raise ValueError("typology must be an instance of Typology.") for attr in _ATTRIBUTES: + a = getattr(self, attr) + + if isinstance(a, BHoMObject): + setattr(self, attr, collection_from_bhom_object(a)) + elif isinstance(a, dict): + setattr(self, attr, HourlyContinuousCollection.from_dict(a)) + if not isinstance( getattr(self, attr), (HourlyContinuousCollection, type(None)) ): raise ValueError( - f"{attr} must be an instance of HourlyContinuousCollection or None." + f"{attr} must be either an HourlyContinuousCollection, or None." ) CONSOLE_LOGGER.info( @@ -167,9 +175,6 @@ def to_dict(self) -> str: } return d - def to_json(self) -> str: - return super().to_json(default=dict) - @classmethod def from_dict(cls, d: dict) -> "ExternalComfort": """Create a dictionary from this object.""" diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_shelterbase.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_shelterbase.py index 2f658738..3998b8b1 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_shelterbase.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_shelterbase.py @@ -52,11 +52,14 @@ def __init__(self, **kwargs ) -> "Shelter": self.vertices = list(vertices) + for i, item in enumerate(self.vertices): if isinstance(item, IObject): self.vertices[i] = Point3D.from_dict(vars(item)) + elif isinstance(item, dict): + self.vertices[i] = Point3D.from_dict(item) elif not isinstance(item, Point3D): - raise ValueError("All vertices must be Point3D objects, or BHoM IObjects.") + raise ValueError("All vertices must be Point3D objects, dictionaries, or BHoM IObjects that can be converted to Point3D.") self.vertices = tuple(self.vertices) diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_simulatebase.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_simulatebase.py index 1e5e06b3..8ae4964f 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_simulatebase.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_simulatebase.py @@ -659,34 +659,34 @@ def __init__( self.epw_file = epw_file if isinstance(ground_material, BHoMObject): - ground_material = dict_to_material(vars(self.ground_material)) + ground_material = dict_to_material(vars(ground_material)) if isinstance(ground_material, dict): - ground_material = dict_to_material(self.ground_material) + ground_material = dict_to_material(ground_material) self.ground_material = ground_material if isinstance(shade_material, BHoMObject): - shade_material = dict_to_material(vars(self.shade_material)) + shade_material = dict_to_material(vars(shade_material)) if isinstance(shade_material, dict): - shade_material = dict_to_material(self.shade_material) + shade_material = dict_to_material(shade_material) self.shade_material = shade_material self.identifier = identifier - self.shaded_down_temperature = collection_from_bhom_object(shaded_down_temperature) if isinstance(shaded_down_temperature, BHoMObject) else shaded_down_temperature - self.shaded_up_temperature = collection_from_bhom_object(shaded_up_temperature) if isinstance(shaded_up_temperature, BHoMObject) else shaded_up_temperature + self.shaded_down_temperature = shaded_down_temperature + self.shaded_up_temperature = shaded_up_temperature - self.unshaded_down_temperature = collection_from_bhom_object(unshaded_down_temperature) if isinstance(unshaded_down_temperature, BHoMObject) else unshaded_down_temperature - self.unshaded_up_temperature = collection_from_bhom_object(unshaded_up_temperature) if isinstance(unshaded_up_temperature, BHoMObject) else unshaded_up_temperature + self.unshaded_down_temperature = unshaded_down_temperature + self.unshaded_up_temperature = unshaded_up_temperature - self.shaded_radiant_temperature = collection_from_bhom_object(shaded_radiant_temperature) if isinstance(shaded_radiant_temperature, BHoMObject) else shaded_radiant_temperature - self.shaded_longwave_mean_radiant_temperature_delta = collection_from_bhom_object(shaded_longwave_mean_radiant_temperature_delta) if isinstance(shaded_longwave_mean_radiant_temperature_delta, BHoMObject) else shaded_longwave_mean_radiant_temperature_delta - self.shaded_shortwave_mean_radiant_temperature_delta = collection_from_bhom_object(shaded_shortwave_mean_radiant_temperature_delta) if isinstance(shaded_shortwave_mean_radiant_temperature_delta, BHoMObject) else shaded_shortwave_mean_radiant_temperature_delta - self.shaded_mean_radiant_temperature = collection_from_bhom_object(shaded_mean_radiant_temperature) if isinstance(shaded_mean_radiant_temperature, BHoMObject) else shaded_mean_radiant_temperature + self.shaded_radiant_temperature = shaded_radiant_temperature + self.shaded_longwave_mean_radiant_temperature_delta = shaded_longwave_mean_radiant_temperature_delta + self.shaded_shortwave_mean_radiant_temperature_delta = shaded_shortwave_mean_radiant_temperature_delta + self.shaded_mean_radiant_temperature = shaded_mean_radiant_temperature - self.unshaded_radiant_temperature = collection_from_bhom_object(unshaded_radiant_temperature) if isinstance(unshaded_radiant_temperature, BHoMObject) else unshaded_radiant_temperature - self.unshaded_longwave_mean_radiant_temperature_delta = collection_from_bhom_object(unshaded_longwave_mean_radiant_temperature_delta) if isinstance(unshaded_longwave_mean_radiant_temperature_delta, BHoMObject) else unshaded_longwave_mean_radiant_temperature_delta - self.unshaded_shortwave_mean_radiant_temperature_delta = collection_from_bhom_object(unshaded_shortwave_mean_radiant_temperature_delta) if isinstance(unshaded_shortwave_mean_radiant_temperature_delta, BHoMObject) else unshaded_shortwave_mean_radiant_temperature_delta - self.unshaded_mean_radiant_temperature = collection_from_bhom_object(unshaded_mean_radiant_temperature) if isinstance(unshaded_mean_radiant_temperature, BHoMObject) else unshaded_mean_radiant_temperature + self.unshaded_radiant_temperature = unshaded_radiant_temperature + self.unshaded_longwave_mean_radiant_temperature_delta = unshaded_longwave_mean_radiant_temperature_delta + self.unshaded_shortwave_mean_radiant_temperature_delta = unshaded_shortwave_mean_radiant_temperature_delta + self.unshaded_mean_radiant_temperature = unshaded_mean_radiant_temperature _t = kwargs.pop("_t", "BH.oM.LadybugTools.SimulationResult") super().__init__(_t, **kwargs) @@ -723,6 +723,13 @@ def __init__( ) for attr in _ATTRIBUTES: + a = getattr(self, attr) + + if isinstance(a, BHoMObject): + setattr(self, attr, collection_from_bhom_object(a)) + elif isinstance(a, dict): + setattr(self, attr, HourlyContinuousCollection.from_dict(a)) + if not isinstance( getattr(self, attr), (HourlyContinuousCollection, type(None)) ): @@ -811,7 +818,7 @@ def __init__( # add some accessors for collections as series for attr in _ATTRIBUTES: setattr(self, f"{attr}_series", collection_to_series(getattr(self, attr))) - + def __repr__(self) -> str: return f"{self.__class__.__name__}({self.identifier})" From 9cff00bc5c8b089af48cfb843d822e81124cbd58 Mon Sep 17 00:00:00 2001 From: Tom Kingstone Date: Thu, 4 Jun 2026 15:36:07 +0100 Subject: [PATCH 04/15] lots of changes to get serialisation working for objects in round trips with BHoM in grasshopper/excel (mainly testing with utci plot commands) --- .../Execute/RunSimulationCommand.cs | 2 +- .../Execute/UTCIHeatPlotCommand.cs | 4 +- .../Convert/Constructions/EnergyMaterial.cs | 4 +- .../Constructions/EnergyMaterialVegetation.cs | 4 +- .../Convert/MetaData/AnalysisPeriod.cs | 4 +- .../Convert/Simulation/SimulationResult.cs | 2 +- LadybugTools_Engine/Create/AnalysisPeriod.cs | 2 +- LadybugTools_Engine/Create/EnergyMaterial.cs | 2 +- .../Create/EnergyMaterialVegetation.cs | 2 +- .../Create/SimulationResult.cs | 2 +- .../ladybugtools_toolkit/bhom/from_bhom.py | 58 +++++++++++++++++++ .../src/ladybugtools_toolkit/bhom/to_bhom.py | 25 ++++++-- .../bhom/wrapped/plot/utci_heatmap.py | 4 +- .../external_comfort/_externalcomfortbase.py | 10 +++- .../external_comfort/_shelterbase.py | 10 +++- .../external_comfort/_simulatebase.py | 35 ++++++++++- .../external_comfort/_typologybase.py | 18 +++++- .../Python/tests/test_bhom/test_to_bhom.py | 7 +-- .../test_analysis_period.py | 1 - .../Collections/HourlyContinuousCollection.cs | 2 + .../Constructions/EnergyMaterial.cs | 4 +- .../Constructions/EnergyMaterialVegetation.cs | 4 +- .../Constructions/IEnergyMaterialOpaque.cs | 2 + LadybugTools_oM/MetaData/AnalysisPeriod.cs | 4 +- LadybugTools_oM/MetaData/DataType.cs | 2 + LadybugTools_oM/MetaData/Header.cs | 2 + .../Simulation/SimulationResult.cs | 6 +- 27 files changed, 185 insertions(+), 37 deletions(-) create mode 100644 LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/from_bhom.py diff --git a/LadybugTools_Adapter/AdapterActions/Execute/RunSimulationCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/RunSimulationCommand.cs index d4d9159f..37923929 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/RunSimulationCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/RunSimulationCommand.cs @@ -77,7 +77,7 @@ private List RunCommand(RunSimulationCommand command, ActionConfig actio EpwFile = command.EPWFile, GroundMaterial = command.GroundMaterial, ShadeMaterial = command.ShadeMaterial, - Name = Engine.LadybugTools.Compute.SimulationID(command.EPWFile.GetFullFileName(), command.GroundMaterial, command.ShadeMaterial) + Identifier = Engine.LadybugTools.Compute.SimulationID(command.EPWFile.GetFullFileName(), command.GroundMaterial, command.ShadeMaterial) }; // push object to json file diff --git a/LadybugTools_Adapter/AdapterActions/Execute/UTCIHeatPlotCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/UTCIHeatPlotCommand.cs index 1388c8d3..1635eec6 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/UTCIHeatPlotCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/UTCIHeatPlotCommand.cs @@ -75,7 +75,7 @@ private List RunCommand(UTCIHeatPlotCommand command, ActionConfig action Dictionary inputObjects = new Dictionary() { - { "external_comfort", command.ExternalComfort.FromBHoM() }, + { "external_comfort", BH.Engine.Serialiser.Convert.ToJson(command.ExternalComfort) }, { "bin_colours", hexColours } }; @@ -113,7 +113,7 @@ private List RunCommand(UTCIHeatPlotCommand command, ActionConfig action { CustomObject obj = (CustomObject)BH.Engine.Serialiser.Convert.FromJson(result); PlotInformation info = Convert.ToPlotInformation(obj, new UTCIData()); - ExternalComfort ec = Convert.ToExternalComfort((obj.CustomData["external_comfort"] as CustomObject).CustomData); + ExternalComfort ec = BH.Engine.Serialiser.Convert.FromJson((string)obj.CustomData["external_comfort"]) as ExternalComfort; m_executeSuccess = true; return new List() { info, ec }; } diff --git a/LadybugTools_Adapter/Convert/Constructions/EnergyMaterial.cs b/LadybugTools_Adapter/Convert/Constructions/EnergyMaterial.cs index a1ea3d31..d2ae772b 100644 --- a/LadybugTools_Adapter/Convert/Constructions/EnergyMaterial.cs +++ b/LadybugTools_Adapter/Convert/Constructions/EnergyMaterial.cs @@ -120,7 +120,7 @@ public static BH.oM.LadybugTools.EnergyMaterial ToEnergyMaterial(Dictionary FromEnergyMaterial(BH.oM.LadybugTools.E return new Dictionary() { { "type", "EnergyMaterial" }, - { "identifier", energyMaterial.Name }, + { "identifier", energyMaterial.Identifier }, { "thickness", energyMaterial.Thickness }, { "conductivity", energyMaterial.Conductivity }, { "density", energyMaterial.Density }, diff --git a/LadybugTools_Adapter/Convert/Constructions/EnergyMaterialVegetation.cs b/LadybugTools_Adapter/Convert/Constructions/EnergyMaterialVegetation.cs index 44bf307b..186f9dac 100644 --- a/LadybugTools_Adapter/Convert/Constructions/EnergyMaterialVegetation.cs +++ b/LadybugTools_Adapter/Convert/Constructions/EnergyMaterialVegetation.cs @@ -179,7 +179,7 @@ public static BH.oM.LadybugTools.EnergyMaterialVegetation ToEnergyMaterialVegeta return new oM.LadybugTools.EnergyMaterialVegetation() { - Name = name, + Identifier = name, Thickness = thickness, Conductivity = conductivity, Density = density, @@ -201,7 +201,7 @@ public static Dictionary FromEnergyMaterialVegetation(BH.oM.Lady return new Dictionary { { "type", "EnergyMaterialVegetation" }, - { "identifier", energyMaterial.Name }, + { "identifier", energyMaterial.Identifier }, { "thickness", energyMaterial.Thickness }, { "conductivity", energyMaterial.Conductivity }, { "density", energyMaterial.Density }, diff --git a/LadybugTools_Adapter/Convert/MetaData/AnalysisPeriod.cs b/LadybugTools_Adapter/Convert/MetaData/AnalysisPeriod.cs index 551e24ec..1381bfeb 100644 --- a/LadybugTools_Adapter/Convert/MetaData/AnalysisPeriod.cs +++ b/LadybugTools_Adapter/Convert/MetaData/AnalysisPeriod.cs @@ -122,7 +122,7 @@ public static BH.oM.LadybugTools.AnalysisPeriod ToAnalysisPeriod(Dictionary FromAnalysisPeriod(BH.oM.LadybugTools.A { "end_day", analysisPeriod.EndDay }, { "end_hour", analysisPeriod.EndHour }, { "is_leap_year", analysisPeriod.IsLeapYear }, - { "timestep", analysisPeriod.TimeStep } + { "timestep", analysisPeriod.Timestep } }; } } diff --git a/LadybugTools_Adapter/Convert/Simulation/SimulationResult.cs b/LadybugTools_Adapter/Convert/Simulation/SimulationResult.cs index 1fb26f59..593a455c 100644 --- a/LadybugTools_Adapter/Convert/Simulation/SimulationResult.cs +++ b/LadybugTools_Adapter/Convert/Simulation/SimulationResult.cs @@ -166,7 +166,7 @@ public static string FromSimulationResult(SimulationResult simulationResult) string epwFile = $"\"epw_file\": \"{simulationResult.EpwFile.GetFullFileName().Replace("\\", "/")}\", "; string groundMaterial = $"\"ground_material\": {FromBHoM(simulationResult.GroundMaterial)}, "; string shadeMaterial = $"\"shade_material\": {FromBHoM(simulationResult.ShadeMaterial)}, "; - string name = $"\"identifier\": \"{simulationResult.Name}\""; + string name = $"\"identifier\": \"{simulationResult.Identifier}\""; List properties = new List(); if (simulationResult.ShadedDownTemperature != null) diff --git a/LadybugTools_Engine/Create/AnalysisPeriod.cs b/LadybugTools_Engine/Create/AnalysisPeriod.cs index 22b12511..df76f119 100644 --- a/LadybugTools_Engine/Create/AnalysisPeriod.cs +++ b/LadybugTools_Engine/Create/AnalysisPeriod.cs @@ -75,7 +75,7 @@ public static AnalysisPeriod AnalysisPeriod(int startMonth = 1, int startDay = 1 EndDay = endDay, EndHour = endHour, IsLeapYear = isLeapYear, - TimeStep = timestep + Timestep = timestep }; } } diff --git a/LadybugTools_Engine/Create/EnergyMaterial.cs b/LadybugTools_Engine/Create/EnergyMaterial.cs index 7f114f86..af65529f 100644 --- a/LadybugTools_Engine/Create/EnergyMaterial.cs +++ b/LadybugTools_Engine/Create/EnergyMaterial.cs @@ -113,7 +113,7 @@ public static EnergyMaterial EnergyMaterial( return new oM.LadybugTools.EnergyMaterial() { - Name = identifier, + Identifier = identifier, Thickness = thickness, Conductivity = conductivity, Density = density, diff --git a/LadybugTools_Engine/Create/EnergyMaterialVegetation.cs b/LadybugTools_Engine/Create/EnergyMaterialVegetation.cs index 9b69ea95..5ffa4e48 100644 --- a/LadybugTools_Engine/Create/EnergyMaterialVegetation.cs +++ b/LadybugTools_Engine/Create/EnergyMaterialVegetation.cs @@ -153,7 +153,7 @@ public static EnergyMaterialVegetation EnergyMaterialVegetation( return new oM.LadybugTools.EnergyMaterialVegetation() { - Name = identifier, + Identifier = identifier, Thickness = thickness, Conductivity = conductivity, Density = density, diff --git a/LadybugTools_Engine/Create/SimulationResult.cs b/LadybugTools_Engine/Create/SimulationResult.cs index 8e3db819..d5a37f02 100644 --- a/LadybugTools_Engine/Create/SimulationResult.cs +++ b/LadybugTools_Engine/Create/SimulationResult.cs @@ -43,7 +43,7 @@ public static SimulationResult SimulationResult(FileSettings epwFile, string ide return new SimulationResult() { EpwFile = epwFile, - Name = identifier, + Identifier = identifier, GroundMaterial = groundMaterial, ShadeMaterial = shadeMaterial }; diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/from_bhom.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/from_bhom.py new file mode 100644 index 00000000..e858ac42 --- /dev/null +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/from_bhom.py @@ -0,0 +1,58 @@ +from honeybee_energy.material.opaque import EnergyMaterial, EnergyMaterialVegetation +from ladybug.analysisperiod import AnalysisPeriod as AnalysisPeriodBase +from ladybug.datacollection import HourlyContinuousCollection as HC +from ladybug.epw import EPW, Location +from ladybug.header import DataTypeBase, Header as HeaderBase +from ladybug_geometry.geometry3d.pointvector import Point3D +from python_toolkit.bhom.bhom_object import BHoMObject, IObject, BHoMJSONDecoder + +#make custom classes for converting to ladybug objects from bhom objects (where type names and some other differences occur) +class Point(): + @classmethod + def from_dict(cls, d) -> Point3D: + d["type"] = "Point3D" + + return Point3D.from_dict(d) + +class DataType(): + @classmethod + def from_dict(cls, d) -> dict: + d["type"] = "DataTypeBase" + d["data_type"] = d["data__type"] + return d #due to Header.from_dict() not handling already deserialised objects, this should just return the correct dictionary instead of the DataType object. + +class AnalysisPeriod(): + @classmethod + def from_dict(cls, d) -> dict: + d["st_hour"] = d["start_hour"] + d["st_day"] = d["start_day"] + d["st_month"] = d["start_month"] + return d + +class HourlyContinuousCollection(): + @classmethod + def from_dict(cls, d) -> dict: + d["type"] = "HourlyContinuous" + return HC.from_dict(d) + +class Header(): + @classmethod + def from_dict(cls, d) -> dict: + return d + +_TYPES: list[type] = [EnergyMaterial, EnergyMaterialVegetation, AnalysisPeriod, HourlyContinuousCollection, Location, DataType, Header, Point] + +class LBTBHoMJSONDecoder(BHoMJSONDecoder): + def deserialise_unknown(self, obj:BHoMObject | IObject | dict): + """custom object-hook method for BHoMJSONDecoder""" + if isinstance(obj, BHoMObject) or isinstance(obj, IObject): + _type = obj._t.split(".")[-1] + + klass = [t for t in _TYPES if t.__name__ == _type] + + if len(klass) == 1: + setattr(obj, "type", _type) + return klass[0].from_dict(obj.to_dict()) + + #default to returning a bhom object if tha above did not work + return obj diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/to_bhom.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/to_bhom.py index 9d3f647d..5aecb66b 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/to_bhom.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/to_bhom.py @@ -6,7 +6,24 @@ from ladybug.epw import EPW, Location from ladybug.header import DataTypeBase, Header from ladybug_geometry.geometry3d.pointvector import Point3D - +from python_toolkit.bhom.bhom_object import BHoMJSONEncoder + +class LBTBHoMJSONEncoder(BHoMJSONEncoder): + def serialise_unknown(self, obj): + if isinstance(obj, EnergyMaterial) or isinstance(obj, EnergyMaterialVegetation): + return material_to_bhom(obj) + if isinstance(obj, Point3D): + return point3d_to_bhom(obj) + if isinstance(obj, AnalysisPeriod): + return analysisperiod_to_bhom(obj) + if isinstance(obj, DataTypeBase): + return datatype_to_bhom(obj) + if isinstance(obj, Header): + return header_to_bhom(obj) + if isinstance(obj, HourlyContinuousCollection): + return hourlycontinuouscollection_to_bhom(obj) + + return super().serialise_unknown(obj) def material_to_bhom(obj: EnergyMaterial | EnergyMaterialVegetation) -> dict: """Convert this object into a BHOM deserialisable dictionary.""" @@ -76,11 +93,11 @@ def analysisperiod_to_bhom(obj: AnalysisPeriod) -> dict: return { "_t": "BH.oM.LadybugTools.AnalysisPeriod", "Type": "AnalysisPeriod", - "StHour": obj.st_hour, + "StartHour": obj.st_hour, "EndHour": obj.end_hour, - "StDay": obj.st_day, + "StartDay": obj.st_day, "EndDay": obj.end_day, - "StMonth": obj.st_month, + "StartMonth": obj.st_month, "EndMonth": obj.end_month, "IsLeapYear": obj.is_leap_year, "Timestep": obj.timestep, diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/utci_heatmap.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/utci_heatmap.py index 410e2950..8bc133c9 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/utci_heatmap.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/utci_heatmap.py @@ -51,7 +51,7 @@ def utci_heatmap(input_json:str, save_path = None, epw_file:str = None) -> str: argsDict = json.loads(input_json) - ec = ExternalComfort.from_dict(json.loads(argsDict["external_comfort"])) + ec = ExternalComfort.from_json(argsDict["external_comfort"]) custom_bins = UTCI_DEFAULT_CATEGORIES @@ -69,7 +69,7 @@ def utci_heatmap(input_json:str, save_path = None, epw_file:str = None) -> str: utci_collection = ec.universal_thermal_climate_index - return_dict = {"data": utci_metadata(utci_collection), "external_comfort": ec.to_dict()} + return_dict = {"data": utci_metadata(utci_collection), "external_comfort": ec.to_json()} plt.tight_layout() diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_externalcomfortbase.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_externalcomfortbase.py index 09caa8bd..f5b0a7a7 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_externalcomfortbase.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_externalcomfortbase.py @@ -15,7 +15,8 @@ from matplotlib.colors import LinearSegmentedColormap from ..bhom.logger import CONSOLE_LOGGER -from ..bhom.to_bhom import hourlycontinuouscollection_to_bhom +from ..bhom.to_bhom import hourlycontinuouscollection_to_bhom, LBTBHoMJSONEncoder +from ..bhom.from_bhom import LBTBHoMJSONDecoder from ..categorical.categories import UTCI_DEFAULT_CATEGORIES, Categorical from ..helpers import convert_keys_to_snake_case from ..ladybug_extension.analysisperiod import describe_analysis_period @@ -158,6 +159,13 @@ def __init__( def __repr__(self) -> str: return f"{self.__class__.__name__}({self.simulation_result}, {self.typology})" + def to_json(self): + return super().to_json(encoder_class=LBTBHoMJSONEncoder) + + @classmethod + def from_json(cls, j): + return super().from_json(j, decoder_class=LBTBHoMJSONDecoder) + def to_dict(self) -> str: """Convert this object to a dictionary.""" attr_dict = {} diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_shelterbase.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_shelterbase.py index 3998b8b1..b932245b 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_shelterbase.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_shelterbase.py @@ -30,7 +30,8 @@ from python_toolkit.bhom.analytics import bhom_analytics from python_toolkit.bhom.bhom_object import BHoMObject, IObject -from ..bhom.to_bhom import point3d_to_bhom +from ..bhom.to_bhom import LBTBHoMJSONEncoder, point3d_to_bhom +from ..bhom.from_bhom import LBTBHoMJSONDecoder from ..ladybug_extension.epw import sun_position_list from ..helpers import convert_keys_to_snake_case @@ -117,6 +118,13 @@ def __repr__(self) -> str: f"avg_radiation_porosity={self.average_radiation_porosity:0.2f}" ")" ) + + def to_json(self): + return super().to_json(encoder_class=LBTBHoMJSONEncoder) + + @classmethod + def from_json(cls, j): + return super().from_json(j, decoder_class=LBTBHoMJSONDecoder) #TODO: maybe these methods aren't needed with the BHoMObject class implemented def to_dict(self) -> str: diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_simulatebase.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_simulatebase.py index 8ae4964f..4f166e60 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_simulatebase.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_simulatebase.py @@ -42,7 +42,9 @@ from python_toolkit.bhom.bhom_object import BHoMObject from ..bhom.logger import CONSOLE_LOGGER +from ..bhom.from_bhom import LBTBHoMJSONDecoder from ..bhom.to_bhom import ( + LBTBHoMJSONEncoder, hourlycontinuouscollection_to_bhom, material_to_bhom, ) @@ -606,6 +608,16 @@ def radiant_temperature( "unshaded_mean_radiant_temperature", ] +def material_from_bhom_object(o: BHoMObject) -> EnergyMaterial | EnergyMaterialVegetation: + v = vars(o).copy() + + default_vars = { + "type": o._t.split(".")[-1] if not hasattr(o, "type") else o.type + } + + v.pop("type", None) + return dict_to_material({**default_vars, **v}) + @dataclass(init=False, repr=True, eq=True) class SimulationResult(BHoMObject): @@ -634,7 +646,7 @@ class SimulationResult(BHoMObject): def __init__( self, - epw_file: Path, + epw_file: Path | BHoMObject, ground_material: EnergyMaterial | EnergyMaterialVegetation | BHoMObject, shade_material: EnergyMaterial | EnergyMaterialVegetation | BHoMObject, identifier: str = None, @@ -656,20 +668,30 @@ def __init__( unshaded_mean_radiant_temperature: HourlyContinuousCollection | BHoMObject = None, **kwargs ) -> "SimulationResult": + + if isinstance(epw_file, BHoMObject): + if epw_file._t == "BH.oM.Adapter.FileSettings": + epw_file = Path(epw_file.directory) / epw_file.file_name + self.epw_file = epw_file if isinstance(ground_material, BHoMObject): - ground_material = dict_to_material(vars(ground_material)) + ground_material = material_from_bhom_object(ground_material) if isinstance(ground_material, dict): ground_material = dict_to_material(ground_material) self.ground_material = ground_material if isinstance(shade_material, BHoMObject): - shade_material = dict_to_material(vars(shade_material)) + shade_material = material_from_bhom_object(shade_material) if isinstance(shade_material, dict): shade_material = dict_to_material(shade_material) self.shade_material = shade_material + name = kwargs.pop("name", None) + + if name is not None and name != '' and identifier is None: + identifier = name + self.identifier = identifier self.shaded_down_temperature = shaded_down_temperature @@ -821,6 +843,13 @@ def __init__( def __repr__(self) -> str: return f"{self.__class__.__name__}({self.identifier})" + + def to_json(self): + return super().to_json(encoder_class=LBTBHoMJSONEncoder) + + @classmethod + def from_json(cls, j): + return super().from_json(j, decoder_class=LBTBHoMJSONDecoder) def to_dict(self) -> dict[str, Any]: """Convert this object to a dictionary.""" diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_typologybase.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_typologybase.py index 7d4e7fc8..fd68c6ea 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_typologybase.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_typologybase.py @@ -1,5 +1,6 @@ """Base class for typology objects.""" # pylint: disable=E0401 +from ctypes import ArgumentError import json from dataclasses import dataclass from pathlib import Path @@ -12,6 +13,8 @@ from python_toolkit.bhom.analytics import bhom_analytics from python_toolkit.bhom.bhom_object import BHoMObject +from ..bhom.to_bhom import LBTBHoMJSONEncoder +from ..bhom.from_bhom import LBTBHoMJSONDecoder from ..helpers import ( convert_keys_to_snake_case, decay_rate_smoother, @@ -42,7 +45,7 @@ class Typology(BHoMObject): radiant_temperature_adjustment: tuple[float] = (0,) * 8760 def __init__(self, - identifier: str, + identifier: str = None, shelters: tuple[Shelter | BHoMObject] = (), evaporative_cooling_effect: tuple[float] = None, target_wind_speed: tuple[float] = None, @@ -50,6 +53,12 @@ def __init__(self, radiant_temperature_adjustment: tuple[float] = None, **kwargs ) -> "Typology": + + identifier = kwargs.pop("name", None) if identifier is None else identifier + + if identifier is None: + raise ArgumentError("Missing required key word argument 'identifier' or 'name.") + self.identifier = identifier self.shelters = list((None,) * len(shelters)) @@ -108,6 +117,13 @@ def __init__(self, def __repr__(self) -> str: return f"{self.__class__.__name__}({self.identifier})" + + def to_json(self): + return super().to_json(encoder_class=LBTBHoMJSONEncoder) + + @classmethod + def from_json(cls, j): + return super().from_json(j, decoder_class=LBTBHoMJSONDecoder) def to_dict(self) -> str: """Convert this object to a dictionary.""" diff --git a/LadybugTools_Engine/Python/tests/test_bhom/test_to_bhom.py b/LadybugTools_Engine/Python/tests/test_bhom/test_to_bhom.py index 9a27c06f..4465433c 100644 --- a/LadybugTools_Engine/Python/tests/test_bhom/test_to_bhom.py +++ b/LadybugTools_Engine/Python/tests/test_bhom/test_to_bhom.py @@ -47,7 +47,6 @@ visible_absorptance=0.8, ) - def test_energymaterialvegetation_to_bhom(): """_""" @@ -125,11 +124,11 @@ def test_analysisperiod_to_bhom(): assert result["_t"] == "BH.oM.LadybugTools.AnalysisPeriod" assert result["Type"] == "AnalysisPeriod" - assert result["StHour"] == 0 + assert result["StartHour"] == 0 assert result["EndHour"] == 23 - assert result["StDay"] == 1 + assert result["StartDay"] == 1 assert result["EndDay"] == 31 - assert result["StMonth"] == 1 + assert result["StartMonth"] == 1 assert result["EndMonth"] == 12 assert result["IsLeapYear"] is False assert result["Timestep"] == 1 diff --git a/LadybugTools_Engine/Python/tests/test_ladybug_extension/test_analysis_period.py b/LadybugTools_Engine/Python/tests/test_ladybug_extension/test_analysis_period.py index e6cf0468..31c0a2c9 100644 --- a/LadybugTools_Engine/Python/tests/test_ladybug_extension/test_analysis_period.py +++ b/LadybugTools_Engine/Python/tests/test_ladybug_extension/test_analysis_period.py @@ -9,7 +9,6 @@ describe_analysis_period, ) - def test_from_datetimes(): """_""" datetimes = [ diff --git a/LadybugTools_oM/Collections/HourlyContinuousCollection.cs b/LadybugTools_oM/Collections/HourlyContinuousCollection.cs index 01eb0ecf..489b1236 100644 --- a/LadybugTools_oM/Collections/HourlyContinuousCollection.cs +++ b/LadybugTools_oM/Collections/HourlyContinuousCollection.cs @@ -36,6 +36,8 @@ public class HourlyContinuousCollection : BHoMObject, ILadybugTools [Description("A list of values.")] public virtual List Values { get; set; } = Enumerable.Repeat(null, 8760).ToList(); + + public virtual string Type { get; set; } = "HourlyContinuousCollection"; } } diff --git a/LadybugTools_oM/Constructions/EnergyMaterial.cs b/LadybugTools_oM/Constructions/EnergyMaterial.cs index 4615f8c7..d7e5ed07 100644 --- a/LadybugTools_oM/Constructions/EnergyMaterial.cs +++ b/LadybugTools_oM/Constructions/EnergyMaterial.cs @@ -32,7 +32,7 @@ namespace BH.oM.LadybugTools public class EnergyMaterial : BHoMObject, IEnergyMaterialOpaque { [Description("The name of this EnergyMaterial.")] - public override string Name { get; set; } = string.Empty; + public virtual string Identifier { get; set; } = string.Empty; [Description("Thickness of material (m).")] [Length] @@ -63,6 +63,8 @@ public class EnergyMaterial : BHoMObject, IEnergyMaterialOpaque [DisplayText("Visible Absorptance")] [Description("Light absorptivity (1 - albedo) of material (0-1).")] public virtual double VisibleAbsorptance { get; set; } + + public virtual string Type { get; set; } = "EnergyMaterial"; } } diff --git a/LadybugTools_oM/Constructions/EnergyMaterialVegetation.cs b/LadybugTools_oM/Constructions/EnergyMaterialVegetation.cs index 360a6d85..554bb33d 100644 --- a/LadybugTools_oM/Constructions/EnergyMaterialVegetation.cs +++ b/LadybugTools_oM/Constructions/EnergyMaterialVegetation.cs @@ -32,7 +32,7 @@ namespace BH.oM.LadybugTools public class EnergyMaterialVegetation : BHoMObject, IEnergyMaterialOpaque { [Description("The name of this EnergyMaterialVegetation.")] - public override string Name { get; set; } = string.Empty; + public virtual string Identifier { get; set; } = string.Empty; [Description("Thickness of material (m).")] [Length] @@ -83,6 +83,8 @@ public class EnergyMaterialVegetation : BHoMObject, IEnergyMaterialOpaque [DisplayText("Minimum Stomatal Resistance")] [Description("A number between 50 and 300 for the resistance of the plants to moisture transport [s/m]. Plants with low values of stomatal resistance will result in higher evapotranspiration rates than plants with high resistance.")] public virtual double MinimumStomatalResistance { get; set; } + + public virtual string Type { get; set; } = "EnergyMaterialVegetation"; } } diff --git a/LadybugTools_oM/Constructions/IEnergyMaterialOpaque.cs b/LadybugTools_oM/Constructions/IEnergyMaterialOpaque.cs index 12588133..f70e6f97 100644 --- a/LadybugTools_oM/Constructions/IEnergyMaterialOpaque.cs +++ b/LadybugTools_oM/Constructions/IEnergyMaterialOpaque.cs @@ -29,6 +29,8 @@ namespace BH.oM.LadybugTools [Description("An interface for opaque energy materials.")] public interface IEnergyMaterialOpaque : ILadybugTools { + [Description("Unique identifier for this material.")] + string Identifier { get; set; } } } diff --git a/LadybugTools_oM/MetaData/AnalysisPeriod.cs b/LadybugTools_oM/MetaData/AnalysisPeriod.cs index 8811f7ed..1c53d938 100644 --- a/LadybugTools_oM/MetaData/AnalysisPeriod.cs +++ b/LadybugTools_oM/MetaData/AnalysisPeriod.cs @@ -60,7 +60,9 @@ public class AnalysisPeriod : BHoMObject, ILadybugTools [DisplayText("Time Step")] [Description("The number of timesteps per hour.")] - public virtual int TimeStep { get; set; } = 1; + public virtual int Timestep { get; set; } = 1; + + public virtual string Type { get; set; } = "AnalysisPeriod"; } } diff --git a/LadybugTools_oM/MetaData/DataType.cs b/LadybugTools_oM/MetaData/DataType.cs index 33caca39..3b3847d9 100644 --- a/LadybugTools_oM/MetaData/DataType.cs +++ b/LadybugTools_oM/MetaData/DataType.cs @@ -39,6 +39,8 @@ public class DataType : BHoMObject, ILadybugTools [DisplayText("Base Unit")] [Description(@"The base type of this data type. This is used if Data_Type is set to ""GenericDataType"".")] public virtual string BaseUnit { get; set; } = string.Empty; + + public virtual string Type { get; set; } = "DataType"; } } diff --git a/LadybugTools_oM/MetaData/Header.cs b/LadybugTools_oM/MetaData/Header.cs index ce8be7b8..25fc36b0 100644 --- a/LadybugTools_oM/MetaData/Header.cs +++ b/LadybugTools_oM/MetaData/Header.cs @@ -43,6 +43,8 @@ public class Header : BHoMObject, ILadybugTools [Description("The metadata associated with this header object.")] public virtual Dictionary Metadata { get; set; } = new Dictionary(); + + public virtual string Type { get; set; } = "Header"; } } diff --git a/LadybugTools_oM/Simulation/SimulationResult.cs b/LadybugTools_oM/Simulation/SimulationResult.cs index 9d2847b2..789a9c6f 100644 --- a/LadybugTools_oM/Simulation/SimulationResult.cs +++ b/LadybugTools_oM/Simulation/SimulationResult.cs @@ -43,7 +43,7 @@ public class SimulationResult : BHoMObject, ILadybugTools, IImmutable public virtual IEnergyMaterialOpaque ShadeMaterial { get; set; } [Description("The identifier used to distinguish existing results for this object.")] - public override string Name { get; set; } + public virtual string Identifier { get; set; } // simulated properties @@ -95,12 +95,12 @@ public class SimulationResult : BHoMObject, ILadybugTools, IImmutable [Description("The Unshaded Mean Radiant Temperature used in the processing of this object")] public virtual HourlyContinuousCollection UnshadedMeanRadiantTemperature { get; } = null; - public SimulationResult(FileSettings epwFile = null, IEnergyMaterialOpaque groundMaterial = null, IEnergyMaterialOpaque shadeMaterial = null, string name = null, HourlyContinuousCollection shadedDownTemperature = null, HourlyContinuousCollection shadedUpTemperature = null, HourlyContinuousCollection shadedRadiantTemperature = null, HourlyContinuousCollection shadedLongwaveMeanRadiantTemperatureDelta = null, HourlyContinuousCollection shadedShortwaveMeanRadiantTemperatureDelta = null, HourlyContinuousCollection shadedMeanRadiantTemperature = null, HourlyContinuousCollection unshadedDownTemperature = null, HourlyContinuousCollection unshadedUpTemperature = null, HourlyContinuousCollection unshadedRadiantTemperature = null, HourlyContinuousCollection unshadedLongwaveMeanRadiantTemperatureDelta = null, HourlyContinuousCollection unshadedShortwaveMeanRadiantTemperatureDelta = null, HourlyContinuousCollection unshadedMeanRadiantTemperature = null) + public SimulationResult(FileSettings epwFile = null, IEnergyMaterialOpaque groundMaterial = null, IEnergyMaterialOpaque shadeMaterial = null, string identifier = null, HourlyContinuousCollection shadedDownTemperature = null, HourlyContinuousCollection shadedUpTemperature = null, HourlyContinuousCollection shadedRadiantTemperature = null, HourlyContinuousCollection shadedLongwaveMeanRadiantTemperatureDelta = null, HourlyContinuousCollection shadedShortwaveMeanRadiantTemperatureDelta = null, HourlyContinuousCollection shadedMeanRadiantTemperature = null, HourlyContinuousCollection unshadedDownTemperature = null, HourlyContinuousCollection unshadedUpTemperature = null, HourlyContinuousCollection unshadedRadiantTemperature = null, HourlyContinuousCollection unshadedLongwaveMeanRadiantTemperatureDelta = null, HourlyContinuousCollection unshadedShortwaveMeanRadiantTemperatureDelta = null, HourlyContinuousCollection unshadedMeanRadiantTemperature = null) { EpwFile = epwFile; GroundMaterial = groundMaterial; ShadeMaterial = shadeMaterial; - Name = name; + Identifier = identifier; ShadedDownTemperature = shadedDownTemperature; ShadedUpTemperature = shadedUpTemperature; ShadedRadiantTemperature = shadedRadiantTemperature; From d28570aa79ac9a71c6711b8be2ecd749f67b5f4c Mon Sep 17 00:00:00 2001 From: Tom Kingstone Date: Tue, 9 Jun 2026 15:16:04 +0100 Subject: [PATCH 05/15] remove whitespace --- .../AdapterActions/Execute/UTCIHeatPlotCommand.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/LadybugTools_Adapter/AdapterActions/Execute/UTCIHeatPlotCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/UTCIHeatPlotCommand.cs index 1635eec6..0739d78d 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/UTCIHeatPlotCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/UTCIHeatPlotCommand.cs @@ -108,7 +108,6 @@ private List RunCommand(UTCIHeatPlotCommand command, ActionConfig action System.IO.File.Delete(argFile); } - try { CustomObject obj = (CustomObject)BH.Engine.Serialiser.Convert.FromJson(result); From 909c4ad23bf7a9da9109530ceaa6112be7281484 Mon Sep 17 00:00:00 2001 From: Tom Kingstone Date: Tue, 9 Jun 2026 15:25:27 +0100 Subject: [PATCH 06/15] remove unnecessary type hinting and converters as they are handled in the LBTBHoMJSONDecoder --- .../external_comfort/_externalcomfortbase.py | 20 ++++----- .../external_comfort/_shelterbase.py | 6 +-- .../external_comfort/_simulatebase.py | 43 ++++++------------- .../ladybug_extension/datacollection.py | 5 --- LadybugTools_Toolkit.sln | 4 +- 5 files changed, 28 insertions(+), 50 deletions(-) diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_externalcomfortbase.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_externalcomfortbase.py index f5b0a7a7..b4037e87 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_externalcomfortbase.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_externalcomfortbase.py @@ -63,11 +63,11 @@ def __init__( self, simulation_result: SimulationResult | BHoMObject, typology: Typology | BHoMObject, - dry_bulb_temperature: HourlyContinuousCollection | BHoMObject = None, - relative_humidity: HourlyContinuousCollection | BHoMObject = None, - wind_speed: HourlyContinuousCollection | BHoMObject = None, - mean_radiant_temperature: HourlyContinuousCollection | BHoMObject = None, - universal_thermal_climate_index: HourlyContinuousCollection | BHoMObject = None, + dry_bulb_temperature: HourlyContinuousCollection = None, + relative_humidity: HourlyContinuousCollection = None, + wind_speed: HourlyContinuousCollection = None, + mean_radiant_temperature: HourlyContinuousCollection = None, + universal_thermal_climate_index: HourlyContinuousCollection = None, **kwargs ) -> "ExternalComfort": if type(simulation_result) is BHoMObject: @@ -79,11 +79,11 @@ def __init__( self.simulation_result = simulation_result self.typology = typology - self.dry_bulb_temperature = collection_from_bhom_object(dry_bulb_temperature) if isinstance(dry_bulb_temperature, BHoMObject) else dry_bulb_temperature - self.relative_humidity = collection_from_bhom_object(relative_humidity) if isinstance(relative_humidity, BHoMObject) else relative_humidity - self.wind_speed = collection_from_bhom_object(wind_speed) if isinstance(wind_speed, BHoMObject) else wind_speed - self.mean_radiant_temperature = collection_from_bhom_object(mean_radiant_temperature) if isinstance(mean_radiant_temperature, BHoMObject) else mean_radiant_temperature - self.universal_thermal_climate_index = collection_from_bhom_object(universal_thermal_climate_index) if isinstance(universal_thermal_climate_index, BHoMObject) else universal_thermal_climate_index + self.dry_bulb_temperature = dry_bulb_temperature + self.relative_humidity = relative_humidity + self.wind_speed = wind_speed + self.mean_radiant_temperature = mean_radiant_temperature + self.universal_thermal_climate_index = universal_thermal_climate_index _t = kwargs.pop("_t", "BH.oM.LadybugTools.ExternalComfort") super().__init__(_t, **kwargs) diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_shelterbase.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_shelterbase.py index b932245b..ad149700 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_shelterbase.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_shelterbase.py @@ -47,7 +47,7 @@ class Shelter(BHoMObject): radiation_porosity: tuple[float] = (0,) * 8760 def __init__(self, - vertices: tuple[Point3D | IObject], + vertices: tuple[Point3D], wind_porosity: tuple[float] = None, radiation_porosity: tuple[float] = None, **kwargs @@ -55,9 +55,7 @@ def __init__(self, self.vertices = list(vertices) for i, item in enumerate(self.vertices): - if isinstance(item, IObject): - self.vertices[i] = Point3D.from_dict(vars(item)) - elif isinstance(item, dict): + if isinstance(item, dict): self.vertices[i] = Point3D.from_dict(item) elif not isinstance(item, Point3D): raise ValueError("All vertices must be Point3D objects, dictionaries, or BHoM IObjects that can be converted to Point3D.") diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_simulatebase.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_simulatebase.py index 4f166e60..88383425 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_simulatebase.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_simulatebase.py @@ -608,17 +608,6 @@ def radiant_temperature( "unshaded_mean_radiant_temperature", ] -def material_from_bhom_object(o: BHoMObject) -> EnergyMaterial | EnergyMaterialVegetation: - v = vars(o).copy() - - default_vars = { - "type": o._t.split(".")[-1] if not hasattr(o, "type") else o.type - } - - v.pop("type", None) - return dict_to_material({**default_vars, **v}) - - @dataclass(init=False, repr=True, eq=True) class SimulationResult(BHoMObject): """_""" @@ -647,25 +636,25 @@ class SimulationResult(BHoMObject): def __init__( self, epw_file: Path | BHoMObject, - ground_material: EnergyMaterial | EnergyMaterialVegetation | BHoMObject, - shade_material: EnergyMaterial | EnergyMaterialVegetation | BHoMObject, + ground_material: EnergyMaterial | EnergyMaterialVegetation, + shade_material: EnergyMaterial | EnergyMaterialVegetation, identifier: str = None, - shaded_down_temperature: HourlyContinuousCollection | BHoMObject = None, - shaded_up_temperature: HourlyContinuousCollection | BHoMObject = None, + shaded_down_temperature: HourlyContinuousCollection = None, + shaded_up_temperature: HourlyContinuousCollection = None, - unshaded_down_temperature: HourlyContinuousCollection | BHoMObject = None, - unshaded_up_temperature: HourlyContinuousCollection | BHoMObject = None, + unshaded_down_temperature: HourlyContinuousCollection = None, + unshaded_up_temperature: HourlyContinuousCollection = None, - shaded_radiant_temperature: HourlyContinuousCollection | BHoMObject = None, - shaded_longwave_mean_radiant_temperature_delta: HourlyContinuousCollection | BHoMObject = None, - shaded_shortwave_mean_radiant_temperature_delta: HourlyContinuousCollection | BHoMObject = None, - shaded_mean_radiant_temperature: HourlyContinuousCollection | BHoMObject = None, + shaded_radiant_temperature: HourlyContinuousCollection = None, + shaded_longwave_mean_radiant_temperature_delta: HourlyContinuousCollection = None, + shaded_shortwave_mean_radiant_temperature_delta: HourlyContinuousCollection = None, + shaded_mean_radiant_temperature: HourlyContinuousCollection = None, - unshaded_radiant_temperature: HourlyContinuousCollection | BHoMObject = None, - unshaded_longwave_mean_radiant_temperature_delta: HourlyContinuousCollection | BHoMObject = None, - unshaded_shortwave_mean_radiant_temperature_delta: HourlyContinuousCollection | BHoMObject = None, - unshaded_mean_radiant_temperature: HourlyContinuousCollection | BHoMObject = None, + unshaded_radiant_temperature: HourlyContinuousCollection = None, + unshaded_longwave_mean_radiant_temperature_delta: HourlyContinuousCollection = None, + unshaded_shortwave_mean_radiant_temperature_delta: HourlyContinuousCollection = None, + unshaded_mean_radiant_temperature: HourlyContinuousCollection = None, **kwargs ) -> "SimulationResult": @@ -675,14 +664,10 @@ def __init__( self.epw_file = epw_file - if isinstance(ground_material, BHoMObject): - ground_material = material_from_bhom_object(ground_material) if isinstance(ground_material, dict): ground_material = dict_to_material(ground_material) self.ground_material = ground_material - if isinstance(shade_material, BHoMObject): - shade_material = material_from_bhom_object(shade_material) if isinstance(shade_material, dict): shade_material = dict_to_material(shade_material) self.shade_material = shade_material diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/ladybug_extension/datacollection.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/ladybug_extension/datacollection.py index fa4c100b..c37f7fe6 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/ladybug_extension/datacollection.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/ladybug_extension/datacollection.py @@ -25,11 +25,6 @@ from .analysisperiod import describe_analysis_period from .header import header_from_string, header_to_string -def collection_from_bhom_object(obj: BHoMObject) -> HourlyContinuousCollection: - """Convert a BHoMObject representation of an HourlyContinuousCollection to an HourlyContinuousCollection instance""" - d = obj.to_dict() - return HourlyContinuousCollection.from_dict(d) - def collection_to_series(collection: BaseCollection, name: str = None) -> pd.Series: """Convert a Ladybug hourlyContinuousCollection object into a Pandas Series object. diff --git a/LadybugTools_Toolkit.sln b/LadybugTools_Toolkit.sln index 2267205a..a9e3c73a 100644 --- a/LadybugTools_Toolkit.sln +++ b/LadybugTools_Toolkit.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.7.34202.233 +# Visual Studio Version 18 +VisualStudioVersion = 18.6.11822.322 stable MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LadybugTools_oM", "LadybugTools_oM\LadybugTools_oM.csproj", "{ABC2CD49-3DCF-46C3-9249-EB09C88ECFFD}" EndProject From 90a651031920360183dacfe267f3f8d079a07645 Mon Sep 17 00:00:00 2001 From: Tom Kingstone Date: Thu, 2 Jul 2026 16:05:02 +0100 Subject: [PATCH 07/15] bhom object fixes, and decorator for bhom callable methods --- .../Execute/WalkabilityPlotCommand.cs | 4 +- .../src/ladybugtools_toolkit/bhom/__init__.py | 60 ++++++++++++++++++- .../ladybugtools_toolkit/bhom/from_bhom.py | 11 +++- .../ladybugtools_toolkit/bhom/run_wrapped.py | 16 +++-- .../bhom/wrapped/metadata/collection.py | 22 +++---- .../bhom/wrapped/metadata/plot_information.py | 15 +++++ .../bhom/wrapped/metadata/utci_metadata.py | 20 ++++--- .../bhom/wrapped/metadata/wind_metadata.py | 20 ++++--- .../bhom/wrapped/plot/utci_heatmap.py | 58 +++++++----------- .../external_comfort/_externalcomfortbase.py | 2 +- .../external_comfort/_simulatebase.py | 3 +- 11 files changed, 154 insertions(+), 77 deletions(-) create mode 100644 LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/plot_information.py diff --git a/LadybugTools_Adapter/AdapterActions/Execute/WalkabilityPlotCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/WalkabilityPlotCommand.cs index f91db0e0..1ce94fb1 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/WalkabilityPlotCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/WalkabilityPlotCommand.cs @@ -62,7 +62,7 @@ private List RunCommand(WalkabilityPlotCommand command, ActionConfig act Dictionary inputObjects = new Dictionary() { - { "external_comfort", command.ExternalComfort.FromBHoM() } + { "external_comfort", BH.Engine.Serialiser.Convert.ToJson(command.ExternalComfort) } }; string epwFile = System.IO.Path.GetFullPath(command.EPWFile.GetFullFileName()); @@ -97,7 +97,7 @@ private List RunCommand(WalkabilityPlotCommand command, ActionConfig act { CustomObject obj = (CustomObject)BH.Engine.Serialiser.Convert.FromJson(result); PlotInformation info = Convert.ToPlotInformation(obj, new UTCIData()); - ExternalComfort ec = Convert.ToExternalComfort((obj.CustomData["external_comfort"] as CustomObject).CustomData); + ExternalComfort ec = BH.Engine.Serialiser.Convert.FromJson((string)obj.CustomData["external_comfort"]) as ExternalComfort; m_executeSuccess = true; return new List() { info, ec }; } diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/__init__.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/__init__.py index 5f282702..0ad62bf6 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/__init__.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/__init__.py @@ -1 +1,59 @@ - \ No newline at end of file +import json +from typing import Callable +from functools import wraps +from python_toolkit.bhom.bhom_object import BHoMJSONDecoder, BHoMJSONEncoder, BHoMObject + +def bhom_callable(argument_types:dict[str, type] = {}, encoder_cls: type = BHoMJSONEncoder, decoder_cls: type = BHoMJSONDecoder): + """Decorator for functions to be made callable from BHoM C# methods/adapters. + + Note: methods that this wraps must not have "__input_json__" as a kwarg, as this is used internally to allow BHoM adapters to call the method. + + Args: + argument_types (dict[str, type]): this is a dictionary that is used to map the argument names to types (specifically BHoMObject types) to subclasses of BHoMObjects. + For example, if you have a class that is a subclass of BHoMObject, the default serialiser will only deserialise json to a BHoMObject. + To go the extra step to get your class, you must provide the type in this dictionary to allow the wrapper to convert the BHoMObject type to your desired type. + + encoder_cls (JSONEncoder): A JSONEncoder (ideally one that is a subclass of BHoMJSONEncoder). Mainly this is for if a custom encoder has been implemented for a specific toolkit. + + decoder_cls (JSONDecoder): same as encoder_cls but for JSONDecoder. + """ + def decorator(function: Callable): + + #TODO: use module and name to tell run_wrapped what to call? + print(function.__module__, function.__name__) + + @wraps(function) + def wrapper(*args, **kwargs): + + do_wrap:bool = False + + if "__input_json__" in kwargs: + do_wrap = True + input_json = kwargs.pop("__input_json__") + #get dictionary from input_file + + if not input_json.startswith("{"): #assume it's a path + with open(input_json, "r") as f: + input_json = f.read() + + kwargs = json.loads(input_json, cls=decoder_cls) + + for arg_name in argument_types: + if arg_name not in kwargs: + continue + + t = argument_types[arg_name] + + if issubclass(t, BHoMObject) and type(kwargs[arg_name] is BHoMObject): + kwargs[arg_name] = t._from_bhom_object(kwargs[arg_name]) + + rtn = function(*args, **kwargs) + + if do_wrap: + json_rtn = json.dumps(rtn, cls=encoder_cls) + + return json_rtn + + return rtn + return wrapper + return decorator \ No newline at end of file diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/from_bhom.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/from_bhom.py index e858ac42..01ea394f 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/from_bhom.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/from_bhom.py @@ -40,7 +40,16 @@ class Header(): def from_dict(cls, d) -> dict: return d -_TYPES: list[type] = [EnergyMaterial, EnergyMaterialVegetation, AnalysisPeriod, HourlyContinuousCollection, Location, DataType, Header, Point] +_TYPES: list[type] = [ + EnergyMaterial, + EnergyMaterialVegetation, + AnalysisPeriod, + HourlyContinuousCollection, + Location, + DataType, + Header, + Point +] class LBTBHoMJSONDecoder(BHoMJSONDecoder): def deserialise_unknown(self, obj:BHoMObject | IObject | dict): diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/run_wrapped.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/run_wrapped.py index e0351591..149a93f1 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/run_wrapped.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/run_wrapped.py @@ -1,7 +1,7 @@ import sys import argparse from pathlib import Path -from typing import List +from typing import List, Callable import matplotlib @@ -54,17 +54,21 @@ "hbjson_to_gem": (hbjson_to_gem_parser, hbjson_to_gem), } +COMMAND_PARSER = argparse.ArgumentParser(description="argument parser for commands.") +COMMAND_PARSER.add_argument("-command", "--command") +COMMAND_PARSER.add_argument("-in", "--input_json") + def resolve(data: List[str], epw_folder: Path = Path("C:/epws")) -> str: """Parses the given data (that looks like sys.argv[1:]), and gets the command arg which is then used to get the parser for that command, parse the rest of the args and finally run the command, then return the output of those commands. """ #parse data as args - command_parser = argparse.ArgumentParser(description="Command parser") + command_parser = argparse.ArgumentParser(description="argument parser for commands.") command_parser.add_argument("-command", "--command") - command_arg, unknown_args = command_parser.parse_known_args(data) + command_parser.add_argument("-in", "--input_json") + command_args = command_parser.parse_args(data) - parser_function = PARSERS[command_arg.command] - args = vars(parser_function[0].parse_args(unknown_args)) + parser_function = PARSERS[command_args.command] if "epw_file" in args: #check if the epw file exists, if not prepend the epw_folder and try to run @@ -73,7 +77,7 @@ def resolve(data: List[str], epw_folder: Path = Path("C:/epws")) -> str: epw = epw_folder / epw.name args["epw_file"] = str(epw) - ret = parser_function[1](**args) + ret = parser_function[1](__input_json__ = command_args.input_json) return ret #gets the function for the requested command, and runs it with arguments parsed with the desired parser. def deconstruct(data: str) -> List[str]: diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/collection.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/collection.py index ce9a498b..3f4005ac 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/collection.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/collection.py @@ -1,7 +1,8 @@ from ladybug.datacollection import BaseCollection from ladybugtools_toolkit.ladybug_extension.datacollection import collection_to_series +from python_toolkit.bhom.bhom_object import IObject -def collection_metadata(collection: BaseCollection) -> dict: +def collection_metadata(collection: BaseCollection) -> IObject: """Returns a dictionary containing useful metadata about the series. Args: @@ -36,12 +37,13 @@ def collection_metadata(collection: BaseCollection) -> dict: month_series = series[series.index.month == month + 1] month_means.append(month_series.mean()) - return { - "lowest": lowest, - "lowest_index": lowest_index, - "highest": highest, - "highest_index": highest_index, - "median": median, - "mean": mean, - "month_means": month_means, - } \ No newline at end of file + return IObject( + _t = "BH.oM.LadybugTools.CollectionData", + lowest_value = lowest, + lowest_index = lowest_index, + highest_index = highest, + highest_index = highest_index, + median_value = median, + mean_value = mean, + monthly_means = month_means, + ) \ No newline at end of file diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/plot_information.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/plot_information.py new file mode 100644 index 00000000..a3a2abb8 --- /dev/null +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/plot_information.py @@ -0,0 +1,15 @@ +from python_toolkit.bhom.bhom_object import BHoMObject, IObject + +class PlotInformation(BHoMObject): + _t: str = "BH.oM.LadybugTools.PlotInformation" + image: str + other_data: dict + + def __init__(self, image:str = "", other_data:IObject = None, **kwargs): + if other_data is None: + other_data = IObject(_t = "BH.oM.LadybugTools.NoData") + + self.other_data = other_data + self.image = image + + super().__init__(_t=self._t, **kwargs) \ No newline at end of file diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/utci_metadata.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/utci_metadata.py index a537e56b..adbc4dc7 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/utci_metadata.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/utci_metadata.py @@ -3,8 +3,9 @@ UniversalThermalClimateIndex as LB_UniversalThermalClimateIndex, ) from ladybugtools_toolkit.ladybug_extension.datacollection import collection_to_series +from python_toolkit.bhom.bhom_object import IObject -def utci_metadata(utci_collection: HourlyContinuousCollection, comfort_lower: float = 9, comfort_higher: float = 26, use_start_hour: int=7, use_end_hour: int=23) -> dict: +def utci_metadata(utci_collection: HourlyContinuousCollection, comfort_lower: float = 9, comfort_higher: float = 26, use_start_hour: int=7, use_end_hour: int=23) -> IObject: """Returns a dictionary of useful metadata for the given collection dependant on the given comfortable range. Args: @@ -53,11 +54,12 @@ def utci_metadata(utci_collection: HourlyContinuousCollection, comfort_lower: fl day_hot = (daytime >= comfort_higher).sum() / len(daytime) day_cold = (daytime < comfort_lower).sum() / len(daytime) - return { - "comfortable_ratio": comfortable_ratio, - "hot_ratio": hot_ratio, - "cold_ratio": cold_ratio, - "daytime_comfortable": day_comfortable, - "daytime_hot": day_hot, - "daytime_cold": day_cold - } \ No newline at end of file + return IObject( + _t = "BH.oM.LadybugTools.UTCIData", + comfortable_ratio = comfortable_ratio, + heat_stress_ratio = hot_ratio, + cold_stress_ratio = cold_ratio, + daytime_comfortable_ratio = day_comfortable, + daytime_heat_stress_ratio = day_hot, + daytime_cold_stress_ratio = day_cold + ) \ No newline at end of file diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/wind_metadata.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/wind_metadata.py index 25b9000a..d241549e 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/wind_metadata.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/wind_metadata.py @@ -1,6 +1,7 @@ from ladybugtools_toolkit.wind import Wind +from python_toolkit.bhom.bhom_object import IObject -def wind_metadata(wind_object: Wind, directions: int=36, ignore_calm: bool=True, threshold: float = 1e-10) -> dict: +def wind_metadata(wind_object: Wind, directions: int=36, ignore_calm: bool=True, threshold: float = 1e-10) -> IObject: """Provides a dictionary containing metadata of this wind object. Args: @@ -31,11 +32,12 @@ def wind_metadata(wind_object: Wind, directions: int=36, ignore_calm: bool=True, prevailing_wind_speed = prevailing_wind_speeds[0] prevailing_direction = prevailing_directions[0] - return { - "95percentile": ws.quantile(0.95), - "50percentile": ws.quantile(0.50), - "calm_percent": wind_object.calm(), - "prevailing_direction": prevailing_direction, - "prevailing_95percentile": prevailing_wind_speed.quantile(0.95), - "prevailing_50percentile": prevailing_wind_speed.quantile(0.5) - } \ No newline at end of file + return IObject( + _t = "BH.oM.LadybugTools.WindroseData", + percentile95 = ws.quantile(0.95), + percentile50 = ws.quantile(0.50), + ratio_of_calm_hours = wind_object.calm(), + prevailing_direction = prevailing_direction, + prevailing_95percentile = prevailing_wind_speed.quantile(0.95), + prevailing_50percentile = prevailing_wind_speed.quantile(0.5) + ) \ No newline at end of file diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/utci_heatmap.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/utci_heatmap.py index 8bc133c9..450f0ae2 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/utci_heatmap.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/utci_heatmap.py @@ -6,26 +6,23 @@ import traceback import matplotlib from ladybugtools_toolkit.external_comfort.externalcomfort import ExternalComfort +from ladybugtools_toolkit.bhom.wrapped.metadata.plot_information import PlotInformation from ladybugtools_toolkit.bhom.wrapped.metadata.utci_metadata import utci_metadata +from ladybugtools_toolkit.bhom.from_bhom import LBTBHoMJSONDecoder +from ladybugtools_toolkit.bhom.to_bhom import LBTBHoMJSONEncoder from ladybugtools_toolkit.plot.utilities import figure_to_base64 from ladybugtools_toolkit.categorical.categories import Categorical, UTCI_DEFAULT_CATEGORIES import matplotlib.pyplot as plt import numpy as np import json from ...logger import CONSOLE_LOGGER +from ... import bhom_callable PARSER = argparse.ArgumentParser( description=( "Given an EPW file path, extract a heatmap" ) ) -PARSER.add_argument( - "-e", - "--epw_file", - help="helptext", - type=str, - required=False -) PARSER.add_argument( "-in", "--input_json", @@ -33,30 +30,13 @@ type=str, required=True, ) -PARSER.add_argument( - "-sp", - "--save_path", - help="helptext", - type=str, - required=False, -) -def utci_heatmap(input_json:str, save_path = None, epw_file:str = None) -> str: +@bhom_callable(argument_types = { "external_comfort": ExternalComfort }, encoder_cls=LBTBHoMJSONEncoder, decoder_cls=LBTBHoMJSONDecoder) +def utci_heatmap(external_comfort: ExternalComfort, bin_colours: list[str], save_path: str = "") -> dict: try: style = os.environ.get("BHOM_style_context", "python_toolkit.bhom") - - if not input_json.startswith("{"): #assume it's a path - with open(input_json, "r") as f: - input_json = f.read() - - argsDict = json.loads(input_json) - - ec = ExternalComfort.from_json(argsDict["external_comfort"]) - custom_bins = UTCI_DEFAULT_CATEGORIES - bin_colours = json.loads(argsDict["bin_colours"]) - if len(bin_colours) == 10: custom_bins = Categorical( bins=(-np.inf, -40, -27, -13, 0, 9, 26, 32, 38, 46, np.inf), @@ -65,24 +45,30 @@ def utci_heatmap(input_json:str, save_path = None, epw_file:str = None) -> str: with plt.style.context(style): fig, ax = plt.subplots(1, 1, figsize=(10, 4)) - ec.plot_utci_heatmap(utci_categories = custom_bins, ax=ax, style_context=style) - - utci_collection = ec.universal_thermal_climate_index - - return_dict = {"data": utci_metadata(utci_collection), "external_comfort": ec.to_json()} - + external_comfort.plot_utci_heatmap(utci_categories = custom_bins, ax=ax, style_context=style) plt.tight_layout() + utci_collection = external_comfort.universal_thermal_climate_index + pi = PlotInformation(other_data = utci_metadata(utci_collection)) + + image:str = "" + if save_path == None or save_path == "": base64 = figure_to_base64(fig,html=False) - return_dict["figure"] = base64 + image = base64 else: fig.savefig(save_path, dpi=150, transparent=True) - return_dict["figure"] = save_path - + image = save_path + plt.close(fig) + pi.image = image + + return_dict = { + "info": pi, + "external_comfort": external_comfort + } + return return_dict - return json.dumps(return_dict, default=str) except Exception: CONSOLE_LOGGER.error("UTCI Heatmap could not be created.", exc_info=1) return traceback.format_exc() diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_externalcomfortbase.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_externalcomfortbase.py index b4037e87..f5d75eaf 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_externalcomfortbase.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_externalcomfortbase.py @@ -20,7 +20,7 @@ from ..categorical.categories import UTCI_DEFAULT_CATEGORIES, Categorical from ..helpers import convert_keys_to_snake_case from ..ladybug_extension.analysisperiod import describe_analysis_period -from ..ladybug_extension.datacollection import collection_to_series, collection_from_bhom_object +from ..ladybug_extension.datacollection import collection_to_series from python_toolkit.plot.heatmap import heatmap from python_toolkit.bhom.bhom_object import BHoMObject from ..plot._utci import utci_day_comfort_metrics, utci_heatmap_histogram diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_simulatebase.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_simulatebase.py index 88383425..05a41bc6 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_simulatebase.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/external_comfort/_simulatebase.py @@ -51,8 +51,7 @@ from ..honeybee_extension.results import load_sql from ..ladybug_extension.datacollection import ( collection_from_series, - collection_to_series, - collection_from_bhom_object + collection_to_series ) from ..ladybug_extension.epw import epw_to_dataframe from ..ladybug_extension.epw import equality as epw_equality From 0132a718590939f3e398321f7b817ac026eda6f9 Mon Sep 17 00:00:00 2001 From: Tom Kingstone Date: Fri, 31 Jul 2026 13:23:10 +0100 Subject: [PATCH 08/15] put the bhom wrapper on everything in bhom/wrapped! --- .../src/ladybugtools_toolkit/bhom/__init__.py | 60 +--------- .../ladybugtools_toolkit/bhom/run_wrapped.py | 64 ++--------- .../bhom/wrapped/__init__.py | 13 +++ .../bhom/wrapped/epw_to_csv.py | 31 +----- .../bhom/wrapped/external_comfort.py | 47 ++------ .../bhom/wrapped/gem_to_hbjson.py | 22 +--- .../bhom/wrapped/get_material.py | 23 +--- .../bhom/wrapped/get_typology.py | 23 +--- .../bhom/wrapped/hbjson_to_gem.py | 23 +--- .../metadata/solar_radiation_metadata.py | 20 ++-- .../bhom/wrapped/metadata/sunpath_metadata.py | 46 ++++---- .../bhom/wrapped/metadata/wind_metadata.py | 4 +- .../plot/directional_solar_radiation.py | 104 +++--------------- .../bhom/wrapped/plot/diurnal.py | 78 ++----------- .../bhom/wrapped/plot/epw_comparison.py | 79 ++----------- .../plot/facade_condensation_risk_chart.py | 65 ++--------- .../plot/facade_condensation_risk_heatmap.py | 65 ++--------- .../bhom/wrapped/plot/heatmap.py | 83 ++++---------- .../bhom/wrapped/plot/sunpath.py | 71 +++--------- .../bhom/wrapped/plot/utci_heatmap.py | 31 +----- .../bhom/wrapped/plot/walkability_heatmap.py | 80 ++++---------- .../bhom/wrapped/plot/windrose.py | 78 +++---------- .../bhom/wrapped/simulation_result.py | 38 ++----- 23 files changed, 237 insertions(+), 911 deletions(-) diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/__init__.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/__init__.py index 0ad62bf6..5f282702 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/__init__.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/__init__.py @@ -1,59 +1 @@ -import json -from typing import Callable -from functools import wraps -from python_toolkit.bhom.bhom_object import BHoMJSONDecoder, BHoMJSONEncoder, BHoMObject - -def bhom_callable(argument_types:dict[str, type] = {}, encoder_cls: type = BHoMJSONEncoder, decoder_cls: type = BHoMJSONDecoder): - """Decorator for functions to be made callable from BHoM C# methods/adapters. - - Note: methods that this wraps must not have "__input_json__" as a kwarg, as this is used internally to allow BHoM adapters to call the method. - - Args: - argument_types (dict[str, type]): this is a dictionary that is used to map the argument names to types (specifically BHoMObject types) to subclasses of BHoMObjects. - For example, if you have a class that is a subclass of BHoMObject, the default serialiser will only deserialise json to a BHoMObject. - To go the extra step to get your class, you must provide the type in this dictionary to allow the wrapper to convert the BHoMObject type to your desired type. - - encoder_cls (JSONEncoder): A JSONEncoder (ideally one that is a subclass of BHoMJSONEncoder). Mainly this is for if a custom encoder has been implemented for a specific toolkit. - - decoder_cls (JSONDecoder): same as encoder_cls but for JSONDecoder. - """ - def decorator(function: Callable): - - #TODO: use module and name to tell run_wrapped what to call? - print(function.__module__, function.__name__) - - @wraps(function) - def wrapper(*args, **kwargs): - - do_wrap:bool = False - - if "__input_json__" in kwargs: - do_wrap = True - input_json = kwargs.pop("__input_json__") - #get dictionary from input_file - - if not input_json.startswith("{"): #assume it's a path - with open(input_json, "r") as f: - input_json = f.read() - - kwargs = json.loads(input_json, cls=decoder_cls) - - for arg_name in argument_types: - if arg_name not in kwargs: - continue - - t = argument_types[arg_name] - - if issubclass(t, BHoMObject) and type(kwargs[arg_name] is BHoMObject): - kwargs[arg_name] = t._from_bhom_object(kwargs[arg_name]) - - rtn = function(*args, **kwargs) - - if do_wrap: - json_rtn = json.dumps(rtn, cls=encoder_cls) - - return json_rtn - - return rtn - return wrapper - return decorator \ No newline at end of file + \ No newline at end of file diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/run_wrapped.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/run_wrapped.py index 149a93f1..a79b4db8 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/run_wrapped.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/run_wrapped.py @@ -2,57 +2,16 @@ import argparse from pathlib import Path from typing import List, Callable +import json +from python_toolkit.bhom.decorators import bhom_wrapper import matplotlib matplotlib.use("Agg") #use a gui-less backend to avoid memory leaking figures #big import list that covers all methods in bhom/wrapped -from ladybugtools_toolkit.external_comfort.externalcomfort import ExternalComfort -from ladybugtools_toolkit.bhom.wrapped.metadata.utci_metadata import utci_metadata -from ladybugtools_toolkit.bhom.wrapped.plot.utci_heatmap import utci_heatmap - -#import methods and parsers -from ladybugtools_toolkit.bhom.wrapped.plot.walkability_heatmap import PARSER as walkability_heatmap_parser, walkability_heatmap -from ladybugtools_toolkit.bhom.wrapped.plot.epw_comparison import PARSER as epw_comparison_parser, epw_comparison -from ladybugtools_toolkit.bhom.wrapped.plot.windrose import PARSER as windrose_parser, windrose -from ladybugtools_toolkit.bhom.wrapped.plot.directional_solar_radiation import PARSER as directional_solar_radiation_parser, directional_solar_radiation -from ladybugtools_toolkit.bhom.wrapped.plot.diurnal import PARSER as diurnal_parser, diurnal -from ladybugtools_toolkit.bhom.wrapped.plot.facade_condensation_risk_chart import PARSER as facade_condensation_risk_chart_parser, facade_condensation_risk_chart -from ladybugtools_toolkit.bhom.wrapped.plot.facade_condensation_risk_heatmap import PARSER as facade_condensation_risk_heatmap_parser, facade_condensation_risk_heatmap -from ladybugtools_toolkit.bhom.wrapped.plot.heatmap import PARSER as heatmap_parser, heatmap -from ladybugtools_toolkit.bhom.wrapped.plot.sunpath import PARSER as sunpath_parser, sunpath -from ladybugtools_toolkit.bhom.wrapped.plot.utci_heatmap import PARSER as utci_heatmap_parser, utci_heatmap -from ladybugtools_toolkit.bhom.wrapped.epw_to_csv import PARSER as epw_to_csv_parser, epw_to_csv -from ladybugtools_toolkit.bhom.wrapped.gem_to_hbjson import PARSER as gem_to_hbjson_parser, gem_to_hbjson -from ladybugtools_toolkit.bhom.wrapped.get_material import PARSER as get_material_parser, get_material -from ladybugtools_toolkit.bhom.wrapped.get_typology import PARSER as get_typology_parser, get_typology -from ladybugtools_toolkit.bhom.wrapped.hbjson_to_gem import PARSER as hbjson_to_gem_parser, hbjson_to_gem - -from ladybugtools_toolkit.plot.utilities import figure_to_base64 -from ladybugtools_toolkit.categorical.categories import Categorical, UTCI_DEFAULT_CATEGORIES -import matplotlib.pyplot as plt -import numpy as np -import json - -#dictionary containing all the parsers for bhom/wrapped commands -PARSERS = { - "plot/walkability_heatmap": (walkability_heatmap_parser, walkability_heatmap), - "plot/epw_comparison": (epw_comparison_parser, epw_comparison), - "plot/windrose": (windrose_parser, windrose), - "plot/directional_solar_radiation": (directional_solar_radiation_parser, directional_solar_radiation), - "plot/diurnal": (diurnal_parser, diurnal), - "plot/facade_condensation_risk_chart": (facade_condensation_risk_chart_parser, facade_condensation_risk_chart), - "plot/facade_condensation_risk_heatmap": (facade_condensation_risk_heatmap_parser, facade_condensation_risk_heatmap), - "plot/heatmap": (heatmap_parser, heatmap), - "plot/sunpath": (sunpath_parser, sunpath), - "plot/utci_heatmap": (utci_heatmap_parser, utci_heatmap), - "epw_to_csv": (epw_to_csv_parser, epw_to_csv), - "gem_to_hbjson": (gem_to_hbjson_parser, gem_to_hbjson), - "get_material": (get_material_parser, get_material), - "get_typology": (get_typology_parser, get_typology), - "hbjson_to_gem": (hbjson_to_gem_parser, hbjson_to_gem), -} +from . import wrapped +from python_toolkit.bhom import wrapped COMMAND_PARSER = argparse.ArgumentParser(description="argument parser for commands.") COMMAND_PARSER.add_argument("-command", "--command") @@ -66,18 +25,19 @@ def resolve(data: List[str], epw_folder: Path = Path("C:/epws")) -> str: command_parser = argparse.ArgumentParser(description="argument parser for commands.") command_parser.add_argument("-command", "--command") command_parser.add_argument("-in", "--input_json") - command_args = command_parser.parse_args(data) - - parser_function = PARSERS[command_args.command] + command_parser.add_argument("-e", "--epw_file", required=False) + command_args, unknown_args = command_parser.parse_known_args(data) - if "epw_file" in args: + if command_args.epw_file is not None: #check if the epw file exists, if not prepend the epw_folder and try to run - epw = Path(args["epw_file"]) + epw = Path(command_args.epw_file) if not epw.exists(): epw = epw_folder / epw.name - args["epw_file"] = str(epw) + command_args.epw_file = str(epw) + + method = bhom_wrapper.get_registered_method(command_args.command) - ret = parser_function[1](__input_json__ = command_args.input_json) + ret = method(epw_file = command_args.epw_file, __input_json__ = command_args.input_json) return ret #gets the function for the requested command, and runs it with arguments parsed with the desired parser. def deconstruct(data: str) -> List[str]: diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/__init__.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/__init__.py index e69de29b..7543761f 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/__init__.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/__init__.py @@ -0,0 +1,13 @@ +from importlib import import_module +from pathlib import Path +import os + +python_files = Path(__file__).parent.glob("**/*.py") + +for file in python_files: + if file.name == "__init__.py": + continue + + rel = file.relative_to(Path(__file__).parent) + module = "." + str(rel).replace(".py", "").replace(os.path.sep, ".") + import_module(module, "ladybugtools_toolkit.bhom.wrapped") \ No newline at end of file diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/epw_to_csv.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/epw_to_csv.py index 06695fa4..566e8d36 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/epw_to_csv.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/epw_to_csv.py @@ -1,33 +1,12 @@ """Method to wrap for conversion of EPW to CSV file.""" # pylint: disable=C0415,E0401,W0703 -import argparse -import sys import traceback -from pathlib import Path from ..logger import CONSOLE_LOGGER from ladybugtools_toolkit.ladybug_extension.epw import epw_to_dataframe, EPW +from python_toolkit.bhom.decorators import bhom_wrapper -PARSER = argparse.ArgumentParser( - description=( - "Given an EPW file path, convert to CSV with optional inclusion of calculated additional data." - ) - ) -PARSER.add_argument( - "-e", - "--epw_file", - help="The EPW file to write as a CSV.", - type=str, - required=True, -) -PARSER.add_argument( - "-a", - "--include_additional", - help="Whether to include additional calculated data (such as hourly ground temperature, sky temperature, sun position, ...).", - type=bool, - required=True, -) - -def epw_to_csv(epw_file: str, include_additional: bool) -> str: +@bhom_wrapper.bhom_callable("epw_to_csv") +def epw_to_csv(epw_file: str, include_additional: bool, **kwargs) -> str: """Create a CSV file version of an EPW.""" try: epw = EPW(epw_file) @@ -37,7 +16,3 @@ def epw_to_csv(epw_file: str, include_additional: bool) -> str: except Exception: CONSOLE_LOGGER.error("CSV file could not be created.", exc_info=1) return traceback.format_exc() - -if __name__ == "__main__": - args = PARSER.parse_args() - epw_to_csv(args.epw_file, args.include_additional) diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/external_comfort.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/external_comfort.py index 090c7f00..92b44f37 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/external_comfort.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/external_comfort.py @@ -1,36 +1,13 @@ -"""Method to wrap for access to pre-defined materials.""" # pylint: disable=C0415,E0401,W0703 -import argparse -import traceback - - -def main(json_file: str) -> None: - """From a json file represention of an ExternalComfort, run the calculation.""" - try: - from ladybugtools_toolkit.external_comfort._externalcomfortbase import ( - ExternalComfort, - ) - - ec = ExternalComfort.from_file(json_file) - ec.to_file(json_file) - - except Exception as e: - print(traceback.format_exc()) - - -if __name__ == "__main__": - parser = argparse.ArgumentParser( - description=( - "Given a JSON file containing the string represention of a ExternalComfort object, " - "run all calculations Python-side for that object." - ) - ) - parser.add_argument( - "-j", - "--json_file", - help="The JSON file to convert into a ExternalComfort object Python-side.", - type=str, - required=True, - ) - args = parser.parse_args() - main(args.json_file) +from python_toolkit.bhom.decorators import bhom_wrapper +from ladybugtools_toolkit.external_comfort._externalcomfortbase import ExternalComfort +from ladybugtools_toolkit.bhom.from_bhom import LBTBHoMJSONDecoder +from ladybugtools_toolkit.bhom.to_bhom import LBTBHoMJSONEncoder + +#Note: All this method does is return the external comfort object given. +#Originally this method converted from json and then back to json, however the bhom_callable decorator does this automatically. +#In order to allow this to still exist as callable from BHoM, this method was simplified to just return. + +@bhom_wrapper.bhom_callable("external_comfort", argument_types = {"external_comfort", ExternalComfort}, encoder_cls=LBTBHoMJSONEncoder, decoder_cls=LBTBHoMJSONDecoder) +def main(external_comfort: ExternalComfort, **kwargs) -> None: + return external_comfort diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/gem_to_hbjson.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/gem_to_hbjson.py index a608ed3e..98fa0135 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/gem_to_hbjson.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/gem_to_hbjson.py @@ -1,26 +1,15 @@ """Method to wrap for conversion of IES GEM to HBJSON file.""" # pylint: disable=C0415,E0401,W0703 -import argparse -import sys import traceback from pathlib import Path import tempfile import json from honeybee_ies.reader import model_from_ies from ..logger import CONSOLE_LOGGER +from python_toolkit.bhom.decorators import bhom_wrapper -PARSER = argparse.ArgumentParser( - description=("Given a GEM file path, convert to a HBJSON file.") -) -PARSER.add_argument( - "-g", - "--gem_file", - help="The GEM file to convert to HBJSON.", - type=str, - required=True, -) - -def gem_to_hbjson(gem_file: str) -> None: +@bhom_wrapper.bhom_callable("gem_to_hbjson") +def gem_to_hbjson(gem_file: str, **kwargs) -> None: """Create a HBJSON file from an IES GEM file.""" try: file_path = None @@ -46,8 +35,3 @@ def gem_to_hbjson(gem_file: str) -> None: except Exception: CONSOLE_LOGGER.error("HBJSON file could not be created.", exc_info=1) return traceback.format_exc() - - -if __name__ == "__main__": - args = PARSER.parse_args() - gem_to_hbjson(args.gem_file) diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/get_material.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/get_material.py index 532b254d..28abc00d 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/get_material.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/get_material.py @@ -1,24 +1,12 @@ """Method to wrap for access to pre-defined materials.""" # pylint: disable=C0415,E0401,W0703 -import argparse import traceback import json from ladybugtools_toolkit.external_comfort.material import Materials +from python_toolkit.bhom.decorators import bhom_wrapper -PARSER = argparse.ArgumentParser( - description=( - "Given a JSON file path, write the pre-defined materials for the External Comfort workflow." - ) -) -PARSER.add_argument( - "-j", - "--json_file", - help="The JSON file to write material objects into.", - type=str, - required=True, -) - -def get_material(json_file: str) -> None: +@bhom_wrapper.bhom_callable("get_material") +def get_material(json_file: str, **kwargs) -> None: """Create a file containing all default materials.""" try: json_str = json.dumps([material.value.to_dict() for material in Materials]) @@ -30,8 +18,3 @@ def get_material(json_file: str) -> None: except Exception as e: return traceback.format_exc() - - -if __name__ == "__main__": - args = PARSER.parse_args() - get_material(args.json_file) diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/get_typology.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/get_typology.py index eb9cb50a..d9dd448f 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/get_typology.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/get_typology.py @@ -1,24 +1,12 @@ """Method to wrap for access to pre-defined typologies.""" # pylint: disable=C0415,E0401,W0703 -import argparse import traceback import json from ladybugtools_toolkit.external_comfort.typology import Typologies +from python_toolkit.bhom.decorators import bhom_wrapper -PARSER = argparse.ArgumentParser( - description=( - "Given a JSON file path, write the pre-defined typologies for the External Comfort workflow." - ) -) -PARSER.add_argument( - "-j", - "--json_file", - help="The JSON file to write Typology objects into.", - type=str, - required=True, -) - -def get_typology(json_file: str) -> None: +@bhom_wrapper.bhom_callable("get_typology") +def get_typology(json_file: str, **kwargs) -> None: """Create a file containing all default typologies.""" try: json_str = json.dumps([typology.value.to_dict() for typology in Typologies]) @@ -30,8 +18,3 @@ def get_typology(json_file: str) -> None: except Exception as e: return traceback.format_exc() - - -if __name__ == "__main__": - args = PARSER.parse_args() - get_typology(args.json_file) diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/hbjson_to_gem.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/hbjson_to_gem.py index 72a7c4a1..fc7e390f 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/hbjson_to_gem.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/hbjson_to_gem.py @@ -1,9 +1,6 @@ """Method to wrap for conversion of HBJSON to GEM file.""" # pylint: disable=C0415,E0401,W0703 -import argparse import json -import random -import sys import traceback from pathlib import Path import uuid @@ -11,19 +8,10 @@ import tempfile from honeybee.model import Model from honeybee_ies.writer import model_to_ies +from python_toolkit.bhom.decorators import bhom_wrapper -PARSER = argparse.ArgumentParser( - description=("Given an HBJSON file path, convert to a GEM file.") -) -PARSER.add_argument( - "-j", - "--hbjson_file", - help="The HBJSON file to convert to GEM.", - type=str, - required=True, -) - -def hbjson_to_gem(hbjson_file: str) -> None: +@bhom_wrapper.bhom_callable("hbjson_to_gem") +def hbjson_to_gem(hbjson_file: str, **kwargs) -> None: """Create an IES GEM file from an HBJSON file.""" try: hbjson_dict = None @@ -52,8 +40,3 @@ def hbjson_to_gem(hbjson_file: str) -> None: except Exception: CONSOLE_LOGGER.error("Could not convert the hbjson file to a gem file.", exc_info=1) return traceback.format_exc() - - -if __name__ == "__main__": - args = PARSER.parse_args() - hbjson_to_gem(args.hbjson_file) diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/solar_radiation_metadata.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/solar_radiation_metadata.py index 9ba38ddb..9bce6d91 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/solar_radiation_metadata.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/solar_radiation_metadata.py @@ -1,14 +1,16 @@ import pandas as pd +from python_toolkit.bhom.bhom_object import IObject -def solar_radiation_metadata(values, directions, tilts): +def solar_radiation_metadata(values, directions, tilts) -> IObject: df = pd.DataFrame(values) df.index = tilts df.columns = directions - return { - "max_value": df.max().max(), - "max_direction": df.max().idxmax(), - "max_tilt": df.idxmax()[df.max().idxmax()], - "min_value": df.min().min(), - "min_direction": df.min().idxmin(), - "min_tilt": df.idxmin()[df.min().idxmin()] - } \ No newline at end of file + return IObject( + _t = "BH.oM.LadybugTools.SolarRadiationData", + max_value = df.max().max(), + max_direction = df.max().idxmax(), + max_tilt = df.idxmax()[df.max().idxmax()], + min_value = df.min().min(), + min_direction = df.min().idxmin(), + min_tilt = df.idxmin()[df.min().idxmin()] + ) \ No newline at end of file diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/sunpath_metadata.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/sunpath_metadata.py index a44a7c9a..351eb9c3 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/sunpath_metadata.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/sunpath_metadata.py @@ -1,8 +1,19 @@ from ladybug.sunpath import Sunpath from ladybugtools_toolkit.ladybug_extension.sunpath import sunrise_sunset_azimuths -from datetime import datetime +from python_toolkit.bhom.bhom_object import IObject -def sunpath_metadata(sunpath: Sunpath) -> dict: +def convert_to_bhom(d) -> IObject: + return IObject( + _t = "BH.oM.LadybugTools.SunData", + sunrise_azimuth = d["sunrise"]["azimuth"], + sunrise_time = d["sunrise"]["time"], + noon_altitude = d["noon"]["altitude"], + noon_time = d["noon"]["time"], + sunset_azimuth = d["sunset"]["azimuth"], + sunset_time = d["sunset"]["time"] + ) + +def sunpath_metadata(sunpath: Sunpath) -> IObject: """Return a dictionary containing equinox and solstice azimuths and altitudes at sunrise, noon and sunset for the given sunpath. Args: @@ -10,25 +21,18 @@ def sunpath_metadata(sunpath: Sunpath) -> dict: A Ladybug sunpath object. Returns: - dict: - A dictionary containing the azimuths and altitudes in the following structure: - - { - 'december_solstice': {'sunrise': azimuth, 'noon': altitude, 'sunset': azimuth}, - 'march_equinox': {...}, - 'june_solstice': {...}, - 'september_equinox': {...} - } + IObject: an IObject of type "BH.oM.LadybugTools.SunPathData", see the oM definition in LadybugTools_oM/MetaData/SunPathData.cs for the structure. """ - december_solstice = sunrise_sunset_azimuths(sunpath, 2023, 12, 22) - march_equinox = sunrise_sunset_azimuths(sunpath, 2023, 3, 20) - june_solstice = sunrise_sunset_azimuths(sunpath, 2023, 6, 21) - september_equinox = sunrise_sunset_azimuths(sunpath, 2023, 9, 22) + december_solstice = convert_to_bhom(sunrise_sunset_azimuths(sunpath, 2023, 12, 22)) + march_equinox = convert_to_bhom(sunrise_sunset_azimuths(sunpath, 2023, 3, 20)) + june_solstice = convert_to_bhom(sunrise_sunset_azimuths(sunpath, 2023, 6, 21)) + september_equinox = convert_to_bhom(sunrise_sunset_azimuths(sunpath, 2023, 9, 22)) - return { - "december_solstice": december_solstice, - "march_equinox": march_equinox, - "june_solstice": june_solstice, - "september_equinox": september_equinox - } \ No newline at end of file + return IObject( + _t = "BH.oM.LadybugTools.SunPathData", + december_solstice = december_solstice, + march_equinox = march_equinox, + june_solstice = june_solstice, + september_equinox = september_equinox + ) \ No newline at end of file diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/wind_metadata.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/wind_metadata.py index d241549e..4fe16b88 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/wind_metadata.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/wind_metadata.py @@ -38,6 +38,6 @@ def wind_metadata(wind_object: Wind, directions: int=36, ignore_calm: bool=True, percentile50 = ws.quantile(0.50), ratio_of_calm_hours = wind_object.calm(), prevailing_direction = prevailing_direction, - prevailing_95percentile = prevailing_wind_speed.quantile(0.95), - prevailing_50percentile = prevailing_wind_speed.quantile(0.5) + prevailing_percentile95 = prevailing_wind_speed.quantile(0.95), + prevailing_percentile50 = prevailing_wind_speed.quantile(0.5) ) \ No newline at end of file diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/directional_solar_radiation.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/directional_solar_radiation.py index 900d0169..be2af86d 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/directional_solar_radiation.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/directional_solar_radiation.py @@ -1,91 +1,27 @@ """Method to wrap creation of panel orientation plots""" # pylint: disable=C0415,E0401,W0703 -import argparse -import sys import traceback from pathlib import Path import os -import matplotlib from ladybugtools_toolkit.solar import IrradianceType, tilt_orientation_factor, create_radiation_matrix +from ladybugtools_toolkit.bhom.wrapped.metadata.plot_information import PlotInformation +from ladybugtools_toolkit.bhom.from_bhom import LBTBHoMJSONDecoder +from ladybugtools_toolkit.bhom.to_bhom import LBTBHoMJSONEncoder from ladybug.wea import AnalysisPeriod from ladybugtools_toolkit.plot.utilities import figure_to_base64 from ladybugtools_toolkit.bhom.wrapped.metadata.solar_radiation_metadata import solar_radiation_metadata import matplotlib.pyplot as plt -from pathlib import Path -import json from ...logger import CONSOLE_LOGGER +from python_toolkit.bhom.decorators import bhom_wrapper -PARSER = argparse.ArgumentParser( - description=( - "Given an EPW file path, extract a heatmap" - ) - ) -PARSER.add_argument( - "-e", - "--epw_file", - help="The EPW file to extract a heatmap from", - type=str, - required=True, -) -PARSER.add_argument( - "-d", - "--directions", - help="The number of directions to use when plotting orientations.", - type=int, - required=True, -) -PARSER.add_argument( - "-ti", - "--tilts", - help="The number of tilts to use when plotting orientations.", - type=int, - required=True, -) -PARSER.add_argument( - "-ir", - "--irradiance_type", - help="The irradiance type to use.", - type=str, - required=True, -) -PARSER.add_argument( - "-cmap", - "--cmap", - help="Matplotlib colour map to use.", - type=str, - required=True, - ) -PARSER.add_argument( - "-ap", - "--analysis_period", - help="Analysis period", - type=str, - required=True, -) -PARSER.add_argument( - "-t", - "--title", - help="The title to be displayed on the plot.", - type=str, - required=True, -) -PARSER.add_argument( - "-p", - "--save_path", - help="Path to save the output image.", - type=str, - required=False, - ) - -def directional_solar_radiation(epw_file, directions, tilts, irradiance_type, analysis_period, cmap, title, save_path) -> str: +@bhom_wrapper.bhom_callable("plot/directional_solar_radiation", argument_types = { "analysis_period": AnalysisPeriod }, decoder_cls=LBTBHoMJSONDecoder) +def directional_solar_radiation(epw_file: str, directions: int, tilts: int, irradiance_type: str, analysis_period: AnalysisPeriod, cmap: str, title: str = None, save_path:str = None) -> PlotInformation: try: style = os.environ.get("BHOM_style_context", "python_toolkit.bhom") if cmap not in plt.colormaps(): cmap = "YlOrRd" - analysis_period = AnalysisPeriod.from_dict(json.loads(analysis_period)) - if irradiance_type == "Total": irradiance_type = IrradianceType.TOTAL elif irradiance_type == "Diffuse": @@ -94,37 +30,29 @@ def directional_solar_radiation(epw_file, directions, tilts, irradiance_type, an irradiance_type = IrradianceType.DIRECT elif irradiance_type == "Reflected": irradiance_type = IrradianceType.REFLECTED + + values, dirs, tts = create_radiation_matrix(Path(epw_file), rad_type=irradiance_type, analysis_period=analysis_period, directions=directions, tilts=tilts) with plt.style.context(style): fig, ax = plt.subplots(1, 1, figsize=(22.8/2, 7.6/2)) - values, dirs, tts = create_radiation_matrix(Path(epw_file), rad_type=irradiance_type, analysis_period=analysis_period, directions=directions, tilts=tilts) tilt_orientation_factor(Path(epw_file), ax=ax, rad_type=irradiance_type, analysis_period=analysis_period, directions=directions, tilts=tilts, cmap=cmap, style_context=style) if not (title == "" or title is None): ax.set_title(title) + plt.tight_layout() - return_dict = {} + pi = PlotInformation(other_data = solar_radiation_metadata(values, dirs, tts)) + image: str = "" if save_path == None or save_path == "": base64 = figure_to_base64(fig,html=False) - return_dict["figure"] = base64 + image = base64 else: fig.savefig(save_path, dpi=150, transparent=True) - return_dict["figure"] = save_path + image = save_path - return_dict["data"] = solar_radiation_metadata(values, dirs, tts) - plt.close(fig) - - return json.dumps(return_dict, default=str) - + pi.image = image + return pi except Exception: CONSOLE_LOGGER.error("Solar Radiation plot could not be created.", exc_info=1) - return traceback.format_exc() - -if __name__ == "__main__": - args = PARSER.parse_args() - - os.environ["TQDM_DISABLE"] = "1" # set an environment variable so that progress bars are disabled for the simulation process - matplotlib.use("Agg") - directional_solar_radiation(args.epw_file, args.directions, args.tilts, args.irradiance_type, args.analysis_period, args.colour_map, args.title, args.save_path) - del os.environ["TQDM_DISABLE"] # unset the env variable \ No newline at end of file + return traceback.format_exc() \ No newline at end of file diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/diurnal.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/diurnal.py index df99c6f0..bb85ce83 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/diurnal.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/diurnal.py @@ -1,14 +1,10 @@ """Method to wrap creation of diurnal plots""" # pylint: disable=C0415,E0401,W0703 -import argparse -import json import os -import sys import traceback -from pathlib import Path -import matplotlib -from ladybug.epw import EPW, AnalysisPeriod +from ladybug.epw import EPW from ladybugtools_toolkit.ladybug_extension.datacollection import collection_to_series +from ladybugtools_toolkit.bhom.wrapped.metadata.plot_information import PlotInformation from ladybugtools_toolkit.ladybug_extension.epw import wet_bulb_temperature from python_toolkit.plot.diurnal import diurnal as dnal from ladybug.datacollection import HourlyContinuousCollection @@ -16,56 +12,10 @@ from ladybugtools_toolkit.bhom.wrapped.metadata.collection import collection_metadata import matplotlib.pyplot as plt from ...logger import CONSOLE_LOGGER +from python_toolkit.bhom.decorators import bhom_wrapper -PARSER = argparse.ArgumentParser( - description=( - "Given an EPW file path, extract a diurnal plot" - ) -) -PARSER.add_argument( - "-e", - "--epw_file", - help="The EPW file to extract a diurnal plot from", - type=str, - required=True, -) -PARSER.add_argument( - "-dtk", - "--data_type_key", - help="Key in EPW data to create a plot from.", - type=str, - required=True, -) -PARSER.add_argument( - "-colour", - "--colour", - help="Colour of the line", - type=str, - required=True, - ) -PARSER.add_argument( - "-t", - "--title", - help="Title that the plot will have", - type=str, - required=True, - ) -PARSER.add_argument( - "-ap", - "--period", - help="Period that will be plotted on the diurnal plot", - type=str, - required=True, - ) -PARSER.add_argument( - "-p", - "--save_path", - help="Path where to save the output image.", - type=str, - required=False, - ) - -def diurnal(epw_file, data_type_key="Dry Bulb Temperature", colour="#000000", title=None, period="monthly", save_path = None) -> str: +@bhom_wrapper.bhom_callable("plot/epw_diurnal") +def diurnal(epw_file: str, data_type_key: str="Dry Bulb Temperature", colour: str="#000000", title: str=None, period: str="monthly", save_path: str=None, **kwargs) -> PlotInformation: try: style = os.environ.get("BHOM_style_context", "python_toolkit.bhom") epw = EPW(epw_file) @@ -77,26 +27,22 @@ def diurnal(epw_file, data_type_key="Dry Bulb Temperature", colour="#000000", ti with plt.style.context(style): fig, ax = plt.subplots() - dnal(collection_to_series(coll), ax=ax, title=title, period=period, color=colour, style_context=style) - return_dict = {"data": collection_metadata(coll)} + pi = PlotInformation(other_data = collection_metadata(coll)) + image: str = "" + if save_path == None or save_path == "": base64 = figure_to_base64(fig, html=False) - return_dict["figure"] = base64 + image = base64 else: fig.savefig(save_path, dpi=150, transparent=True) - return_dict["figure"] = save_path + image = save_path plt.close(fig) - - return json.dumps(return_dict, default=str) + pi.image = image + return pi except Exception: CONSOLE_LOGGER.error("Diurnal plot could not be created.", exc_info=1) return traceback.format_exc() - -if __name__ == "__main__": - args = PARSER.parse_args() - matplotlib.use("Agg") - diurnal(args.epw_file, args.return_file, args.data_type_key, args.colour, args.title, args.period, args.save_path) diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/epw_comparison.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/epw_comparison.py index 1b085e08..59aa81be 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/epw_comparison.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/epw_comparison.py @@ -1,100 +1,45 @@ """Method to wrap for conversion of EPW to CSV file.""" # pylint: disable=C0415,E0401,W0703 -import argparse import os -import json -import sys import traceback -import matplotlib -import matplotlib.figure from ladybug.epw import EPW from ladybugtools_toolkit.plot.compare import compare_epw_key_line, compare_epw_key_hist +from ladybugtools_toolkit.bhom.wrapped.metadata.plot_information import PlotInformation from ladybugtools_toolkit.plot.utilities import figure_to_base64 import matplotlib.pyplot as plt from ...logger import CONSOLE_LOGGER from typing import List +from python_toolkit.bhom.decorators import bhom_wrapper -PARSER = argparse.ArgumentParser( - description=( - "Given an EPW file path, and a list of epws to compare to, construct a line chart for a specific epw key." - ) -) -PARSER.add_argument( - "-e", - "--epw_file", - help="The EPW file to compare from", - type=str, - required=True, -) -PARSER.add_argument( - "-el", - "--epw_list", - help="List of EPW files to compare with the base", - type=str, - nargs='*', - action="extend", - required=True, -) -PARSER.add_argument( - "-dtk", - "--data_type_key", - help="Key to compare.", - type=str, - required=True, -) -PARSER.add_argument( - "-p", - "--save_path", - help="Path where to save the output image.", - type=str, - required=False, - ) -PARSER.add_argument( - "-l", - "--line", - help="Produce a line plot instead of a histogram", - action="store_true", - default=False - ) - -def epw_comparison(epw_file: str, epw_list: List[str], data_type_key: str, line:bool, save_path:str = None) -> str: +@bhom_wrapper.bhom_callable("plot/epw_comparison") +def epw_comparison(epw_file: str, epw_list: List[str], data_type_key: str, line:bool, save_path:str = None) -> PlotInformation: """Create a timeseries plot with a line for each epw file for the specified data key and return it in a format readable by the LadybugToolsAdapter.""" try: style = os.environ.get("BHOM_style_context", "python_toolkit.bhom") + epws = [EPW(epw_file)] + epws.extend([EPW(f) for f in epw_list]) with plt.style.context(style): fig, ax = plt.subplots() - epws = [EPW(epw_file)] - epws.extend([EPW(f) for f in epw_list]) - if line: compare_epw_key_line(epws, key=data_type_key.lower().strip().replace(" ", "_"), style_context=style, ax=ax) else: compare_epw_key_hist(epws, key=data_type_key.lower().strip().replace(" ", "_"), style_context=style, ax=ax) - return_dict = {} + pi = PlotInformation() #Unsure of how to create representative collection metadata for a comparison plot type that doesn't simply list every epw file compared + image: str = "" if save_path == None or save_path == "": base64 = figure_to_base64(fig,html=False) - return_dict["figure"] = base64 + image = base64 else: fig.savefig(save_path, dpi=150, transparent=True) - return_dict["figure"] = save_path + image = save_path plt.close(fig) - - return_dict["data"] = None #Unsure of how to create representative collection metadata for a comparison plot type that doesn't simply list every epw file compared - - return json.dumps(return_dict, default=str) - + pi.image = image + return pi except Exception: CONSOLE_LOGGER.error("Timeseries comparison could not be created.", exc_info=1) return traceback.format_exc() - -if __name__ == "__main__": - - args = PARSER.parse_args() - matplotlib.use("Agg") - - epw_comparison(args.epw_file, args.epw_list, args.data_type_key, args.line, args.save_path) diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/facade_condensation_risk_chart.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/facade_condensation_risk_chart.py index 95e9f55d..89695ba2 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/facade_condensation_risk_chart.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/facade_condensation_risk_chart.py @@ -1,56 +1,16 @@ """Method to wrap creation of diurnal plots""" # pylint: disable=C0415,E0401,W0703 -import argparse import os -import textwrap - -from pathlib import Path -import matplotlib import matplotlib.pyplot as plt -from matplotlib.figure import Figure -from mpl_toolkits.axes_grid1 import make_axes_locatable -import json -import numpy as np from ladybug.epw import EPW -from python_toolkit.plot.heatmap import heatmap -from matplotlib.colors import LinearSegmentedColormap -from ladybugtools_toolkit.ladybug_extension.header import header_from_string -from ladybug.epw import AnalysisPeriod, HourlyContinuousCollection -from ladybugtools_toolkit.ladybug_extension.datacollection import collection_to_series from ladybugtools_toolkit.bhom.wrapped.metadata.collection import collection_metadata +from ladybugtools_toolkit.bhom.wrapped.metadata.plot_information import PlotInformation from ladybugtools_toolkit.plot.utilities import figure_to_base64 from ladybugtools_toolkit.plot.facades.condensation_risk.heatmap import facade_condensation_risk_chart_table +from python_toolkit.bhom.decorators import bhom_wrapper -PARSER = argparse.ArgumentParser( - description=( - "Given an EPW file path, extract a heatmap of condensation risk" - ) -) -PARSER.add_argument( - "-e", - "--epw_file", - help="The EPW file to extract a heatmap from", - type=str, - required=True, -) -PARSER.add_argument( - "-t", - "--thresholds", - help="thresholds to use.", - type = float, - nargs='*', - required=True, -) -PARSER.add_argument( - "-p", - "--save_path", - help="Path where to save the output image.", - type=str, - required=False, - ) - - -def facade_condensation_risk_chart(epw_file: str, thresholds: list[float], save_path: str = None) -> None: +@bhom_wrapper.bhom_callable("plot/facade_condensation_risk_chart") +def facade_condensation_risk_chart(epw_file: str, thresholds: list[float], save_path: str = None, **kwargs) -> PlotInformation: style = os.environ.get("BHOM_style_context", "python_toolkit.bhom") epw = EPW(epw_file) @@ -58,22 +18,17 @@ def facade_condensation_risk_chart(epw_file: str, thresholds: list[float], save_ fig = facade_condensation_risk_chart_table(epw_file, thresholds, style_context=style) - return_dict = {"data": collection_metadata(hcc)} + pi = PlotInformation(other_data = collection_metadata(hcc)) + image: str = "" if save_path == None or save_path == "": base64 = figure_to_base64(fig,html=False) - return_dict["figure"] = base64 + image = base64 else: fig.savefig(save_path, dpi=300, transparent=True) - return_dict["figure"] = save_path + image = save_path plt.close(fig) + pi.image = image - return json.dumps(return_dict, default=str) - - -if __name__ == "__main__": - - args = PARSER.parse_args() - matplotlib.use("Agg") - facade_condensation_risk_chart(args.epw_file, args.thresholds, args.save_path) + return pi diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/facade_condensation_risk_heatmap.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/facade_condensation_risk_heatmap.py index 231641eb..8309f052 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/facade_condensation_risk_heatmap.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/facade_condensation_risk_heatmap.py @@ -1,55 +1,16 @@ """Method to wrap creation of diurnal plots""" # pylint: disable=C0415,E0401,W0703 -import argparse import os -import textwrap - -from pathlib import Path -import matplotlib import matplotlib.pyplot as plt -from matplotlib.figure import Figure -from mpl_toolkits.axes_grid1 import make_axes_locatable -import json -import numpy as np from ladybug.epw import EPW -from python_toolkit.plot.heatmap import heatmap -from matplotlib.colors import LinearSegmentedColormap -from ladybugtools_toolkit.ladybug_extension.header import header_from_string -from ladybug.epw import AnalysisPeriod, HourlyContinuousCollection -from ladybugtools_toolkit.ladybug_extension.datacollection import collection_to_series from ladybugtools_toolkit.bhom.wrapped.metadata.collection import collection_metadata +from ladybugtools_toolkit.bhom.wrapped.metadata.plot_information import PlotInformation from ladybugtools_toolkit.plot.utilities import figure_to_base64 from ladybugtools_toolkit.plot.facades.condensation_risk.heatmap import facade_condensation_risk_heatmap_histogram +from python_toolkit.bhom.decorators import bhom_wrapper -PARSER = argparse.ArgumentParser( - description=( - "Given an EPW file path, extract a heatmap of condensation risk" - ) -) -PARSER.add_argument( - "-e", - "--epw_file", - help="The EPW file to extract a heatmap from", - type=str, - required=True, -) -PARSER.add_argument( - "-t", - "--thresholds", - help="thresholds to use.", - type = float, - nargs='*', - required=True, -) -PARSER.add_argument( - "-p", - "--save_path", - help="Path where to save the output image.", - type=str, - required=False, - ) - -def facade_condensation_risk_heatmap(epw_file: str, thresholds: list[float], save_path: str = None) -> None: +@bhom_wrapper.bhom_callable("plot/facade_condensation_risk_heatmap") +def facade_condensation_risk_heatmap(epw_file: str, thresholds: list[float], save_path: str = None) -> PlotInformation: style = os.environ.get("BHOM_style_context", "python_toolkit.bhom") epw = EPW(epw_file) @@ -57,22 +18,16 @@ def facade_condensation_risk_heatmap(epw_file: str, thresholds: list[float], sav fig = facade_condensation_risk_heatmap_histogram(epw_file, thresholds, style_context=style) - return_dict = {"data": collection_metadata(hcc)} + pi = PlotInformation(other_data = collection_metadata(hcc)) + image: str = "" if save_path == None or save_path == "": base64 = figure_to_base64(fig,html=False) - return_dict["figure"] = base64 + image = base64 else: fig.savefig(save_path, dpi=300, transparent=True) - return_dict["figure"] = save_path + image = save_path plt.close(fig) - - return json.dumps(return_dict, default=str) - - -if __name__ == "__main__": - - args = PARSER.parse_args() - matplotlib.use("Agg") - facade_condensation_risk_heatmap(args.epw_file, args.thresholds, args.save_path) + pi.image = image + return pi \ No newline at end of file diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/heatmap.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/heatmap.py index a1368c60..188c1d5d 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/heatmap.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/heatmap.py @@ -1,98 +1,53 @@ """Method to wrap for conversion of EPW to CSV file.""" # pylint: disable=C0415,E0401,W0703 -import argparse import os -from pathlib import Path -import json -import sys import traceback -import matplotlib -import matplotlib.figure from ladybug.epw import EPW from ladybug.datacollection import HourlyContinuousCollection from python_toolkit.plot.heatmap import heatmap as hmap from ladybugtools_toolkit.ladybug_extension.datacollection import collection_to_series +from ladybugtools_toolkit.bhom.wrapped.metadata.plot_information import PlotInformation from ladybugtools_toolkit.bhom.wrapped.metadata.collection import collection_metadata from ladybugtools_toolkit.ladybug_extension.epw import wet_bulb_temperature from ladybugtools_toolkit.plot.utilities import figure_to_base64 import matplotlib.pyplot as plt from ...logger import CONSOLE_LOGGER +from python_toolkit.bhom.decorators import bhom_wrapper -PARSER = argparse.ArgumentParser( - description=( - "Given an EPW file path, extract a heatmap" - ) -) -PARSER.add_argument( - "-e", - "--epw_file", - help="The EPW file to extract a heatmap from", - type=str, - required=True, -) -PARSER.add_argument( - "-dtk", - "--data_type_key", - help="Key in EPW data to create a plot from.", - type=str, - required=True, -) -PARSER.add_argument( - "-cmap", - "--colour_map", - help="Matplotlib colour map to use.", - type=str, - required=True, - ) -PARSER.add_argument( - "-p", - "--save_path", - help="Path where to save the output image.", - type=str, - required=False, - ) - -def heatmap(epw_file: str, data_type_key: str, colour_map: str, save_path:str = None) -> str: +@bhom_wrapper.bhom_callable("plot/epw_heatmap") +def heatmap(epw_file: str, data_type_key: str, colour_map: str, save_path:str = None, **kwargs) -> PlotInformation: """Create a CSV file version of an EPW.""" try: style = os.environ.get("BHOM_style_context", "python_toolkit.bhom") + if colour_map not in plt.colormaps(): colour_map = "YlGnBu" + epw = EPW(epw_file) + + if data_type_key == "Wet Bulb Temperature": + coll = wet_bulb_temperature(epw) + else: + coll = HourlyContinuousCollection.from_dict([a for a in epw.to_dict()["data_collections"] if a["header"]["data_type"]["name"] == data_type_key][0]) + with plt.style.context(style): fig, ax = plt.subplots() - - epw = EPW(epw_file) - - if data_type_key == "Wet Bulb Temperature": - coll = wet_bulb_temperature(epw) - else: - coll = HourlyContinuousCollection.from_dict([a for a in epw.to_dict()["data_collections"] if a["header"]["data_type"]["name"] == data_type_key][0]) - hmap(collection_to_series(coll), ax=ax, cmap=colour_map, style_context=style) + plt.tight_layout() - return_dict = {} + pi = PlotInformation(other_data = collection_metadata(coll)) + image: str = "" if save_path == None or save_path == "": base64 = figure_to_base64(fig,html=False) - return_dict["figure"] = base64 + image = base64 else: fig.savefig(save_path, dpi=150, transparent=True) - return_dict["figure"] = save_path + image = save_path plt.close(fig) - - return_dict["data"] = collection_metadata(coll) - - return json.dumps(return_dict, default=str) - + pi.image = image + return pi except Exception: CONSOLE_LOGGER.error("Heatmap could not be created.", exc_info=1) return traceback.format_exc() - - -if __name__ == "__main__": - - args = PARSER.parse_args() - matplotlib.use("Agg") - heatmap(args.epw_file, args.data_type_key, args.colour_map, args.save_path) diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/sunpath.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/sunpath.py index f0155acd..615eea05 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/sunpath.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/sunpath.py @@ -1,85 +1,44 @@ """Method to wrap creation of sunpath plots""" # pylint: disable=C0415,E0401,W0703 -import argparse import os -import sys import traceback -from pathlib import Path -import matplotlib from ladybugtools_toolkit.plot._sunpath import sunpath as spath from ladybug.epw import EPW, AnalysisPeriod -from ladybug.datacollection import HourlyContinuousCollection from ladybug.sunpath import Sunpath from ladybugtools_toolkit.bhom.wrapped.metadata.sunpath_metadata import sunpath_metadata +from ladybugtools_toolkit.bhom.wrapped.metadata.plot_information import PlotInformation +from ladybugtools_toolkit.bhom.from_bhom import LBTBHoMJSONDecoder +from ladybugtools_toolkit.bhom.to_bhom import LBTBHoMJSONEncoder from ladybugtools_toolkit.plot.utilities import figure_to_base64 import matplotlib.pyplot as plt -from pathlib import Path -import json from ...logger import CONSOLE_LOGGER +from python_toolkit.bhom.decorators import bhom_wrapper -PARSER = argparse.ArgumentParser( - description=( - "Given an EPW file path, create a plot of its' sun path" - ) -) -PARSER.add_argument( - "-e", - "--epw_file", - help="The EPW file to extract a sun path plot from", - type=str, - required=True, -) -PARSER.add_argument( - "-s", - "--size", - help="Size of the sun", - type=float, - required=True, - ) -PARSER.add_argument( - "-ap", - "--analysis_period", - help="Analysis perioderiod of the sun path", - type=str, - required=True, - ) -PARSER.add_argument( - "-p", - "--save_path", - help="Path where to save the output image.", - type=str, - required=False, - ) - -def sunpath(epw_file, analysis_period, size, save_path) -> str: +@bhom_wrapper.bhom_callable("plot/sunpath", argument_types = { "analysis_period": AnalysisPeriod }, decoder_cls=LBTBHoMJSONDecoder) +def sunpath(epw_file: str, analysis_period: AnalysisPeriod, size: int, save_path: str = None, **kwargs) -> PlotInformation: try: style = os.environ.get("BHOM_style_context", "python_toolkit.bhom") + epw = EPW(epw_file) + with plt.style.context(style): fig, ax = plt.subplots() - - analysis_period = AnalysisPeriod.from_dict(json.loads(analysis_period)) - epw = EPW(epw_file) spath(location=epw.location, analysis_period=analysis_period, sun_size=size, ax=ax, style_context=style) + plt.tight_layout() - return_dict = {"data": sunpath_metadata(Sunpath.from_location(epw.location))} + pi = PlotInformation(other_data = sunpath_metadata(Sunpath.from_location(epw.location))) + image: str = "" if save_path is None or save_path == "": base64 = figure_to_base64(fig, html=False) - return_dict["figure"] = base64 + image = base64 else: fig.savefig(save_path, dpi=150, transparent=True) - return_dict["figure"] = save_path + image = save_path + pi.image = image plt.close(fig) - - return json.dumps(return_dict, default=str) + return pi except Exception: CONSOLE_LOGGER.error("Sunpath could not be created.", exc_info=1) return traceback.format_exc() - -if __name__ == "__main__": - - args = PARSER.parse_args() - matplotlib.use("Agg") - sunpath(args.epw_file, args.analysis_period, args.size, args.save_path) \ No newline at end of file diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/utci_heatmap.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/utci_heatmap.py index 450f0ae2..f3348901 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/utci_heatmap.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/utci_heatmap.py @@ -1,9 +1,8 @@ """Method to wrap UTCI plots""" # pylint: disable=C0415,E0401,W0703 -import argparse import os -import sys import traceback +from typing import Dict import matplotlib from ladybugtools_toolkit.external_comfort.externalcomfort import ExternalComfort from ladybugtools_toolkit.bhom.wrapped.metadata.plot_information import PlotInformation @@ -14,25 +13,11 @@ from ladybugtools_toolkit.categorical.categories import Categorical, UTCI_DEFAULT_CATEGORIES import matplotlib.pyplot as plt import numpy as np -import json from ...logger import CONSOLE_LOGGER -from ... import bhom_callable +from python_toolkit.bhom.decorators import bhom_wrapper -PARSER = argparse.ArgumentParser( - description=( - "Given an EPW file path, extract a heatmap" - ) -) -PARSER.add_argument( - "-in", - "--input_json", - help="helptext", - type=str, - required=True, -) - -@bhom_callable(argument_types = { "external_comfort": ExternalComfort }, encoder_cls=LBTBHoMJSONEncoder, decoder_cls=LBTBHoMJSONDecoder) -def utci_heatmap(external_comfort: ExternalComfort, bin_colours: list[str], save_path: str = "") -> dict: +@bhom_wrapper.bhom_callable("plot/utci_heatmap", argument_types = { "external_comfort": ExternalComfort }, encoder_cls=LBTBHoMJSONEncoder, decoder_cls=LBTBHoMJSONDecoder) +def utci_heatmap(external_comfort: ExternalComfort, bin_colours: list[str], save_path: str = "", **kwargs) -> Dict[str, object]: try: style = os.environ.get("BHOM_style_context", "python_toolkit.bhom") custom_bins = UTCI_DEFAULT_CATEGORIES @@ -62,7 +47,6 @@ def utci_heatmap(external_comfort: ExternalComfort, bin_colours: list[str], save plt.close(fig) pi.image = image - return_dict = { "info": pi, "external_comfort": external_comfort @@ -71,9 +55,4 @@ def utci_heatmap(external_comfort: ExternalComfort, bin_colours: list[str], save except Exception: CONSOLE_LOGGER.error("UTCI Heatmap could not be created.", exc_info=1) - return traceback.format_exc() - -if __name__ == "__main__": - args = PARSER.parse_args() - matplotlib.use("Agg") - utci_heatmap(args.input_json, args.save_path) \ No newline at end of file + return traceback.format_exc() \ No newline at end of file diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/walkability_heatmap.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/walkability_heatmap.py index 4dcd4662..ec80e9d3 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/walkability_heatmap.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/walkability_heatmap.py @@ -1,82 +1,48 @@ -import argparse -import os -import sys +import os +from typing import Dict import matplotlib import traceback from ladybugtools_toolkit.external_comfort.externalcomfort import ExternalComfort from ladybugtools_toolkit.bhom.wrapped.metadata.utci_metadata import utci_metadata +from ladybugtools_toolkit.bhom.wrapped.metadata.plot_information import PlotInformation +from ladybugtools_toolkit.bhom.from_bhom import LBTBHoMJSONDecoder +from ladybugtools_toolkit.bhom.to_bhom import LBTBHoMJSONEncoder from ladybugtools_toolkit.plot.utilities import figure_to_base64 import json import matplotlib.pyplot as plt from ...logger import CONSOLE_LOGGER +from python_toolkit.bhom.decorators import bhom_wrapper -PARSER = argparse.ArgumentParser( - description=( - "Given an external comfort object, extract a walkability heatmap" - ) -) -PARSER.add_argument( - "-e", - "--epw_file", - help="helptext", - type=str, - required=False -) -PARSER.add_argument( - "-in", - "--input_json", - help="helptext", - type=str, - required=True, -) -PARSER.add_argument( - "-sp", - "--save_path", - help="helptext", - type=str, - required=False, -) - -def walkability_heatmap(input_json: str, save_path: str, epw_file:str = None) -> str: +@bhom_wrapper.bhom_callable("plot/walkability_heatmap", argument_types = { "external_comfort": ExternalComfort }, encoder_cls=LBTBHoMJSONEncoder, decoder_cls=LBTBHoMJSONDecoder) +def walkability_heatmap(external_comfort: ExternalComfort, save_path: str, **kwargs) -> Dict[str, object]: try: style = os.environ.get("BHOM_style_context", "python_toolkit.bhom") - if not input_json.startswith("{"): #assume it's a path - with open(input_json, "r") as f: - input_json = f.read() - - argsDict = json.loads(input_json) - - ec = ExternalComfort.from_dict(json.loads(argsDict["external_comfort"])) - with plt.style.context(style): fig, ax = plt.subplots(1, 1, figsize=(10, 4)) - ec.plot_walkability_heatmap(ax=ax, style_context=style) - - #TODO: create walkability collection metadata - utci_collection = ec.universal_thermal_climate_index + external_comfort.plot_walkability_heatmap(ax=ax, style_context=style) + plt.tight_layout() + + image:str = "" - return_dict = {"data": utci_metadata(utci_collection), "external_comfort": ec.to_dict()} + utci_collection = external_comfort.universal_thermal_climate_index + pi = PlotInformation(other_data = utci_metadata(utci_collection)) - plt.tight_layout() - if save_path == None or save_path == "": base64 = figure_to_base64(fig,html=False) - return_dict["figure"] = base64 + image = base64 else: fig.savefig(save_path, dpi=150, transparent=True) - return_dict["figure"] = save_path - + image = save_path + plt.close(fig) - - return json.dumps(return_dict, default=str) + pi.image = image + return_dict = { + "info": pi, + "external_comfort": external_comfort + } + return return_dict except Exception: CONSOLE_LOGGER.error("Walkability plot could not be created.", exc_info=1) return traceback.format_exc() - -if __name__ == "__main__": - - args = PARSER.parse_args() - matplotlib.use("Agg") - walkability_heatmap(args.json_args, args.save_path) \ No newline at end of file diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/windrose.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/windrose.py index b1282c70..497bedf5 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/windrose.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/windrose.py @@ -10,91 +10,45 @@ from ladybug.datacollection import HourlyContinuousCollection from ladybugtools_toolkit.wind import Wind from ladybugtools_toolkit.bhom.wrapped.metadata.wind_metadata import wind_metadata +from ladybugtools_toolkit.bhom.wrapped.metadata.plot_information import PlotInformation +from ladybugtools_toolkit.bhom.from_bhom import LBTBHoMJSONDecoder +from ladybugtools_toolkit.bhom.to_bhom import LBTBHoMJSONEncoder from ladybugtools_toolkit.plot.utilities import figure_to_base64 import matplotlib.pyplot as plt from pathlib import Path import json from ...logger import CONSOLE_LOGGER +from python_toolkit.bhom.decorators import bhom_wrapper -PARSER = argparse.ArgumentParser( - description=( - "Given an EPW file path, extract a heatmap" - ) -) -PARSER.add_argument( - "-e", - "--epw_file", - help="The EPW file to extract a heatmap from", - type=str, - required=True, -) -PARSER.add_argument( - "-ap", - "--analysis_period", - help="Analysis period", - type=str, - required=True, -) -PARSER.add_argument( - "-cmap", - "--colour_map", - help="Matplotlib colour map to use.", - type=str, - required=True, - ) -PARSER.add_argument( - "-bins", - "--bins", - help="Number of bins", - type=int, - required=True, -) -PARSER.add_argument( - "-p", - "--save_path", - help="Path where to save the output image.", - type=str, - required=False, - ) - -def windrose(epw_file: str, analysis_period: str, colour_map: str, bins: int, save_path: str = None) -> str: +@bhom_wrapper.bhom_callable("plot/windrose", argument_types = { "analysis_period": AnalysisPeriod }, decoder_cls=LBTBHoMJSONDecoder) +def windrose(epw_file: str, analysis_period: AnalysisPeriod, colour_map: str, bins: int, save_path: str = None, **kwargs) -> PlotInformation: """Method to wrap for creating wind roses from epw files.""" try: style = os.environ.get("BHOM_style_context", "python_toolkit.bhom") + if colour_map not in plt.colormaps(): colour_map = "YlGnBu" - epw = EPW(epw_file) - analysis_period = AnalysisPeriod.from_dict(json.loads(analysis_period)) w_epw = Wind.from_epw(epw_file) + wind_filtered = w_epw.filter_by_analysis_period(analysis_period=analysis_period) with plt.style.context(style): fig, ax = plt.subplots(1, 1, figsize=(6, 6), subplot_kw={"projection": "polar"}) - - wind_filtered = w_epw.filter_by_analysis_period(analysis_period=analysis_period) - wind_filtered.plot_windrose(ax=ax, directions=bins, ylim=(0, 3.6/bins), colors=colour_map, style_context=style) - - return_dict = {"data": wind_metadata(wind_filtered, directions=bins)} - plt.tight_layout() + + pi = PlotInformation(other_data = wind_metadata(wind_filtered, directions=bins)) + image:str = "" + if save_path == None or save_path == "": - return_dict["figure"] = figure_to_base64(fig,html=False) + image = figure_to_base64(fig,html=False) else: fig.savefig(save_path, dpi=150, transparent=True) - return_dict["figure"] = save_path + image = save_path + pi.image = image plt.close(fig) - - return json.dumps(return_dict, default=str) - + return pi except Exception: CONSOLE_LOGGER.error("Windrose could not be created.", exc_info=1) return traceback.format_exc() - - -if __name__ == "__main__": - - args = PARSER.parse_args() - matplotlib.use("Agg") - windrose(args.epw_file, args.analysis_period, args.colour_map, args.bins, args.save_path) \ No newline at end of file diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/simulation_result.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/simulation_result.py index a3459c58..cbf77267 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/simulation_result.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/simulation_result.py @@ -1,33 +1,11 @@ -"""Method to wrap for access to pre-defined materials.""" # pylint: disable=C0415,E0401,W0703 -import argparse -import traceback +from python_toolkit.bhom.decorators import bhom_wrapper +from ladybugtools_toolkit.external_comfort._simulatebase import SimulationResult +from ladybugtools_toolkit.bhom.from_bhom import LBTBHoMJSONDecoder +from ladybugtools_toolkit.bhom.to_bhom import LBTBHoMJSONEncoder +#see external_comfort.py -def main(json_file: str) -> None: - """From a json file represention of a SimulationResult, run the simulation.""" - try: - from ladybugtools_toolkit.external_comfort._simulatebase import SimulationResult - - res = SimulationResult.from_file(json_file) - res.to_file(json_file) - - except Exception as e: # pylint: disable=W0703 - print(traceback.format_exc()) - - -if __name__ == "__main__": - parser = argparse.ArgumentParser( - description=( - "Given a JSON file containing the string represention of a SimulationResult, run the simulation." - ) - ) - parser.add_argument( - "-j", - "--json_file", - help="The JSON file to convert into a SimulationResult object Python-side.", - type=str, - required=True, - ) - args = parser.parse_args() - main(args.json_file) +@bhom_wrapper.bhom_callable("simulation_result", argument_types = {"simulation_result", SimulationResult}, encoder_cls=LBTBHoMJSONEncoder, decoder_cls=LBTBHoMJSONDecoder) +def main(simulation_result: SimulationResult, **kwargs) -> None: + return simulation_result From 53abb8511c46bb5ea86461125a6e6400b886ef0e Mon Sep 17 00:00:00 2001 From: Tom Kingstone Date: Tue, 18 Aug 2026 13:54:40 +0100 Subject: [PATCH 09/15] implement new execute methods for all adapter commands except run simulation/external comfort commands --- .../AdapterActions/Execute.cs | 40 +++++++++++++++--- .../Execute/CompareEPWKeyPlotCommand.cs | 40 +++++++++--------- .../Execute/DiurnalPlotCommand.cs | 38 ++++++++++------- .../AdapterActions/Execute/EPWToCSVCommand.cs | 29 +++++-------- .../Execute/FacadeCondensationRiskCommand.cs | 35 +++++++++------- .../Execute/GEMToHBJSONCommand.cs | 28 +++++-------- .../Execute/GetMaterialCommand.cs | 31 +++++++------- .../Execute/GetTypologyCommand.cs | 31 +++++++------- .../Execute/HBJSONToGEMCommand.cs | 28 +++++-------- .../AdapterActions/Execute/HeatPlotCommand.cs | 39 ++++++++++-------- .../Execute/SolarRadiationPlotCommand.cs | 41 ++++++++++++------- .../Execute/SunPathPlotCommand.cs | 36 +++++++++------- .../Execute/UTCIHeatPlotCommand.cs | 41 ++++++++----------- .../Execute/WalkabilityPlotCommand.cs | 41 ++++++++----------- .../AdapterActions/Execute/WindroseCommand.cs | 38 ++++++++++------- .../ladybugtools_toolkit/bhom/run_wrapped.py | 6 ++- .../plot/facade_condensation_risk_heatmap.py | 2 +- 17 files changed, 298 insertions(+), 246 deletions(-) diff --git a/LadybugTools_Adapter/AdapterActions/Execute.cs b/LadybugTools_Adapter/AdapterActions/Execute.cs index 8f35c4a7..31922272 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute.cs @@ -21,24 +21,25 @@ */ using BH.Engine.Adapter; +using BH.Engine.Base; using BH.Engine.LadybugTools; +using BH.Engine.Python; +using BH.Engine.Serialiser; using BH.oM.Adapter; using BH.oM.Adapter.Commands; using BH.oM.Base; using BH.oM.Data.Requests; using BH.oM.LadybugTools; using BH.oM.Python; -using BH.Engine.Python; using System; using System.Collections.Generic; +using System.Drawing; using System.IO; using System.Linq; -using System.Text; -using BH.Engine.Base; -using System.Drawing; -using BH.Engine.Serialiser; -using System.Reflection; using System.Net.Http; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; namespace BH.Adapter.LadybugTools { @@ -85,6 +86,33 @@ private List RunCommand(IExecuteCommand command, ActionConfig actionConf BH.Engine.Base.Compute.RecordError($"The command {command.GetType().FullName} is not valid for the LadybugTools Adapter. Please use a LadybugCommand, or use the correct adapter for the input command."); return new List(); } + + private (string, bool) ExecutePython(List args, string json) + { + string result = ""; + bool success; + + if (m_httpClient != null) + { + Task<(string, bool)> task = Compute.SendHttp(m_httpClient, args, json); + task.Wait(); + (result, success) = task.Result; + } + else + { + //if the server was not running or some other error happened, try running the python directly. + string script = Path.Combine(Engine.LadybugTools.Query.PythonCodeDirectory(), "LadybugTools_Toolkit\\src\\ladybugtools_toolkit\\bhom", "run_wrapped.py"); + string tempFileName = System.IO.Path.GetTempFileName(); + System.IO.File.WriteAllText(tempFileName, json); + string cmdCommand = $"{m_environment.Executable} {script} {args.Select(x => x.Contains(' ') || string.IsNullOrEmpty(x) ? '"' + x + '"' : x).Aggregate((a, b) => a + " " + b)} -in \"{tempFileName}\""; + System.IO.File.Delete(tempFileName); + result = Engine.Python.Compute.RunCommandStdout(command: cmdCommand, hideWindows: true); + } + + success = !result.Contains("Traceback (most recent call last):"); + + return (result, success); + } } } diff --git a/LadybugTools_Adapter/AdapterActions/Execute/CompareEPWKeyPlotCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/CompareEPWKeyPlotCommand.cs index 61081c23..c56bab62 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/CompareEPWKeyPlotCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/CompareEPWKeyPlotCommand.cs @@ -22,6 +22,7 @@ using BH.Engine.Adapter; using BH.Engine.Base; +using BH.Engine.Serialiser; using BH.oM.Adapter; using BH.oM.Base; using BH.oM.LadybugTools; @@ -75,37 +76,33 @@ private List RunCommand(CompareEPWKeyPlotCommand command, ActionConfig a string epwFile = System.IO.Path.GetFullPath(command.EPWFile.GetFullFileName()).Replace('\\', '/'); List epwFileList = command.EPWCompareFiles.Select(e => e.GetFullFileName().Replace('\\', '/')).ToList(); + Dictionary dict = new Dictionary() + { + { "epw_list", epwFileList }, + { "data_type_key", command.EPWKey.ToText() }, + { "line", command.PlotTimeseries }, + { "save_path", command.OutputLocation.Replace('\\', '/') } + }; + + string json = dict.ToJson(); + // run the process List args = new List { "--command", "plot/epw_comparison", - "-e", epwFile, - "-dtk", command.EPWKey.ToText(), - "-p", command.OutputLocation.Replace('\\', '/'), - "-el" //append compare epw file list here + "-e", epwFile }; - args.AddRange(epwFileList); - if (command.PlotTimeseries) - args.Add("-l"); + (string result, bool success) = ExecutePython(args, json); - string result = ""; - bool success; - - if (m_httpClient != null) + if (!success) { - Task<(string, bool)> task = Compute.SendHttp(m_httpClient, args); - task.Wait(); - (result, success) = task.Result; + BH.Engine.Base.Compute.RecordError($"A python error occurred while running the command `{command.GetType().Name}`. Python output:\n{result}"); + m_executeSuccess = success; + return new List(); } - else - { - //if the server was not running or some other error happened, try running the python directly. - string script = Path.Combine(Engine.LadybugTools.Query.PythonCodeDirectory(), "LadybugTools_Toolkit\\src\\ladybugtools_toolkit\\bhom", "run_wrapped.py"); - string cmdCommand = $"{m_environment.Executable} {script} {args.Select(x => x.Contains(' ') || string.IsNullOrEmpty(x) ? '"' + x + '"' : x).Aggregate((a, b) => a + " " + b)}"; - result = Engine.Python.Compute.RunCommandStdout(command: cmdCommand, hideWindows: true).Split('\n').Last(); - } + result = result.Split('\n').Last(); try { @@ -117,6 +114,7 @@ private List RunCommand(CompareEPWKeyPlotCommand command, ActionConfig a catch (Exception ex) { BH.Engine.Base.Compute.RecordError(ex, $"An error occurred when deserialising the output from the script.\n Python output: {result}"); + m_executeSuccess = false; return new List(); } } diff --git a/LadybugTools_Adapter/AdapterActions/Execute/DiurnalPlotCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/DiurnalPlotCommand.cs index 76801915..f19cb742 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/DiurnalPlotCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/DiurnalPlotCommand.cs @@ -23,6 +23,7 @@ using BH.Engine.Adapter; using BH.Engine.Base; using BH.Engine.LadybugTools; +using BH.Engine.Serialiser; using BH.oM.Adapter; using BH.oM.Base; using BH.oM.LadybugTools; @@ -73,26 +74,34 @@ private List RunCommand(DiurnalPlotCommand command, ActionConfig actionC //string returnFile = Path.GetTempFileName(); + Dictionary dict = new Dictionary() + { + { "data_type_key", command.EPWKey.ToText() }, + { "colour", command.Colour.ToHexCode() }, + { "title", command.Title }, + { "period", command.Period.ToString().ToLower() }, + { "save_path", command.OutputLocation.Replace('\\', '/') } + }; + + string json = dict.ToJson(); + // run the process - List args = new List() { "--command", "plot/diurnal", "-e", epwFile.Replace('\\', '/'), "-dtk", command.EPWKey.ToText(), "--colour", command.Colour.ToHexCode(), "-t", command.Title, "-ap", command.Period.ToString().ToLower(), "-p", command.OutputLocation.Replace('\\', '/') }; + List args = new List() + { + "--command", "plot/diurnal", + "-e", epwFile.Replace('\\', '/') + }; - string result = ""; - bool success; + (string result, bool success) = ExecutePython(args, json); - if (m_httpClient != null) + if (!success) { - Task<(string, bool)> task = Compute.SendHttp(m_httpClient, args); - task.Wait(); - (result, success) = task.Result; + BH.Engine.Base.Compute.RecordError($"A python error occurred while running the command `{command.GetType().Name}`. Python output:\n{result}"); + m_executeSuccess = success; + return new List(); } - else - { - //if the server was not running or some other error happened, try running the python directly. - string script = Path.Combine(Engine.LadybugTools.Query.PythonCodeDirectory(), "LadybugTools_Toolkit\\src\\ladybugtools_toolkit\\bhom", "run_wrapped.py"); - string cmdCommand = $"{m_environment.Executable} {script} {args.Select(x => x.Contains(' ') || string.IsNullOrEmpty(x) ? '"' + x + '"' : x).Aggregate((a, b) => a + " " + b)}"; - result = Engine.Python.Compute.RunCommandStdout(command: cmdCommand, hideWindows: true).Split('\n').Last(); - } + result = result.Split('\n').Last(); try { @@ -104,6 +113,7 @@ private List RunCommand(DiurnalPlotCommand command, ActionConfig actionC catch (Exception ex) { BH.Engine.Base.Compute.RecordError(ex, $"An error occurred when deserialising the output from the script.\n Python output: {result}"); + m_executeSuccess = false; return new List(); } } diff --git a/LadybugTools_Adapter/AdapterActions/Execute/EPWToCSVCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/EPWToCSVCommand.cs index eaeebc3d..72d27ca2 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/EPWToCSVCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/EPWToCSVCommand.cs @@ -21,6 +21,7 @@ */ using BH.Engine.Adapter; +using BH.Engine.Serialiser; using BH.oM.Adapter; using BH.oM.LadybugTools; using System; @@ -59,28 +60,20 @@ private List RunCommand(EPWToCSVCommand command, ActionConfig actionConf return null; } - List args = new List() { "--command", "epw_to_csv", "-e", command.EPWFile.GetFullFileName().Replace('\\', '/'), "-a", command.IncludeAdditionalCalculated.ToString() }; + Dictionary dict = new Dictionary() + { + { "include_additional", command.IncludeAdditionalCalculated } + }; - string result = ""; - bool success = true; + string json = dict.ToJson(); - if (m_httpClient != null) - { - Task<(string, bool)> task = Compute.SendHttp(m_httpClient, args); - task.Wait(); - (result, success) = task.Result; //in this case, result is the text of the csv file. - } - else + List args = new List() { - //if the server was not running or some other error happened, try running the python directly. - string script = Path.Combine(Engine.LadybugTools.Query.PythonCodeDirectory(), "LadybugTools_Toolkit\\src\\ladybugtools_toolkit\\bhom", "run_wrapped.py"); - string cmdCommand = $"{m_environment.Executable} {script} {args.Select(x => x.Contains(' ') || string.IsNullOrEmpty(x) ? '"' + x + '"' : x).Aggregate((a, b) => a + " " + b)}"; - - result = Engine.Python.Compute.RunCommandStdout(command: cmdCommand, hideWindows: true); - } + "--command", "epw_to_csv", + "-e", command.EPWFile.GetFullFileName().Replace('\\', '/'), + }; - //as the file output is hard to verify by itself, check that no errors got output to stderr log - success &= !result.Contains("Traceback (most recent call last):"); + (string result, bool success) = ExecutePython(args, json); if (!success) { diff --git a/LadybugTools_Adapter/AdapterActions/Execute/FacadeCondensationRiskCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/FacadeCondensationRiskCommand.cs index d43e5599..f57e6cd2 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/FacadeCondensationRiskCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/FacadeCondensationRiskCommand.cs @@ -73,28 +73,32 @@ private List RunCommand(FacadeCondensationRiskCommand command, ActionCon else commandArg = "plot/facade_condensation_risk_chart"; + Dictionary dict = new Dictionary() + { + { "thresholds", thresholds }, + { "save_path", command.OutputLocation.Replace('\\', '/') } + }; + + string json = dict.ToJson(); + //construct args: insert thresholds as a range as concatenating them into a space delimited string causes the numbers to be wrapped in quotes which breaks the python argument parser - List args = new List() { "-command", commandArg, "-e", epwFile.Replace('\\', '/'), "-t", "-p", command.OutputLocation.Replace('\\', '/') }; - args.InsertRange(args.IndexOf("-t") + 1, thresholds.Select(x => x.ToString())); + List args = new List() + { + "-command", commandArg, + "-e", epwFile.Replace('\\', '/') + }; // run the process - string result = ""; - bool success; + (string result, bool success) = ExecutePython(args, json); - if (m_httpClient != null) + if (!success) { - Task<(string, bool)> task = Compute.SendHttp(m_httpClient, args); - task.Wait(); - (result, success) = task.Result; + BH.Engine.Base.Compute.RecordError($"A python error occurred while running the command `{command.GetType().Name}`. Python output:\n{result}"); + m_executeSuccess = success; + return new List(); } - else - { - //if the server was not running or some other error happened, try running the python directly. - string script = Path.Combine(Engine.LadybugTools.Query.PythonCodeDirectory(), "LadybugTools_Toolkit\\src\\ladybugtools_toolkit\\bhom", "run_wrapped.py"); - string cmdCommand = $"{m_environment.Executable} {script} {args.Select(x => x.Contains(' ') || string.IsNullOrEmpty(x) ? '"' + x + '"' : x).Aggregate((a, b) => a + " " + b)}"; - result = Engine.Python.Compute.RunCommandStdout(command: cmdCommand, hideWindows: true).Split('\n').Last(); - } + result = result.Split('\n').Last(); try { @@ -106,6 +110,7 @@ private List RunCommand(FacadeCondensationRiskCommand command, ActionCon catch (Exception ex) { BH.Engine.Base.Compute.RecordError(ex, $"An error occurred when deserialising the output from the script.\n Python output: {result}"); + m_executeSuccess = false; return new List(); } } diff --git a/LadybugTools_Adapter/AdapterActions/Execute/GEMToHBJSONCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/GEMToHBJSONCommand.cs index d1aef4d7..604b7ab1 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/GEMToHBJSONCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/GEMToHBJSONCommand.cs @@ -21,6 +21,7 @@ */ using BH.Engine.Adapter; +using BH.Engine.Serialiser; using BH.oM.Adapter; using BH.oM.LadybugTools; using System; @@ -58,28 +59,19 @@ public List RunCommand(GEMToHBJSONCommand command, ActionConfig actionCo return null; } - List args = new List() { "--command", "gem_to_hbjson", "-g", command.GEMFile.GetFullFileName().Replace('\\', '/') }; + Dictionary dict = new Dictionary() + { + { "gem_file", command.GEMFile.GetFullFileName().Replace('\\', '/') } + }; - string result = ""; - bool success = true; + string json = dict.ToJson(); - if (m_httpClient != null) - { - Task<(string, bool)> task = Compute.SendHttp(m_httpClient, args); - task.Wait(); - (result, success) = task.Result; //in this case, result is the text of the csv file. - } - else + List args = new List() { - //if the server was not running or some other error happened, try running the python directly. - string script = Path.Combine(Engine.LadybugTools.Query.PythonCodeDirectory(), "LadybugTools_Toolkit\\src\\ladybugtools_toolkit\\bhom", "run_wrapped.py"); - string cmdCommand = $"{m_environment.Executable} {script} {args.Select(x => x.Contains(' ') || string.IsNullOrEmpty(x) ? '"' + x + '"' : x).Aggregate((a, b) => a + " " + b)}"; - - result = Engine.Python.Compute.RunCommandStdout(command: cmdCommand, hideWindows: true); - } + "--command", "gem_to_hbjson" + }; - //as the file output is hard to verify by itself, check that no errors got output to stderr log - success &= !result.Contains("Traceback (most recent call last):"); + (string result, bool success) = ExecutePython(args, json); if (!success) { diff --git a/LadybugTools_Adapter/AdapterActions/Execute/GetMaterialCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/GetMaterialCommand.cs index 4f500bea..f5b4d6c9 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/GetMaterialCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/GetMaterialCommand.cs @@ -21,6 +21,7 @@ */ using BH.Engine.Adapter; +using BH.Engine.Serialiser; using BH.oM.Adapter; using BH.oM.Data.Requests; using BH.oM.LadybugTools; @@ -56,26 +57,28 @@ private List RunCommand(GetMaterialCommand command, ActionConfig actionC // run the process if (!File.Exists(config.JsonFile.GetFullFileName())) { - List args = new List() { "--command", "get_material", "-j", config.JsonFile.GetFullFileName().Replace('\\', '/') }; + Dictionary dict = new Dictionary() + { + { "json_file", config.JsonFile.GetFullFileName().Replace('\\', '/') } + }; - string result = ""; - bool success; + string json = dict.ToJson(); - if (m_httpClient != null) - { - Task<(string, bool)> task = Compute.SendHttp(m_httpClient, args); - task.Wait(); - (result, success) = task.Result; - } - else + List args = new List() { - //if the server was not running or some other error happened, try running the python directly. - string script = Path.Combine(Engine.LadybugTools.Query.PythonCodeDirectory(), "LadybugTools_Toolkit\\src\\ladybugtools_toolkit\\bhom", "run_wrapped.py"); - string cmdCommand = $"{m_environment.Executable} {script} {args.Select(x => x.Contains(' ') || string.IsNullOrEmpty(x) ? '"' + x + '"' : x).Aggregate((a, b) => a + " " + b)}"; + "--command", "get_material" + }; - result = Engine.Python.Compute.RunCommandStdout(command: cmdCommand, hideWindows: true).Split('\n').Last(); + (string result, bool success) = ExecutePython(args, json); + + if (!success) + { + BH.Engine.Base.Compute.RecordError($"A python error occurred while getting materials. Python output:\n{result}"); + m_executeSuccess = success; + return new List(); } + result = result.Split('\n').Last(); File.WriteAllText(config.JsonFile.GetFullFileName(), result); } diff --git a/LadybugTools_Adapter/AdapterActions/Execute/GetTypologyCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/GetTypologyCommand.cs index 048048cb..1dbffd2a 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/GetTypologyCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/GetTypologyCommand.cs @@ -21,6 +21,7 @@ */ using BH.Engine.Adapter; +using BH.Engine.Serialiser; using BH.oM.Adapter; using BH.oM.Data.Requests; using BH.oM.LadybugTools; @@ -57,26 +58,28 @@ private List RunCommand(GetTypologyCommand command, ActionConfig actionC // run the process if (!File.Exists(config.JsonFile.GetFullFileName())) { - List args = new List() { "--command", "get_typology", "-j", config.JsonFile.GetFullFileName().Replace('\\', '/') }; + Dictionary dict = new Dictionary() + { + { "json_file", config.JsonFile.GetFullFileName().Replace('\\', '/') } + }; - string result = ""; - bool success; + string json = dict.ToJson(); - if (m_httpClient != null) - { - Task<(string, bool)> task = Compute.SendHttp(m_httpClient, args); - task.Wait(); - (result, success) = task.Result; - } - else + List args = new List() { - //if the server was not running or some other error happened, try running the python directly. - string script = Path.Combine(Engine.LadybugTools.Query.PythonCodeDirectory(), "LadybugTools_Toolkit\\src\\ladybugtools_toolkit\\bhom", "run_wrapped.py"); - string cmdCommand = $"{m_environment.Executable} {script} {args.Select(x => x.Contains(' ') || string.IsNullOrEmpty(x) ? '"' + x + '"' : x).Aggregate((a, b) => a + " " + b)}"; + "--command", "get_typology" + }; - result = Engine.Python.Compute.RunCommandStdout(command: cmdCommand, hideWindows: true).Split('\n').Last(); + (string result, bool success) = ExecutePython(args, json); + + if (!success) + { + BH.Engine.Base.Compute.RecordError($"A python error occurred while getting typologies. Python output:\n{result}"); + m_executeSuccess = success; + return new List(); } + result = result.Split('\n').Last(); File.WriteAllText(config.JsonFile.GetFullFileName(), result); } diff --git a/LadybugTools_Adapter/AdapterActions/Execute/HBJSONToGEMCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/HBJSONToGEMCommand.cs index 8aaa0c6e..663bdbaa 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/HBJSONToGEMCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/HBJSONToGEMCommand.cs @@ -21,6 +21,7 @@ */ using BH.Engine.Adapter; +using BH.Engine.Serialiser; using BH.oM.Adapter; using BH.oM.LadybugTools; using System; @@ -58,28 +59,19 @@ public List RunCommand(HBJSONToGEMCommand command, ActionConfig actionCo return null; } - List args = new List() { "--command", "hbjson_to_gem", "-j", command.HBJSONFile.GetFullFileName().Replace('\\', '/') }; + Dictionary dict = new Dictionary() + { + { "hbjson_file", command.HBJSONFile.GetFullFileName().Replace('\\', '/') } + }; - string result = ""; - bool success = true; + string json = dict.ToJson(); - if (m_httpClient != null) - { - Task<(string, bool)> task = Compute.SendHttp(m_httpClient, args); - task.Wait(); - (result, success) = task.Result; //in this case, result is the text of the csv file. - } - else + List args = new List() { - //if the server was not running or some other error happened, try running the python directly. - string script = Path.Combine(Engine.LadybugTools.Query.PythonCodeDirectory(), "LadybugTools_Toolkit\\src\\ladybugtools_toolkit\\bhom", "run_wrapped.py"); - string cmdCommand = $"{m_environment.Executable} {script} {args.Select(x => x.Contains(' ') || string.IsNullOrEmpty(x) ? '"' + x + '"' : x).Aggregate((a, b) => a + " " + b)}"; - - result = Engine.Python.Compute.RunCommandStdout(command: cmdCommand, hideWindows: true); - } + "--command", "hbjson_to_gem" + }; - //as the file output is hard to verify by itself, check that no errors got output to stderr log - success &= (!result.Contains("Traceback (most recent call last):") || result.Length == 0); + (string result, bool success) = ExecutePython(args, json); if (!success) { diff --git a/LadybugTools_Adapter/AdapterActions/Execute/HeatPlotCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/HeatPlotCommand.cs index 2bfeb8b2..55acfa7c 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/HeatPlotCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/HeatPlotCommand.cs @@ -22,6 +22,7 @@ using BH.Engine.Adapter; using BH.Engine.Base; +using BH.Engine.Serialiser; using BH.oM.Adapter; using BH.oM.Base; using BH.oM.LadybugTools; @@ -62,37 +63,43 @@ private List RunCommand(HeatPlotCommand command, ActionConfig actionConf if (colourMap.ColourMapValidity()) colourMap = colourMap.ToColourMap().FromColourMap(); + Dictionary dict = new Dictionary() + { + { "data_type_key", command.EPWKey.ToText() }, + { "colour_map", colourMap }, + { "save_path", command.OutputLocation.Replace('\\', '/') } + }; + + string json = dict.ToJson(); + // run the process - List args = new List() { "-command", "plot/heatmap", "-e", epwFile.Replace('\\', '/'), "-dtk", command.EPWKey.ToText(), "-cmap", colourMap, "-p", command.OutputLocation.Replace('\\', '/') }; + List args = new List() + { + "-command", "plot/heatmap", + "-e", epwFile.Replace('\\', '/'), + }; - string result = ""; - bool success; + (string result, bool success) = ExecutePython(args, json); - if (m_httpClient != null) + if (!success) { - Task<(string, bool)> task = Compute.SendHttp(m_httpClient, args); - task.Wait(); - (result, success) = task.Result; + BH.Engine.Base.Compute.RecordError($"A python error occurred while running the command `{command.GetType().Name}`. Python output:\n{result}"); + m_executeSuccess = success; + return new List(); } - else - { - //if the server was not running or some other error happened, try running the python directly. - string script = Path.Combine(Engine.LadybugTools.Query.PythonCodeDirectory(), "LadybugTools_Toolkit\\src\\ladybugtools_toolkit\\bhom", "run_wrapped.py"); - string cmdCommand = $"{m_environment.Executable} {script} {args.Select(x => x.Contains(' ') || string.IsNullOrEmpty(x) ? '"' + x + '"' : x).Aggregate((a, b) => a + " " + b)}"; - result = Engine.Python.Compute.RunCommandStdout(command: cmdCommand, hideWindows: true).Split('\n').Last(); - } + result = result.Split('\n').Last(); try { - CustomObject obj = (CustomObject)BH.Engine.Serialiser.Convert.FromJson(result); - PlotInformation info = Convert.ToPlotInformation(obj, new CollectionData()); + PlotInformation info = (PlotInformation)BH.Engine.Serialiser.Convert.FromJson(result); m_executeSuccess = true; return new List() { info }; } catch (Exception ex) { BH.Engine.Base.Compute.RecordError(ex, $"An error occurred when deserialising the output from the script.\n Python output: {result}"); + m_executeSuccess = false; return new List(); } } diff --git a/LadybugTools_Adapter/AdapterActions/Execute/SolarRadiationPlotCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/SolarRadiationPlotCommand.cs index bd6830bd..3d094731 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/SolarRadiationPlotCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/SolarRadiationPlotCommand.cs @@ -21,6 +21,7 @@ */ using BH.Engine.Adapter; +using BH.Engine.Serialiser; using BH.oM.Adapter; using BH.oM.Base; using BH.oM.LadybugTools; @@ -84,26 +85,37 @@ private List RunCommand(SolarRadiationPlotCommand command, ActionConfig string epwFile = System.IO.Path.GetFullPath(command.EPWFile.GetFullFileName()); + Dictionary dict = new Dictionary() + { + { "directions", command.Directions }, + { "tilts", command.Tilts }, + { "irradiance_type", command.IrradianceType.ToString() }, + { "cmap", colourMap }, + { "analysis_period", command.AnalysisPeriod }, + { "title", command.Title }, + { "save_path", command.OutputLocation.Replace('\\', '/') } + + }; + + string json = dict.ToJson(); + // run the process - List args = new List() { "-command", "plot/directional_solar_radiation", "-e", epwFile.Replace('\\', '/'), "-d", command.Directions.ToString(), "-ti", command.Tilts.ToString(), "-ir", command.IrradianceType.ToString(), "-cmap", colourMap, "-t", command.Title, "-ap", command.AnalysisPeriod.FromBHoM().Replace("\"", "\\\""), "-p", command.OutputLocation.Replace('\\', '/') }; + List args = new List() + { + "-command", "plot/directional_solar_radiation", + "-e", epwFile + }; - string result = ""; - bool success; + (string result, bool success) = ExecutePython(args, json); - if (m_httpClient != null) + if (!success) { - Task<(string, bool)> task = Compute.SendHttp(m_httpClient, args); - task.Wait(); - (result, success) = task.Result; + BH.Engine.Base.Compute.RecordError($"A python error occurred while running the command `{command.GetType().Name}`. Python output:\n{result}"); + m_executeSuccess = success; + return new List(); } - else - { - //if the server was not running or some other error happened, try running the python directly. - string script = Path.Combine(Engine.LadybugTools.Query.PythonCodeDirectory(), "LadybugTools_Toolkit\\src\\ladybugtools_toolkit\\bhom", "run_wrapped.py"); - string cmdCommand = $"{m_environment.Executable} {script} {args.Select(x => x.Contains(' ') || string.IsNullOrEmpty(x) ? '"' + x + '"' : x).Aggregate((a, b) => a + " " + b)}"; - result = Engine.Python.Compute.RunCommandStdout(command: cmdCommand, hideWindows: true).Split('\n').Last(); - } + result = result.Split('\n').Last(); try { @@ -115,6 +127,7 @@ private List RunCommand(SolarRadiationPlotCommand command, ActionConfig catch (Exception ex) { BH.Engine.Base.Compute.RecordError(ex, $"An error occurred when deserialising the output from the script.\n Python output: {result}"); + m_executeSuccess = false; return new List(); } } diff --git a/LadybugTools_Adapter/AdapterActions/Execute/SunPathPlotCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/SunPathPlotCommand.cs index 321826cd..6a001971 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/SunPathPlotCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/SunPathPlotCommand.cs @@ -21,6 +21,7 @@ */ using BH.Engine.Adapter; +using BH.Engine.Serialiser; using BH.oM.Adapter; using BH.oM.Base; using BH.oM.LadybugTools; @@ -67,26 +68,32 @@ private List RunCommand(SunPathPlotCommand command, ActionConfig actionC string epwFile = System.IO.Path.GetFullPath(command.EPWFile.GetFullFileName()); + Dictionary dict = new Dictionary() + { + { "size", command.SunSize }, + { "analysis_period", command.AnalysisPeriod }, + { "save_path", command.OutputLocation.Replace('\\', '/') } + }; + + string json = dict.ToJson(); + // run the process - List args = new List() { "-command", "plot/sunpath", "-e", epwFile.Replace('\\', '/'), "-s", command.SunSize.ToString(), "-ap", command.AnalysisPeriod.FromBHoM().Replace("\"", "\\\""), "-p", command.OutputLocation.Replace('\\', '/') }; + List args = new List() + { + "-command", "plot/sunpath", + "-e", epwFile.Replace('\\', '/') + }; - string result = ""; - bool success; + (string result, bool success) = ExecutePython(args, json); - if (m_httpClient != null) + if (!success) { - Task<(string, bool)> task = Compute.SendHttp(m_httpClient, args); - task.Wait(); - (result, success) = task.Result; + BH.Engine.Base.Compute.RecordError($"A python error occurred while running the command `{command.GetType().Name}`. Python output:\n{result}"); + m_executeSuccess = success; + return new List(); } - else - { - //if the server was not running or some other error happened, try running the python directly. - string script = Path.Combine(Engine.LadybugTools.Query.PythonCodeDirectory(), "LadybugTools_Toolkit\\src\\ladybugtools_toolkit\\bhom", "run_wrapped.py"); - string cmdCommand = $"{m_environment.Executable} {script} {args.Select(x => x.Contains(' ') || string.IsNullOrEmpty(x) ? '"' + x + '"' : x).Aggregate((a, b) => a + " " + b)}"; - result = Engine.Python.Compute.RunCommandStdout(command: cmdCommand, hideWindows: true).Split('\n').Last(); - } + result = result.Split('\n').Last(); try { @@ -98,6 +105,7 @@ private List RunCommand(SunPathPlotCommand command, ActionConfig actionC catch (Exception ex) { BH.Engine.Base.Compute.RecordError(ex, $"An error occurred when deserialising the output from the script.\n Python output: {result}"); + m_executeSuccess = false; return new List(); } } diff --git a/LadybugTools_Adapter/AdapterActions/Execute/UTCIHeatPlotCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/UTCIHeatPlotCommand.cs index 0739d78d..cc247711 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/UTCIHeatPlotCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/UTCIHeatPlotCommand.cs @@ -73,40 +73,34 @@ private List RunCommand(UTCIHeatPlotCommand command, ActionConfig action if (hexColours == "[\"\"]") hexColours = "[]"; - Dictionary inputObjects = new Dictionary() + string epwFile = System.IO.Path.GetFullPath(command.EPWFile.GetFullFileName()); + + Dictionary dict = new Dictionary() { { "external_comfort", BH.Engine.Serialiser.Convert.ToJson(command.ExternalComfort) }, - { "bin_colours", hexColours } + { "bin_colours", hexColours }, + { "save_path", command.OutputLocation.Replace('\\', '/') } }; - string epwFile = System.IO.Path.GetFullPath(command.EPWFile.GetFullFileName()); + string json = dict.ToJson(); // run the process - List args = new List() { "-command", "plot/utci_heatmap", "-e", epwFile.Replace('\\', '/'), "-sp", command.OutputLocation.Replace('\\', '/') }; + List args = new List() + { + "-command", "plot/utci_heatmap", + "-e", epwFile.Replace('\\', '/') + }; - string result = ""; - bool success; + (string result, bool success) = ExecutePython(args, json); - if (m_httpClient != null) + if (!success) { - Task<(string, bool)> task = Compute.SendHttp(m_httpClient, args, inputObjects.ToJson()); - task.Wait(); - (result, success) = task.Result; + BH.Engine.Base.Compute.RecordError($"A python error occurred while running the command `{command.GetType().Name}`. Python output:\n{result}"); + m_executeSuccess = success; + return new List(); } - else - { - //if the server was not running or some other error happened, try running the python directly. - string argFile = Path.GetTempFileName(); - File.WriteAllText(argFile, inputObjects.ToJson()); - args.Add("-in"); - args.Add(argFile); - string script = Path.Combine(Engine.LadybugTools.Query.PythonCodeDirectory(), "LadybugTools_Toolkit\\src\\ladybugtools_toolkit\\bhom", "run_wrapped.py"); - string cmdCommand = $"{m_environment.Executable} {script} {args.Select(x => x.Contains(' ') || string.IsNullOrEmpty(x) ? '"' + x + '"' : x).Aggregate((a, b) => a + " " + b)}"; - result = Engine.Python.Compute.RunCommandStdout(command: cmdCommand, hideWindows: true).Split('\n').Last(); - - System.IO.File.Delete(argFile); - } + result = result.Split('\n').Last(); try { @@ -119,6 +113,7 @@ private List RunCommand(UTCIHeatPlotCommand command, ActionConfig action catch (Exception ex) { BH.Engine.Base.Compute.RecordError(ex, $"An error occurred when deserialising the output from the script.\n Python output: {result}"); + m_executeSuccess = false; return new List(); } } diff --git a/LadybugTools_Adapter/AdapterActions/Execute/WalkabilityPlotCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/WalkabilityPlotCommand.cs index 1ce94fb1..00ced3e1 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/WalkabilityPlotCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/WalkabilityPlotCommand.cs @@ -60,38 +60,32 @@ private List RunCommand(WalkabilityPlotCommand command, ActionConfig act return null; } - Dictionary inputObjects = new Dictionary() + string epwFile = System.IO.Path.GetFullPath(command.EPWFile.GetFullFileName()); + + Dictionary dict = new Dictionary() { - { "external_comfort", BH.Engine.Serialiser.Convert.ToJson(command.ExternalComfort) } + { "external_comfort", BH.Engine.Serialiser.Convert.ToJson(command.ExternalComfort) }, + { "save_path", command.OutputLocation.Replace('\\', '/') } }; - string epwFile = System.IO.Path.GetFullPath(command.EPWFile.GetFullFileName()); + string json = dict.ToJson(); - // run the process - List args = new List() { "-command", "plot/walkability_heatmap", "-e", epwFile.Replace('\\', '/'), "-sp", command.OutputLocation.Replace('\\', '/') }; + List args = new List() + { + "-command", "plot/walkability_heatmap", + "-e", epwFile.Replace('\\', '/') + }; - string result = ""; - bool success; + (string result, bool success) = ExecutePython(args, json); - if (m_httpClient != null) + if (!success) { - Task<(string, bool)> task = Compute.SendHttp(m_httpClient, args, inputObjects.ToJson()); - task.Wait(); - (result, success) = task.Result; + BH.Engine.Base.Compute.RecordError($"A python error occurred while running the command `{command.GetType().Name}`. Python output:\n{result}"); + m_executeSuccess = success; + return new List(); } - else - { - //if the server was not running or some other error happened, try running the python directly. - string argFile = Path.GetTempFileName(); - File.WriteAllText(argFile, inputObjects.ToJson()); - args.Add("-in"); - args.Add(argFile); - string script = Path.Combine(Engine.LadybugTools.Query.PythonCodeDirectory(), "LadybugTools_Toolkit\\src\\ladybugtools_toolkit\\bhom", "run_wrapped.py"); - string cmdCommand = $"{m_environment.Executable} {script} {args.Select(x => x.Contains(' ') || string.IsNullOrEmpty(x) ? '"' + x + '"' : x).Aggregate((a, b) => a + " " + b)}"; - result = Engine.Python.Compute.RunCommandStdout(command: cmdCommand, hideWindows: true).Split('\n').Last(); - System.IO.File.Delete(argFile); - } + result = result.Split('\n').Last(); try { @@ -104,6 +98,7 @@ private List RunCommand(WalkabilityPlotCommand command, ActionConfig act catch (Exception ex) { BH.Engine.Base.Compute.RecordError(ex, $"An error occurred when deserialising the output from the script.\n Python output: {result}"); + m_executeSuccess = false; return new List(); } } diff --git a/LadybugTools_Adapter/AdapterActions/Execute/WindroseCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/WindroseCommand.cs index 799fb2af..962b6d69 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/WindroseCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/WindroseCommand.cs @@ -21,6 +21,7 @@ */ using BH.Engine.Adapter; +using BH.Engine.Serialiser; using BH.oM.Adapter; using BH.oM.Base; using BH.oM.LadybugTools; @@ -66,27 +67,33 @@ private List RunCommand(WindroseCommand command, ActionConfig actionConf if (colourMap.ColourMapValidity()) colourMap = colourMap.ToColourMap().FromColourMap(); - // run the process - List args = new List() { "-command", "plot/windrose", "-e", epwFile.Replace('\\', '/'), "-ap", command.AnalysisPeriod.FromBHoM().Replace("\"", "\\\""), "-cmap", colourMap, "-bins", command.NumberOfDirectionBins.ToString(), "-p", command.OutputLocation.Replace('\\', '/') }; + Dictionary dict = new Dictionary() + { + { "analysis_period", command.AnalysisPeriod }, + { "cmap", colourMap }, + { "bins", command.NumberOfDirectionBins }, + { "save_path", command.OutputLocation.Replace('\\', '/') } + }; - string result = ""; - bool success; + string json = dict.ToJson(); - if (m_httpClient != null) - { - Task<(string, bool)> task = Compute.SendHttp(m_httpClient, args); - task.Wait(); - (result, success) = task.Result; - } - else + List args = new List() { - //if the server was not running or some other error happened, try running the python directly. - string script = Path.Combine(Engine.LadybugTools.Query.PythonCodeDirectory(), "LadybugTools_Toolkit\\src\\ladybugtools_toolkit\\bhom", "run_wrapped.py"); - string cmdCommand = $"{m_environment.Executable} {script} {args.Select(x => x.Contains(' ') || string.IsNullOrEmpty(x) ? '"' + x + '"' : x).Aggregate((a, b) => a + " " + b)}"; + "-command", "plot/windrose", + "-e", epwFile.Replace('\\', '/') + }; + + (string result, bool success) = ExecutePython(args, json); - result = Engine.Python.Compute.RunCommandStdout(command: cmdCommand, hideWindows: true).Split('\n').Last(); + if (!success) + { + BH.Engine.Base.Compute.RecordError($"A python error occurred while running the command `{command.GetType().Name}`. Python output:\n{result}"); + m_executeSuccess = success; + return new List(); } + result = result.Split('\n').Last(); + try { CustomObject obj = (CustomObject)BH.Engine.Serialiser.Convert.FromJson(result); @@ -97,6 +104,7 @@ private List RunCommand(WindroseCommand command, ActionConfig actionConf catch (Exception ex) { BH.Engine.Base.Compute.RecordError(ex, $"An error occurred when deserialising the output from the script.\n Python output: {result}"); + m_executeSuccess = false; return new List(); } } diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/run_wrapped.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/run_wrapped.py index a79b4db8..5615770d 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/run_wrapped.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/run_wrapped.py @@ -18,8 +18,10 @@ COMMAND_PARSER.add_argument("-in", "--input_json") def resolve(data: List[str], epw_folder: Path = Path("C:/epws")) -> str: - """Parses the given data (that looks like sys.argv[1:]), and gets the command arg which is then used to get the parser for that command, - parse the rest of the args and finally run the command, then return the output of those commands. + """Parses the given data (that looks like sys.argv[1:]), and gets the command arg which is an identifier for the command which is requested, + and the input json string (or file) to be given to the BHoMJSONDecoder wrapped method. + + Also if the given epw file doesn't exist, assume that it is a file name and append it to the epw folder as a backup. """ #parse data as args command_parser = argparse.ArgumentParser(description="argument parser for commands.") diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/facade_condensation_risk_heatmap.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/facade_condensation_risk_heatmap.py index 8309f052..a6b7bcb7 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/facade_condensation_risk_heatmap.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/plot/facade_condensation_risk_heatmap.py @@ -10,7 +10,7 @@ from python_toolkit.bhom.decorators import bhom_wrapper @bhom_wrapper.bhom_callable("plot/facade_condensation_risk_heatmap") -def facade_condensation_risk_heatmap(epw_file: str, thresholds: list[float], save_path: str = None) -> PlotInformation: +def facade_condensation_risk_heatmap(epw_file: str, thresholds: list[float], save_path: str = None, **kwargs) -> PlotInformation: style = os.environ.get("BHOM_style_context", "python_toolkit.bhom") epw = EPW(epw_file) From 96b09c424f261bcb67206b000d2be8fa67c2b991 Mon Sep 17 00:00:00 2001 From: Tom Kingstone Date: Tue, 18 Aug 2026 14:24:28 +0100 Subject: [PATCH 10/15] fix silly mistakes --- LadybugTools_Adapter/AdapterActions/Execute.cs | 2 +- LadybugTools_Adapter/AdapterActions/Execute/HeatPlotCommand.cs | 2 +- .../Python/src/ladybugtools_toolkit/bhom/run_wrapped.py | 2 +- .../ladybugtools_toolkit/bhom/wrapped/metadata/collection.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/LadybugTools_Adapter/AdapterActions/Execute.cs b/LadybugTools_Adapter/AdapterActions/Execute.cs index 31922272..14955000 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute.cs @@ -105,8 +105,8 @@ private List RunCommand(IExecuteCommand command, ActionConfig actionConf string tempFileName = System.IO.Path.GetTempFileName(); System.IO.File.WriteAllText(tempFileName, json); string cmdCommand = $"{m_environment.Executable} {script} {args.Select(x => x.Contains(' ') || string.IsNullOrEmpty(x) ? '"' + x + '"' : x).Aggregate((a, b) => a + " " + b)} -in \"{tempFileName}\""; - System.IO.File.Delete(tempFileName); result = Engine.Python.Compute.RunCommandStdout(command: cmdCommand, hideWindows: true); + System.IO.File.Delete(tempFileName); } success = !result.Contains("Traceback (most recent call last):"); diff --git a/LadybugTools_Adapter/AdapterActions/Execute/HeatPlotCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/HeatPlotCommand.cs index 55acfa7c..6341464c 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/HeatPlotCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/HeatPlotCommand.cs @@ -75,7 +75,7 @@ private List RunCommand(HeatPlotCommand command, ActionConfig actionConf // run the process List args = new List() { - "-command", "plot/heatmap", + "-command", "plot/epw_heatmap", "-e", epwFile.Replace('\\', '/'), }; diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/run_wrapped.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/run_wrapped.py index 5615770d..7015cbd9 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/run_wrapped.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/run_wrapped.py @@ -10,7 +10,7 @@ matplotlib.use("Agg") #use a gui-less backend to avoid memory leaking figures #big import list that covers all methods in bhom/wrapped -from . import wrapped +from ladybugtools_toolkit.bhom import wrapped from python_toolkit.bhom import wrapped COMMAND_PARSER = argparse.ArgumentParser(description="argument parser for commands.") diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/collection.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/collection.py index 3f4005ac..5d8d1ad7 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/collection.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/metadata/collection.py @@ -41,7 +41,7 @@ def collection_metadata(collection: BaseCollection) -> IObject: _t = "BH.oM.LadybugTools.CollectionData", lowest_value = lowest, lowest_index = lowest_index, - highest_index = highest, + highest_value = highest, highest_index = highest_index, median_value = median, mean_value = mean, From 593c97af57ce72fa003ba38d075721f50c6083a5 Mon Sep 17 00:00:00 2001 From: Tom Kingstone Date: Tue, 18 Aug 2026 16:21:28 +0100 Subject: [PATCH 11/15] add simulation commands to standard execute structure --- .../Execute/RunExternalComfortCommand.cs | 54 ++++++++++-------- .../Execute/RunSimulationCommand.cs | 55 +++++++++++-------- 2 files changed, 64 insertions(+), 45 deletions(-) diff --git a/LadybugTools_Adapter/AdapterActions/Execute/RunExternalComfortCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/RunExternalComfortCommand.cs index ecabb83c..3fe02bc7 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/RunExternalComfortCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/RunExternalComfortCommand.cs @@ -21,6 +21,7 @@ */ using BH.Engine.Adapter; +using BH.Engine.Serialiser; using BH.oM.Adapter; using BH.oM.Data.Requests; using BH.oM.LadybugTools; @@ -48,15 +49,6 @@ private List RunCommand(RunExternalComfortCommand command, ActionConfig return null; } - LadybugConfig config = new LadybugConfig() - { - JsonFile = new FileSettings() - { - FileName = $"LBTBHoM_{Guid.NewGuid()}.json", - Directory = Path.GetTempPath() - } - }; - // construct the base object ExternalComfort externalComfort = new ExternalComfort() { @@ -64,24 +56,42 @@ private List RunCommand(RunExternalComfortCommand command, ActionConfig Typology = command.Typology, }; - // push objects to json file - Push(new List() { externalComfort }, actionConfig: config); + Dictionary dict = new Dictionary() + { + { "external_comfort", externalComfort } + }; - // locate the Python file containing the simulation code - string script = Path.Combine(Engine.LadybugTools.Query.PythonCodeDirectory(), "LadybugTools_Toolkit\\src\\ladybugtools_toolkit\\bhom\\wrapped", "external_comfort.py"); + string json = dict.ToJson(); - // run the calculation - string cmdCommand = $"{m_environment.Executable} {script} -j \"{config.JsonFile.GetFullFileName()}\""; - Engine.Python.Compute.RunCommandStdout(command: cmdCommand, hideWindows: true); + List args = new List() + { + "-c", "external_comfort" + }; - // reload from Python results - List externalComfortPopulated = Pull(new FilterRequest(), actionConfig: config).ToList(); + (string result, bool success) = ExecutePython(args, json); - // remove temporary file - File.Delete(config.JsonFile.GetFullFileName()); + if (!success) + { + BH.Engine.Base.Compute.RecordError($"A python error occurred while running the command `{command.GetType().Name}`. Python output:\n{result}"); + m_executeSuccess = success; + return new List(); + } + + string resultJson = result.Split('\n').Last(); + ExternalComfort ec = null; + + try + { + ec = (ExternalComfort)BH.Engine.Serialiser.Convert.FromJson(resultJson); + } + catch (Exception ex) + { + BH.Engine.Base.Compute.RecordError(ex, $"Could not deserialise python output into ExternalComfort. Python output:\n{result}"); + m_executeSuccess = false; + return new List(); + } - m_executeSuccess = true; - return externalComfortPopulated; + return new List() { ec }; } } } diff --git a/LadybugTools_Adapter/AdapterActions/Execute/RunSimulationCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/RunSimulationCommand.cs index 37923929..53a4e9c2 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/RunSimulationCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/RunSimulationCommand.cs @@ -21,6 +21,7 @@ */ using BH.Engine.Adapter; +using BH.Engine.Serialiser; using BH.oM.Adapter; using BH.oM.Data.Requests; using BH.oM.LadybugTools; @@ -61,16 +62,6 @@ private List RunCommand(RunSimulationCommand command, ActionConfig actio return null; } - // construct adapter and config - LadybugConfig config = new LadybugConfig() - { - JsonFile = new FileSettings() - { - FileName = $"LBTBHoM_{Guid.NewGuid()}.json", - Directory = Path.GetTempPath() - } - }; - // construct the base object and file to be passed to Python for simulation SimulationResult simulationResult = new SimulationResult() { @@ -80,24 +71,42 @@ private List RunCommand(RunSimulationCommand command, ActionConfig actio Identifier = Engine.LadybugTools.Compute.SimulationID(command.EPWFile.GetFullFileName(), command.GroundMaterial, command.ShadeMaterial) }; - // push object to json file - Push(new List() { simulationResult }, actionConfig: config); + Dictionary dict = new Dictionary() + { + { "simulation_result", simulationResult } + }; - // locate the Python file containing the simulation code - string script = Path.Combine(Engine.LadybugTools.Query.PythonCodeDirectory(), "LadybugTools_Toolkit\\src\\ladybugtools_toolkit\\bhom\\wrapped", "simulation_result.py"); + string json = dict.ToJson(); - // run the simulation - string cmdCommand = $"{m_environment.Executable} {script} -j \"{config.JsonFile.GetFullFileName()}\""; - Engine.Python.Compute.RunCommandStdout(command: cmdCommand, hideWindows: true); + List args = new List() + { + "-c", "simulation_result" + }; - // reload from Python results - List simulationResultPopulated = Pull(new FilterRequest(), actionConfig: config).ToList(); + (string result, bool success) = ExecutePython(args, json); - // remove temporary file - File.Delete(config.JsonFile.GetFullFileName()); + if (!success) + { + BH.Engine.Base.Compute.RecordError($"A python error occurred while running the command `{command.GetType().Name}`. Python output:\n{result}"); + m_executeSuccess = success; + return new List(); + } + + string resultJson = result.Split('\n').Last(); + SimulationResult sr = null; + + try + { + sr = (SimulationResult)BH.Engine.Serialiser.Convert.FromJson(resultJson); + } + catch (Exception ex) + { + BH.Engine.Base.Compute.RecordError(ex, $"Could not deserialise python output into SimulationResult. Python output:\n{result}"); + m_executeSuccess = false; + return new List(); + } - m_executeSuccess = true; - return simulationResultPopulated; + return new List() { sr }; } } } From c0d93ac6393e2c719a487fb38ea97b500c2e2ba7 Mon Sep 17 00:00:00 2001 From: Tom Kingstone Date: Mon, 24 Aug 2026 11:43:56 +0100 Subject: [PATCH 12/15] clean up execute and run_wrapped. Make Execute use the given python environment name to find the path to run_wrapped in case the environment is in an unexpected location --- LadybugTools_Adapter/AdapterActions/Execute.cs | 2 +- .../Python/src/ladybugtools_toolkit/bhom/run_wrapped.py | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/LadybugTools_Adapter/AdapterActions/Execute.cs b/LadybugTools_Adapter/AdapterActions/Execute.cs index 14955000..75bafeca 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute.cs @@ -101,7 +101,7 @@ private List RunCommand(IExecuteCommand command, ActionConfig actionConf else { //if the server was not running or some other error happened, try running the python directly. - string script = Path.Combine(Engine.LadybugTools.Query.PythonCodeDirectory(), "LadybugTools_Toolkit\\src\\ladybugtools_toolkit\\bhom", "run_wrapped.py"); + string script = Path.Combine(Engine.Python.Query.DirectoryCode(), m_environment.Name, "src", m_environment.Name.ToLower() , "bhom", "run_wrapped.py"); string tempFileName = System.IO.Path.GetTempFileName(); System.IO.File.WriteAllText(tempFileName, json); string cmdCommand = $"{m_environment.Executable} {script} {args.Select(x => x.Contains(' ') || string.IsNullOrEmpty(x) ? '"' + x + '"' : x).Aggregate((a, b) => a + " " + b)} -in \"{tempFileName}\""; diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/run_wrapped.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/run_wrapped.py index 7015cbd9..65aa61e1 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/run_wrapped.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/run_wrapped.py @@ -10,12 +10,13 @@ matplotlib.use("Agg") #use a gui-less backend to avoid memory leaking figures #big import list that covers all methods in bhom/wrapped -from ladybugtools_toolkit.bhom import wrapped from python_toolkit.bhom import wrapped +from ladybugtools_toolkit.bhom import wrapped COMMAND_PARSER = argparse.ArgumentParser(description="argument parser for commands.") COMMAND_PARSER.add_argument("-command", "--command") COMMAND_PARSER.add_argument("-in", "--input_json") +COMMAND_PARSER.add_argument("-e", "--epw_file", required=False) def resolve(data: List[str], epw_folder: Path = Path("C:/epws")) -> str: """Parses the given data (that looks like sys.argv[1:]), and gets the command arg which is an identifier for the command which is requested, @@ -24,11 +25,7 @@ def resolve(data: List[str], epw_folder: Path = Path("C:/epws")) -> str: Also if the given epw file doesn't exist, assume that it is a file name and append it to the epw folder as a backup. """ #parse data as args - command_parser = argparse.ArgumentParser(description="argument parser for commands.") - command_parser.add_argument("-command", "--command") - command_parser.add_argument("-in", "--input_json") - command_parser.add_argument("-e", "--epw_file", required=False) - command_args, unknown_args = command_parser.parse_known_args(data) + command_args, unknown_args = COMMAND_PARSER.parse_known_args(data) if command_args.epw_file is not None: #check if the epw file exists, if not prepend the epw_folder and try to run From bd8fce662e623d106ef4ad088819680806fe71aa Mon Sep 17 00:00:00 2001 From: Tom Kingstone Date: Mon, 24 Aug 2026 17:16:24 +0100 Subject: [PATCH 13/15] fix versioning and unit test/milestone test failures --- .../Execute/CompareEPWKeyPlotCommand.cs | 6 ++---- .../Execute/DiurnalPlotCommand.cs | 8 +++----- .../Execute/FacadeCondensationRiskCommand.cs | 6 ++---- .../Execute/GetMaterialCommand.cs | 4 ++-- .../Execute/GetTypologyCommand.cs | 2 +- .../AdapterActions/Execute/HeatPlotCommand.cs | 3 +-- .../Execute/RunExternalComfortCommand.cs | 2 +- .../Execute/RunSimulationCommand.cs | 2 +- .../Execute/SolarRadiationPlotCommand.cs | 6 ++---- .../Execute/SunPathPlotCommand.cs | 6 ++---- .../Execute/UTCIHeatPlotCommand.cs | 17 ++++++----------- .../Execute/WalkabilityPlotCommand.cs | 11 +++++------ .../AdapterActions/Execute/WindroseCommand.cs | 8 +++----- .../src/ladybugtools_toolkit/bhom/from_bhom.py | 12 +++++++++--- .../bhom/wrapped/external_comfort.py | 2 +- .../bhom/wrapped/simulation_result.py | 2 +- LadybugTools_oM/Versioning93.json | 10 ++++++++++ 17 files changed, 52 insertions(+), 55 deletions(-) create mode 100644 LadybugTools_oM/Versioning93.json diff --git a/LadybugTools_Adapter/AdapterActions/Execute/CompareEPWKeyPlotCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/CompareEPWKeyPlotCommand.cs index c56bab62..03da2092 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/CompareEPWKeyPlotCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/CompareEPWKeyPlotCommand.cs @@ -94,11 +94,11 @@ private List RunCommand(CompareEPWKeyPlotCommand command, ActionConfig a }; (string result, bool success) = ExecutePython(args, json); + m_executeSuccess = success; if (!success) { BH.Engine.Base.Compute.RecordError($"A python error occurred while running the command `{command.GetType().Name}`. Python output:\n{result}"); - m_executeSuccess = success; return new List(); } @@ -106,9 +106,7 @@ private List RunCommand(CompareEPWKeyPlotCommand command, ActionConfig a try { - CustomObject obj = (CustomObject)BH.Engine.Serialiser.Convert.FromJson(result); - PlotInformation info = Convert.ToPlotInformation(obj, new NoData()); //this plot type doesn't have collection metadata yet... - m_executeSuccess = true; + PlotInformation info = (PlotInformation)BH.Engine.Serialiser.Convert.FromJson(result); return new List() { info }; } catch (Exception ex) diff --git a/LadybugTools_Adapter/AdapterActions/Execute/DiurnalPlotCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/DiurnalPlotCommand.cs index f19cb742..fbb88f9e 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/DiurnalPlotCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/DiurnalPlotCommand.cs @@ -88,16 +88,16 @@ private List RunCommand(DiurnalPlotCommand command, ActionConfig actionC // run the process List args = new List() { - "--command", "plot/diurnal", + "--command", "plot/epw_diurnal", "-e", epwFile.Replace('\\', '/') }; (string result, bool success) = ExecutePython(args, json); + m_executeSuccess = success; if (!success) { BH.Engine.Base.Compute.RecordError($"A python error occurred while running the command `{command.GetType().Name}`. Python output:\n{result}"); - m_executeSuccess = success; return new List(); } @@ -105,9 +105,7 @@ private List RunCommand(DiurnalPlotCommand command, ActionConfig actionC try { - CustomObject obj = (CustomObject)BH.Engine.Serialiser.Convert.FromJson(result); - PlotInformation info = Convert.ToPlotInformation(obj, new CollectionData()); - m_executeSuccess = true; + PlotInformation info = (PlotInformation)BH.Engine.Serialiser.Convert.FromJson(result); return new List() { info }; } catch (Exception ex) diff --git a/LadybugTools_Adapter/AdapterActions/Execute/FacadeCondensationRiskCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/FacadeCondensationRiskCommand.cs index f57e6cd2..063fbe67 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/FacadeCondensationRiskCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/FacadeCondensationRiskCommand.cs @@ -90,11 +90,11 @@ private List RunCommand(FacadeCondensationRiskCommand command, ActionCon // run the process (string result, bool success) = ExecutePython(args, json); + m_executeSuccess = success; if (!success) { BH.Engine.Base.Compute.RecordError($"A python error occurred while running the command `{command.GetType().Name}`. Python output:\n{result}"); - m_executeSuccess = success; return new List(); } @@ -102,9 +102,7 @@ private List RunCommand(FacadeCondensationRiskCommand command, ActionCon try { - CustomObject obj = (CustomObject)BH.Engine.Serialiser.Convert.FromJson(result); - PlotInformation info = Convert.ToPlotInformation(obj, new CollectionData()); - m_executeSuccess = true; + PlotInformation info = (PlotInformation)BH.Engine.Serialiser.Convert.FromJson(result); return new List() { info }; } catch (Exception ex) diff --git a/LadybugTools_Adapter/AdapterActions/Execute/GetMaterialCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/GetMaterialCommand.cs index f5b4d6c9..894321d4 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/GetMaterialCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/GetMaterialCommand.cs @@ -70,11 +70,11 @@ private List RunCommand(GetMaterialCommand command, ActionConfig actionC }; (string result, bool success) = ExecutePython(args, json); + m_executeSuccess = success; if (!success) { BH.Engine.Base.Compute.RecordError($"A python error occurred while getting materials. Python output:\n{result}"); - m_executeSuccess = success; return new List(); } @@ -85,7 +85,7 @@ private List RunCommand(GetMaterialCommand command, ActionConfig actionC List materialObjects = Pull(new FilterRequest(), actionConfig: config).ToList(); m_executeSuccess = true; - return materialObjects.Where(m => (m as IEnergyMaterialOpaque).Name.Contains(command.Filter)).ToList(); + return materialObjects.Where(m => (m as IEnergyMaterialOpaque).Identifier.Contains(command.Filter)).ToList(); } } } diff --git a/LadybugTools_Adapter/AdapterActions/Execute/GetTypologyCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/GetTypologyCommand.cs index 1dbffd2a..70925abd 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/GetTypologyCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/GetTypologyCommand.cs @@ -71,11 +71,11 @@ private List RunCommand(GetTypologyCommand command, ActionConfig actionC }; (string result, bool success) = ExecutePython(args, json); + m_executeSuccess = success; if (!success) { BH.Engine.Base.Compute.RecordError($"A python error occurred while getting typologies. Python output:\n{result}"); - m_executeSuccess = success; return new List(); } diff --git a/LadybugTools_Adapter/AdapterActions/Execute/HeatPlotCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/HeatPlotCommand.cs index 6341464c..75045e81 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/HeatPlotCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/HeatPlotCommand.cs @@ -80,11 +80,11 @@ private List RunCommand(HeatPlotCommand command, ActionConfig actionConf }; (string result, bool success) = ExecutePython(args, json); + m_executeSuccess = success; if (!success) { BH.Engine.Base.Compute.RecordError($"A python error occurred while running the command `{command.GetType().Name}`. Python output:\n{result}"); - m_executeSuccess = success; return new List(); } @@ -93,7 +93,6 @@ private List RunCommand(HeatPlotCommand command, ActionConfig actionConf try { PlotInformation info = (PlotInformation)BH.Engine.Serialiser.Convert.FromJson(result); - m_executeSuccess = true; return new List() { info }; } catch (Exception ex) diff --git a/LadybugTools_Adapter/AdapterActions/Execute/RunExternalComfortCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/RunExternalComfortCommand.cs index 3fe02bc7..d2a06510 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/RunExternalComfortCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/RunExternalComfortCommand.cs @@ -69,11 +69,11 @@ private List RunCommand(RunExternalComfortCommand command, ActionConfig }; (string result, bool success) = ExecutePython(args, json); + m_executeSuccess = success; if (!success) { BH.Engine.Base.Compute.RecordError($"A python error occurred while running the command `{command.GetType().Name}`. Python output:\n{result}"); - m_executeSuccess = success; return new List(); } diff --git a/LadybugTools_Adapter/AdapterActions/Execute/RunSimulationCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/RunSimulationCommand.cs index 53a4e9c2..4303eec2 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/RunSimulationCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/RunSimulationCommand.cs @@ -84,11 +84,11 @@ private List RunCommand(RunSimulationCommand command, ActionConfig actio }; (string result, bool success) = ExecutePython(args, json); + m_executeSuccess = success; if (!success) { BH.Engine.Base.Compute.RecordError($"A python error occurred while running the command `{command.GetType().Name}`. Python output:\n{result}"); - m_executeSuccess = success; return new List(); } diff --git a/LadybugTools_Adapter/AdapterActions/Execute/SolarRadiationPlotCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/SolarRadiationPlotCommand.cs index 3d094731..60258f80 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/SolarRadiationPlotCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/SolarRadiationPlotCommand.cs @@ -107,11 +107,11 @@ private List RunCommand(SolarRadiationPlotCommand command, ActionConfig }; (string result, bool success) = ExecutePython(args, json); + m_executeSuccess = success; if (!success) { BH.Engine.Base.Compute.RecordError($"A python error occurred while running the command `{command.GetType().Name}`. Python output:\n{result}"); - m_executeSuccess = success; return new List(); } @@ -119,9 +119,7 @@ private List RunCommand(SolarRadiationPlotCommand command, ActionConfig try { - CustomObject obj = (CustomObject)BH.Engine.Serialiser.Convert.FromJson(result); - PlotInformation info = Convert.ToPlotInformation(obj, new SolarRadiationData()); - m_executeSuccess = true; + PlotInformation info = (PlotInformation)BH.Engine.Serialiser.Convert.FromJson(result); return new List() { info }; } catch (Exception ex) diff --git a/LadybugTools_Adapter/AdapterActions/Execute/SunPathPlotCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/SunPathPlotCommand.cs index 6a001971..65c97aa3 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/SunPathPlotCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/SunPathPlotCommand.cs @@ -85,11 +85,11 @@ private List RunCommand(SunPathPlotCommand command, ActionConfig actionC }; (string result, bool success) = ExecutePython(args, json); + m_executeSuccess = success; if (!success) { BH.Engine.Base.Compute.RecordError($"A python error occurred while running the command `{command.GetType().Name}`. Python output:\n{result}"); - m_executeSuccess = success; return new List(); } @@ -97,9 +97,7 @@ private List RunCommand(SunPathPlotCommand command, ActionConfig actionC try { - CustomObject obj = (CustomObject)BH.Engine.Serialiser.Convert.FromJson(result); - PlotInformation info = Convert.ToPlotInformation(obj, new SunPathData()); - m_executeSuccess = true; + PlotInformation info = (PlotInformation)BH.Engine.Serialiser.Convert.FromJson(result); return new List() { info }; } catch (Exception ex) diff --git a/LadybugTools_Adapter/AdapterActions/Execute/UTCIHeatPlotCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/UTCIHeatPlotCommand.cs index cc247711..d57c56aa 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/UTCIHeatPlotCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/UTCIHeatPlotCommand.cs @@ -69,16 +69,12 @@ private List RunCommand(UTCIHeatPlotCommand command, ActionConfig action List colours = command.BinColours.Select(x => x.ToHexCode()).ToList(); - string hexColours = $"[\"{string.Join("\",\"", colours)}\"]"; - if (hexColours == "[\"\"]") - hexColours = "[]"; - string epwFile = System.IO.Path.GetFullPath(command.EPWFile.GetFullFileName()); - Dictionary dict = new Dictionary() + Dictionary dict = new Dictionary() { - { "external_comfort", BH.Engine.Serialiser.Convert.ToJson(command.ExternalComfort) }, - { "bin_colours", hexColours }, + { "external_comfort", command.ExternalComfort }, + { "bin_colours", colours }, { "save_path", command.OutputLocation.Replace('\\', '/') } }; @@ -92,11 +88,11 @@ private List RunCommand(UTCIHeatPlotCommand command, ActionConfig action }; (string result, bool success) = ExecutePython(args, json); + m_executeSuccess = success; if (!success) { BH.Engine.Base.Compute.RecordError($"A python error occurred while running the command `{command.GetType().Name}`. Python output:\n{result}"); - m_executeSuccess = success; return new List(); } @@ -105,9 +101,8 @@ private List RunCommand(UTCIHeatPlotCommand command, ActionConfig action try { CustomObject obj = (CustomObject)BH.Engine.Serialiser.Convert.FromJson(result); - PlotInformation info = Convert.ToPlotInformation(obj, new UTCIData()); - ExternalComfort ec = BH.Engine.Serialiser.Convert.FromJson((string)obj.CustomData["external_comfort"]) as ExternalComfort; - m_executeSuccess = true; + PlotInformation info = (PlotInformation)obj.CustomData["info"]; + ExternalComfort ec = (ExternalComfort)obj.CustomData["external_comfort"]; return new List() { info, ec }; } catch (Exception ex) diff --git a/LadybugTools_Adapter/AdapterActions/Execute/WalkabilityPlotCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/WalkabilityPlotCommand.cs index 00ced3e1..c433d88f 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/WalkabilityPlotCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/WalkabilityPlotCommand.cs @@ -62,9 +62,9 @@ private List RunCommand(WalkabilityPlotCommand command, ActionConfig act string epwFile = System.IO.Path.GetFullPath(command.EPWFile.GetFullFileName()); - Dictionary dict = new Dictionary() + Dictionary dict = new Dictionary() { - { "external_comfort", BH.Engine.Serialiser.Convert.ToJson(command.ExternalComfort) }, + { "external_comfort", command.ExternalComfort }, { "save_path", command.OutputLocation.Replace('\\', '/') } }; @@ -77,11 +77,11 @@ private List RunCommand(WalkabilityPlotCommand command, ActionConfig act }; (string result, bool success) = ExecutePython(args, json); + m_executeSuccess = success; if (!success) { BH.Engine.Base.Compute.RecordError($"A python error occurred while running the command `{command.GetType().Name}`. Python output:\n{result}"); - m_executeSuccess = success; return new List(); } @@ -90,9 +90,8 @@ private List RunCommand(WalkabilityPlotCommand command, ActionConfig act try { CustomObject obj = (CustomObject)BH.Engine.Serialiser.Convert.FromJson(result); - PlotInformation info = Convert.ToPlotInformation(obj, new UTCIData()); - ExternalComfort ec = BH.Engine.Serialiser.Convert.FromJson((string)obj.CustomData["external_comfort"]) as ExternalComfort; - m_executeSuccess = true; + PlotInformation info = (PlotInformation)obj.CustomData["info"]; + ExternalComfort ec = (ExternalComfort)obj.CustomData["external_comfort"]; return new List() { info, ec }; } catch (Exception ex) diff --git a/LadybugTools_Adapter/AdapterActions/Execute/WindroseCommand.cs b/LadybugTools_Adapter/AdapterActions/Execute/WindroseCommand.cs index 962b6d69..b03215ca 100644 --- a/LadybugTools_Adapter/AdapterActions/Execute/WindroseCommand.cs +++ b/LadybugTools_Adapter/AdapterActions/Execute/WindroseCommand.cs @@ -70,7 +70,7 @@ private List RunCommand(WindroseCommand command, ActionConfig actionConf Dictionary dict = new Dictionary() { { "analysis_period", command.AnalysisPeriod }, - { "cmap", colourMap }, + { "colour_map", colourMap }, { "bins", command.NumberOfDirectionBins }, { "save_path", command.OutputLocation.Replace('\\', '/') } }; @@ -84,11 +84,11 @@ private List RunCommand(WindroseCommand command, ActionConfig actionConf }; (string result, bool success) = ExecutePython(args, json); + m_executeSuccess = success; if (!success) { BH.Engine.Base.Compute.RecordError($"A python error occurred while running the command `{command.GetType().Name}`. Python output:\n{result}"); - m_executeSuccess = success; return new List(); } @@ -96,9 +96,7 @@ private List RunCommand(WindroseCommand command, ActionConfig actionConf try { - CustomObject obj = (CustomObject)BH.Engine.Serialiser.Convert.FromJson(result); - PlotInformation info = Convert.ToPlotInformation(obj, new WindroseData()); - m_executeSuccess = true; + PlotInformation info = (PlotInformation)BH.Engine.Serialiser.Convert.FromJson(result); return new List() { info }; } catch (Exception ex) diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/from_bhom.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/from_bhom.py index 01ea394f..558b09a7 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/from_bhom.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/from_bhom.py @@ -19,7 +19,7 @@ class DataType(): def from_dict(cls, d) -> dict: d["type"] = "DataTypeBase" d["data_type"] = d["data__type"] - return d #due to Header.from_dict() not handling already deserialised objects, this should just return the correct dictionary instead of the DataType object. + return DataTypeBase.from_dict(d) class AnalysisPeriod(): @classmethod @@ -27,18 +27,24 @@ def from_dict(cls, d) -> dict: d["st_hour"] = d["start_hour"] d["st_day"] = d["start_day"] d["st_month"] = d["start_month"] - return d + return AnalysisPeriodBase.from_dict(d) class HourlyContinuousCollection(): @classmethod def from_dict(cls, d) -> dict: d["type"] = "HourlyContinuous" + #see comment in Header() below for the reason the header is converted to a dict. + d["header"] = d["header"].to_dict() return HC.from_dict(d) class Header(): @classmethod def from_dict(cls, d) -> dict: - return d + #convert parts of header from class to dictionary so that HeaderBase.from_dict() still works (for some reason ladybug hasn't used a JSONDecoder for json decoding...) + #this works because the python json decoder works depth first. + d["data_type"] = d["data_type"].to_dict() + d["analysis_period"] = d["analysis_period"].to_dict() + return HeaderBase.from_dict(d) _TYPES: list[type] = [ EnergyMaterial, diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/external_comfort.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/external_comfort.py index 92b44f37..0d71feef 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/external_comfort.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/external_comfort.py @@ -8,6 +8,6 @@ #Originally this method converted from json and then back to json, however the bhom_callable decorator does this automatically. #In order to allow this to still exist as callable from BHoM, this method was simplified to just return. -@bhom_wrapper.bhom_callable("external_comfort", argument_types = {"external_comfort", ExternalComfort}, encoder_cls=LBTBHoMJSONEncoder, decoder_cls=LBTBHoMJSONDecoder) +@bhom_wrapper.bhom_callable("external_comfort", argument_types = {"external_comfort": ExternalComfort}, encoder_cls=LBTBHoMJSONEncoder, decoder_cls=LBTBHoMJSONDecoder) def main(external_comfort: ExternalComfort, **kwargs) -> None: return external_comfort diff --git a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/simulation_result.py b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/simulation_result.py index cbf77267..574bcb8c 100644 --- a/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/simulation_result.py +++ b/LadybugTools_Engine/Python/src/ladybugtools_toolkit/bhom/wrapped/simulation_result.py @@ -6,6 +6,6 @@ #see external_comfort.py -@bhom_wrapper.bhom_callable("simulation_result", argument_types = {"simulation_result", SimulationResult}, encoder_cls=LBTBHoMJSONEncoder, decoder_cls=LBTBHoMJSONDecoder) +@bhom_wrapper.bhom_callable("simulation_result", argument_types = {"simulation_result": SimulationResult}, encoder_cls=LBTBHoMJSONEncoder, decoder_cls=LBTBHoMJSONDecoder) def main(simulation_result: SimulationResult, **kwargs) -> None: return simulation_result diff --git a/LadybugTools_oM/Versioning93.json b/LadybugTools_oM/Versioning93.json new file mode 100644 index 00000000..ff4ffb92 --- /dev/null +++ b/LadybugTools_oM/Versioning93.json @@ -0,0 +1,10 @@ +{ + "Property": { + "ToNew": { + "BH.oM.LadybugTools.AnalysisPeriod.TimeStep": "BH.oM.LadybugTools.AnalysisPeriod.Timestep" + }, + "ToOld": { + "BH.oM.LadybugTools.AnalysisPeriod.Timestep": "BH.oM.LadybugTools.AnalysisPeriod.TimeStep" + } + } +} From 3611285b6faa511caa6de2cc7caa2f5578ceb9a7 Mon Sep 17 00:00:00 2001 From: Tom Kingstone Date: Wed, 26 Aug 2026 08:42:00 +0100 Subject: [PATCH 14/15] rename versioning file --- LadybugTools_oM/{Versioning93.json => Versioning_93.json} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename LadybugTools_oM/{Versioning93.json => Versioning_93.json} (100%) diff --git a/LadybugTools_oM/Versioning93.json b/LadybugTools_oM/Versioning_93.json similarity index 100% rename from LadybugTools_oM/Versioning93.json rename to LadybugTools_oM/Versioning_93.json From 2f41d2759c02bfac03dc228bfe4c635eff5f7339 Mon Sep 17 00:00:00 2001 From: Tom Kingstone Date: Wed, 26 Aug 2026 14:04:33 +0100 Subject: [PATCH 15/15] name -> identifier --- LadybugTools_oM/Versioning_93.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/LadybugTools_oM/Versioning_93.json b/LadybugTools_oM/Versioning_93.json index ff4ffb92..30497177 100644 --- a/LadybugTools_oM/Versioning_93.json +++ b/LadybugTools_oM/Versioning_93.json @@ -1,10 +1,12 @@ { "Property": { "ToNew": { - "BH.oM.LadybugTools.AnalysisPeriod.TimeStep": "BH.oM.LadybugTools.AnalysisPeriod.Timestep" + "BH.oM.LadybugTools.AnalysisPeriod.TimeStep": "BH.oM.LadybugTools.AnalysisPeriod.Timestep", + "BH.oM.LadybugTools.SimulationResult.Name": "BH.oM.LadybugTools.SimulationResult.Identifier" }, "ToOld": { - "BH.oM.LadybugTools.AnalysisPeriod.Timestep": "BH.oM.LadybugTools.AnalysisPeriod.TimeStep" + "BH.oM.LadybugTools.AnalysisPeriod.Timestep": "BH.oM.LadybugTools.AnalysisPeriod.TimeStep", + "BH.oM.LadybugTools.SimulationResult.Identifier": "BH.oM.LadybugTools.SimulationResult.Name" } } }