Skip to content

Joint candidates #499

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

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open

Joint candidates #499

wants to merge 8 commits into from

Conversation

chenkasirer
Copy link
Contributor

@chenkasirer chenkasirer commented Jul 22, 2025

Adding joint candidates in their own attribute so they can co-exist with joints (interactions).
Left the promotion logic out of it, done manually (see example script)

  • Added joint_candidates property to TimberModel.
  • Added add_joint_candidate method to TimberModel.
  • Added remove_joint_candidate method to TimberModel.
  • NBeamKDTreeAnalyzer now uses model.joint_candidates instead of filtering model.joints.

What type of change is this?

  • Bug fix in a backwards-compatible manner.
  • New feature in a backwards-compatible manner.
  • Breaking change: bug fix or new feature that involve incompatible API changes.
  • Other (e.g. doc update, configuration, etc)

Checklist

Put an x in 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.

  • I added a line to the CHANGELOG.md file in the Unreleased section under the most fitting heading (e.g. Added, Changed, Removed).
  • I ran all tests on my computer and it's all green (i.e. invoke test).
  • I ran lint on my computer and there are no errors (i.e. invoke lint).
  • I added new functions/classes and made them available on a second-level import, e.g. compas_timber.datastructures.Beam.
  • I have added tests that prove my fix is effective or that my feature works.
  • I have added necessary documentation (if appropriate)

Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements a joint candidate system that separates the identification of potential beam joints from actual joint creation. Joint candidates are stored separately from actual joints, allowing for a two-stage workflow where candidates are first identified and then selectively promoted to actual joints based on analysis.

  • Added joint_candidates property and management methods to TimberModel class
  • Renamed GenericJoint to JointCandidate to better reflect its purpose
  • Modified connect_adjacent_beams() to create candidates instead of actual joints

Reviewed Changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/compas_timber/model/model.py Added joint candidates property, add/remove methods, and modified beam connection logic
src/compas_timber/connections/generic_joint.py Renamed GenericJoint class to JointCandidate
src/compas_timber/connections/analyzers.py Updated to use joint_candidates instead of filtering joints
tests/compas_timber/test_model.py Added comprehensive tests for joint candidate functionality
tests/compas_timber/test_joint.py Updated tests to reflect new candidate behavior
examples/model/0008_joint_candidates_demo.py Added demonstration of joint candidate workflow
Comments suppressed due to low confidence (1)

tests/compas_timber/test_joint.py:279

  • This test verifies that JointCandidate.create() creates actual joints, but there's no test verifying that the created joint has the correct topology and location parameters that were passed to create().
    joint = JointCandidate.create(model, beams[0], beams[1], topology=JointTopology.TOPO_T, location=Point(0.5, 0, 0))

edge = (element_a.graph_node, element_b.graph_node)
if edge not in self._graph.edges():
self._graph.add_edge(*edge)
self._graph.edge_attribute(edge, "interactions", []) # initialize new edge, otherwise calls to model.joints breaks
Copy link
Preview

Copilot AI Jul 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment refers to a potential issue but doesn't explain why this initialization is necessary. Consider expanding the comment to explain what specific scenario would cause model.joints to break without this initialization.

Suggested change
self._graph.edge_attribute(edge, "interactions", []) # initialize new edge, otherwise calls to model.joints breaks
self._graph.edge_attribute(edge, "interactions", [])
# Initialize the "interactions" attribute for the new edge.
# This ensures that subsequent calls to `model.joints` do not break.
# Without this initialization, accessing or modifying `model.joints` could raise
# a KeyError because the "interactions" attribute would not exist for the edge.
# This step is critical for maintaining the integrity of the model's graph structure.

Copilot uses AI. Check for mistakes.

# Create candidates
model.connect_adjacent_beams()

# Should have 4 candidates (one for each edge of the square)
Copy link
Preview

Copilot AI Jul 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment suggests 4 candidates for a square pattern, but this depends on the specific beam intersection logic. The comment should clarify what type of intersections are expected (corner joints, overlapping beams, etc.).

Suggested change
# Should have 4 candidates (one for each edge of the square)
# Should have 4 candidates, one for each corner joint where beams meet at the vertices of the square.
# This behavior is determined by the `connect_adjacent_beams()` method, which identifies corner intersections.

Copilot uses AI. Check for mistakes.

Copy link

codecov bot commented Jul 29, 2025

Codecov Report

❌ Patch coverage is 96.87500% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 64.12%. Comparing base (158cd45) to head (e4a8dcc).

Files with missing lines Patch % Lines
src/compas_timber/model/model.py 96.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #499      +/-   ##
==========================================
+ Coverage   64.05%   64.12%   +0.06%     
==========================================
  Files          76       76              
  Lines       10887    10913      +26     
==========================================
+ Hits         6974     6998      +24     
- Misses       3913     3915       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@chenkasirer
Copy link
Contributor Author

@obucklin ready for review!

Copy link
Contributor

@obucklin obucklin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was like, "where'd the JointCandidate class go... I guess you just merged that one. Do you want to change the name of the generic_joint.py module as well?

Do we want the JointCandidate to be persistent? in that case we need to support multiple edges per element pair. Not sure if this is currently possible, but see #498 to see what I mean. Is that what the "interaction" and "candidate" edge attributes are for?

candidate : :class:`~compas_timber.connections.JointCandidate`
The joint candidate to remove.
"""
for interaction in candidate.interactions:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please take a quick look at issue #498. I don't know what happens under the hood of the Graph, but it looks like we need to support multiple edges for a pair of elements.

for interaction in candidate.interactions:
element_a, element_b = interaction
edge = (element_a.graph_node, element_b.graph_node)
if edge in self._graph.edges():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to do the same thing for TimberModel.remove_joint(). As I understand it, that method will just remove any edge between the two elements. It should probably filter to just remove interactions.

# HACK: calls to `model.joints` expect there to be a "interactions" on any edges
self._graph.edge_attribute(edge, "interactions", [])

# this is how joints and candidates co-exist on the same edge, they are stored under different attributes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clarification

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants