Source code for anastruct.preprocess.truss_class

from abc import ABC, abstractmethod
from typing import Iterable, Literal, Optional, Sequence, Union, cast, overload

import numpy as np

from anastruct._types import LoadDirection, SectionProps
from anastruct.fem.system import SystemElements
from anastruct.fem.system_components.util import add_node
from anastruct.vertex import Vertex

[docs] DEFAULT_TRUSS_SECTION: SectionProps = { "EI": 1e6, "EA": 1e8, "g": 0.0, }
[docs] class Truss(ABC): """Abstract base class for 2D truss structures. Provides a framework for creating parametric truss geometries with automated node generation, connectivity, and support definitions. Subclasses implement specific truss types (Howe, Pratt, Warren, etc.). The truss generation follows a three-phase process: 1. define_nodes() - Generate node coordinates 2. define_connectivity() - Define which nodes connect to form elements 3. define_supports() - Define support locations and types Attributes: width (float): Total span of the truss (length units) height (float): Height of the truss (length units) top_chord_section (SectionProps): Section properties for top chord elements bottom_chord_section (SectionProps): Section properties for bottom chord elements web_section (SectionProps): Section properties for diagonal web elements web_verticals_section (SectionProps): Section properties for vertical web elements top_chord_continuous (bool): If True, top chord is continuous; if False, pinned at joints bottom_chord_continuous (bool): If True, bottom chord is continuous; if False, pinned at joints supports_type (Literal["simple", "pinned", "fixed"]): Type of supports to apply system (SystemElements): The FEM system containing all nodes, elements, and supports """ # Common geometry
[docs] width: float
[docs] height: float
# Material properties
[docs] top_chord_section: SectionProps
[docs] bottom_chord_section: SectionProps
[docs] web_section: SectionProps
[docs] web_verticals_section: SectionProps
# Configuration
[docs] top_chord_continuous: bool
[docs] bottom_chord_continuous: bool
[docs] supports_type: Literal["simple", "pinned", "fixed"]
# Defined by subclass (initialized in define_* methods)
[docs] nodes: list[Vertex]
[docs] top_chord_node_ids: Union[list[int], dict[str, list[int]]]
[docs] bottom_chord_node_ids: Union[list[int], dict[str, list[int]]]
[docs] web_node_pairs: list[tuple[int, int]]
[docs] web_verticals_node_pairs: list[tuple[int, int]]
[docs] support_definitions: dict[int, Literal["fixed", "pinned", "roller"]]
[docs] top_chord_length: float
[docs] bottom_chord_length: float
# Defined by main class (initialized in add_elements)
[docs] top_chord_element_ids: Union[list[int], dict[str, list[int]]]
[docs] bottom_chord_element_ids: Union[list[int], dict[str, list[int]]]
[docs] web_element_ids: list[int]
[docs] web_verticals_element_ids: list[int]
# System
[docs] system: SystemElements
def __init__( self, width: float, height: float, top_chord_section: Optional[SectionProps] = None, bottom_chord_section: Optional[SectionProps] = None, web_section: Optional[SectionProps] = None, web_verticals_section: Optional[SectionProps] = None, top_chord_continuous: bool = True, bottom_chord_continuous: bool = True, supports_type: Literal["simple", "pinned", "fixed"] = "simple", ): """Initialize a truss structure. Args: width (float): Total span of the truss. Must be positive. height (float): Height of the truss. Must be positive. top_chord_section (Optional[SectionProps]): Section properties for top chord. Defaults to DEFAULT_TRUSS_SECTION if not provided. bottom_chord_section (Optional[SectionProps]): Section properties for bottom chord. Defaults to DEFAULT_TRUSS_SECTION if not provided. web_section (Optional[SectionProps]): Section properties for diagonal web members. Defaults to DEFAULT_TRUSS_SECTION if not provided. web_verticals_section (Optional[SectionProps]): Section properties for vertical web members. Defaults to web_section if not provided. top_chord_continuous (bool): If True, top chord is continuous at joints (moment connection). If False, top chord is pinned at joints. Defaults to True. bottom_chord_continuous (bool): If True, bottom chord is continuous at joints. If False, bottom chord is pinned at joints. Defaults to True. supports_type (Literal["simple", "pinned", "fixed"]): Type of supports. "simple" creates pinned+roller, "pinned" creates pinned+pinned, "fixed" creates fixed+fixed. Defaults to "simple". Raises: ValueError: If width or height is not positive. """ if width <= 0: raise ValueError(f"width must be positive, got {width}") if height <= 0: raise ValueError(f"height must be positive, got {height}") self.width = width self.height = height self.top_chord_section = top_chord_section or DEFAULT_TRUSS_SECTION self.bottom_chord_section = bottom_chord_section or DEFAULT_TRUSS_SECTION self.web_section = web_section or DEFAULT_TRUSS_SECTION self.web_verticals_section = web_verticals_section or self.web_section self.top_chord_continuous = top_chord_continuous self.bottom_chord_continuous = bottom_chord_continuous self.supports_type = supports_type def ensure_valid_section(section: SectionProps) -> SectionProps: """Ensure section has all required properties, filling in defaults.""" valid_section = dict(DEFAULT_TRUSS_SECTION) # Start with defaults valid_section.update(section) # Override with provided values return cast(SectionProps, valid_section) self.top_chord_section = ensure_valid_section(self.top_chord_section) self.bottom_chord_section = ensure_valid_section(self.bottom_chord_section) self.web_section = ensure_valid_section(self.web_section) self.web_verticals_section = ensure_valid_section(self.web_verticals_section) # Initialize mutable attributes (prevents sharing between instances) self.nodes = [] self.web_node_pairs = [] self.web_verticals_node_pairs = [] self.support_definitions = {} self.top_chord_length = 0.0 self.bottom_chord_length = 0.0 self.define_nodes() self.define_connectivity() self.define_supports() self.system = SystemElements() self.add_nodes() self.add_elements() self.add_supports() @property @abstractmethod
[docs] def type(self) -> str: """Return the human-readable name of the truss type."""
@abstractmethod
[docs] def define_nodes(self) -> None: """Generate node coordinates and populate self.nodes list. Must be implemented by subclasses. Should create Vertex objects representing all node locations in the truss. """
@abstractmethod
[docs] def define_connectivity(self) -> None: """Define element connectivity by populating node ID lists. Must be implemented by subclasses. Should populate: - self.top_chord_node_ids - self.bottom_chord_node_ids - self.web_node_pairs - self.web_verticals_node_pairs """
@abstractmethod
[docs] def define_supports(self) -> None: """Define support locations and types by populating self.support_definitions. Must be implemented by subclasses. """
[docs] def add_nodes(self) -> None: """Add all nodes from self.nodes to the SystemElements.""" for i, vertex in enumerate(self.nodes): add_node(self.system, point=vertex, node_id=i + 1)
[docs] def add_elements(self) -> None: """Create elements from connectivity definitions and add to SystemElements. Populates element ID lists: - self.top_chord_element_ids - self.bottom_chord_element_ids - self.web_element_ids - self.web_verticals_element_ids """ def add_segment_elements( node_pairs: Iterable[tuple[int, int]], section: SectionProps, continuous: bool, ) -> list[int]: """Helper to add a sequence of connected elements. Args: node_pairs (Iterable[tuple[int, int]]): Pairs of node IDs to connect section (SectionProps): Section properties for the elements continuous (bool): If True, create moment connections; if False, pin connections Returns: list[int]: Element IDs of created elements """ element_ids = [] for i, j in node_pairs: element_ids.append( self.system.add_element( location=(self.nodes[i - 1], self.nodes[j - 1]), EA=section["EA"], EI=section["EI"], g=section["g"], spring=None if continuous else {1: 0.0, 2: 0.0}, ) ) return element_ids # Bottom chord elements if isinstance(self.bottom_chord_node_ids, dict): self.bottom_chord_element_ids = {} for key, segment_node_ids in self.bottom_chord_node_ids.items(): self.bottom_chord_element_ids[key] = add_segment_elements( node_pairs=zip(segment_node_ids[:-1], segment_node_ids[1:]), section=self.bottom_chord_section, continuous=self.bottom_chord_continuous, ) else: self.bottom_chord_element_ids = add_segment_elements( node_pairs=zip( self.bottom_chord_node_ids[:-1], self.bottom_chord_node_ids[1:] ), section=self.bottom_chord_section, continuous=self.bottom_chord_continuous, ) # Top chord elements if isinstance(self.top_chord_node_ids, dict): self.top_chord_element_ids = {} for key, segment_node_ids in self.top_chord_node_ids.items(): self.top_chord_element_ids[key] = add_segment_elements( node_pairs=zip(segment_node_ids[:-1], segment_node_ids[1:]), section=self.top_chord_section, continuous=self.top_chord_continuous, ) else: self.top_chord_element_ids = add_segment_elements( node_pairs=zip( self.top_chord_node_ids[:-1], self.top_chord_node_ids[1:] ), section=self.top_chord_section, continuous=self.top_chord_continuous, ) # Web diagonal elements self.web_element_ids = add_segment_elements( node_pairs=self.web_node_pairs, section=self.web_section, continuous=False, ) # Web vertical elements self.web_verticals_element_ids = add_segment_elements( node_pairs=self.web_verticals_node_pairs, section=self.web_verticals_section, continuous=False, )
[docs] def add_supports(self) -> None: """Add supports from self.support_definitions to the SystemElements.""" for node_id, support_type in self.support_definitions.items(): if support_type == "fixed": self.system.add_support_fixed(node_id=node_id) elif support_type == "pinned": self.system.add_support_hinged(node_id=node_id) elif support_type == "roller": self.system.add_support_roll(node_id=node_id)
[docs] def _resolve_support_type( self, is_primary: bool = True ) -> Literal["fixed", "pinned", "roller"]: """Helper to resolve support type from "simple" to specific type. Args: is_primary (bool): If True, this is the primary (left) support. If False, this is the secondary (right) support. Returns: Literal["fixed", "pinned", "roller"]: The resolved support type. For "simple", returns "pinned" if primary, "roller" if secondary. """ if self.supports_type != "simple": return self.supports_type return "pinned" if is_primary else "roller"
@overload
[docs] def get_element_ids_of_chord( self, chord: Literal["top", "bottom"], chord_segment: None = None ) -> list[int]: ...
@overload def get_element_ids_of_chord( self, chord: Literal["top", "bottom"], chord_segment: str ) -> list[int]: ... def get_element_ids_of_chord( self, chord: Literal["top", "bottom"], chord_segment: Optional[str] = None ) -> list[int]: """Get element IDs for a chord (top or bottom). Args: chord (Literal["top", "bottom"]): Which chord to query chord_segment (Optional[str]): If the chord is segmented (dict of segments), specify which segment to get. If None and chord is segmented, returns all element IDs from all segments concatenated. Returns: list[int]: Element IDs of the requested chord (segment) Raises: ValueError: If chord is not "top" or "bottom" KeyError: If chord_segment is specified but doesn't exist in the chord """ if chord == "top": if isinstance(self.top_chord_element_ids, dict): if chord_segment is None: all_ids = [] for ids in self.top_chord_element_ids.values(): all_ids.extend(ids) return all_ids if chord_segment not in self.top_chord_element_ids: available = list(self.top_chord_element_ids.keys()) raise KeyError( f"chord_segment '{chord_segment}' not found. " f"Available segments: {available}" ) return self.top_chord_element_ids[chord_segment] return self.top_chord_element_ids if chord == "bottom": if isinstance(self.bottom_chord_element_ids, dict): if chord_segment is None: all_ids = [] for ids in self.bottom_chord_element_ids.values(): all_ids.extend(ids) return all_ids if chord_segment not in self.bottom_chord_element_ids: available = list(self.bottom_chord_element_ids.keys()) raise KeyError( f"chord_segment '{chord_segment}' not found. " f"Available segments: {available}" ) return self.bottom_chord_element_ids[chord_segment] return self.bottom_chord_element_ids raise ValueError("chord must be either 'top' or 'bottom'.")
[docs] def apply_q_load_to_top_chord( self, q: Union[float, Sequence[float]], direction: Union[LoadDirection, Sequence[LoadDirection]] = "element", rotation: Optional[Union[float, Sequence[float]]] = None, q_perp: Optional[Union[float, Sequence[float]]] = None, chord_segment: Optional[str] = None, ) -> None: """Apply distributed load to all elements in the top chord. Args: q (Union[float, Sequence[float]]): Load magnitude (force/length units) direction (Union[LoadDirection, Sequence[LoadDirection]]): Load direction. Options: "element", "x", "y", "parallel", "perpendicular", "angle" rotation (Optional[Union[float, Sequence[float]]]): Rotation angle in degrees (used with direction="angle") q_perp (Optional[Union[float, Sequence[float]]]): Perpendicular load component chord_segment (Optional[str]): If specified, apply load only to this segment (for trusses with segmented chords like roof trusses) """ element_ids = self.get_element_ids_of_chord( chord="top", chord_segment=chord_segment ) for el_id in element_ids: self.system.q_load( element_id=el_id, q=q, direction=direction, rotation=rotation, q_perp=q_perp, )
[docs] def apply_q_load_to_bottom_chord( self, q: Union[float, Sequence[float]], direction: Union[LoadDirection, Sequence[LoadDirection]] = "element", rotation: Optional[Union[float, Sequence[float]]] = None, q_perp: Optional[Union[float, Sequence[float]]] = None, chord_segment: Optional[str] = None, ) -> None: """Apply distributed load to all elements in the bottom chord. Args: q (Union[float, Sequence[float]]): Load magnitude (force/length units) direction (Union[LoadDirection, Sequence[LoadDirection]]): Load direction. Options: "element", "x", "y", "parallel", "perpendicular", "angle" rotation (Optional[Union[float, Sequence[float]]]): Rotation angle in degrees (used with direction="angle") q_perp (Optional[Union[float, Sequence[float]]]): Perpendicular load component chord_segment (Optional[str]): If specified, apply load only to this segment (for trusses with segmented chords like roof trusses) """ element_ids = self.get_element_ids_of_chord( chord="bottom", chord_segment=chord_segment ) for el_id in element_ids: self.system.q_load( element_id=el_id, q=q, direction=direction, rotation=rotation, q_perp=q_perp, )
[docs] def validate(self) -> bool: """Validate truss geometry and connectivity. Checks for common truss definition issues: - All node IDs in connectivity lists reference valid nodes - No duplicate nodes at the same location - All elements have non-zero length Returns: bool: True if validation passes Raises: ValueError: If validation fails with description of the issue """ # Check that all node IDs in connectivity are valid (1-based) max_node_id = len(self.nodes) # Helper to validate node ID list def validate_node_ids( node_ids: Union[list[int], dict[str, list[int]]], name: str ) -> None: if isinstance(node_ids, dict): for segment_name, ids in node_ids.items(): for node_id in ids: if node_id < 1 or node_id > max_node_id: raise ValueError( f"{name} segment '{segment_name}' references invalid node ID {node_id}. " f"Valid range: 1-{max_node_id}" ) else: for node_id in node_ids: if node_id < 1 or node_id > max_node_id: raise ValueError( f"{name} references invalid node ID {node_id}. " f"Valid range: 1-{max_node_id}" ) validate_node_ids(self.top_chord_node_ids, "top_chord_node_ids") validate_node_ids(self.bottom_chord_node_ids, "bottom_chord_node_ids") for i, (node_a, node_b) in enumerate(self.web_node_pairs): if node_a < 1 or node_a > max_node_id: raise ValueError( f"web_node_pairs[{i}] references invalid node ID {node_a}. " f"Valid range: 1-{max_node_id}" ) if node_b < 1 or node_b > max_node_id: raise ValueError( f"web_node_pairs[{i}] references invalid node ID {node_b}. " f"Valid range: 1-{max_node_id}" ) for i, (node_a, node_b) in enumerate(self.web_verticals_node_pairs): if node_a < 1 or node_a > max_node_id: raise ValueError( f"web_verticals_node_pairs[{i}] references invalid node ID {node_a}. " f"Valid range: 1-{max_node_id}" ) if node_b < 1 or node_b > max_node_id: raise ValueError( f"web_verticals_node_pairs[{i}] references invalid node ID {node_b}. " f"Valid range: 1-{max_node_id}" ) # Check for duplicate node locations (within tolerance) tolerance = 1e-6 for i, node_i in enumerate(self.nodes): for j in range(i + 1, len(self.nodes)): node_j = self.nodes[j] dx = abs(node_i.x - node_j.x) dy = abs(node_i.y - node_j.y) if dx < tolerance and dy < tolerance: raise ValueError( f"Duplicate nodes at position ({node_i.x:.6f}, {node_i.y:.6f}): " f"node {i} and node {j}" ) # Check for zero-length elements def check_element_length( node_a_id: int, node_b_id: int, element_type: str ) -> None: node_a = self.nodes[node_a_id - 1] node_b = self.nodes[node_b_id - 1] dx = node_b.x - node_a.x dy = node_b.y - node_a.y length = np.sqrt(dx**2 + dy**2) if length < tolerance: raise ValueError( f"Zero-length element in {element_type}: nodes {node_a_id} and {node_b_id} " f"at position ({node_a.x:.6f}, {node_a.y:.6f})" ) # Check chord elements def check_chord_elements( node_ids: Union[list[int], dict[str, list[int]]], chord_name: str ) -> None: if isinstance(node_ids, dict): for segment_name, ids in node_ids.items(): for i in range(len(ids) - 1): check_element_length( ids[i], ids[i + 1], f"{chord_name} segment '{segment_name}'" ) else: for i in range(len(node_ids) - 1): check_element_length(node_ids[i], node_ids[i + 1], chord_name) check_chord_elements(self.top_chord_node_ids, "top chord") check_chord_elements(self.bottom_chord_node_ids, "bottom chord") for i, (node_a, node_b) in enumerate(self.web_node_pairs): check_element_length(node_a, node_b, f"web diagonal {i}") for i, (node_a, node_b) in enumerate(self.web_verticals_node_pairs): check_element_length(node_a, node_b, f"web vertical {i}") return True
[docs] def show_structure(self) -> None: """Display the truss structure using matplotlib.""" self.system.show_structure()
[docs] class FlatTruss(Truss): """Abstract base class for flat (parallel chord) truss structures. Flat trusses have parallel top and bottom chords and are divided into repeating panel units. Specific truss patterns (Howe, Pratt, Warren) are implemented by subclasses. Attributes: unit_width (float): Width of each panel/bay end_type (EndType): Configuration of truss ends - "flat", "triangle_down", or "triangle_up" supports_loc (SupportLoc): Where supports are placed - "bottom_chord", "top_chord", or "both" min_end_fraction (float): Minimum width of end panels as fraction of unit_width enforce_even_units (bool): If True, ensure even number of panels for symmetry n_units (int): Computed number of panel units end_width (float): Computed width of end panels """ # Data types specific to this truss type
[docs] EndType = Literal["flat", "triangle_down", "triangle_up"]
[docs] SupportLoc = Literal["bottom_chord", "top_chord", "both"]
# Additional geometry for this truss type
[docs] unit_width: float
[docs] end_type: EndType
[docs] supports_loc: SupportLoc
# Additional configuration
[docs] min_end_fraction: float
[docs] enforce_even_units: bool
# Computed properties
[docs] n_units: int
[docs] end_width: float
@property @abstractmethod
[docs] def type(self) -> str: return "[Generic] Flat Truss"
def __init__( self, width: float, height: float, unit_width: float, end_type: EndType = "triangle_down", supports_loc: SupportLoc = "bottom_chord", min_end_fraction: float = 0.5, enforce_even_units: bool = True, top_chord_section: Optional[SectionProps] = None, bottom_chord_section: Optional[SectionProps] = None, web_section: Optional[SectionProps] = None, web_verticals_section: Optional[SectionProps] = None, ): """Initialize a flat truss. Args: width (float): Total span of the truss. Must be positive. height (float): Height of the truss. Must be positive. unit_width (float): Width of each panel. Must be positive and less than width - 2*min_end_fraction*unit_width. end_type (EndType): End panel configuration. "triangle_down" has diagonals pointing down at ends, "triangle_up" has diagonals pointing up, "flat" has vertical end panels. supports_loc (SupportLoc): Location of supports - "bottom_chord" (typical), "top_chord" (hanging truss), or "both" (supported at both chords). min_end_fraction (float): Minimum end panel width as fraction of unit_width. Must be between 0 and 1. Defaults to 0.5. enforce_even_units (bool): If True, ensure even number of units for symmetry. Defaults to True. top_chord_section (Optional[SectionProps]): Section properties for top chord bottom_chord_section (Optional[SectionProps]): Section properties for bottom chord web_section (Optional[SectionProps]): Section properties for diagonal webs web_verticals_section (Optional[SectionProps]): Section properties for vertical webs Raises: ValueError: If dimensions are invalid or result in negative/zero units """ if unit_width <= 0: raise ValueError(f"unit_width must be positive, got {unit_width}") if not 0 < min_end_fraction <= 1: raise ValueError( f"min_end_fraction must be in (0, 1], got {min_end_fraction}" ) self.unit_width = unit_width self.end_type = end_type self.supports_loc = supports_loc self.min_end_fraction = min_end_fraction self.enforce_even_units = enforce_even_units # Compute number of units n_units_float = (width - unit_width * 2 * min_end_fraction) / unit_width if n_units_float < 1: raise ValueError( f"Width {width} is too small for unit_width {unit_width} and " f"min_end_fraction {min_end_fraction}. Would result in {n_units_float:.2f} units." ) self.n_units = int(np.floor(n_units_float)) if self.enforce_even_units and self.n_units % 2 != 0: self.n_units -= 1 if self.n_units < 2: raise ValueError( f"Truss must have at least 2 units. Computed {self.n_units} units. " f"Reduce unit_width or increase width." ) self.end_width = (width - self.n_units * unit_width) / 2 super().__init__( width, height, top_chord_section, bottom_chord_section, web_section, web_verticals_section, ) @abstractmethod
[docs] def define_nodes(self) -> None: pass
@abstractmethod
[docs] def define_connectivity(self) -> None: pass
[docs] def define_supports(self) -> None: """Define support locations for flat trusses. Default implementation places supports at the ends of the truss. Assumes single-segment (non-dict) chord node ID lists. """ assert isinstance(self.bottom_chord_node_ids, list) assert isinstance(self.top_chord_node_ids, list) bottom_left = 1 bottom_right = max(self.bottom_chord_node_ids) top_left = min(self.top_chord_node_ids) top_right = max(self.top_chord_node_ids) if self.supports_loc in ["bottom_chord", "both"]: self.support_definitions[bottom_left] = self._resolve_support_type( is_primary=True ) self.support_definitions[bottom_right] = self._resolve_support_type( is_primary=False ) if self.supports_loc in ["top_chord", "both"]: self.support_definitions[top_left] = self._resolve_support_type( is_primary=True ) self.support_definitions[top_right] = self._resolve_support_type( is_primary=False )
[docs] class RoofTruss(Truss): """Abstract base class for peaked roof truss structures. Roof trusses have sloped top chords meeting at a peak, forming a triangular profile. Height is computed from span and roof pitch. Specific truss patterns (King Post, Queen Post, Fink, etc.) are implemented by subclasses. Attributes: overhang_length (float): Length of roof overhang beyond supports roof_pitch_deg (float): Roof pitch angle in degrees roof_pitch (float): Roof pitch angle in radians (computed) """ # Additional geometry for this truss type
[docs] overhang_length: float
[docs] roof_pitch_deg: float
# Computed properties
[docs] roof_pitch: float
@property @abstractmethod
[docs] def type(self) -> str: return "[Generic] Roof Truss"
def __init__( self, width: float, roof_pitch_deg: float, overhang_length: float = 0.0, top_chord_section: Optional[SectionProps] = None, bottom_chord_section: Optional[SectionProps] = None, web_section: Optional[SectionProps] = None, web_verticals_section: Optional[SectionProps] = None, ): """Initialize a roof truss. Args: width (float): Total span of the truss (building width). Must be positive. roof_pitch_deg (float): Roof pitch angle in degrees. Must be positive and less than 90 degrees. Common values: 18-45 degrees. overhang_length (float): Length of roof overhang beyond the supports. Must be non-negative. Defaults to 0.0. top_chord_section (Optional[SectionProps]): Section properties for top chord bottom_chord_section (Optional[SectionProps]): Section properties for bottom chord web_section (Optional[SectionProps]): Section properties for diagonal webs web_verticals_section (Optional[SectionProps]): Section properties for vertical webs Raises: ValueError: If dimensions or angles are invalid """ if roof_pitch_deg <= 0 or roof_pitch_deg >= 90: raise ValueError( f"roof_pitch_deg must be between 0 and 90, got {roof_pitch_deg}" ) if overhang_length < 0: raise ValueError( f"overhang_length must be non-negative, got {overhang_length}" ) self.roof_pitch_deg = roof_pitch_deg self.roof_pitch = np.radians(roof_pitch_deg) height = (width / 2) * np.tan(self.roof_pitch) self.overhang_length = overhang_length super().__init__( width, height, top_chord_section, bottom_chord_section, web_section, web_verticals_section, ) @abstractmethod
[docs] def define_nodes(self) -> None: pass
@abstractmethod
[docs] def define_connectivity(self) -> None: pass
[docs] def define_supports(self) -> None: """Define support locations for roof trusses. Default implementation places supports at the ends of the bottom chord. Assumes single-segment (non-dict) bottom chord node ID list. """ assert isinstance(self.bottom_chord_node_ids, list) bottom_left = 1 bottom_right = max(self.bottom_chord_node_ids) self.support_definitions[bottom_left] = self._resolve_support_type( is_primary=True ) self.support_definitions[bottom_right] = self._resolve_support_type( is_primary=False )