Skip to content

compas_cem.diagrams ¤

Topology and form diagrams.

Classes¤

Diagram ¤

Diagram(*args, **kwargs)

Base class that shares functionality across diagrams.

Source code in src/compas_cem/diagrams/diagram.py
def __init__(self, *args, **kwargs):
    super(Diagram, self).__init__(*args, **kwargs)

    self.update_default_node_attributes(
        {
            "x": 0.0,
            "y": 0.0,
            "z": 0.0,
            "qx": 0.0,
            "qy": 0.0,
            "qz": 0.0,
            "rx": 0.0,
            "ry": 0.0,
            "rz": 0.0,
            "_k": None,
            "type": None,
        }
    )

    self.update_default_edge_attributes({"type": None, "length": 0.0, "force": 0.0})

    self.attributes["gkey_node"] = {}
    self.attributes["tol"] = 3

Attributes¤

gkey_node property ¤
gkey_node

A dictionary that maps geometric keys to node keys.

Notes

This shadows the base graph method of the same name. The mapping here is a cache maintained as elements are added, rather than one recomputed from the current node coordinates on every call.

tol property writable ¤
tol

The number of decimal places used to compare node coordinates.

Methods:¤

add_edge ¤
add_edge(edge=None, v=None, attr_dict=None, **kwattr)

Add an edge to the diagram, from an edge element or from two node keys.

Parameters:

  • edge

    An edge element, or the first node of the edge given positionally.

  • v

    The second node of the edge, when the first was given as a key.

  • attr_dict

    The attributes to store on the edge.

  • **kwattr

    Extra attributes to store on the edge.

Returns:

  • key

    The two node keys of the added edge.

Notes

This accepts both vocabularies, as add_node does. An edge element carries its own attributes and may name its end nodes by coordinates, creating them if no node sits there yet.

Source code in src/compas_cem/diagrams/diagram.py
def add_edge(self, edge=None, v=None, attr_dict=None, **kwattr):
    """
    Add an edge to the diagram, from an edge element or from two node keys.

    Parameters
    ----------
    edge :
        An edge element, or the first node of the edge given positionally.
    v :
        The second node of the edge, when the first was given as a key.
    attr_dict :
        The attributes to store on the edge.
    **kwattr :
        Extra attributes to store on the edge.

    Returns
    -------
    key :
        The two node keys of the added edge.

    Notes
    -----
    This accepts both vocabularies, as `add_node` does. An edge element
    carries its own attributes and may name its end nodes by coordinates,
    creating them if no node sits there yet.
    """
    if isinstance(edge, Edge):
        if v is not None:
            raise ValueError("an edge element already names both of its nodes")
        return self._add_edge_element(edge)

    if isinstance(edge, Node):
        raise TypeError("a node element must be added with add_node")

    return super(Diagram, self).add_edge(edge, v, attr_dict=attr_dict, **kwattr)
add_node ¤
add_node(node=None, key=None, attr_dict=None, **kwattr)

Add a node to the diagram, from a node element or from a key.

Parameters:

  • node

    A node element, or a node key given positionally.

  • key

    A node key. If None, the next available key is assigned.

  • attr_dict

    The attributes to store on the node.

  • **kwattr

    Extra attributes to store on the node.

Returns:

  • key

    The key of the added node.

Notes

This accepts both vocabularies. A node element carries its own coordinates and is indexed by its geometric key, which is how a diagram is authored. A bare key with attributes is how the base graph adds a node, and how deserialization replays one.

Source code in src/compas_cem/diagrams/diagram.py
def add_node(self, node=None, key=None, attr_dict=None, **kwattr):
    """
    Add a node to the diagram, from a node element or from a key.

    Parameters
    ----------
    node :
        A node element, or a node key given positionally.
    key :
        A node key. If `None`, the next available key is assigned.
    attr_dict :
        The attributes to store on the node.
    **kwattr :
        Extra attributes to store on the node.

    Returns
    -------
    key :
        The key of the added node.

    Notes
    -----
    This accepts both vocabularies. A node element carries its own
    coordinates and is indexed by its geometric key, which is how a diagram
    is authored. A bare key with attributes is how the base graph adds a
    node, and how deserialization replays one.
    """
    if isinstance(node, Node):
        return self._add_node_element(node)

    if isinstance(node, Edge):
        raise TypeError("an edge element must be added with add_edge")

    if node is not None:
        if key is not None:
            raise ValueError("a node key was given both positionally and by name")
        key = node

    if isinstance(key, Data):
        raise TypeError(f"{key!r} is not a node key")

    return super(Diagram, self).add_node(key=key, attr_dict=attr_dict, **kwattr)
edge_force ¤
edge_force(edge)

Get the force value at an edge.

Parameters:

  • edge

    An edge key.

Returns:

  • force

    The force value in the edge. Negative in compression, positive in tension.

Source code in src/compas_cem/diagrams/diagram.py
def edge_force(self, edge):
    """
    Get the force value at an edge.

    Parameters
    ----------
    edge :
        An edge key.

    Returns
    -------
    force :
        The force value in the edge. Negative in compression, positive in
        tension.
    """
    return self.edge_attribute(key=edge, name="force")
edge_length_2 ¤
edge_length_2(edge)

Get the stored length attribute of an edge.

Parameters:

  • edge

    An edge key.

Returns:

  • length

    The signed length of the edge.

Notes

This is the length carried as an edge attribute, which is signed and set by the user, not the distance between the two end nodes.

Source code in src/compas_cem/diagrams/diagram.py
def edge_length_2(self, edge):
    """
    Get the stored length attribute of an edge.

    Parameters
    ----------
    edge :
        An edge key.

    Returns
    -------
    length :
        The signed length of the edge.

    Notes
    -----
    This is the length carried as an edge attribute, which is signed and set
    by the user, not the distance between the two end nodes.
    """
    return self.edge_attribute(key=edge, name="length")
edge_plane ¤
edge_plane(edge)

Get the projection plane at an edge.

Parameters:

  • edge

    An edge key.

Returns:

  • plane

    The projection plane of the edge, or None if it has none.

Source code in src/compas_cem/diagrams/diagram.py
def edge_plane(self, edge):
    """
    Get the projection plane at an edge.

    Parameters
    ----------
    edge :
        An edge key.

    Returns
    -------
    plane :
        The projection plane of the edge, or `None` if it has none.
    """
    return self.edge_attribute(key=edge, name="plane")
gkey ¤
gkey(xyz)

Compute the geometric key of a point at the tolerance of the diagram.

Parameters:

  • xyz

    The xyz coordinates of a point.

Returns:

  • gkey

    The geometric key of the point.

Source code in src/compas_cem/diagrams/diagram.py
def gkey(self, xyz):
    """
    Compute the geometric key of a point at the tolerance of the diagram.

    Parameters
    ----------
    xyz :
        The xyz coordinates of a point.

    Returns
    -------
    gkey :
        The geometric key of the point.
    """
    return TOL.geometric_key(xyz, self.tol)
is_edge_supported ¤
is_edge_supported(edge)

Check if any of the nodes of an edge is a support.

Parameters:

  • edge

    An edge key.

Returns:

  • flag

    True if any of the edge nodes is a support. False otherwise.

Source code in src/compas_cem/diagrams/diagram.py
def is_edge_supported(self, edge):
    """
    Check if any of the nodes of an edge is a support.

    Parameters
    ----------
    edge :
        An edge key.

    Returns
    -------
    flag :
        `True` if any of the edge nodes is a support. `False` otherwise.
    """
    return any([self.is_node_support(node) for node in edge])
is_node_loaded ¤
is_node_loaded(node, min_force=1e-06)

Check if there is a large-enough load applied to a node.

Parameters:

  • node

    A node key.

  • min_force

    The minimum force magnitude to consider a node loaded.

Returns:

  • flag

    True if the node is loaded. False otherwise.

Source code in src/compas_cem/diagrams/diagram.py
def is_node_loaded(self, node, min_force=1e-6):
    """
    Check if there is a large-enough load applied to a node.

    Parameters
    ----------
    node :
        A node key.
    min_force :
        The minimum force magnitude to consider a node loaded.

    Returns
    -------
    flag :
        `True` if the node is loaded. `False` otherwise.
    """
    return length_vector(self.node_load(node)) > min_force
is_node_support ¤
is_node_support(node)

Check if a node is a support.

Parameters:

  • node

    A node key.

Returns:

  • flag

    True if the node is a support. False otherwise.

Source code in src/compas_cem/diagrams/diagram.py
def is_node_support(self, node):
    """
    Check if a node is a support.

    Parameters
    ----------
    node :
        A node key.

    Returns
    -------
    flag :
        `True` if the node is a support. `False` otherwise.
    """
    return self.node_attribute(key=node, name="type") == "support"
loaded_nodes ¤
loaded_nodes(min_force=1e-06)

Iterate over all the nodes with a large-enough load applied.

Parameters:

  • min_force

    The minimum force magnitude to consider a node loaded.

Yields:

  • loaded_node

    The key of the next loaded node.

Source code in src/compas_cem/diagrams/diagram.py
def loaded_nodes(self, min_force=1e-6):
    """
    Iterate over all the nodes with a large-enough load applied.

    Parameters
    ----------
    min_force :
        The minimum force magnitude to consider a node loaded.

    Yields
    ------
    loaded_node :
        The key of the next loaded node.
    """
    for node in self.nodes():
        if self.is_node_loaded(node, min_force):
            yield node
node_connected_edges ¤
node_connected_edges(node)

The edges incident to a node.

Parameters:

  • node

    A node key.

Returns:

  • edges

    The keys of the edges connected to the node.

Notes

Each edge is reported in the direction it is stored in, so that the key returned here can be used to look edge attributes up directly.

Source code in src/compas_cem/diagrams/diagram.py
def node_connected_edges(self, node):
    """
    The edges incident to a node.

    Parameters
    ----------
    node :
        A node key.

    Returns
    -------
    edges :
        The keys of the edges connected to the node.

    Notes
    -----
    Each edge is reported in the direction it is stored in, so that the key
    returned here can be used to look edge attributes up directly.
    """
    edges = []

    for neighbor in self.neighbors(node):
        if neighbor in self.edge[node]:
            edges.append((node, neighbor))
        else:
            edges.append((neighbor, node))

    return edges
node_exists ¤
node_exists(value)

Check whether a node key or a point resolves to a node in the diagram.

Parameters:

  • value

    A node key, or the xyz coordinates of a point.

Returns:

  • flag

    True if the value resolves to a node. False otherwise.

Source code in src/compas_cem/diagrams/diagram.py
def node_exists(self, value):
    """
    Check whether a node key or a point resolves to a node in the diagram.

    Parameters
    ----------
    value :
        A node key, or the xyz coordinates of a point.

    Returns
    -------
    flag :
        `True` if the value resolves to a node. `False` otherwise.
    """
    if self.node_key(value) is not None:
        return True
    return False
node_key ¤
node_key(value)

Resolve a node key or a point to a node key.

Parameters:

  • value

    A node key, or the xyz coordinates of a point.

Returns:

  • key

    The node key, or None if no node sits at the given point.

Notes

An integer is taken to be a node key and is returned unchanged, whether or not a node with that key exists.

Source code in src/compas_cem/diagrams/diagram.py
def node_key(self, value):
    """
    Resolve a node key or a point to a node key.

    Parameters
    ----------
    value :
        A node key, or the xyz coordinates of a point.

    Returns
    -------
    key :
        The node key, or `None` if no node sits at the given point.

    Notes
    -----
    An integer is taken to be a node key and is returned unchanged, whether
    or not a node with that key exists.
    """
    if isinstance(value, int):
        return value
    return self.gkey_node.get(self.gkey(value))
node_load ¤
node_load(node)

Get the load applied at a node.

Parameters:

  • node

    A node key.

Returns:

  • load_vector

    A vector with the xyz components of the load.

Source code in src/compas_cem/diagrams/diagram.py
def node_load(self, node):
    """
    Get the load applied at a node.

    Parameters
    ----------
    node :
        A node key.

    Returns
    -------
    load_vector :
        A vector with the xyz components of the load.
    """
    return self.node_attributes(key=node, names=["qx", "qy", "qz"])
node_xyz ¤
node_xyz(key, xyz=None)

Get or set the coordinates of a node.

Parameters:

  • key

    A node key.

  • xyz

    The new xyz coordinates of the node. If None, the current coordinates are returned instead.

Returns:

  • xyz

    The coordinates of the node, or None when setting them.

Source code in src/compas_cem/diagrams/diagram.py
def node_xyz(self, key, xyz=None):
    """
    Get or set the coordinates of a node.

    Parameters
    ----------
    key :
        A node key.
    xyz :
        The new xyz coordinates of the node. If `None`, the current
        coordinates are returned instead.

    Returns
    -------
    xyz :
        The coordinates of the node, or `None` when setting them.
    """
    if xyz is None:
        return self.node_coordinates(key)
    self.update_node_xyz(key, xyz)
number_of_loaded_nodes ¤
number_of_loaded_nodes()

Number of nodes in the diagram where a load is applied.

Returns:

  • number

    The number of nodes with an applied load.

Source code in src/compas_cem/diagrams/diagram.py
def number_of_loaded_nodes(self):
    """
    Number of nodes in the diagram where a load is applied.

    Returns
    -------
    number :
        The number of nodes with an applied load.
    """
    return len(list(self.loaded_nodes()))
number_of_support_nodes ¤
number_of_support_nodes()

Number of nodes in the diagram with an assigned support.

Returns:

  • number

    The number of nodes with a support.

Source code in src/compas_cem/diagrams/diagram.py
def number_of_support_nodes(self):
    """
    Number of nodes in the diagram with an assigned support.

    Returns
    -------
    number :
        The number of nodes with a support.
    """
    return len(list(self.support_nodes()))
reaction_force ¤
reaction_force(node)

Get the reaction force vector at a node.

Parameters:

  • node

    A node key.

Returns:

  • reaction_vector

    A vector with the xyz components of the reaction force.

Source code in src/compas_cem/diagrams/diagram.py
def reaction_force(self, node):
    """
    Get the reaction force vector at a node.

    Parameters
    ----------
    node :
        A node key.

    Returns
    -------
    reaction_vector :
        A vector with the xyz components of the reaction force.
    """
    return self.node_attributes(key=node, names=["rx", "ry", "rz"])
support_nodes ¤
support_nodes()

Nodes where a support has been assigned.

Yields:

  • support_node

    The key of the next node with a support.

Source code in src/compas_cem/diagrams/diagram.py
def support_nodes(self):
    """
    Nodes where a support has been assigned.

    Yields
    ------
    support_node :
        The key of the next node with a support.
    """
    return self.nodes_where({"type": "support"})
update_node_xyz ¤
update_node_xyz(key, xyz)

Move a node to new coordinates and reindex its geometric key.

Parameters:

  • key

    A node key.

  • xyz

    The new xyz coordinates of the node.

Source code in src/compas_cem/diagrams/diagram.py
def update_node_xyz(self, key, xyz):
    """
    Move a node to new coordinates and reindex its geometric key.

    Parameters
    ----------
    key :
        A node key.
    xyz :
        The new xyz coordinates of the node.
    """
    gkey = self.gkey(xyz)
    if gkey in self.gkey_node:
        del self.gkey_node[gkey]
    self._add_node_element(Node(key, xyz))

FormDiagram ¤

FormDiagram(*args, **kwargs)

The output of the form-finding algorithm.

A form diagram carries the same nodes and edges as the topology diagram it came from, positioned in static equilibrium, with the resulting edge forces and support reactions stored on it.

Parameters:

  • *args

    Arguments forwarded to the base diagram.

  • **kwargs

    Keyword arguments forwarded to the base diagram.

Source code in src/compas_cem/diagrams/form.py
def __init__(self, *args, **kwargs):
    super(FormDiagram, self).__init__(*args, **kwargs)

Methods:¤

from_equilibrium_state classmethod ¤
from_equilibrium_state(eq_state, structure)

Build a form diagram from an equilibrium state.

Parameters:

  • eq_state

    An equilibrium state computed by the numerical kernel.

  • structure

    The structure the equilibrium state was computed on.

Returns:

  • form

    A form diagram carrying the equilibrium state.

Source code in src/compas_cem/diagrams/form.py
@classmethod
def from_equilibrium_state(cls, eq_state, structure):
    """
    Build a form diagram from an equilibrium state.

    Parameters
    ----------
    eq_state :
        An equilibrium state computed by the numerical kernel.
    structure :
        The structure the equilibrium state was computed on.

    Returns
    -------
    form :
        A form diagram carrying the equilibrium state.
    """
    return form_from_eqstate(eq_state, structure, cls)
from_topology_diagram classmethod ¤
from_topology_diagram(topology)

Construct a form diagram from a topology diagram.

Parameters:

  • topology

    The topology diagram to copy.

Returns:

  • form

    A form diagram with the same nodes and edges as the topology.

Notes

The trail bookkeeping is dropped, because a form diagram is the result of walking the trails rather than a description of them.

Source code in src/compas_cem/diagrams/form.py
@classmethod
def from_topology_diagram(cls, topology):
    """
    Construct a form diagram from a topology diagram.

    Parameters
    ----------
    topology :
        The topology diagram to copy.

    Returns
    -------
    form :
        A form diagram with the same nodes and edges as the topology.

    Notes
    -----
    The trail bookkeeping is dropped, because a form diagram is the result
    of walking the trails rather than a description of them.
    """
    form = topology.copy(cls=cls)

    del form.attributes["_trails"]
    del form.attributes["_auxiliary_trails"]
    del form.attributes["_aux_length"]
    del form.attributes["_aux_vector"]

    return form