-
Notifications
You must be signed in to change notification settings - Fork 36
Add BTLx UserReferencePlane support, remove CutPlaneSpec/MiterPlaneSpec
#803
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
base: main
Are you sure you want to change the base?
Changes from 22 commits
bb8abc4
d6b9aea
f523de8
a8f1f27
ec42333
5997035
eef736b
92e205c
61a70a2
ad9e3fc
58217cc
e05ed77
bf69228
aec9d1b
88effa3
3a6232d
385bace
41a6923
e036a61
5eecc89
638c278
319a218
885a5a6
9e3fdb0
30aba2c
d5815b0
24c9ff2
5f3fece
93690a3
710fb48
8eb7cba
559b9ca
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,8 @@ | ||
| import abc | ||
| from functools import wraps | ||
| from typing import Optional | ||
|
|
||
| from compas.data import Data | ||
| from compas.geometry import Frame | ||
| from compas.geometry import Line | ||
| from compas.geometry import PlanarSurface | ||
|
|
@@ -10,6 +12,47 @@ | |
| from compas_model.elements import reset_computed | ||
|
|
||
|
|
||
| class UserReferencePlane(Data): | ||
| """A reference plane attached to a timber element for use in BTLx processings. | ||
|
|
||
| ``UserReferencePlane`` objects are registered on a :class:`TimberElement` via | ||
| :meth:`TimberElement.add_user_ref_plane`, which stores this ``frame`` relative | ||
| to the element's ``ref_frame`` (BTLx's ``PartRef``: "The ReferencePlane refers to the PartRef"). | ||
| Use :meth:`TimberElement.get_user_ref_plane` to resolve it to model (world) coordinates. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| frame : :class:`compas.geometry.Frame` | ||
| The plane expressed relative to the owning element's ``ref_frame``. | ||
| ID : int | ||
| The BTLx integer ID for this plane. Must be an integer >= 100. | ||
|
|
||
| Attributes | ||
| ---------- | ||
| frame : :class:`compas.geometry.Frame` | ||
| The plane relative to the owning element's ``ref_frame``. | ||
| ID : int | ||
| The BTLx integer ID of this plane. | ||
|
|
||
| """ | ||
|
|
||
| def __init__(self, frame: Frame, ID: int): | ||
| super(UserReferencePlane, self).__init__() | ||
| if type(ID) is not int: | ||
| raise TypeError("BTLx reference plane IDs must be integers.") | ||
| if ID < 100: | ||
| raise ValueError("BTLx reference plane IDs must be >= 100.") | ||
| self.frame = frame | ||
| self.ID = ID | ||
|
|
||
| def __repr__(self): | ||
| return "UserReferencePlane(ID={!r}, frame={!r})".format(self.ID, self.frame) | ||
|
|
||
| @property | ||
| def __data__(self): | ||
| return {"frame": self.frame, "ID": self.ID} | ||
|
|
||
|
|
||
| def reset_timber_attrs(f): | ||
| """Decorator to reset cached timber-specific attributes.""" | ||
|
|
||
|
|
@@ -32,7 +75,7 @@ class TimberElement(Element, abc.ABC): | |
| Parameters | ||
| ---------- | ||
| frame : :class:`compas.geometry.Frame`, optional | ||
| The frame representing the beam's local coordinate system in its hierarchical context. | ||
| The frame representing the elements's local coordinate system in its hierarchical context. | ||
|
Copilot marked this conversation as resolved.
Outdated
Copilot marked this conversation as resolved.
Outdated
|
||
| Defaults to ``None``, in which case the world coordinate system is used. | ||
| length : float | ||
| Length of the timber element. | ||
|
|
@@ -383,3 +426,115 @@ def get_dimensions_relative_to_side(self, ref_side_index): | |
| if ref_side_index in [1, 3]: | ||
| return self.height, self.width | ||
| return self.width, self.height | ||
|
|
||
| ######################################################################## | ||
| # User Reference Planes | ||
| ######################################################################## | ||
|
|
||
| @property | ||
| def user_ref_planes(self): | ||
| """User reference planes attached to this element. | ||
|
|
||
| These correspond to the BTLx ``UserReferencePlane`` concept. The BTLx | ||
| integer ``ID`` is the zero-based insertion index of the plane plus 100, | ||
| so the first plane gets ID 100, second 101, and so on. | ||
|
papachap marked this conversation as resolved.
Outdated
|
||
|
|
||
| Each plane's ``frame`` is stored relative to :attr:`ref_frame` (BTLx's ``PartRef``, | ||
| per the BTLx spec: "The ReferencePlane refers to the PartRef"), the same coordinate | ||
| system BTLx processing parameters are defined in when a custom reference plane is declared. | ||
|
|
||
| Use :meth:`add_user_ref_plane` to add a plane in model (world) coordinates, and | ||
| :meth:`get_user_ref_plane` to resolve a plane back to model (world) coordinates. | ||
|
|
||
| Returns | ||
| ------- | ||
| list[:class:`UserReferencePlane`] | ||
| """ | ||
| return self.attributes.get("user_ref_planes", []) | ||
|
papachap marked this conversation as resolved.
Outdated
|
||
|
|
||
| def _register_user_ref_plane(self, local_frame: Frame, ID: int = None) -> int: | ||
|
papachap marked this conversation as resolved.
Outdated
|
||
| """Store a reference plane already expressed relative to :attr:`ref_frame`. | ||
|
|
||
| Used internally by :meth:`add_user_ref_plane` (which converts from model coordinates first) | ||
| and by the BTLx reader (which parses planes already in ref_frame-local coordinates, so it can | ||
| register them directly without a needless local -> world -> local round trip). | ||
|
|
||
| Parameters | ||
| ---------- | ||
| local_frame : :class:`compas.geometry.Frame` | ||
| The plane expressed relative to :attr:`ref_frame`. | ||
| ID : int, optional | ||
| The BTLx integer ID to assign to this plane. See :meth:`add_user_ref_plane`. | ||
|
|
||
| Returns | ||
| ------- | ||
| int | ||
| The BTLx integer ID assigned to this plane (>= 100). | ||
|
|
||
| """ | ||
| if ID is not None: | ||
| if any(ID == p.ID for p in self.attributes.get("user_ref_planes", [])): | ||
| raise ValueError("A reference plane with ID {} already exists. Call remove_user_ref_plane first.".format(ID)) | ||
| else: | ||
| ID = len(self.attributes.get("user_ref_planes", [])) + 100 | ||
|
|
||
| self.attributes.setdefault("user_ref_planes", []).append(UserReferencePlane(frame=local_frame, ID=ID)) | ||
| return ID | ||
|
papachap marked this conversation as resolved.
Outdated
papachap marked this conversation as resolved.
Outdated
|
||
|
|
||
| def add_user_ref_plane(self, frame: Frame, ID: int = None) -> int: | ||
|
papachap marked this conversation as resolved.
Outdated
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should probably guard here against planes which are completely unrelated to the beam. like a plane whose origin is on the other side of the scene
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm yeah it makes sense.. But I am wondering if The BTLx processing itself will throw a Happy to hear your ideas on it. |
||
| """Add a named reference plane to this element. | ||
|
|
||
| The BTLx ``ID`` is assigned as the current number of registered planes | ||
| plus 100 (first plane → 100, second → 101, …). | ||
|
|
||
| Parameters | ||
| ---------- | ||
| frame : :class:`compas.geometry.Frame` | ||
| The plane expressed in model (world) coordinates. It is converted to and stored relative | ||
| to :attr:`ref_frame` (BTLx's ``PartRef``), matching how BTLx itself defines a custom | ||
| reference plane. | ||
| ID : int, optional | ||
| The BTLx integer ID to assign to this plane. This should be a unique integer >= 100. | ||
| If None, the ID will be assigned as the current number of registered planes plus 100 (first plane → 100, second → 101, …). | ||
|
|
||
| Returns | ||
| ------- | ||
| int | ||
| The BTLx integer ID assigned to this plane (>= 100). | ||
|
|
||
| """ | ||
| local_frame = frame.transformed(Transformation.from_frame(self.ref_frame).inverted()) | ||
| return self._register_user_ref_plane(local_frame, ID) | ||
|
|
||
| def get_user_ref_plane(self, ID: int) -> Optional[Frame]: | ||
| """Retrieve the frame of a reference plane stored under ``ID``. | ||
|
|
||
| The returned frame is transformed to model coordinates, so it can be used directly in the model space. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| ID : int | ||
| The BTLx integer ID of the reference plane to retrieve. | ||
|
|
||
| Returns | ||
| ------- | ||
| :class:`compas.geometry.Frame` or None | ||
| The frame of the reference plane with the given ID, transformed to model coordinates, | ||
| or None if no such plane exists. | ||
| """ | ||
| planes = self.attributes.get("user_ref_planes", []) | ||
| for plane in planes: | ||
| if plane.ID == ID: | ||
| return plane.frame.transformed(Transformation.from_frame(self.ref_frame)) | ||
| return None | ||
|
|
||
| def remove_user_ref_plane(self, ID: int): | ||
| """Remove the reference plane stored under ``ID``. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| ID : int | ||
| The BTLx integer ID of the reference plane to remove. | ||
| """ | ||
| planes = self.attributes.get("user_ref_planes", []) | ||
| self.attributes["user_ref_planes"] = [p for p in planes if p.ID != ID] | ||
Uh oh!
There was an error while loading. Please reload this page.