Skip to content

Commit

Permalink
added convinience methods to improve readability
Browse files Browse the repository at this point in the history
  • Loading branch information
chenkasirer committed Feb 12, 2024
1 parent 9d10aed commit 6a3377d
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 40 deletions.
8 changes: 2 additions & 6 deletions src/compas_timber/connections/french_ridge_lap.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,17 +76,13 @@ def joint_type(self):

@property
def cutting_plane_top(self):
angles_faces = self.beam_side_incidence(self.beam_a, self.beam_b, ignore_ends=True)
index = max(angles_faces, key=angle_vectors.get)
cfr = self.beam_b.faces[index]
_, cfr = self.get_face_most_towards_beam(self.beam_a, self.beam_b, ignore_ends=True)
cfr = Frame(cfr.point, cfr.xaxis, cfr.yaxis * -1.0) # flip normal
return cfr

@property
def cutting_plane_bottom(self):
angles_faces = self.beam_side_incidence(self.beam_b, self.beam_a, ignore_ends=True)
index = max(angles_faces, key=angle_vectors.get)
cfr = self.beam_a.faces[index]
_, cfr = self.get_face_most_towards_beam(self.beam_b, self.beam_b, ignore_ends=True)
return cfr

def restore_beams_from_keys(self, assemly):
Expand Down
88 changes: 71 additions & 17 deletions src/compas_timber/connections/joint.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,52 +152,106 @@ def ends(self):
return self._ends

@staticmethod
def beam_side_incidence(beam1, beam2, ignore_ends=True):
"""Returns a map of face indices of beam2 and the angle of their normal with beam1's centerline.
def get_face_most_towards_beam(beam_a, beam_b, ignore_ends=True):
"""Of all the faces of `beam_b`, returns the one whose normal most faces `beam_a`.
This is done by calculating the inner-product of `beam_a`'s centerline which each of the face normals of `beam_b`.
The face with the result closest to 1 is chosen.
Parameters
----------
beam_a : :class:`~compas_timber.parts.Beam`
The beam that attaches with one of its ends to `beam_b`.
beam_b : :class:`~compas_timber.parts.Beam`
The other beam.
ignore_ends : bool, optional
If True, the faces at each end of `beam_b` are ignored.
Returns
-------
tuple(face_index, :class:`~compas.geometry.Frame`)
Tuple containing the index of the chosen face and a frame at the center of if.
"""
face_dict = Joint._beam_side_incidence(beam_a, beam_b, ignore_ends)
face_index = max(face_dict, key=face_dict.get) # type: ignore
return face_index, beam_b[face_index]


@staticmethod
def get_face_most_ortho_to_beam(beam_a, beam_b, ignore_ends=True):
"""Of all the faces of `beam_b`, returns the one whose normal is most orthogonal to `beam_a`.
This is done by calculating the inner-product of `beam_a`'s centerline which each of the face normals of `beam_b`.
The face with the result closest to 0 is chosen.
Parameters
----------
beam_a : :class:`~compas_timber.parts.Beam`
The beam that attaches with one of its ends to `beam_b`.
beam_b : :class:`~compas_timber.parts.Beam`
The other beam.
ignore_ends : bool, optional
If True, the faces at each end of `beam_b` are ignored.
Returns
-------
tuple(face_index, :class:`~compas.geometry.Frame`)
Tuple containing the index of the chosen face and a frame at the center of if.
"""
face_dict = Joint._beam_side_incidence(beam_a, beam_b, ignore_ends)
face_index = min(face_dict, key=face_dict.get) # type: ignore
return face_index, beam_b[face_index]


@staticmethod
def _beam_side_incidence(beam_a, beam_b, ignore_ends=True):
"""Returns a map of face indices of beam_b and the angle of their normal with beam_a's centerline.
This is used to find a cutting plane when joining the two beams.
Parameters
----------
beam1 : :class:`~compas_timber.parts.Beam`
The beam that attaches with one of its ends to the side of Beam2.
beam2 : :class:`~compas_timber.parts.Beam`
beam_a : :class:`~compas_timber.parts.Beam`
The beam that attaches with one of its ends to the side of beam_b.
beam_b : :class:`~compas_timber.parts.Beam`
The other beam.
ignore_ends : bool, optional
If True, only the first four faces of `beam2` are considered. Otherwise all faces are considered.
If True, only the first four faces of `beam_b` are considered. Otherwise all faces are considered.
Examples
--------
>>> face_angles = Joint.beam_side_incidence(beam1, beam2)
>>> face_angles = Joint.beam_side_incidence(beam_a, beam_b)
>>> closest_face_index = min(face_angles, key=face_angles.get)
>>> cutting_plane = beam2.faces[closest_face_index]
>>> cutting_plane = beam_b.faces[closest_face_index]
Returns
-------
dict(int, float)
A map of face indices and their respective angle with beam1's centerline.
A map of face indices of beam_b and their respective angle with beam_a's centerline.
"""
# find the orientation of beam1's centerline so that it's pointing outward of the joint
# find the orientation of beam_a's centerline so that it's pointing outward of the joint
# find the closest end
p1x, _ = intersection_line_line(beam1.centerline, beam2.centerline)
p1x, _ = intersection_line_line(beam_a.centerline, beam_b.centerline)
if p1x is None:
raise AssertionError("No intersection found")

end, _ = beam1.endpoint_closest_to_point(Point(*p1x))
end, _ = beam_a.endpoint_closest_to_point(Point(*p1x))

if end == "start":
centerline_vec = beam1.centerline.vector
centerline_vec = beam_a.centerline.vector
else:
centerline_vec = beam1.centerline.vector * -1
centerline_vec = beam_a.centerline.vector * -1

if ignore_ends:
beam2_faces = beam2.faces[:4]
beam_b_faces = beam_b.faces[:4]
else:
beam2_faces = beam2.faces
beam_b_faces = beam_b.faces

face_angles = {}
for face_index, face in enumerate(beam2_faces):
for face_index, face in enumerate(beam_b_faces):
face_angles[face_index] = angle_vectors(face.normal, centerline_vec)

return face_angles
11 changes: 3 additions & 8 deletions src/compas_timber/connections/l_butt.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,21 +86,16 @@ def joint_type(self):
def get_main_cutting_plane(self):
assert self.main_beam and self.cross_beam

face_angles = self.beam_side_incidence(self.main_beam, self.cross_beam, ignore_ends=False)
face_index = min(face_angles, key=face_angles.get) # type: ignore
if face_index in [5, 6]:
index, cfr = self.get_face_most_ortho_to_beam(self.main_beam, self.cross_beam, ignore_ends=False)
if index in [5, 6]: # end faces
raise BeamJoinningError(beams=self.beams, joint=self, debug_info="Can't join to end faces")

cfr = self.cross_beam.faces[face_index]
cfr = Frame(cfr.point, cfr.xaxis, cfr.yaxis * -1.0) # flip normal
return cfr

def get_cross_cutting_plane(self):
assert self.main_beam and self.cross_beam

face_angles = self.beam_side_incidence(self.cross_beam, self.main_beam)
face_index = max(face_angles, key=face_angles.get) # type: ignore
cfr = self.main_beam.faces[face_index]
_, cfr = self.get_face_most_towards_beam(self.cross_beam, self.main_beam)
return cfr

def restore_beams_from_keys(self, assemly):
Expand Down
8 changes: 2 additions & 6 deletions src/compas_timber/connections/lap_joint.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,18 +133,14 @@ def get_main_cutting_frame(self):
beam_a, beam_b = self.beams
assert beam_a and beam_b

angles_faces = self.beam_side_incidence(beam_a, beam_b)
f_index = max(angles_faces, key=angles_faces.get) # type: ignore
cfr = beam_b.faces[f_index]
_, cfr = self.get_face_most_towards_beam(beam_a, beam_b)
cfr = Frame(cfr.point, cfr.yaxis, cfr.xaxis) # flip normal towards the inside of main beam
return cfr

def get_cross_cutting_frame(self):
beam_a, beam_b = self.beams
assert beam_a and beam_b

angles_faces = self.beam_side_incidence(beam_b, beam_a)
cfr = max(angles_faces, key=lambda x: x[0])[1]
_, cfr = self.get_face_most_towards_beam(beam_b, beam_a)
return cfr

def _create_negative_volumes(self):
Expand Down
4 changes: 1 addition & 3 deletions src/compas_timber/connections/t_butt.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,7 @@ def joint_type(self):
def get_cutting_plane(self):
assert self.main_beam and self.cross_beam # should never happen

angles_faces = self.beam_side_incidence(self.main_beam, self.cross_beam)
frame_index = min(angles_faces, key=angles_faces.get) # type: ignore
cfr = self.cross_beam.faces[frame_index]
_, cfr = self.get_face_most_ortho_to_beam(self.main_beam, self.cross_beam)
cfr = Frame(cfr.point, cfr.yaxis, cfr.xaxis) # flip normal towards the inside of main beam
return cfr

Expand Down

0 comments on commit 6a3377d

Please sign in to comment.