Skip to content

Feats: extract additional beam/joints data for results csv export #143

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ Open your Grasshopper canvas and search for the `DF` components!
The full documentation, with tutorials, automatic documentation for GHComponents and PythonAPI is available [here](https://diffcheckorg.github.io/diffCheck/).



## Roadmap

```mermaid
Expand Down
74 changes: 37 additions & 37 deletions src/gh/components/DF_csv_exporter/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,44 +66,29 @@ def __init__(self):
"export_dist",
input_indx, X_cord, Y_cord)

def _get_id(self,
idx: int,
i_result: DFVizResults
) -> str:
""" Get the ID of the element """
counter = 0

if self.prefix == "beam":
return idx
elif self.prefix == "joint":
for idx_b, beam in enumerate(i_result.assembly.beams):
for idx_j, joint in enumerate(beam.joints):
if counter == idx:
return f"{idx_b}--{idx_j}--{0}"
counter += 1
elif self.prefix == "joint_face":
for idx_b, beam in enumerate(i_result.assembly.beams):
for idx_j, joint in enumerate(beam.joints):
for idx_f, face in enumerate(joint.faces):
if counter == idx:
return f"{idx_b}--{idx_j}--{idx_f}"
counter += 1

def _prepare_row(self,
idx: int,
i_result: DFVizResults
) -> typing.Dict:
) -> 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 containng as keys the header and as items the values to be written in the CSV file
: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 [self._get_id(idx, i_result), invalid_type, invalid_type, invalid_type, invalid_type, invalid_type, invalid_type]
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)
Expand All @@ -112,27 +97,47 @@ def _prepare_row(self,
rmse = round(i_result.distances_rmse[idx], 4)
mean = round(i_result.distances_mean[idx], 4)

row: typing.Dict = {
f"{self.prefix} id": self._get_id(idx, i_result),
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],
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: Dict of values to be written in 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
Expand All @@ -157,20 +162,15 @@ def RunScript(self,
i_file_name: str,
i_export_seperate_files: bool,
i_export_distances: bool,
i_result):
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)

if len(i_result.assembly.beams) == len(i_result.source):
self.prefix = "beam"
elif len(i_result.assembly.all_joints) == len(i_result.source):
self.prefix = "joint"
elif len(i_result.assembly.all_joint_faces) == len(i_result.source):
self.prefix = "joint_face"
self.prefix = i_result.analysis_type

if i_export_seperate_files:
for idx in range(len(i_result.source)):
Expand Down
11 changes: 6 additions & 5 deletions src/gh/components/DF_export_results/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import System

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


def add_button(self,
Expand All @@ -24,7 +24,7 @@ def add_button(self,
"""
param = ghenv.Component.Params.Input[indx] # noqa: F821
if param.SourceCount == 0:
button = gh.Kernel.Special.GH_ButtonObject()
button = Grasshopper.Kernel.Special.GH_ButtonObject()
button.NickName = ""
button.Description = ""
button.CreateAttributes()
Expand All @@ -33,7 +33,7 @@ def add_button(self,
Y_param_coord - (button.Attributes.Bounds.Height / 2 - 0.1)
)
button.Attributes.ExpireLayout()
gh.Instances.ActiveCanvas.Document.AddObject(button, False)
Grasshopper.Instances.ActiveCanvas.Document.AddObject(button, False)
ghenv.Component.Params.Input[indx].AddSource(button) # noqa: F821

class DFExportResults(component):
Expand All @@ -52,7 +52,8 @@ def RunScript(self, i_dump: bool, i_export_dir: str, i_results):
if i_dump is None or i_export_dir is None or i_results is None:
return None

o_path = None
if i_dump:
i_results.dump_serialization(i_export_dir)
o_path = i_results.dump_serialization(i_export_dir)

return None
return o_path
11 changes: 10 additions & 1 deletion src/gh/components/DF_export_results/metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@
"typeHintID": "ghdoc"
}
],
"outputParameters": []
"outputParameters": [
{
"name": "o_path",
"nickname": "o_path",
"description": "The file path of the generated .diffCheck files.",
"optional": false,
"sourceCount": 0,
"graft": false
}
]
}
}
15 changes: 1 addition & 14 deletions src/gh/components/DF_preview_assembly/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import System

import typing

import Rhino.Geometry as rg

from ghpythonlib.componentbase import executingcomponent as component
Expand Down Expand Up @@ -93,18 +91,7 @@ def DrawViewportWires(self, args):
## DFBeams
#######################################
if len(self._dfassembly.beams) > 1:
# beams' obb
df_cloud = diffCheck.diffcheck_bindings.dfb_geometry.DFPointCloud()
vertices_pt3d_rh : typing.List[rg.Point3d] = [vertex.to_rg_point3d() for vertex in beam.vertices]
df_cloud.points = [np.array([vertex.X, vertex.Y, vertex.Z]).reshape(3, 1) for vertex in vertices_pt3d_rh]
obb: rg.Brep = diffCheck.df_cvt_bindings.cvt_dfOBB_2_rhbrep(df_cloud.get_tight_bounding_box())
# args.Display.DrawBrepWires(obb, System.Drawing.Color.Red) ## keep for debugging

# axis arrow
obb_faces = obb.Faces
obb_faces = sorted(obb_faces, key=lambda face: rg.AreaMassProperties.Compute(face).Area)
obb_endfaces = obb_faces[:2]
beam_axis = rg.Line(obb_endfaces[0].GetBoundingBox(True).Center, obb_endfaces[1].GetBoundingBox(True).Center)
beam_axis = beam.axis
extension_length = 0.5 * diffCheck.df_util.get_doc_2_meters_unitf()
beam_axis.Extend(extension_length, extension_length)
args.Display.DrawArrow(beam_axis, System.Drawing.Color.Magenta)
Expand Down
57 changes: 55 additions & 2 deletions src/gh/diffCheck/diffCheck/df_error_estimation.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class NumpyEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, np.ndarray):
return obj.tolist()
elif isinstance(obj, Enum):
return obj.value # or use obj.name if you prefer
return super().default(obj)

class DFInvalidData(Enum):
Expand All @@ -41,7 +43,7 @@ class DFInvalidData(Enum):

class DFVizResults:
"""
This class compiles the resluts of the error estimation into one object
This class compiles the results of the error estimation into one object
"""
__serial_file_extenion: str = ".diffCheck"

Expand All @@ -61,6 +63,8 @@ def __init__(self, assembly):
self.distances_sd_deviation = []
self.distances = []

self._analysis_type: str = None

def __repr__(self):
return f"DFVizResults of({self.assembly})"

Expand Down Expand Up @@ -109,7 +113,8 @@ def dump_serialization(self, dir: str) -> str:

timestamp: str = datetime.now().strftime("%Y%m%d_%H%M%S")
assembly_name: str = self.assembly.name
serial_file_path = os.path.join(dir, f"{assembly_name}_{timestamp}{self.__serial_file_extenion}")
result_type: str = self.analysis_type
serial_file_path = os.path.join(dir, f"{assembly_name}_{result_type}_{timestamp}{self.__serial_file_extenion}")

try:
with open(serial_file_path, "w") as f:
Expand All @@ -135,6 +140,49 @@ def load_serialization(file_path: str) -> 'DFVizResults':
raise e
return obj

def _compute_dfresult_type(self):
"""
Detect if the DFVizResults object contains results of beam, joint of joint_face level analysis
"""
# check that source and target have the same length
if len(self.source) != len(self.target):
raise ValueError("Source and target have different length, cannot determine the type of analysis")
if len(self.assembly.beams) == len(self.source):
self._analysis_type = "beam"
elif len(self.assembly.all_joints) == len(self.source):
self._analysis_type = "joint"
elif len(self.assembly.all_joint_faces) == len(self.source):
self._analysis_type = "joint_face"
return self._analysis_type

def find_id(self, idx: int,) -> str:
"""
Return the ID in str format of the element. This func is used during
the csv export. With the following format:
- beam: idx
- joint: idx_b--idx_j--0
- joint_face: idx_b--idx_j--idx_f

:param idx: the index of the element
"""
counter = 0

if self.analysis_type == "beam":
return str(idx)
elif self.analysis_type == "joint":
for idx_b, beam in enumerate(self.assembly.beams):
for idx_j, joint in enumerate(beam.joints):
if counter == idx:
return f"{idx_b}--{idx_j}--{0}"
counter += 1
elif self.analysis_type == "joint_face":
for idx_b, beam in enumerate(self.assembly.beams):
for idx_j, joint in enumerate(beam.joints):
for idx_f, face in enumerate(joint.faces):
if counter == idx:
return f"{idx_b}--{idx_j}--{idx_f}"
counter += 1
return ""

def add(self, source, target, distances, sanity_check: DFInvalidData = DFInvalidData.VALID):

Expand Down Expand Up @@ -215,6 +263,11 @@ def filter_values_based_on_valuetype(self, settings):
def is_source_cloud(self):
return type(self.source[0]) is diffcheck_bindings.dfb_geometry.DFPointCloud

@property
def analysis_type(self):
self._analysis_type = self._compute_dfresult_type()
return self._analysis_type

# FIXME: ths is currently broken, we need to fix it
def df_cloud_2_df_cloud_comparison(
assembly: DFAssembly,
Expand Down
Loading
Loading