Add BTLx UserReferencePlane support, remove CutPlaneSpec/MiterPlaneSpec - #803
Add BTLx UserReferencePlane support, remove CutPlaneSpec/MiterPlaneSpec#803papachap wants to merge 32 commits into
UserReferencePlane support, remove CutPlaneSpec/MiterPlaneSpec#803Conversation
…into user_ref_planes
…er_plane_id field
… reference planes
…d avoid circular imports refactor so that the plane is stored in local coords
…into user_ref_planes
…te ButtJoint and LButtJoint to use user_ref_plane IDs
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #803 +/- ##
==========================================
- Coverage 82.52% 82.30% -0.22%
==========================================
Files 81 81
Lines 11861 11910 +49
==========================================
+ Hits 9788 9803 +15
- Misses 2073 2107 +34 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR introduces first-class support for BTLx UserReferencePlane objects on TimberElement, and refactors multiple joints/processings to reference override planes by BTLx plane ID (>= 100) instead of the removed ad-hoc CutPlaneSpec / MiterPlaneSpec objects. This aligns the joint plane-override workflow with the BTLx model, improves round-tripping through BTLx XML, and updates tests and changelog accordingly.
Changes:
- Added
TimberElement.user_ref_planesAPI (add/get/remove) plusUserReferencePlanedata class, including BTLx XML write/read support. - Refactored
ButtJoint/LButtJoint/TButtJointandLMiterJointto use*_plane_idoverrides instead of*PlaneSpecobjects; removed exports of the spec types. - Updated tests and changelog to cover new round-trips and the updated pocket/lap behavior when a butt plane is overridden.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/compas_timber/test_t_butt_joint.py | Updates T-butt serialization expectations and adds tests for overridden butt planes with/without mill depth. |
| tests/compas_timber/test_L_miter_joint.py | Migrates LMiter tests from miter_plane_args to miter_plane_id using user_ref_planes. |
| tests/compas_timber/test_L_butt_joint.py | Migrates L-butt override-plane tests to butt_plane_id/back_plane_id and adds new pocket-depth direction assertions. |
| tests/compas_timber/test_btlx.py | Adds BTLx write/read roundtrip coverage for user reference planes. |
| tests/compas_timber/test_beam.py | Adds unit tests for user reference plane lifecycle + transform/model-transform behavior. |
| src/compas_timber/fabrication/btlx.py | Emits UserReferencePlanes XML and updates ReferencePlaneID handling to pass through user plane IDs (>=100). |
| src/compas_timber/connections/t_butt.py | Updates TButtJoint signature/docs to accept butt_plane_id. |
| src/compas_timber/connections/l_miter.py | Removes MiterPlaneSpec and switches LMiter override to miter_plane_id resolution via user_ref_planes. |
| src/compas_timber/connections/l_butt.py | Switches LButt override planes to ID-based resolution (butt_plane_id, back_plane_id). |
| src/compas_timber/connections/butt_joint.py | Removes CutPlaneSpec, adds _resolve_user_ref_plane, and updates pocket/lap selection logic for overridden planes. |
| src/compas_timber/connections/init.py | Removes CutPlaneSpec / MiterPlaneSpec exports. |
| src/compas_timber/btlx/reader.py | Parses UserReferencePlanes from XML and registers them on elements. |
| src/compas_timber/base.py | Introduces UserReferencePlane and TimberElement user-plane registration/lookup/removal APIs. |
| CHANGELOG.md | Documents the new feature, API changes, and removals. |
chenkasirer
left a comment
There was a problem hiding this comment.
Generally fine by me but wrote some more high-level take on the recent movements in this system here Hopefully it'll help us evaluate the alternatives in a more engineer-y fashion.
In the meantime, I would jut push back about using the attributes dict - I think it's too unstructured for this purpose, and should be reserved for user or project specific data, not part of core functionality.
chenkasirer
left a comment
There was a problem hiding this comment.
LGTM!
left some comments + documented the different alternatives we went through in #804
| self.attributes.setdefault("user_ref_planes", []).append(UserReferencePlane(frame=local_frame, ID=ID)) | ||
| return ID | ||
|
|
||
| def add_user_ref_plane(self, frame: Frame, ID: int = None) -> int: |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Hmm yeah it makes sense.. But I am wondering if add_user_ref_plane is the best spot for this. It's generic storage with no idea what the plane will be used for, so there's no principled distance threshold to check against.
The BTLx processing itself will throw a ValueError if the geometry can't be used, or a joint using that plane would have its own lazy checks against it. Maybe that's enough already?
Happy to hear your ideas on it.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…pdate ref_side_index description
…nd update its implementation for BTLx processing
…into UserRefPlane-against-spec-classes
|
@obucklin I have implemented some changes based on @chenkasirer comments.. please have another look :) |
obucklin
left a comment
There was a problem hiding this comment.
Hey @papachap, I'm glad that you finally implemented this in BTLx. However there is a critical issue when applying this to the Joints with custom plane inputs:
In order to use this as a design input, you will have to find a way to deal with joint extensions. in hindsight, the old *_plane_spec didn't take into account the extensions, and fails when extensions are added on the start end of the beam. (see the image below)
This is of course because we use the ref_sides for parameterizing the plane and not the beam.frame or some other "hard-coded" property. I would fix this by using the beam.frame as the initial reference frame, not the ref_side. (Actually I would still just serialize the Plane in the beam local space.) This may be a case where we need to differentiate between the design-side and fabrication-side of model.process_joinery and say that the input is different than the output, in which case the BTLx.UserReferencePlane is only a result of joinery used for fabrication.
…ranfroming it to ref_frame happens only when writing the btlx
|
@obucklin Thanks for pointing the bug out! please have another look |
obucklin
left a comment
There was a problem hiding this comment.
One comment in the code. I added thoughts to #804 @chenkasirer s conversation about this topic. I am glad you fixed the extension bug but I see two issues:
- Joint doesn't own a joint attribute, meaning e.g. the
LMiterJoint.miter_planecan be changed in theTimberElement.add_user_ref_plane()method. I think the Joints should own those attributes. - you now essentially have 2
user_planetypes. one is theUserReferencePlaneCollectiononTimberElement, and one is the BTLx user_plane, which is generated via 2 transformations and is a different object. IMO this deviates from the "feature is aBTLxprocessing" philosophy of CT.
Since this is a recurring issue, I suggest we sit together and discuss to come to a consensus on how to solve this.
| user_plane = self._resolve_user_ref_plane(self.cross_beam, self.butt_plane_id, "butt_plane") | ||
| if self.mill_depth: | ||
| main_centerline = point_centerline_towards_joint(self.main_beam, self.cross_beam) | ||
| user_plane.translate(-main_centerline.direction * self.mill_depth) # offset the plane along the beam's centerline direction |
There was a problem hiding this comment.
I'm glad you are implementing this! I would think the mill depth would offset normal to the user_plane.
|
@obucklin Thanks for your feedback! I would like to reflect to the two points that you raised: 1. Joint owning the plane:
2. BTLx alignment issue:
|
|
@papachap fair points. I still think the joint should own their own properties. For simplicity sake I would do this as a transformation from the element_a.frame to the plane. I think we should sit with @chenkasirer and discuss to decide. |
…BTLx standard, remove diagram pipeline from repo - CutPlaneSpec/MiterPlaneSpec removed from the Connections diagram and prose (mechanism contested in #803); LMiterJoint's miter_plane attribute keeps its extracted type. - Fabrication intro links the BTLx standard specification. - The generation pipeline moves out of the repo to https://github.com/daniel-locatelli/compas_timber-class-diagrams; the page header no longer points at in-repo scripts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This PR adds support for BTLx's
UserReferencePlaneconcept and uses it to replace the ad-hoc plane-override system joints were using before.TimberElements can now register arbitrary named planes viaadd_user_ref_plane()/get_user_ref_plane(), stored onTimberElement.attributes(that I believe is much underused) relative to the element'sref_frame. These round-trip through BTLx XML, survivetransform(), and survive model (de)serialization.ButtJoint/LButtJoint/TButtJointandLMiterJointnow reference an override plane by a plainintID (butt_plane_id,back_plane_id,miter_plane_id) instead of building aCutPlaneSpec/MiterPlaneSpecobject.CutPlaneSpecandMiterPlaneSpecare removed entirely — they each encoded basically the same thing (a plane relative to a beam) in a slightly different way per joint family. One mechanism now covers all of them.What type of change is this?
Checklist
Put an
xin the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code.CHANGELOG.mdfile in theUnreleasedsection under the most fitting heading (e.g.Added,Changed,Removed).invoke test).invoke lint).compas_timber.datastructures.Beam.class_diagrams.rst(if appropriate).