Skip to content

Commit b575435

Browse files
committed
Merge branch 'main' of https://github.com/gramaziokohler/compas_timber into compas_model_update
2 parents b87cb2c + 63b6292 commit b575435

40 files changed

Lines changed: 2374 additions & 563 deletions

File tree

.github/workflows/coverage.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: coverage
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- main
10+
11+
jobs:
12+
coverage:
13+
if: "!contains(github.event.pull_request.labels.*.name, 'docs-only')"
14+
runs-on: ${{ matrix.os }}
15+
strategy:
16+
matrix:
17+
os: [ubuntu-latest]
18+
python: ["3.12"]
19+
20+
steps:
21+
- uses: compas-dev/compas-actions.build@v4
22+
with:
23+
invoke_lint: false
24+
invoke_test: false
25+
python: ${{ matrix.python }}
26+
- name: Run tests collecting coverage reports
27+
run: pytest --cov src/compas_timber --cov-report=html
28+
- name: Upload coverage reports to Codecov
29+
uses: codecov/codecov-action@v4.0.1
30+
with:
31+
token: ${{ secrets.CODECOV_TOKEN }}

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3636
* Added `geometry` property in `compas_timber.elements.TimberElement` following its removal from the base `Element`.
3737
* Added `frame` property in serialized output in `compas_timber.elements.TimberElement` following its removal from the base `Element`.
3838
* Added `interactions()` method to `TimberModel` for iterating over edge-level joints and contacts in the model graph.
39+
* Added `topology` to class `Joint`.
40+
* Added `location` to class `Joint`.
41+
* Added `NBeamKDTreeAnalyzer` to `compas_timber.connections`.
42+
* Added `TripletAnalyzer` to `compas_timber.connections`.
43+
* Added `QuadAnalyzer` to `compas_timber.connections`.
44+
* Added `CompositeAnalyzer` to `compas_timber.connections`.
45+
* Added method `connect_adjacent_beams` to `TimberModel`.
46+
* Added `PlateJoint`.
47+
* Added `PlateButtJoint`.
48+
* Added `PlateMiterJoint`.
49+
* Added `PlateConnectionSolver`.
50+
* Added generic `ButtJoint` class from which `TButtJoint` and `LButtJoint` inherit.
3951

4052
### Changed
4153

@@ -46,6 +58,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4658
* Processings which are not the result of joinery are now serialized with `TimberElement`.
4759
* Fixed visualization bug in `Plate` due to loft resulting in flipped volume.
4860
* Fixed a few bugs in the `WallPopulator` workflow including GH component updates.
61+
* Renamed `NullJoint` to `GenericJoint`.
4962
* Fixed bug in show_ref_faces GH component.
5063
* Changed `compas_timber.connections.Joint` to inherit from `Data` instead of the depricated `Interaction`.
5164
* Renamed `compute_geometry` to `compute_elementgeometry` in `compas_timber.elements.Beam` following the renaming in `compas_model`.
@@ -59,11 +72,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5972
* Replaced all `GroupNode` references with the new `Group` element class from `compas_model`.
6073
* Updated default edge attributes in the model graph to include `joints` and `contacts`.
6174
* Updated `compas_model` version pinning from `0.4.4` to `0.8.0` to align with the latest development.
75+
* `BTLxProcessing.ref_side_index` defaults to `0` if not set, instead of the invalid `None`.
76+
* Fixed several GH Components for Rhino8 compatibility.
77+
* Fixed `graph_node` is `None` after deserializing a `TimberModel`.
6278

6379
### Removed
6480

6581
* Removed Grasshopper after-install plugin. Components should be installed via Rhino's Plugin Manager.
6682
* Removed the `add_element()` method from `compas_timber.model.TimberModel`, as the inherited method from `Model` now covers this functionality.
83+
* Removed `get_face_most_towards_beam` from `Joint` as not used anywhere.
84+
* Removed `get_face_most_ortho_to_beam` from `Joint` as not used anywhere.
6785

6886

6987
## [0.16.2] 2025-05-07
@@ -77,6 +95,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7795
### Removed
7896

7997

98+
8099
## [0.16.1] 2025-04-30
81100

82101
### Added

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# compas_timber
22

33
[![Github Actions Build Status](https://github.com/gramaziokohler/compas_timber/workflows/build/badge.svg)](https://github.com/gramaziokohler/compas_timber/actions)
4+
[![codecov](https://codecov.io/gh/gramaziokohler/compas_timber/graph/badge.svg?token=EFI7G1T18Z)](https://codecov.io/gh/gramaziokohler/compas_timber)
45
[![License](https://img.shields.io/github/license/gramaziokohler/compas_timber.svg)](https://pypi.python.org/pypi/compas_timber)
56
[![pip downloads](https://img.shields.io/pypi/dm/compas_timber)](https://pypi.python.org/project/compas_timber)
67
[![PyPI Package latest release](https://img.shields.io/pypi/v/compas_timber.svg)](https://pypi.python.org/pypi/compas_timber)

docs/api/compas_timber.connections.rst

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,21 @@ compas_timber.connections
44

55
.. currentmodule:: compas_timber.connections
66

7-
Classes
8-
=======
7+
Joints
8+
======
99

1010
.. autosummary::
1111
:toctree: generated/
1212
:nosignatures:
1313

1414
BallNodeJoint
15-
ConnectionSolver
1615
Joint
17-
JointTopology
1816
LapJoint
1917
LButtJoint
2018
LFrenchRidgeLapJoint
2119
LLapJoint
2220
LMiterJoint
23-
NullJoint
21+
GenericJoint
2422
TBirdsmouthJoint
2523
TButtJoint
2624
TDovetailJoint
@@ -31,6 +29,22 @@ Classes
3129
XNotchJoint
3230
YButtJoint
3331

32+
Solvers
33+
=======
34+
35+
.. autosummary::
36+
:toctree: generated/
37+
:nosignatures:
38+
39+
ConnectionSolver
40+
JointTopology
41+
Cluster
42+
BeamGroupAnalyzer
43+
NBeamKDTreeAnalyzer
44+
TripletAnalyzer
45+
QuadAnalyzer
46+
CompositeAnalyzer
47+
3448
Functions
3549
=========
3650

examples/model/0006_stand_topo.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import os
2+
3+
from compas.data import json_load
4+
from compas.geometry import Vector
5+
from compas_viewer.scene import Tag
6+
from compas_viewer.viewer import Viewer
7+
8+
from compas_timber.connections import JointTopology
9+
from compas_timber.elements import Beam
10+
from compas_timber.model import TimberModel
11+
12+
HERE = os.path.dirname(__file__)
13+
LINES = os.path.join(HERE, "stand.json")
14+
15+
16+
def create_viewer():
17+
# draw inflated centerlines
18+
viewer = Viewer()
19+
viewer.renderer.camera.far = 1000000.0
20+
viewer.renderer.camera.position = [10000.0, 10000.0, 10000.0]
21+
viewer.renderer.camera.pandelta = 0.05
22+
viewer.renderer.rendermode = "ghosted"
23+
return viewer
24+
25+
26+
def main():
27+
# Load centerlines from file
28+
lines = json_load(LINES)
29+
30+
model = TimberModel()
31+
32+
# Add beams to model
33+
CROSS_SQUARE = (120, 120)
34+
CROSS_TALL = (120, 60)
35+
NORMAL_VERTICALS = Vector(0, 1, 0)
36+
37+
# Create the beams with the right cross section depending on their category
38+
# Assign normal to the beams whose centerlines are aligned with the zaxis
39+
for category, lines in lines.items():
40+
for line in lines:
41+
if category in ["main", "support", "window"]:
42+
height, width = CROSS_SQUARE
43+
else:
44+
height, width = CROSS_TALL
45+
normal = NORMAL_VERTICALS if category == "verticals" else None
46+
beam = Beam.from_centerline(centerline=line, height=height, width=width, z_vector=normal)
47+
beam.attributes["category"] = category
48+
model.add_element(beam)
49+
50+
model.connect_adjacent_beams()
51+
52+
model.process_joinery()
53+
54+
# setup the viewer
55+
viewer = create_viewer()
56+
57+
for joint in model.joints:
58+
viewer.scene.add(joint.location)
59+
viewer.scene.add(Tag(JointTopology.get_name(joint.topology).split("_")[-1], joint.location))
60+
61+
# draw centerline
62+
for beam in model.beams:
63+
viewer.scene.add(beam.centerline)
64+
65+
# draw geometry (with features)
66+
for beam in model.beams:
67+
viewer.scene.add(beam.geometry)
68+
69+
viewer.show()
70+
71+
72+
if __name__ == "__main__":
73+
main()

examples/model/0007_analyzers.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import random
2+
3+
from compas.colors import Color
4+
from compas.colors import ColorMap
5+
from compas.geometry import Line
6+
from compas.geometry import Point
7+
from compas.tolerance import Tolerance
8+
from compas_viewer.scene import Tag
9+
from compas_viewer.viewer import Viewer
10+
11+
from compas_timber.connections import JointTopology
12+
from compas_timber.connections import MaxNCompositeAnalyzer
13+
from compas_timber.elements import Beam
14+
from compas_timber.model import TimberModel
15+
16+
17+
def create_viewer():
18+
# draw inflated centerlines
19+
viewer = Viewer()
20+
viewer.renderer.camera.far = 1000000.0
21+
viewer.renderer.camera.position = [10000.0, 10000.0, 10000.0]
22+
viewer.renderer.camera.pandelta = 0.05
23+
viewer.renderer.rendermode = "ghosted"
24+
return viewer
25+
26+
27+
def create_color_map(keys):
28+
# map cluster sizes to random colors which are hopefully visually distinct
29+
cmap = ColorMap.from_palette("imola")
30+
n = len(keys)
31+
values = [i / max(n - 1, 1) for i in range(n)]
32+
random.shuffle(values)
33+
return {key: cmap(val) for key, val in zip(keys, values)}
34+
35+
36+
def main():
37+
lines = [
38+
Line(Point(x=-10.0, y=-10.0, z=0.0), Point(x=300.0, y=200.0, z=0.0)),
39+
Line(Point(x=-10.0, y=-10.0, z=0.0), Point(x=-40.0, y=270.0, z=0.0)),
40+
Line(Point(x=-10.0, y=-10.0, z=0.0), Point(x=0.0, y=20.0, z=160.0)),
41+
Line(Point(x=45.89488087618746, y=234.15459672257862, z=0.0), Point(x=168.58797240614388, y=-95.31137353132192, z=0.0)),
42+
Line(Point(x=45.89488087618746, y=234.15459672257862, z=0.0), Point(x=330.0, y=350.0, z=0.0)),
43+
Line(Point(x=300.0, y=200.0, z=0.0), Point(x=500.0, y=0.0, z=0.0)),
44+
Line(Point(x=300.0, y=200.0, z=0.0), Point(x=220.0, y=170.0, z=-120.0)),
45+
Line(Point(x=-10.0, y=-10.0, z=0.0), Point(x=90.0, y=-220.0, z=0.0)),
46+
Line(Point(x=45.89488087618746, y=234.15459672257862, z=0.0), Point(x=0.0, y=220.0, z=130.0)),
47+
Line(Point(x=45.89488087618746, y=234.15459672257862, z=0.0), Point(x=0.0, y=260.0, z=-120.0)),
48+
]
49+
50+
model = TimberModel()
51+
52+
height, width = (12, 6)
53+
54+
for line in lines:
55+
beam = Beam.from_centerline(centerline=line, height=height, width=width)
56+
model.add_element(beam)
57+
58+
# pairs of adjacent beams are connected by GenericJoint instances
59+
model.connect_adjacent_beams()
60+
61+
# setup the viewer
62+
viewer = create_viewer()
63+
64+
for joint in model.joints:
65+
topo_name = JointTopology.get_name(joint.topology).split("_")[-1]
66+
viewer.scene.add(Tag(text=topo_name, position=joint.location, height=40, color=Color.blue()))
67+
68+
# draw centerline
69+
for beam in model.beams:
70+
viewer.scene.add(beam.centerline)
71+
viewer.scene.add(beam.geometry)
72+
viewer.scene.add(Tag(text=str(beam.graph_node), position=beam.centerline.midpoint, height=40, color=Color.black()))
73+
74+
tol = Tolerance(absolute=0.01)
75+
num_of_beams = len(list(model.beams))
76+
77+
### find triplets only
78+
# analyzer = TripletAnalyzer(model, tolerance=Tolerance(absolute=0.01))
79+
80+
### find quads only
81+
# analyzer = QuadAnalyzer(model, tolerance=Tolerance(absolute=0.01))
82+
83+
### find all clusters up to n beams, then n-1 beams, then n-2 beams, etc.
84+
analyzer = MaxNCompositeAnalyzer(model, n=num_of_beams, tolerance=tol)
85+
86+
clusters = analyzer.find()
87+
88+
size_to_color = create_color_map(list(sorted({len(cluster) for cluster in clusters})))
89+
for cluster in clusters:
90+
size = len(cluster)
91+
color = size_to_color[size]
92+
viewer.scene.add(cluster.location, pointsize=50, color=color)
93+
94+
viewer.show()
95+
96+
97+
if __name__ == "__main__":
98+
main()

requirements-dev.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ build
1313
pythonnet
1414
tomlkit
1515
compas_viewer
16+
pytest-cov

src/compas_timber/connections/__init__.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from .joint import Joint
2+
from .butt_joint import ButtJoint
23
from .l_butt import LButtJoint
34
from .l_lap import LLapJoint
45
from .l_miter import LMiterJoint
56
from .l_french_ridge_lap import LFrenchRidgeLapJoint
67
from .lap_joint import LapJoint
7-
from .null_joint import NullJoint
8+
from .generic_joint import GenericJoint
89
from .solver import ConnectionSolver
10+
from .solver import PlateConnectionSolver
911
from .solver import JointTopology
1012
from .solver import find_neighboring_elements
1113
from .t_butt import TButtJoint
@@ -25,10 +27,24 @@
2527
from .wall_joint import WallJoint
2628
from .wall_joint import InterfaceLocation
2729
from .wall_joint import InterfaceRole
30+
from .plate_joint import PlateJoint
31+
from .plate_butt_joint import PlateButtJoint
32+
from .plate_butt_joint import PlateLButtJoint
33+
from .plate_butt_joint import PlateTButtJoint
34+
from .plate_miter_joint import PlateMiterJoint
35+
from .plate_joint import PlateToPlateInterface
36+
from .analyzers import NBeamKDTreeAnalyzer
37+
from .analyzers import TripletAnalyzer
38+
from .analyzers import QuadAnalyzer
39+
from .analyzers import CompositeAnalyzer
40+
from .analyzers import Cluster
41+
from .analyzers import BeamGroupAnalyzer
42+
from .analyzers import MaxNCompositeAnalyzer
2843

2944
__all__ = [
3045
"Joint",
3146
"LapJoint",
47+
"ButtJoint",
3248
"TButtJoint",
3349
"LButtJoint",
3450
"TButtJoint",
@@ -39,10 +55,11 @@
3955
"XNotchJoint",
4056
"TLapJoint",
4157
"LLapJoint",
42-
"NullJoint",
58+
"GenericJoint",
4359
"LFrenchRidgeLapJoint",
4460
"JointTopology",
4561
"ConnectionSolver",
62+
"PlateConnectionSolver",
4663
"find_neighboring_elements",
4764
"TDovetailJoint",
4865
"BallNodeJoint",
@@ -55,4 +72,17 @@
5572
"WallJoint",
5673
"InterfaceLocation",
5774
"InterfaceRole",
75+
"PlateJoint",
76+
"PlateButtJoint",
77+
"PlateLButtJoint",
78+
"PlateTButtJoint",
79+
"PlateMiterJoint",
80+
"PlateToPlateInterface",
81+
"NBeamKDTreeAnalyzer",
82+
"TripletAnalyzer",
83+
"QuadAnalyzer",
84+
"CompositeAnalyzer",
85+
"Cluster",
86+
"BeamGroupAnalyzer",
87+
"MaxNCompositeAnalyzer",
5888
]

0 commit comments

Comments
 (0)