Skip to content

compas_timber.structural ¤

Classes¤

BeamSegmentGenerator ¤

Base class for beam segment generators.

Subclasses should implement generate_segments to produce structural segments for a beam, given its joints.

Methods:¤

generate_segments abstractmethod ¤
generate_segments(beam: Beam, joints: Sequence[Joint]) -> list[StructuralSegment]

Generate structural segments for a beam.

Parameters:

  • beam (:class:`compas_timber.elements.Beam`) –

    The beam for which to generate segments.

  • joints (list of :class:`compas_timber.connections.Joint`) –

    The joints associated with the beam.

Returns:

  • list of :class:`StructuralSegment` –
Source code in src/compas_timber/structural.py
@abstractmethod
def generate_segments(self, beam: Beam, joints: Sequence[Joint]) -> List[StructuralSegment]:
    """Generate structural segments for a beam.

    Parameters
    ----------
    beam : :class:`compas_timber.elements.Beam`
        The beam for which to generate segments.
    joints : list of :class:`compas_timber.connections.Joint`
        The joints associated with the beam.

    Returns
    -------
    list of :class:`StructuralSegment`

    """
    raise NotImplementedError

BeamStructuralElementSolver ¤

BeamStructuralElementSolver(
    beam_segment_generator: BeamSegmentGenerator | None = None,
    joint_connector_generator: JointConnectorGenerator | None = None,
    interaction_type: InteractionType | None = None,
)

Produces structural segments for beams and joints in a timber model.

Parameters:

  • beam_segment_generator (:class:`BeamSegmentGenerator`, default: None ) –

    Generator used to produce structural segments for beams. Defaults to :class:SimpleBeamSegmentGenerator.

  • joint_connector_generator (:class:`JointConnectorGenerator`, default: None ) –

    Generator used to produce connector segments for joints. Defaults to :class:SimpleJointConnectorGenerator.

  • interaction_type (:class:`InteractionSource`, default: None ) –

    Which interaction types to consider when creating structural segments. Defaults to InteractionSource.AUTO.

Source code in src/compas_timber/structural.py
def __init__(
    self,
    beam_segment_generator: Optional[BeamSegmentGenerator] = None,
    joint_connector_generator: Optional[JointConnectorGenerator] = None,
    interaction_type: Optional[InteractionType] = None,
) -> None:
    self.beam_segment_generator = beam_segment_generator or SimpleBeamSegmentGenerator()
    self.joint_connector_generator = joint_connector_generator or SimpleJointConnectorGenerator()
    self._interaction_type = interaction_type

Methods:¤

add_joint_structural_segments ¤
add_joint_structural_segments(
    model: TimberModel, joints: Iterable[Joint]
) -> Iterable[StructuralSegment]

Creates and adds structural segments for a given joint to the timber model.

For joints connecting non-intersecting beams (e.g. crossing beams), this creates a 'virtual' element connecting the centerlines of the beams.

Parameters:

  • model (:class:`compas_timber.model.TimberModel`) –

    The timber model containing the beams and joints.

  • joints (iterable of :class:`compas_timber.connections.Joint`) –

    The joints for which to create structural segments.

Returns:

  • list of :class:`StructuralSegment` –

    The structural segments that were created and added to the model.

Source code in src/compas_timber/structural.py
def add_joint_structural_segments(self, model: TimberModel, joints: Iterable[Joint]) -> Iterable[StructuralSegment]:
    """Creates and adds structural segments for a given joint to the timber model.

    For joints connecting non-intersecting beams (e.g. crossing beams), this creates
    a 'virtual' element connecting the centerlines of the beams.

    Parameters
    ----------
    model : :class:`compas_timber.model.TimberModel`
        The timber model containing the beams and joints.
    joints : iterable of :class:`compas_timber.connections.Joint`
        The joints for which to create structural segments.

    Returns
    -------
    list of :class:`StructuralSegment`
        The structural segments that were created and added to the model.

    """
    results = []
    for joint in joints:
        connectors = self.joint_connector_generator.generate_connectors(joint)
        for beam_a, beam_b, segments in connectors:
            model.add_structural_connector_segments(beam_a, beam_b, segments)
            results.extend(segments)
    return results
add_structural_segments ¤
add_structural_segments(
    model: TimberModel,
) -> tuple[list[StructuralSegment], Iterable[Joint]]

Creates and adds structural segments for a given beam to the timber model.

These are essentially segments of the beam's centerline split at the locations of the joints.

Parameters:

  • model (:class:`compas_timber.model.TimberModel`) –

    The timber model containing the beams and joints.

Returns:

  • list of :class:`StructuralSegment` –

    The structural segments that were created and added to the model.

  • set of :class:`compas_timber.connections.Joint` –

    The joints that were traversed when creating the structural segments. This is the definitive set of joints traversed when creating the beam segments, and should be used when creating joint connector segments to avoid duplicates.

Source code in src/compas_timber/structural.py
def add_structural_segments(self, model: TimberModel) -> Tuple[List[StructuralSegment], Iterable[Joint]]:
    """Creates and adds structural segments for a given beam to the timber model.

    These are essentially segments of the beam's centerline split at the locations of the joints.

    Parameters
    ----------
    model : :class:`compas_timber.model.TimberModel`
        The timber model containing the beams and joints.

    Returns
    -------
    list of :class:`StructuralSegment`
        The structural segments that were created and added to the model.
    set of :class:`compas_timber.connections.Joint`
        The joints that were traversed when creating the structural segments. This is the definitive set of joints traversed when creating the beam segments,
        and should be used when creating joint connector segments to avoid duplicates.

    """
    joints_traversed = set()
    segments = []
    for beam in model.beams:
        joints_for_beam = self._get_interactions(beam, model)
        segments = self.beam_segment_generator.generate_segments(beam, joints_for_beam)
        model.add_beam_structural_segments(beam, segments)
        joints_traversed.update(joints_for_beam)
    return segments, joints_traversed

InteractionType ¤

Defines which interaction types to consider when creating structural segments.

Attributes:

  • AUTO (int) –

    Per connection: use joints if available, fall back to candidates.

  • JOINTS (int) –

    Only use joints, ignore candidates.

  • CANDIDATES (int) –

    Only use candidates, ignore joints.

JointConnectorGenerator ¤

Base class for joint connector segment generators.

Subclasses should implement generate_connectors to produce structural connector segments for a joint.

Methods:¤

generate_connectors abstractmethod ¤
generate_connectors(joint: Joint) -> list[tuple[Beam, Beam, list[StructuralSegment]]]

Generate connector segments for a joint.

Parameters:

  • joint (:class:`compas_timber.connections.Joint`) –

    The joint for which to generate connector segments.

Returns:

  • list of tuple(Beam, Beam, list of :class:`StructuralSegment`) –

    Each tuple contains the two beams being connected and the connector segments between them.

Source code in src/compas_timber/structural.py
@abstractmethod
def generate_connectors(self, joint: Joint) -> List[Tuple[Beam, Beam, List[StructuralSegment]]]:
    """Generate connector segments for a joint.

    Parameters
    ----------
    joint : :class:`compas_timber.connections.Joint`
        The joint for which to generate connector segments.

    Returns
    -------
    list of tuple(Beam, Beam, list of :class:`StructuralSegment`)
        Each tuple contains the two beams being connected and the connector
        segments between them.

    """
    raise NotImplementedError

SimpleBeamSegmentGenerator ¤

Generates structural segments by splitting the beam centerline at joint locations.

SimpleJointConnectorGenerator ¤

Generates connector segments as virtual lines between non-intersecting beam centerlines.

StructuralGraph ¤

StructuralGraph(*args, **kwargs)

A structural graph derived from a :class:~compas_timber.model.TimberModel.

Wraps a :class:~compas.datastructures.Graph and provides a domain-specific API so callers never need to touch raw graph attributes directly.

Nodes are unique structural points (DOF locations — the endpoints of all structural segments, deduplicated within tolerance). Edges are the structural segments themselves, each tagged as either "beam" or "connector" and carrying a back-reference to the originating timber model element.

Create instances via :meth:from_model rather than directly.

Attributes:

  • nodes (iterator) –

    The node keys of all nodes in the graph.

  • edges (iterator) –

    All (u, v) edge pairs in the graph.

  • beam_edges (iterator) –

    (u, v) pairs for edges that represent beam-centerline segments.

  • connector_edges (iterator) –

    (u, v) pairs for edges that represent virtual connector segments between non-intersecting beam centerlines.

Examples:

>>> sg = StructuralGraph.from_model(model)
>>> for u, v in sg.beam_edges:
...     pt_i = sg.node_point(u)
...     pt_j = sg.node_point(v)
...     seg = sg.segment(u, v)
...     beam = sg.beam(u, v)
>>> for u, v in sg.connector_edges:
...     joint = sg.joint(u, v)
>>> segs = sg.segments_for_beam(my_beam)
Source code in src/compas_timber/structural.py
def __init__(self, *args, **kwargs) -> None:
    super().__init__(*args, **kwargs)
    self._cached_node_index = None  # built lazily

Attributes¤

beam_edges property ¤
beam_edges

Iterate over (u, v) pairs for all beam-segment edges.

connector_edges property ¤
connector_edges

Iterate over (u, v) pairs for all connector-segment edges.

Methods:¤

beam ¤
beam(u, v)

Return the source :class:~compas_timber.elements.Beam for a beam edge.

Parameters:

  • u (int, graph node) –
  • v (int, graph node) –

Returns:

  • class:`~compas_timber.elements.Beam` or None –

    None for connector edges.

Source code in src/compas_timber/structural.py
def beam(self, u, v):
    """Return the source :class:`~compas_timber.elements.Beam` for a beam edge.

    Parameters
    ----------
    u : int, graph node
    v : int, graph node

    Returns
    -------
    :class:`~compas_timber.elements.Beam` or None
        ``None`` for connector edges.
    """
    return self.edge_attribute((u, v), "beam")
from_model classmethod ¤
from_model(model: TimberModel) -> StructuralGraph

Builds a :class:StructuralGraph from the structural segments stored in a timber model.

Nodes represent unique endpoints of structural segments (identified within tolerance). Edges represent the segments themselves. Each edge carries:

  • segment — the :class:StructuralSegment;
  • type — "beam" or "connector";
  • beam — (beam edges) the source :class:~compas_timber.elements.Beam;
  • joint — (connector edges) the source :class:~compas_timber.connections.Joint, or None when the connector was derived from a candidate rather than a resolved joint.

Parameters:

  • model (:class:`~compas_timber.model.TimberModel`) –

    The timber model whose structural segments are used to build the graph.

Returns:

  • class:`StructuralGraph` –

Raises:

  • ValueError –

    If no structural segments are found in the model. Call :meth:~compas_timber.model.TimberModel.create_beam_structural_segments first.

Examples:

>>> model.create_beam_structural_segments()
>>> sg = StructuralGraph.from_model(model)
>>> for u, v in sg.beam_edges:
...     print(sg.node_index(u), sg.node_index(v), sg.segment(u, v).line.length, sg.beam(u, v).name)
Source code in src/compas_timber/structural.py
@classmethod
def from_model(cls, model: TimberModel) -> StructuralGraph:
    """Builds a :class:`StructuralGraph` from the structural segments stored in a timber model.

    Nodes represent unique endpoints of structural segments (identified within tolerance).
    Edges represent the segments themselves.  Each edge carries:

    - ``segment`` — the :class:`StructuralSegment`;
    - ``type`` — ``"beam"`` or ``"connector"``;
    - ``beam`` — *(beam edges)* the source :class:`~compas_timber.elements.Beam`;
    - ``joint`` — *(connector edges)* the source
      :class:`~compas_timber.connections.Joint`, or ``None`` when the connector
      was derived from a candidate rather than a resolved joint.

    Parameters
    ----------
    model : :class:`~compas_timber.model.TimberModel`
        The timber model whose structural segments are used to build the graph.

    Returns
    -------
    :class:`StructuralGraph`

    Raises
    ------
    ValueError
        If no structural segments are found in the model.  Call
        :meth:`~compas_timber.model.TimberModel.create_beam_structural_segments`
        first.

    Examples
    --------
    >>> model.create_beam_structural_segments()
    >>> sg = StructuralGraph.from_model(model)
    >>> for u, v in sg.beam_edges:
    ...     print(sg.node_index(u), sg.node_index(v), sg.segment(u, v).line.length, sg.beam(u, v).name)

    """
    instance = cls(default_edge_attributes={"beam": None, "structural_segments": None, "connector": None})

    _node_positions = {}  # node_key -> Point, used for tolerance-based deduplication

    def _find_or_create_node(point):
        # type: (Point) -> object
        for node_key, existing in _node_positions.items():
            if TOL.is_zero(distance_point_point(existing, point)):
                return node_key
        node_key = instance.add_node(x=point.x, y=point.y, z=point.z)
        _node_positions[node_key] = point
        return node_key

    # --- beam structural segments (stored per node in the model graph) ---
    for beam in model.beams:
        segments = model.get_beam_structural_segments(beam)
        for segment in segments:
            u = _find_or_create_node(segment.line.start)
            v = _find_or_create_node(segment.line.end)
            instance.add_edge(u, v, segment=segment, type="beam", beam=beam)

    # --- connector segments (stored per edge in the model graph) ---
    for edge in model._graph.edges():
        connector_segments = model._graph.edge_attribute(edge, "structural_segments")
        if not connector_segments:
            continue
        # Retrieve the joint that sits on this edge (may be None when only a candidate exists)
        joint_guid = model._graph.edge_attribute(edge, "joints")  # TODO: shouldn't joints be on nodes?
        joint = model._joints.get(joint_guid) if joint_guid else None
        for segment in connector_segments:
            u = _find_or_create_node(segment.line.start)
            v = _find_or_create_node(segment.line.end)
            instance.add_edge(u, v, segment=segment, type="connector", joint=joint)

    if instance.number_of_nodes() == 0:
        raise ValueError("No structural segments found in the model. Call TimberModel.create_beam_structural_segments() before building the structural graph.")

    return instance
joint ¤
joint(u, v)

Return the source :class:~compas_timber.connections.Joint for a connector edge.

Parameters:

  • u (int, graph node) –
  • v (int, graph node) –

Returns:

  • class:`~compas_timber.connections.Joint` or None –

    None for beam edges or when the connector was derived from a candidate rather than a resolved joint.

Source code in src/compas_timber/structural.py
def joint(self, u, v):
    """Return the source :class:`~compas_timber.connections.Joint` for a connector edge.

    Parameters
    ----------
    u : int, graph node
    v : int, graph node

    Returns
    -------
    :class:`~compas_timber.connections.Joint` or None
        ``None`` for beam edges or when the connector was derived from a
        candidate rather than a resolved joint.
    """
    return self.edge_attribute((u, v), "joint")
node_index ¤
node_index(node)

Return the sequential integer index of node.

The index is stable for the lifetime of this object and is suitable for building connectivity tables (e.g. for exporting to FEM software).

Parameters:

  • node (object) –

    A node key as returned by iterating :attr:nodes.

Returns:

Source code in src/compas_timber/structural.py
def node_index(self, node):
    # type: (object) -> int
    """Return the sequential integer index of *node*.

    The index is stable for the lifetime of this object and is suitable for
    building connectivity tables (e.g. for exporting to FEM software).

    Parameters
    ----------
    node : object
        A node key as returned by iterating :attr:`nodes`.

    Returns
    -------
    int
    """
    if self._cached_node_index is None:
        self._cached_node_index = {n: i for i, n in enumerate(self.nodes())}
    return self._cached_node_index[node]
node_point ¤
node_point(node)

Return the 3-D position of a node as a :class:~compas.geometry.Point.

Parameters:

  • node (object) –

    A node key as returned by iterating :attr:nodes.

Returns:

  • class:`~compas.geometry.Point` –
Source code in src/compas_timber/structural.py
def node_point(self, node):
    # type: (object) -> Point
    """Return the 3-D position of a node as a :class:`~compas.geometry.Point`.

    Parameters
    ----------
    node : object
        A node key as returned by iterating :attr:`nodes`.

    Returns
    -------
    :class:`~compas.geometry.Point`
    """
    x = self.node_attribute(node, "x")
    y = self.node_attribute(node, "y")
    z = self.node_attribute(node, "z")
    return Point(x, y, z)
segment ¤
segment(u, v)

Return the :class:StructuralSegment on the edge (u, v).

Parameters:

  • u (int, graph node) –
  • v (int, graph node) –

Returns:

  • class:`StructuralSegment` –
Source code in src/compas_timber/structural.py
def segment(self, u, v):
    """Return the :class:`StructuralSegment` on the edge ``(u, v)``.

    Parameters
    ----------
    u : int, graph node
    v : int, graph node

    Returns
    -------
    :class:`StructuralSegment`
    """
    return self.edge_attribute((u, v), "segment")
segments_for_beam ¤
segments_for_beam(beam)

Return all (u, v) beam edges that belong to beam.

Parameters:

  • beam (:class:`~compas_timber.elements.Beam`) –

Returns:

  • list of tuple(u, v) –
Source code in src/compas_timber/structural.py
def segments_for_beam(self, beam):
    # type: (Beam) -> List[Tuple]
    """Return all ``(u, v)`` beam edges that belong to *beam*.

    Parameters
    ----------
    beam : :class:`~compas_timber.elements.Beam`

    Returns
    -------
    list of tuple(u, v)
    """
    return [(u, v) for u, v in self.beam_edges if self.edge_attribute((u, v), "beam") is beam]
segments_for_joint ¤
segments_for_joint(joint)

Return all (u, v) connector edges that were generated for joint.

Parameters:

  • joint (:class:`~compas_timber.connections.Joint`) –

Returns:

  • list of tuple(u, v) –
Source code in src/compas_timber/structural.py
def segments_for_joint(self, joint):
    # type: (Joint) -> List[Tuple]
    """Return all ``(u, v)`` connector edges that were generated for *joint*.

    Parameters
    ----------
    joint : :class:`~compas_timber.connections.Joint`

    Returns
    -------
    list of tuple(u, v)
    """
    return [(u, v) for u, v in self.connector_edges if self.edge_attribute((u, v), "joint") is joint]