From d43f79f3188cd6bed3fe46113a1addd13fcc0c06 Mon Sep 17 00:00:00 2001 From: obucklin Date: Mon, 28 Oct 2024 13:45:05 +0100 Subject: [PATCH 01/14] added generic fastener --- .../elements/fasteners/fastener.py | 200 ++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 src/compas_timber/elements/fasteners/fastener.py diff --git a/src/compas_timber/elements/fasteners/fastener.py b/src/compas_timber/elements/fasteners/fastener.py new file mode 100644 index 000000000..932759640 --- /dev/null +++ b/src/compas_timber/elements/fasteners/fastener.py @@ -0,0 +1,200 @@ +import math + +from compas.geometry import Box +from compas.geometry import Brep +from compas.geometry import Frame +from compas.geometry import Line +from compas.geometry import PlanarSurface +from compas.geometry import Plane +from compas.geometry import Point +from compas.geometry import Vector +from compas.geometry import add_vectors +from compas.geometry import angle_vectors +from compas.geometry import bounding_box +from compas.geometry import cross_vectors +from compas_timber.utils import intersection_line_plane +from compas.tolerance import TOL +from compas_model.elements import reset_computed + +from .features import FeatureApplicationError +from .timber import TimberElement + + +class Fastener(TimberElement): + """ + A class to represent timber fasteners (screws, dowels, brackets). + + Parameters + ---------- + elements : list(:class:`~compas_timber.parts.Element`) + The elements that are connected with this fastener. + + Attributes + ---------- + frame : :class:`~compas.geometry.Frame` + The coordinate system (frame) of this fastener. + elements : list(:class:`~compas_timber.parts.Element`) + The elements that are connected with this fastener. + + """ + + OPPOSING_SIDE_MAP = { + 0: 2, + 2: 0, + 1: 3, + 3: 1, + 4: 5, + 5: 4, + } + + @property + def __data__(self): + data = super(Fastener, self).__data__ + + return data + + def __init__(self, elements, **kwargs): + super(Fastener, self).__init__(elements=elements, **kwargs) + self.elements = elements + self.features = [] + self.attributes = {} + self.attributes.update(kwargs) + self.debug_info = [] + + def __repr__(self): + # type: () -> str + element_str = ["{} {}".format(element.type, element.key) for element in self.elements] + return "Fastener({})".format(", ".join(element_str)) + + # ========================================================================== + # Computed attributes + # ========================================================================== + + @property + def is_fastener(self): + return True + + @property + def shape(self): + # type: () -> Brep + assert self.frame + return self._create_shape(self.frame, self.beams) + + @property + def key(self): + # type: () -> int | None + return self.graph_node + + def __str__(self): + element_str = ["{} {}".format(element.type, element.key) for element in self.elements] + return "Fastener connecting {}".format(", ".join(element_str)) + + # ========================================================================== + # Implementations of abstract methods + # ========================================================================== + + def compute_geometry(self): + # type: (bool) -> compas.geometry.Brep + """Compute the geometry of the fastener. + + Returns + ------- + :class:`compas.geometry.Brep` + + """ + raise NotImplementedError + + + def compute_aabb(self, inflate=0.0): + # type: (float) -> compas.geometry.Box + """Computes the Axis Aligned Bounding Box (AABB) of the element. + + Parameters + ---------- + inflate : float, optional + Offset of box to avoid floating point errors. + + Returns + ------- + :class:`~compas.geometry.Box` + The AABB of the element. + + """ + raise NotImplementedError + + def compute_obb(self, inflate=0.0): + # type: (float | None) -> compas.geometry.Box + """Computes the Oriented Bounding Box (OBB) of the element. + + Parameters + ---------- + inflate : float + Offset of box to avoid floating point errors. + + Returns + ------- + :class:`compas.geometry.Box` + The OBB of the element. + + """ + raise NotImplementedError + + def compute_collision_mesh(self): + # type: () -> compas.datastructures.Mesh + """Computes the collision geometry of the element. + + Returns + ------- + :class:`compas.datastructures.Mesh` + The collision geometry of the element. + + """ + return self.shape.to_mesh() + + # ========================================================================== + # Alternative constructors + # ========================================================================== + + + + @staticmethod + def _create_shape(frame, beams): + # type: (Frame, list[TimberElement]) -> Brep + raise NotImplementedError + + # ========================================================================== + # Featrues + # ========================================================================== + + @reset_computed + def add_features(self, features): + # type: (Feature | list[Feature]) -> None + """Adds one or more features to the fastener. + + Parameters + ---------- + features : :class:`~compas_timber.parts.Feature` | list(:class:`~compas_timber.parts.Feature`) + The feature to be added. + + """ + if not isinstance(features, list): + features = [features] + self.features.extend(features) # type: ignore + + @reset_computed + def remove_features(self, features=None): + # type: (None | Feature | list[Feature]) -> None + """Removes a feature from the fastener. + + Parameters + ---------- + feature : :class:`~compas_timber.parts.Feature` | list(:class:`~compas_timber.parts.Feature`) + The feature to be removed. If None, all features will be removed. + + """ + if features is None: + self.features = [] + else: + if not isinstance(features, list): + features = [features] + self.features = [f for f in self.features if f not in features] From 078808e67b02443eccad17329366c6019a2bfcad Mon Sep 17 00:00:00 2001 From: obucklin Date: Mon, 28 Oct 2024 13:49:36 +0100 Subject: [PATCH 02/14] format, lint, Changelog --- CHANGELOG.md | 1 + src/compas_timber/_fabrication/step_joint.py | 2 +- .../_fabrication/step_joint_notch.py | 2 +- src/compas_timber/elements/beam.py | 3 ++- .../elements/fasteners/fastener.py | 20 ------------------- 5 files changed, 5 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c4d2e867..9ef81363f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added new `compas_timber._fabrication.StepJointNotch`. * Added new `compas_timber.connections.TStepJoint`. * Added new `utilities` module in `connections` package. +* Added new `fasteners` module with new `Fastener` element type. ### Changed diff --git a/src/compas_timber/_fabrication/step_joint.py b/src/compas_timber/_fabrication/step_joint.py index 36aa08f07..7d8720ad0 100644 --- a/src/compas_timber/_fabrication/step_joint.py +++ b/src/compas_timber/_fabrication/step_joint.py @@ -75,7 +75,7 @@ def __init__( tenon=False, tenon_width=40.0, tenon_height=40.0, - **kwargs + **kwargs, ): super(StepJoint, self).__init__(**kwargs) self._orientation = None diff --git a/src/compas_timber/_fabrication/step_joint_notch.py b/src/compas_timber/_fabrication/step_joint_notch.py index b9e3219c7..649ca09a0 100644 --- a/src/compas_timber/_fabrication/step_joint_notch.py +++ b/src/compas_timber/_fabrication/step_joint_notch.py @@ -91,7 +91,7 @@ def __init__( mortise=False, mortise_width=40.0, mortise_height=40.0, - **kwargs + **kwargs, ): super(StepJointNotch, self).__init__(**kwargs) self._orientation = None diff --git a/src/compas_timber/elements/beam.py b/src/compas_timber/elements/beam.py index 72d486e19..b120285a0 100644 --- a/src/compas_timber/elements/beam.py +++ b/src/compas_timber/elements/beam.py @@ -12,10 +12,11 @@ from compas.geometry import angle_vectors from compas.geometry import bounding_box from compas.geometry import cross_vectors -from compas_timber.utils import intersection_line_plane from compas.tolerance import TOL from compas_model.elements import reset_computed +from compas_timber.utils import intersection_line_plane + from .features import FeatureApplicationError from .timber import TimberElement diff --git a/src/compas_timber/elements/fasteners/fastener.py b/src/compas_timber/elements/fasteners/fastener.py index 932759640..c2a6d92b2 100644 --- a/src/compas_timber/elements/fasteners/fastener.py +++ b/src/compas_timber/elements/fasteners/fastener.py @@ -1,22 +1,5 @@ -import math - -from compas.geometry import Box -from compas.geometry import Brep -from compas.geometry import Frame -from compas.geometry import Line -from compas.geometry import PlanarSurface -from compas.geometry import Plane -from compas.geometry import Point -from compas.geometry import Vector -from compas.geometry import add_vectors -from compas.geometry import angle_vectors -from compas.geometry import bounding_box -from compas.geometry import cross_vectors -from compas_timber.utils import intersection_line_plane -from compas.tolerance import TOL from compas_model.elements import reset_computed -from .features import FeatureApplicationError from .timber import TimberElement @@ -104,7 +87,6 @@ def compute_geometry(self): """ raise NotImplementedError - def compute_aabb(self, inflate=0.0): # type: (float) -> compas.geometry.Box """Computes the Axis Aligned Bounding Box (AABB) of the element. @@ -155,8 +137,6 @@ def compute_collision_mesh(self): # Alternative constructors # ========================================================================== - - @staticmethod def _create_shape(frame, beams): # type: (Frame, list[TimberElement]) -> Brep From 2fc935536bcbd1e3cb40041c79929151cf23d170 Mon Sep 17 00:00:00 2001 From: obucklin Date: Mon, 28 Oct 2024 18:23:26 +0100 Subject: [PATCH 03/14] added unit test --- tests/compas_timber/test_fastener.py | 53 ++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 tests/compas_timber/test_fastener.py diff --git a/tests/compas_timber/test_fastener.py b/tests/compas_timber/test_fastener.py new file mode 100644 index 000000000..6c7d4e762 --- /dev/null +++ b/tests/compas_timber/test_fastener.py @@ -0,0 +1,53 @@ +import pytest +from compas_timber.elements.fasteners.fastener import Fastener +from compas_model.elements import Element +from compas_timber.elements import Beam +from compas.geometry import Frame + + +@pytest.fixture +def mock_elements(): + beam_1 = Beam(Frame([0.0,0.0,0.0], [1.0,0.0,0.0], [0.0,1.0,0.0]), 100.0, 10.0, 20.0) + beam_2 = Beam(Frame([0.0,0.0,0.0], [0.0,1.0,0.0], [-1.0,0.0,0.0]), 100.0, 10.0, 20.0) + + return [beam_1, beam_2] + +def test_fastener_initialization(mock_elements): + fastener = Fastener(mock_elements) + assert fastener.elements == mock_elements + assert fastener.features == [] + assert fastener.attributes == {} + assert fastener.debug_info == [] + +def test_fastener_repr(mock_elements): + fastener = Fastener(mock_elements) + assert repr(fastener) == "Fastener(Beam None, Beam None)" + +def test_fastener_str(mock_elements): + fastener = Fastener(mock_elements) + assert str(fastener) == "Fastener connecting Beam None, Beam None" + +def test_fastener_is_fastener(mock_elements): + fastener = Fastener(mock_elements) + assert fastener.is_fastener is True + +def test_fastener_add_features(mock_elements): + fastener = Fastener(mock_elements) + feature = "feature1" + fastener.add_features(feature) + assert fastener.features == [feature] + +def test_fastener_remove_features(mock_elements): + fastener = Fastener(mock_elements) + feature = "feature1" + fastener.add_features(feature) + fastener.remove_features(feature) + assert fastener.features == [] + +def test_fastener_remove_all_features(mock_elements): + fastener = Fastener(mock_elements) + feature1 = "feature1" + feature2 = "feature2" + fastener.add_features([feature1, feature2]) + fastener.remove_features() + assert fastener.features == [] From 8de38020bc81981565535b565299da287c51c51f Mon Sep 17 00:00:00 2001 From: obucklin Date: Mon, 28 Oct 2024 18:26:53 +0100 Subject: [PATCH 04/14] moment --- src/compas_timber/elements/fasteners/fastener.py | 4 ++-- tests/compas_timber/test_fastener.py | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/compas_timber/elements/fasteners/fastener.py b/src/compas_timber/elements/fasteners/fastener.py index c2a6d92b2..85862aa2a 100644 --- a/src/compas_timber/elements/fasteners/fastener.py +++ b/src/compas_timber/elements/fasteners/fastener.py @@ -1,9 +1,9 @@ from compas_model.elements import reset_computed -from .timber import TimberElement +from compas_model.elements import Element -class Fastener(TimberElement): +class Fastener(Element): """ A class to represent timber fasteners (screws, dowels, brackets). diff --git a/tests/compas_timber/test_fastener.py b/tests/compas_timber/test_fastener.py index 6c7d4e762..a3bb639de 100644 --- a/tests/compas_timber/test_fastener.py +++ b/tests/compas_timber/test_fastener.py @@ -1,6 +1,5 @@ import pytest from compas_timber.elements.fasteners.fastener import Fastener -from compas_model.elements import Element from compas_timber.elements import Beam from compas.geometry import Frame @@ -9,7 +8,6 @@ def mock_elements(): beam_1 = Beam(Frame([0.0,0.0,0.0], [1.0,0.0,0.0], [0.0,1.0,0.0]), 100.0, 10.0, 20.0) beam_2 = Beam(Frame([0.0,0.0,0.0], [0.0,1.0,0.0], [-1.0,0.0,0.0]), 100.0, 10.0, 20.0) - return [beam_1, beam_2] def test_fastener_initialization(mock_elements): From 6060706179f208714519638bfa17db23c05d9721 Mon Sep 17 00:00:00 2001 From: obucklin Date: Mon, 28 Oct 2024 18:29:02 +0100 Subject: [PATCH 05/14] passed tests --- src/compas_timber/elements/fasteners/fastener.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/compas_timber/elements/fasteners/fastener.py b/src/compas_timber/elements/fasteners/fastener.py index 85862aa2a..6b9d10b33 100644 --- a/src/compas_timber/elements/fasteners/fastener.py +++ b/src/compas_timber/elements/fasteners/fastener.py @@ -1,7 +1,4 @@ -from compas_model.elements import reset_computed - -from compas_model.elements import Element - +from compas_model.elements import reset_computed, Element class Fastener(Element): """ @@ -37,7 +34,7 @@ def __data__(self): return data def __init__(self, elements, **kwargs): - super(Fastener, self).__init__(elements=elements, **kwargs) + super(Fastener, self).__init__(elements, **kwargs) self.elements = elements self.features = [] self.attributes = {} @@ -46,7 +43,7 @@ def __init__(self, elements, **kwargs): def __repr__(self): # type: () -> str - element_str = ["{} {}".format(element.type, element.key) for element in self.elements] + element_str = ["{} {}".format(element.__class__.__name__, element.key) for element in self.elements] return "Fastener({})".format(", ".join(element_str)) # ========================================================================== @@ -69,7 +66,7 @@ def key(self): return self.graph_node def __str__(self): - element_str = ["{} {}".format(element.type, element.key) for element in self.elements] + element_str = ["{} {}".format(element.__class__.__name__, element.key) for element in self.elements] return "Fastener connecting {}".format(", ".join(element_str)) # ========================================================================== @@ -178,3 +175,6 @@ def remove_features(self, features=None): if not isinstance(features, list): features = [features] self.features = [f for f in self.features if f not in features] + if not isinstance(features, list): + features = [features] + self.features = [f for f in self.features if f not in features] From 874eb522cb0005d800059590fa605e7fc2a4a601 Mon Sep 17 00:00:00 2001 From: obucklin Date: Mon, 28 Oct 2024 18:30:13 +0100 Subject: [PATCH 06/14] changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ef81363f..b75621461 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added new `compas_timber._fabrication.StepJointNotch`. * Added new `compas_timber.connections.TStepJoint`. * Added new `utilities` module in `connections` package. -* Added new `fasteners` module with new `Fastener` element type. +* Added new `fasteners.py` module with new `Fastener` element type. +* Added unit tests for `fasteners.py` module. ### Changed From ab7bca3fac727319b28b35923f5bff5eff11d52c Mon Sep 17 00:00:00 2001 From: obucklin Date: Mon, 28 Oct 2024 18:31:16 +0100 Subject: [PATCH 07/14] lint format clean --- src/compas_timber/elements/fasteners/fastener.py | 4 +++- tests/compas_timber/test_fastener.py | 11 +++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/compas_timber/elements/fasteners/fastener.py b/src/compas_timber/elements/fasteners/fastener.py index 6b9d10b33..6ec30cec0 100644 --- a/src/compas_timber/elements/fasteners/fastener.py +++ b/src/compas_timber/elements/fasteners/fastener.py @@ -1,4 +1,6 @@ -from compas_model.elements import reset_computed, Element +from compas_model.elements import Element +from compas_model.elements import reset_computed + class Fastener(Element): """ diff --git a/tests/compas_timber/test_fastener.py b/tests/compas_timber/test_fastener.py index a3bb639de..fd0f862e6 100644 --- a/tests/compas_timber/test_fastener.py +++ b/tests/compas_timber/test_fastener.py @@ -6,10 +6,11 @@ @pytest.fixture def mock_elements(): - beam_1 = Beam(Frame([0.0,0.0,0.0], [1.0,0.0,0.0], [0.0,1.0,0.0]), 100.0, 10.0, 20.0) - beam_2 = Beam(Frame([0.0,0.0,0.0], [0.0,1.0,0.0], [-1.0,0.0,0.0]), 100.0, 10.0, 20.0) + beam_1 = Beam(Frame([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]), 100.0, 10.0, 20.0) + beam_2 = Beam(Frame([0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [-1.0, 0.0, 0.0]), 100.0, 10.0, 20.0) return [beam_1, beam_2] + def test_fastener_initialization(mock_elements): fastener = Fastener(mock_elements) assert fastener.elements == mock_elements @@ -17,24 +18,29 @@ def test_fastener_initialization(mock_elements): assert fastener.attributes == {} assert fastener.debug_info == [] + def test_fastener_repr(mock_elements): fastener = Fastener(mock_elements) assert repr(fastener) == "Fastener(Beam None, Beam None)" + def test_fastener_str(mock_elements): fastener = Fastener(mock_elements) assert str(fastener) == "Fastener connecting Beam None, Beam None" + def test_fastener_is_fastener(mock_elements): fastener = Fastener(mock_elements) assert fastener.is_fastener is True + def test_fastener_add_features(mock_elements): fastener = Fastener(mock_elements) feature = "feature1" fastener.add_features(feature) assert fastener.features == [feature] + def test_fastener_remove_features(mock_elements): fastener = Fastener(mock_elements) feature = "feature1" @@ -42,6 +48,7 @@ def test_fastener_remove_features(mock_elements): fastener.remove_features(feature) assert fastener.features == [] + def test_fastener_remove_all_features(mock_elements): fastener = Fastener(mock_elements) feature1 = "feature1" From d92dc48cf23d1741e994a6501c7238508a191c88 Mon Sep 17 00:00:00 2001 From: obucklin Date: Mon, 4 Nov 2024 13:21:05 +0100 Subject: [PATCH 08/14] merge main --- src/compas_timber/elements/fasteners/fastener.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/compas_timber/elements/fasteners/fastener.py b/src/compas_timber/elements/fasteners/fastener.py index 6ec30cec0..a36133956 100644 --- a/src/compas_timber/elements/fasteners/fastener.py +++ b/src/compas_timber/elements/fasteners/fastener.py @@ -13,27 +13,13 @@ class Fastener(Element): Attributes ---------- - frame : :class:`~compas.geometry.Frame` - The coordinate system (frame) of this fastener. elements : list(:class:`~compas_timber.parts.Element`) The elements that are connected with this fastener. """ - OPPOSING_SIDE_MAP = { - 0: 2, - 2: 0, - 1: 3, - 3: 1, - 4: 5, - 5: 4, - } - @property - def __data__(self): - data = super(Fastener, self).__data__ - return data def __init__(self, elements, **kwargs): super(Fastener, self).__init__(elements, **kwargs) @@ -59,7 +45,6 @@ def is_fastener(self): @property def shape(self): # type: () -> Brep - assert self.frame return self._create_shape(self.frame, self.beams) @property From 5e5411157fa8a9eacc5d02dd455dbc01ec1f1023 Mon Sep 17 00:00:00 2001 From: obucklin Date: Mon, 4 Nov 2024 13:22:24 +0100 Subject: [PATCH 09/14] lint, format, etc --- src/compas_timber/elements/fasteners/fastener.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/compas_timber/elements/fasteners/fastener.py b/src/compas_timber/elements/fasteners/fastener.py index a36133956..223db8026 100644 --- a/src/compas_timber/elements/fasteners/fastener.py +++ b/src/compas_timber/elements/fasteners/fastener.py @@ -18,9 +18,6 @@ class Fastener(Element): """ - - - def __init__(self, elements, **kwargs): super(Fastener, self).__init__(elements, **kwargs) self.elements = elements From 118603bc0c6124c4921464ecb91a86cfb505217e Mon Sep 17 00:00:00 2001 From: obucklin Date: Mon, 4 Nov 2024 13:26:41 +0100 Subject: [PATCH 10/14] cmmt --- src/compas_timber/elements/fasteners/fastener.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/compas_timber/elements/fasteners/fastener.py b/src/compas_timber/elements/fasteners/fastener.py index 223db8026..c640ee361 100644 --- a/src/compas_timber/elements/fasteners/fastener.py +++ b/src/compas_timber/elements/fasteners/fastener.py @@ -39,6 +39,7 @@ def __repr__(self): def is_fastener(self): return True + @property def shape(self): # type: () -> Brep From c2d9a1cabc706f3fc670b3c447ee7adcdf5ad844 Mon Sep 17 00:00:00 2001 From: obucklin Date: Tue, 12 Nov 2024 13:39:43 +0100 Subject: [PATCH 11/14] slimmed to minimum --- src/compas_timber/elements/__init__.py | 2 + src/compas_timber/elements/fastener.py | 52 ++++++ .../elements/fasteners/fastener.py | 165 ------------------ tests/compas_timber/test_fastener.py | 61 ++----- 4 files changed, 68 insertions(+), 212 deletions(-) create mode 100644 src/compas_timber/elements/fastener.py delete mode 100644 src/compas_timber/elements/fasteners/fastener.py diff --git a/src/compas_timber/elements/__init__.py b/src/compas_timber/elements/__init__.py index 6fc15a33a..c0cce0907 100644 --- a/src/compas_timber/elements/__init__.py +++ b/src/compas_timber/elements/__init__.py @@ -1,6 +1,7 @@ from .beam import Beam from .plate import Plate from .wall import Wall +from .fastener import Fastener from .features import BrepSubtraction from .features import CutFeature from .features import DrillFeature @@ -11,6 +12,7 @@ "Wall", "Beam", "Plate", + "Fastener", "CutFeature", "DrillFeature", "MillVolume", diff --git a/src/compas_timber/elements/fastener.py b/src/compas_timber/elements/fastener.py new file mode 100644 index 000000000..12461a0db --- /dev/null +++ b/src/compas_timber/elements/fastener.py @@ -0,0 +1,52 @@ +from compas.geometry import Frame +from compas_model.elements import Element + + +class Fastener(Element): + """ + A class to represent timber fasteners (screws, dowels, brackets). + + Parameters + ---------- + geometry : :class:`~compas.geometry.Geometry` + The geometry of the fastener. + frame : :class:`~compas.geometry.Frame` + The frame of the fastener. + + Attributes + ---------- + geometry : :class:`~compas.geometry.Geometry` + The geometry of the fastener. + frame : :class:`~compas.geometry.Frame` + The frame of the fastener. + + """ + + def __init__(self, geometry=None, frame=None, **kwargs): + super(Fastener, self).__init__(**kwargs) + self._geometry = geometry + self.frame = frame or Frame.worldXY() + self.attributes = {} + self.attributes.update(kwargs) + self.debug_info = [] + + def __repr__(self): + # type: () -> str + return "Fastener(frame={!r}, name={})".format(self.frame, self.name) + + def __str__(self): + # type: () -> str + return "".format(self.name) + + # ========================================================================== + # Computed attributes + # ========================================================================== + + @property + def is_fastener(self): + return True + + @property + def key(self): + # type: () -> int | None + return self.graph_node diff --git a/src/compas_timber/elements/fasteners/fastener.py b/src/compas_timber/elements/fasteners/fastener.py deleted file mode 100644 index c640ee361..000000000 --- a/src/compas_timber/elements/fasteners/fastener.py +++ /dev/null @@ -1,165 +0,0 @@ -from compas_model.elements import Element -from compas_model.elements import reset_computed - - -class Fastener(Element): - """ - A class to represent timber fasteners (screws, dowels, brackets). - - Parameters - ---------- - elements : list(:class:`~compas_timber.parts.Element`) - The elements that are connected with this fastener. - - Attributes - ---------- - elements : list(:class:`~compas_timber.parts.Element`) - The elements that are connected with this fastener. - - """ - - def __init__(self, elements, **kwargs): - super(Fastener, self).__init__(elements, **kwargs) - self.elements = elements - self.features = [] - self.attributes = {} - self.attributes.update(kwargs) - self.debug_info = [] - - def __repr__(self): - # type: () -> str - element_str = ["{} {}".format(element.__class__.__name__, element.key) for element in self.elements] - return "Fastener({})".format(", ".join(element_str)) - - # ========================================================================== - # Computed attributes - # ========================================================================== - - @property - def is_fastener(self): - return True - - - @property - def shape(self): - # type: () -> Brep - return self._create_shape(self.frame, self.beams) - - @property - def key(self): - # type: () -> int | None - return self.graph_node - - def __str__(self): - element_str = ["{} {}".format(element.__class__.__name__, element.key) for element in self.elements] - return "Fastener connecting {}".format(", ".join(element_str)) - - # ========================================================================== - # Implementations of abstract methods - # ========================================================================== - - def compute_geometry(self): - # type: (bool) -> compas.geometry.Brep - """Compute the geometry of the fastener. - - Returns - ------- - :class:`compas.geometry.Brep` - - """ - raise NotImplementedError - - def compute_aabb(self, inflate=0.0): - # type: (float) -> compas.geometry.Box - """Computes the Axis Aligned Bounding Box (AABB) of the element. - - Parameters - ---------- - inflate : float, optional - Offset of box to avoid floating point errors. - - Returns - ------- - :class:`~compas.geometry.Box` - The AABB of the element. - - """ - raise NotImplementedError - - def compute_obb(self, inflate=0.0): - # type: (float | None) -> compas.geometry.Box - """Computes the Oriented Bounding Box (OBB) of the element. - - Parameters - ---------- - inflate : float - Offset of box to avoid floating point errors. - - Returns - ------- - :class:`compas.geometry.Box` - The OBB of the element. - - """ - raise NotImplementedError - - def compute_collision_mesh(self): - # type: () -> compas.datastructures.Mesh - """Computes the collision geometry of the element. - - Returns - ------- - :class:`compas.datastructures.Mesh` - The collision geometry of the element. - - """ - return self.shape.to_mesh() - - # ========================================================================== - # Alternative constructors - # ========================================================================== - - @staticmethod - def _create_shape(frame, beams): - # type: (Frame, list[TimberElement]) -> Brep - raise NotImplementedError - - # ========================================================================== - # Featrues - # ========================================================================== - - @reset_computed - def add_features(self, features): - # type: (Feature | list[Feature]) -> None - """Adds one or more features to the fastener. - - Parameters - ---------- - features : :class:`~compas_timber.parts.Feature` | list(:class:`~compas_timber.parts.Feature`) - The feature to be added. - - """ - if not isinstance(features, list): - features = [features] - self.features.extend(features) # type: ignore - - @reset_computed - def remove_features(self, features=None): - # type: (None | Feature | list[Feature]) -> None - """Removes a feature from the fastener. - - Parameters - ---------- - feature : :class:`~compas_timber.parts.Feature` | list(:class:`~compas_timber.parts.Feature`) - The feature to be removed. If None, all features will be removed. - - """ - if features is None: - self.features = [] - else: - if not isinstance(features, list): - features = [features] - self.features = [f for f in self.features if f not in features] - if not isinstance(features, list): - features = [features] - self.features = [f for f in self.features if f not in features] diff --git a/tests/compas_timber/test_fastener.py b/tests/compas_timber/test_fastener.py index fd0f862e6..90b4b3601 100644 --- a/tests/compas_timber/test_fastener.py +++ b/tests/compas_timber/test_fastener.py @@ -1,58 +1,25 @@ -import pytest -from compas_timber.elements.fasteners.fastener import Fastener -from compas_timber.elements import Beam -from compas.geometry import Frame +from compas_timber.elements import Fastener -@pytest.fixture -def mock_elements(): - beam_1 = Beam(Frame([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]), 100.0, 10.0, 20.0) - beam_2 = Beam(Frame([0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [-1.0, 0.0, 0.0]), 100.0, 10.0, 20.0) - return [beam_1, beam_2] - - -def test_fastener_initialization(mock_elements): - fastener = Fastener(mock_elements) - assert fastener.elements == mock_elements - assert fastener.features == [] +def test_fastener_initialization(): + fastener = Fastener() assert fastener.attributes == {} assert fastener.debug_info == [] -def test_fastener_repr(mock_elements): - fastener = Fastener(mock_elements) - assert repr(fastener) == "Fastener(Beam None, Beam None)" +def test_fastener_repr(): + fastener = Fastener() + assert ( + repr(fastener) + == "Fastener(frame=Frame(point=Point(x=0.0, y=0.0, z=0.0), xaxis=Vector(x=1.0, y=0.0, z=0.0), yaxis=Vector(x=0.0, y=1.0, z=0.0)), name=Fastener)" + ) -def test_fastener_str(mock_elements): - fastener = Fastener(mock_elements) - assert str(fastener) == "Fastener connecting Beam None, Beam None" +def test_fastener_str(): + fastener = Fastener() + assert str(fastener) == "" -def test_fastener_is_fastener(mock_elements): - fastener = Fastener(mock_elements) +def test_fastener_is_fastener(): + fastener = Fastener() assert fastener.is_fastener is True - - -def test_fastener_add_features(mock_elements): - fastener = Fastener(mock_elements) - feature = "feature1" - fastener.add_features(feature) - assert fastener.features == [feature] - - -def test_fastener_remove_features(mock_elements): - fastener = Fastener(mock_elements) - feature = "feature1" - fastener.add_features(feature) - fastener.remove_features(feature) - assert fastener.features == [] - - -def test_fastener_remove_all_features(mock_elements): - fastener = Fastener(mock_elements) - feature1 = "feature1" - feature2 = "feature2" - fastener.add_features([feature1, feature2]) - fastener.remove_features() - assert fastener.features == [] From 1b6fb64e49f4a5b14a7966adf3e7356eb5c884c6 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Thu, 14 Nov 2024 10:14:35 +0100 Subject: [PATCH 12/14] Update src/compas_timber/_fabrication/step_joint.py --- src/compas_timber/_fabrication/step_joint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compas_timber/_fabrication/step_joint.py b/src/compas_timber/_fabrication/step_joint.py index f247d3691..02255e242 100644 --- a/src/compas_timber/_fabrication/step_joint.py +++ b/src/compas_timber/_fabrication/step_joint.py @@ -76,7 +76,7 @@ def __init__( tenon=False, tenon_width=40.0, tenon_height=40.0, - **kwargs, + **kwargs ): super(StepJoint, self).__init__(**kwargs) self._orientation = None From 876edd04c92d9351478e80e527c39838fd278734 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Thu, 14 Nov 2024 10:14:52 +0100 Subject: [PATCH 13/14] Update src/compas_timber/_fabrication/step_joint_notch.py --- src/compas_timber/_fabrication/step_joint_notch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compas_timber/_fabrication/step_joint_notch.py b/src/compas_timber/_fabrication/step_joint_notch.py index f4f77aec6..abd3113f2 100644 --- a/src/compas_timber/_fabrication/step_joint_notch.py +++ b/src/compas_timber/_fabrication/step_joint_notch.py @@ -92,7 +92,7 @@ def __init__( mortise=False, mortise_width=40.0, mortise_height=40.0, - **kwargs, + **kwargs ): super(StepJointNotch, self).__init__(**kwargs) self._orientation = None From 0c4a790c6b0b011f84e87f6d11a6ef9c721d81c0 Mon Sep 17 00:00:00 2001 From: Chen Kasirer Date: Thu, 14 Nov 2024 10:15:35 +0100 Subject: [PATCH 14/14] Update CHANGELOG.md --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d748e60a1..4f1fde077 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,7 +36,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added new method `get_elements_in_group` to `TimberModel`. * Added attribute `is_group_element` to `TimberElement`. * Added new `fasteners.py` module with new `Fastener` element type. -* Added unit tests for `fasteners.py` module. ### Changed