Skip to content

Commit

Permalink
Merge branch 'MAX_DISTANCE_to_joint_rules' of https://github.com/gram…
Browse files Browse the repository at this point in the history
…aziokohler/compas_timber into MAX_DISTANCE_to_joint_rules
  • Loading branch information
obucklin committed Feb 7, 2025
2 parents d3c60e1 + e41310a commit 558ebd6
Show file tree
Hide file tree
Showing 10 changed files with 214 additions and 80 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased

### Added
* Added `distance_segment_segment` to `compas_timber.utils`
* Added `BTLxFromGeometryDefinition` class to replace the depricated `FeatureDefinition`. This allows deferred calculation of BTLx processings.
* Added `from_shapes_and_element` class method to `Drilling`, `JackRafterCut`, and `DoubleCut` as a wrapper for their geometry based constructors for use with `BTLxFromGeometryDefinition`.
* Added `YButtJoint` which joins the ends of three joints where the `cross_beams` get a miter cut and the `main_beam` gets a double cut.
Expand All @@ -30,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Fixed `restore_beams_from_keys` in `LMiterJoint` to use the correct variable names.
* Reworked `DoubleCut` to more reliably produce the feature and geometry with the `from_planes_and_element` class method.
* Renamed `intersection_box_line()` to `intersection_beam_line_param()`, which now take a beam input and outputs the intersecting ref_face_index.
* Added `max_distance` argument to `JointRule` subclasses and GH components so that max_distance can be set for each joint rule individually.
* Changed referenced to `beam` in `Drilling` to `element`.
* Changed `Drill Hole` and `Trim Feature` GH components to generate the relevant `BTLxProcessing` type rather than the deprecated `FeatureDefinition` type.

Expand Down
80 changes: 45 additions & 35 deletions src/compas_timber/design/workflow.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
from itertools import combinations
from compas.geometry import distance_point_point
from compas.geometry import intersection_line_line
from compas.geometry import closest_point_on_segment

from compas_timber.connections import ConnectionSolver
from compas_timber.connections import JointTopology
from compas_timber.connections import LMiterJoint
from compas_timber.connections import TButtJoint
from compas_timber.connections import XLapJoint
from compas_timber.utils import distance_segment_segment
from compas_timber.utils import intersection_line_line_param


Expand Down Expand Up @@ -95,8 +93,8 @@ def joints_from_beams_and_rules(elements, rules, max_distance=1e-6):
pair = element_pairs.pop()
match_found = False
for rule in direct_rules:
if rule.contains(pair): # see if pair is used in a direct rule
if rule.comply(pair, model_max_distance=max_distance): # see if pair complies with max distance
if rule.contains(pair): # see if pair is used in a direct rule
if rule.comply(pair, model_max_distance=max_distance): # see if pair complies with max distance
joint_defs.append(JointDefinition(rule.joint_type, rule.elements, **rule.kwargs))
match_found = True
break
Expand All @@ -121,9 +119,21 @@ def joints_from_beams_and_rules(elements, rules, max_distance=1e-6):


class DirectRule(JointRule):
"""Creates a Joint Rule that directly joins multiple elements."""
"""Creates a Joint Rule that directly joins multiple elements.
def __init__(self, joint_type, elements, max_distance = None, **kwargs):
Parameters
----------
joint_type : cls(:class:`~compas_timber.connections.Joint`)
The joint type to be applied to the elements.
elements : list(:class:`~compas_timber.elements.TimberElement`)
The elements to be joined.
max_distance : float, optional
The maximum distance to consider two elements as intersecting.
kwargs : dict
The keyword arguments to be passed to the joint.
"""

def __init__(self, joint_type, elements, max_distance=None, **kwargs):
self.elements = elements
self.joint_type = joint_type
self.max_distance = max_distance
Expand All @@ -144,7 +154,10 @@ def contains(self, elements):
raise UserWarning("unable to comply direct joint element sets")

def comply(self, elements, model_max_distance=1e-6):

"""Returns True if the given elements comply with this DirectRule.
only checks if the distance between the centerlines of the elements is less than the max_distance.
allows joint topology overrides.
"""
if self.max_distance:
max_distance = self.max_distance
else:
Expand All @@ -158,9 +171,25 @@ def comply(self, elements, model_max_distance=1e-6):


class CategoryRule(JointRule):
"""Based on the category attribute attached to the elements, this rule assigns"""
"""Based on the category attribute attached to the elements, this rule assigns
def __init__(self, joint_type, category_a, category_b, topos=None, max_distance = None, **kwargs):
Parameters
----------
joint_type : cls(:class:`~compas_timber.connections.Joint`)
The joint type to be applied to the elements.
category_a : str
The category of the first element.
category_b : str
The category of the second element.
topos : list(:class:`~compas_timber.connections.JointTopology`), optional
The topologies that are supported by this rule.
max_distance : float, optional
The maximum distance to consider two elements as intersecting.
kwargs : dict
The keyword arguments to be passed to the joint.
"""

def __init__(self, joint_type, category_a, category_b, topos=None, max_distance=None, **kwargs):
self.joint_type = joint_type
self.category_a = category_a
self.category_b = category_b
Expand Down Expand Up @@ -233,7 +262,7 @@ class TopologyRule(JointRule):
The keyword arguments to be passed to the joint.
"""

def __init__(self, topology_type, joint_type, max_distance = None, **kwargs):
def __init__(self, topology_type, joint_type, max_distance=None, **kwargs):
self.topology_type = topology_type
self.joint_type = joint_type
self.max_distance = max_distance
Expand All @@ -244,7 +273,11 @@ def ToString(self):
return repr(self)

def __repr__(self):
return "{}({}, {})".format(TopologyRule, self.topology_type, self.joint_type, self.max_distance)
return "{}({}, {})".format(
TopologyRule,
self.topology_type,
self.joint_type,
)

def comply(self, elements, model_max_distance=1e-6):
if self.max_distance:
Expand Down Expand Up @@ -440,26 +473,3 @@ def add_joint_error(self, error):
self.joint_errors.extend(error)
else:
self.joint_errors.append(error)


def distance_segment_segment(segment_a, segment_b):
"""Computes the distance between two segments.
Parameters
----------
segment_a : tuple(tuple(float, float, float), tuple(float, float, float))
The first segment, defined by two points.
segment_b : tuple(tuple(float, float, float), tuple(float, float, float))
The second segment, defined by two points.
Returns
-------
float
The distance between the two segments.
"""
pta, ptb = intersection_line_line(segment_a, segment_b)
pt_seg_a = closest_point_on_segment(pta, segment_a)
pt_seg_b = closest_point_on_segment(ptb, segment_b)

return distance_point_point(pt_seg_a, pt_seg_b)
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def arg_names(self):
names = inspect.getargspec(self.joint_type.__init__)[0][1:]
for i in range(2):
names[i] += " category"
return [name for name in names if (name != "key") and (name != "frame")]+["max_distance"]
return [name for name in names if (name != "key") and (name != "frame")] + ["max_distance"]

def AppendAdditionalMenuItems(self, menu):
for name in self.classes.keys():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def RunScript(self, *args):
return Rules

def arg_names(self):
return inspect.getargspec(self.joint_type.__init__)[0][1:]+["max_distance"]
return inspect.getargspec(self.joint_type.__init__)[0][1:] + ["max_distance"]

def AppendAdditionalMenuItems(self, menu):
for name in self.classes.keys():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def arg_start_index(self):

@property
def arg_names(self):
return inspect.getargspec(self.joint_type.__init__)[0][self.arg_start_index :]+["max_distance"]
return inspect.getargspec(self.joint_type.__init__)[0][self.arg_start_index :] + ["max_distance"]

def AppendAdditionalMenuItems(self, menu):
for name in self.classes.keys():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def RunScript(self, *args):

def arg_names(self):
names = inspect.getargspec(self.joint_type.__init__)[0][3:]
return [name for name in names if (name != "key") and (name != "frame")]+["max_distance"]
return [name for name in names if (name != "key") and (name != "frame")] + ["max_distance"]

def AppendAdditionalMenuItems(self, menu):
for name in self.classes.keys():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def RunScript(self, *args):

def arg_names(self):
names = inspect.getargspec(self.joint_type.__init__)[0][3:]
return [name for name in names if (name != "key") and (name != "frame")]+["max_distance"]
return [name for name in names if (name != "key") and (name != "frame")] + ["max_distance"]

def AppendAdditionalMenuItems(self, menu):
for name in self.classes.keys():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def RunScript(self, *args):

def arg_names(self):
names = inspect.getargspec(self.joint_type.__init__)[0][3:]
return [name for name in names if (name != "key") and (name != "frame")]+["max_distance"]
return [name for name in names if (name != "key") and (name != "frame")] + ["max_distance"]

def AppendAdditionalMenuItems(self, menu):
for name in self.classes.keys():
Expand Down
26 changes: 25 additions & 1 deletion src/compas_timber/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
from compas.geometry import Frame
from compas.geometry import Transformation
from compas.geometry import intersection_line_plane
from compas.geometry import closest_point_on_segment
from compas.geometry import intersection_line_line


def intersection_line_line_param(line1, line2, max_distance=1e-6, limit_to_segments=True, tol=1e-6):
Expand Down Expand Up @@ -171,4 +173,26 @@ def intersection_line_beam_param(line, beam, ignore_ends=False):
return [Point(*coords) for coords in pts], ref_side_indices


__all__ = ["intersection_line_line_param", "intersection_line_plane_param", "intersection_line_beam_param"]
def distance_segment_segment(segment_a, segment_b):
"""Computes the distance between two segments.
Parameters
----------
segment_a : tuple(tuple(float, float, float), tuple(float, float, float))
The first segment, defined by two points.
segment_b : tuple(tuple(float, float, float), tuple(float, float, float))
The second segment, defined by two points.
Returns
-------
float
The distance between the two segments.
"""
pta, ptb = intersection_line_line(segment_a, segment_b)
pt_seg_a = closest_point_on_segment(pta, segment_a)
pt_seg_b = closest_point_on_segment(ptb, segment_b)
return distance_point_point(pt_seg_a, pt_seg_b)


__all__ = ["intersection_line_line_param", "intersection_line_plane_param", "intersection_line_beam_param", "distance_segment_segment"]
Loading

0 comments on commit 558ebd6

Please sign in to comment.