DFCsvExporter component

Contents

_images/icon10.png

DFCsvExporter component#

Exports distance calculation to csv.

Inputs:

i_dump (bool ,item)

A flag indicating whether to perform the export.

i_export_dir (str ,item)

The directory where the CSV file will be saved.

i_file_name (str ,item)

The name for the file.

i_export_seperate_files (bool ,item)

Whether to export one single file or seperate files per element.

i_export_distances (bool ,item)

Whether to export the calculated error distances for each point of the analysed point cloud.

i_result (ghdoc ,item)

The result of the distance calculation to export

Outputs:

Code:

#! python3

import System
import csv
import os
import typing

from ghpythonlib.componentbase import executingcomponent as component
import Grasshopper as gh

from diffCheck.df_error_estimation import DFInvalidData, DFVizResults


def add_bool_toggle(self,
    nickname: str,
    indx: int,
    X_param_coord: float,
    Y_param_coord: float,
    X_offset: int=87
    ) -> None:
    """
        Adds a boolean toggle to the component input

        :param nickname: the nickname of the value list
        :param indx: the index of the input parameter
        :param X_param_coord: the x coordinate of the input parameter
        :param Y_param_coord: the y coordinate of the input parameter
        :param X_offset: the offset of the value list from the input parameter
    """
    param = ghenv.Component.Params.Input[indx]  # noqa: F821
    if param.SourceCount == 0:
        toggle = gh.Kernel.Special.GH_BooleanToggle()
        toggle.NickName = nickname
        toggle.Description = "Toggle the value to use with DFVizSettings"
        toggle.CreateAttributes()
        toggle.Attributes.Pivot = System.Drawing.PointF(
            X_param_coord - (toggle.Attributes.Bounds.Width) - X_offset,
            Y_param_coord - (toggle.Attributes.Bounds.Height / 2 + 0.1)
            )
        toggle.Attributes.ExpireLayout()
        gh.Instances.ActiveCanvas.Document.AddObject(toggle, False)
        ghenv.Component.Params.Input[indx].AddSource(toggle)  # noqa: F821


class DFCsvExporter(component):
    def __init__(self):
        super(DFCsvExporter, self).__init__()
        self.prefix = ""
        self.counter = 0

        ghenv.Component.ExpireSolution(True)  # noqa: F821
        ghenv.Component.Attributes.PerformLayout()  # noqa: F821
        params = getattr(ghenv.Component.Params, "Input")  # noqa: F821
        for j in range(len(params)):
            Y_cord = params[j].Attributes.InputGrip.Y + 1
            X_cord = params[j].Attributes.Pivot.X + 10
            input_indx = j
            if "i_export_seperate_files" == params[j].NickName:
                add_bool_toggle(
                    ghenv.Component,  # noqa: F821
                    "export_asfiles",
                    input_indx, X_cord, Y_cord)
            if "i_export_distances" == params[j].NickName:
                add_bool_toggle(
                    ghenv.Component,  # noqa: F821
                    "export_dist",
                    input_indx, X_cord, Y_cord)

    def _prepare_row(self,
            idx: int,
            i_result: DFVizResults
        ) -> typing.Dict[str, typing.Any]:
        """
            Convert the results contained in the DFVizResults object to a dict to be written in the CSV file

            :param idx: Index of the element
            :param i_result: DFVizResults object containing all the values

            :return: Dict of values containing as keys the header and as items the values to be written in the CSV file
        """
        if i_result.sanity_check[idx].value != DFInvalidData.VALID.value:
            invalid_type = i_result.sanity_check[idx].name
            return {
                f"{self.prefix} id": i_result.find_id(idx),
                "invalid_type": invalid_type,
                "min_deviation": invalid_type,
                "max_deviation": invalid_type,
                "std_deviation": invalid_type,
                "rmse": invalid_type,
                "mean": invalid_type
            }

        distances = [round(value, 4) for value in i_result.distances[idx]]
        min_dev = round(i_result.distances_min_deviation[idx], 4)
        max_dev = round(i_result.distances_max_deviation[idx], 4)
        std_dev = round(i_result.distances_sd_deviation[idx], 4)
        rmse = round(i_result.distances_rmse[idx], 4)
        mean = round(i_result.distances_mean[idx], 4)

        row: typing.Dict[str, typing.Any] = {
            f"{self.prefix} id": i_result.find_id(idx),
            "distances": distances,
            "min_deviation": min_dev,
            "max_deviation": max_dev,
            "std_deviation": std_dev,
            "rmse": rmse,
            "mean": mean
        }

        # Add extra geometric info based on analysis type here:
        if i_result.analysis_type == "beam":
            row.update({
                "beam_length": i_result.assembly.beams[idx].length
            })
        elif i_result.analysis_type == "joint":
            # NB:: for conviniency, if there is only one beam, we add the lenght of the beam i nthe joint csv analysis output
            if i_result.assembly.has_only_one_beam:
                row.update({
                    "beam_length": i_result.assembly.beams[0].length
                })
            row.update({
                "joint_distance_to_beam_midpoint": i_result.assembly.compute_all_joint_distances_to_midpoint()[idx]
            })
        elif i_result.analysis_type == "joint_face":
            row.update({
                "jointface_angle": i_result.assembly.compute_all_joint_angles()[idx]
            })

        return row

    def _write_csv(self,
        csv_path: str,
        rows: typing.List[typing.Dict[str, typing.Any]],
        is_writing_only_distances: bool = False
        ) -> None:
        """
            Write the CSV file

            :param csv_path: Path of the CSV file
            :param rows: List of dictionaries containing values to be written in the CSV file
            :param is_writing_only_distances: Flag to check if to write ONLY distances or the whole analysis

            :return: None
        """
        with open(csv_path, mode='w', newline='') as file:
            writer = csv.writer(file, quoting=csv.QUOTE_MINIMAL)
            if is_writing_only_distances:
                writer.writerow(list(rows[0].keys())[:2])  # header
                element_id = [row[f"{self.prefix} id"] for row in rows]
                dist_rows = [row["distances"] for row in rows]
                for idx, dist_row in enumerate(dist_rows):
                    for dist in dist_row:
                        writer.writerow([element_id[idx], dist])
            else:
                rows = [{k: v for k, v in row.items() if k != "distances"} for row in rows]  # no distances
                writer.writerow(list(rows[0].keys()))  # header
                writer.writerows([list(row.values()) for row in rows])

    def RunScript(self,
            i_dump: bool,
            i_export_dir: str,
            i_file_name: str,
            i_export_seperate_files: bool,
            i_export_distances: bool,
            i_result: DFVizResults) -> None:

        csv_analysis_path: str = None
        csv_distances_path: str = None

        if i_dump:
            os.makedirs(i_export_dir, exist_ok=True)

            self.prefix = i_result.analysis_type

            if i_export_seperate_files:
                for idx in range(len(i_result.source)):
                    element_id = self._get_id(idx, i_result)
                    csv_analysis_path = os.path.join(i_export_dir, f"{i_file_name}_{self.prefix}_{element_id}.csv")
                    rows = [self._prepare_row(idx, i_result)]
                    self._write_csv(csv_analysis_path, rows)
                    if i_export_distances:
                        csv_distances_path = os.path.join(i_export_dir, f"{i_file_name}_{self.prefix}_{element_id}_distances.csv")
                        self._write_csv(csv_distances_path, rows, is_writing_only_distances=True)
            else:
                csv_analysis_path = os.path.join(i_export_dir, f"{i_file_name}.csv")
                merged_rows = [self._prepare_row(idx, i_result) for idx in range(len(i_result.source))]
                self._write_csv(csv_analysis_path, merged_rows)
                if i_export_distances:
                    csv_distances_path = os.path.join(i_export_dir, f"{i_file_name}_distances.csv")
                    self._write_csv(csv_distances_path, merged_rows, is_writing_only_distances=True)