civilpy.structural package
Structural engineering — the most developed CivilPy package: member and
section tools, AASHTO LRFD / AREMA design checks, strut-and-tie tools,
bridge substructure designers, and the hub-and-spoke bridge pipeline —
bridge_layout builds the canonical model, midas_models feeds a
live MIDAS Civil NX session, and the rhino_bim emit layer produces
the tagged BrIM geometry (drawn live in Rhino 8 or baked headlessly to
.3dm with emit_to_3dm), with per-object ODOT pay items rolling
up into quantities.
Subpackages
- civilpy.structural.aashto package
- Subpackages
- civilpy.structural.aashto.lrfd package
- Submodules
- civilpy.structural.aashto.lrfd.appendix_a6 module
- civilpy.structural.aashto.lrfd.appendix_b6 module
- civilpy.structural.aashto.lrfd.bolted_field_splice module
- civilpy.structural.aashto.lrfd.columns module
- civilpy.structural.aashto.lrfd.concrete module
- civilpy.structural.aashto.lrfd.core module
- civilpy.structural.aashto.lrfd.creep_shrinkage module
- civilpy.structural.aashto.lrfd.distribution module
- civilpy.structural.aashto.lrfd.editions module
- civilpy.structural.aashto.lrfd.lrfr module
- civilpy.structural.aashto.lrfd.plots module
- civilpy.structural.aashto.lrfd.prestressed module
- civilpy.structural.aashto.lrfd.railing module
- civilpy.structural.aashto.lrfd.splices module
- civilpy.structural.aashto.lrfd.steel module
- civilpy.structural.aashto.lrfd.stm module
- civilpy.structural.aashto.lrfd.timber module
- Module contents
- civilpy.structural.aashto.lrfd package
- Submodules
- civilpy.structural.aashto.bearings module
BearingSuitabilityMethodABearingMethodABearing.widthMethodABearing.lengthMethodABearing.durometerMethodABearing.internal_tMethodABearing.external_tMethodABearing.steel_tMethodABearing.plysMethodABearing.spanMethodABearing.expansion_lengthMethodABearing.loadsMethodABearing.max_dl_deltaMethodABearing.max_ll_deltaMethodABearing.max_ll_locMethodABearing.deck_slopeMethodABearing.plate_bevMethodABearing.edge_coverMethodABearing.checksMethodABearing.internal_shape_factorMethodABearing.typeMethodABearing.holesMethodABearing.steel_yield_strengthMethodABearing.shear_modulusMethodABearing.exp_coeffMethodABearing.temp_minMethodABearing.temp_maxMethodABearing.__init__()MethodABearing.check_edge_cover()MethodABearing.check_layer_thickness()MethodABearing.get_deflections()MethodABearing.get_shape_factors()MethodABearing.run_checks()
get_bearing_strain()
- civilpy.structural.aashto.durometer module
- civilpy.structural.aashto.load_definitions module
- civilpy.structural.aashto.vehicles module
- Module contents
- Subpackages
- civilpy.structural.aashtoware package
- civilpy.structural.arema package
- civilpy.structural.odot package
- Submodules
- civilpy.structural.odot.approach_slab module
ApproachSlabDesignApproachSlabDesign.a_bar_dimension_ftApproachSlabDesign.a_bar_length_ftApproachSlabDesign.a_bar_markApproachSlabDesign.a_bar_spacing_inApproachSlabDesign.b501_bottom_countApproachSlabDesign.b501_bottom_spacing_inApproachSlabDesign.b501_top_countApproachSlabDesign.c_bar_length_ftApproachSlabDesign.c_bar_markApproachSlabDesign.length_ftApproachSlabDesign.thickness_in
ApproachSlabInputApproachSlabLayoutBarRunSeatConfigurationa_bar_count()anchor_bar_mark()approach_slab_design()b501_length_ft()c_bar_count()d801_length_ft()d802_length_ft()d_bar_count()layout_approach_slab()pay_area_sy()
- civilpy.structural.odot.bikeway_railing module
- civilpy.structural.odot.box_beam module
ANCHOR_DOWELAnchorDowelDetailBDM_ASPHALT_MIN_INBDM_ASPHALT_PCFBDM_COMPOSITE_DECK_MIN_INBDM_FUTURE_WEARING_SURFACE_KSFBEARING_DESIGN_DATABEARING_PADSBEVELED_LOAD_PLATEBOX_BEAM_DEPTHSBOX_BOTTOM_CHAMFER_INBOX_FLANGE_THICKNESS_INBOX_SECTION_PROPERTIESBOX_VOID_FILLET_INBOX_WEB_THICKNESS_INBOX_WIDTH_INBearingDesignDataBearingPadBeveledLoadPlateBoxBeamDesignSpecBoxSectionPropertiesCOMPOSITE_MODULAR_RATIOCOMPOSITE_SLAB_STRUCTURAL_THICKNESS_INDESIGN_DATA_SHEETDESIGN_SPECKEYWAY_BOTTOM_BAND_INKEYWAY_LOWER_CHAMFER_INKEYWAY_RECESS_DEPTH_INKEYWAY_TOP_BAND_INKEYWAY_UPPER_CHAMFER_INLoadPlateLayoutMAX_SKEW_DEGPSBD_2_07_SECTION_PROPERTIESSHEAR_KEYSOLID_DIAPHRAGM_BLOCK_INSOLID_END_BLOCK_INShearKeyDetailTIE_RODTieRodDetailbearing_pad()box_section_properties()box_void_dimensions()diaphragm_count()diaphragm_end_offset()diaphragm_stations_ft()end_diaphragm_stations_ft()intermediate_diaphragm_stations_ft()layout_load_plate()load_plate_bevel()solid_diaphragm_block_in()solid_end_block_in()
- civilpy.structural.odot.box_beam_design module
BOX_BEAM_DESIGNSBOX_BEAM_RATINGSBOX_DESIGNATIONSBoxBeamDesignBoxBeamDesign.beam_typeBoxBeamDesign.bearing_typeBoxBeamDesign.boxBoxBeamDesign.camber_d0BoxBeamDesign.camber_d30BoxBeamDesign.deflectionBoxBeamDesign.depthBoxBeamDesign.e_beamBoxBeamDesign.e_compositeBoxBeamDesign.n_strandsBoxBeamDesign.spanBoxBeamDesign.stirrup_w_pairsBoxBeamDesign.stirrup_yBoxBeamDesign.stirrup_zBoxBeamDesign.stirrup_zone_xBoxBeamDesign.strands_2inBoxBeamDesign.strands_4inBoxBeamDesign.strands_6inBoxBeamDesign.width
BoxBeamRatingRATING_VEHICLESSTRAND_ROW_HEIGHTS_INbox_beam_design()box_beam_rating()designs_for_box()strand_group_height_in()
- civilpy.structural.odot.box_culvert_headwall module
BOX_SPAN_RANGE_FTCUTOFF_WALL_WIDTH_FTFOOTING_EXTENSION_FTFOOTING_REINFORCINGFORESLOPE_WALL_HEIGHTS_INFORESLOPE_WALL_QUANTITIESHeadwallDesignHeadwallInputHeadwallRowHeadwallRow.HHeadwallRow.L1HeadwallRow.L2HeadwallRow.aHeadwallRow.bHeadwallRow.cHeadwallRow.culvert_footing_cy_per_ftHeadwallRow.culvert_footing_lbs_per_ftHeadwallRow.footing_conc_cyHeadwallRow.footing_designHeadwallRow.footing_reinf_lbsHeadwallRow.footing_tHeadwallRow.footing_wHeadwallRow.h1HeadwallRow.h2HeadwallRow.hcwHeadwallRow.wingwall_conc_cyHeadwallRow.wingwall_reinf_lbsHeadwallRow.x_barHeadwallRow.x_spa_inHeadwallRow.y_barHeadwallRow.y_spa_in
PAY_ITEMSTYPE_A_TABLETYPE_C_TABLEWINGWALL_BACKSLOPEWingwallInputWingwallLayoutbend_shape()box_wall_thickness_in()design_headwall()layout_wingwall()
- civilpy.structural.odot.bridge_railing module
BRIDGE_RAILINGSBridgeRailingBridgeRailing.bar_sizesBridgeRailing.base_widthBridgeRailing.design_force_check()BridgeRailing.designationBridgeRailing.f_cBridgeRailing.f_yBridgeRailing.f_y_steelBridgeRailing.heightBridgeRailing.materialBridgeRailing.meets_minimum_height()BridgeRailing.nameBridgeRailing.notesBridgeRailing.post_shapeBridgeRailing.post_spacingBridgeRailing.rail_elementBridgeRailing.rail_height_above_inBridgeRailing.scdBridgeRailing.scd_dateBridgeRailing.section_areaBridgeRailing.segment_length_ftBridgeRailing.shapeBridgeRailing.test_levelBridgeRailing.test_level_load()BridgeRailing.top_widthBridgeRailing.transition_length_ftBridgeRailing.transition_volume_cyBridgeRailing.vertical_bar_spacingBridgeRailing.weight_per_ft
railing()railings_for_test_level()
- civilpy.structural.odot.capped_pile_abutment module
- civilpy.structural.odot.capped_pile_pier module
- civilpy.structural.odot.concrete_curb module
- civilpy.structural.odot.continuous_slab_bridge module
- civilpy.structural.odot.deck_design module
BarMatDESIGN_METHODDeckDesignPolicyDeckDesignPolicy.bottom_coverDeckDesignPolicy.concrete_classDeckDesignPolicy.epoxy_coatedDeckDesignPolicy.exposure_factorDeckDesignPolicy.f_cDeckDesignPolicy.f_yDeckDesignPolicy.future_wearing_surface_ksfDeckDesignPolicy.live_loadDeckDesignPolicy.methodDeckDesignPolicy.monolithic_wearing_surfaceDeckDesignPolicy.top_cover
HaunchMIN_BEAM_LINESMIN_DESIGN_HAUNCHMIN_OVERHANG_THICKNESSPOLICYPROHIBITED_METHODSSTANDARD_DECK_DESIGNSStandardDeckDesignStandardDeckDesign.deck_thicknessStandardDeckDesign.effective_span_ftStandardDeckDesign.longitudinal_bottomStandardDeckDesign.longitudinal_topStandardDeckDesign.overhang_bar_sizeStandardDeckDesign.overhang_cutoffStandardDeckDesign.overhang_thicknessStandardDeckDesign.transverse_bottomStandardDeckDesign.transverse_top
VALID_RAILINGShaunch_depth_at()minimum_deck_thickness()overhang_thickness()secondary_longitudinal_reinforcement()standard_deck_design()structural_design_thickness()
- civilpy.structural.odot.drip_strip module
- civilpy.structural.odot.fixed_bearing module
- civilpy.structural.odot.full_height_headwall module
FULL_HEIGHT_HEADWALLSFullHeightHeadwallDesignFullHeightHeadwallLayoutFullHeightHeadwallLayout.center_faceFullHeightHeadwallLayout.concrete_cyFullHeightHeadwallLayout.inputsFullHeightHeadwallLayout.notesFullHeightHeadwallLayout.skewFullHeightHeadwallLayout.skew_bucket_degFullHeightHeadwallLayout.steel_lbFullHeightHeadwallLayout.tableFullHeightHeadwallLayout.type_FullHeightHeadwallLayout.wing1FullHeightHeadwallLayout.wing2
HeadwallInputMIN_DIAMETER_INSKEW_BUCKETSSkewGroupfull_height_headwall_design()layout_full_height_headwall()nearest_skew_bucket()
- civilpy.structural.odot.guardrail module
BridgeTerminalAssemblyBridgeTerminalAssembly.connectionBridgeTerminalAssembly.connects_toBridgeTerminalAssembly.curb_noteBridgeTerminalAssembly.designationBridgeTerminalAssembly.length_inBridgeTerminalAssembly.n_postsBridgeTerminalAssembly.notesBridgeTerminalAssembly.originBridgeTerminalAssembly.pay_itemBridgeTerminalAssembly.post_groupsBridgeTerminalAssembly.post_spacings_inBridgeTerminalAssembly.post_stations_in()BridgeTerminalAssembly.rail_elementsBridgeTerminalAssembly.scdBridgeTerminalAssembly.scd_dateBridgeTerminalAssembly.start_offset_in
BridgeTerminalLayoutMGSMGSDrawingMGSRunLayoutMGSStandardMGSStandard.blockout_sectionMGSStandard.embedmentMGSStandard.long_post_lengthMGSStandard.post_bolt_diameterMGSStandard.post_spacing()MGSStandard.railMGSStandard.rail_heightMGSStandard.rail_height_tolerance_existingMGSStandard.rail_height_tolerance_newMGSStandard.rail_panel_lengthsMGSStandard.round_wood_embedmentMGSStandard.round_wood_post_lengthMGSStandard.scdMGSStandard.scd_dateMGSStandard.standard_post_lengthMGSStandard.transition_rate_in_per_ft
MGS_DRAWINGSMGS_POST_SPACINGSMGS_STEEL_POSTSPostSpacingSteelPostTerminalPostGroupbridge_terminal()bridge_terminal_assemblies()layout_bridge_terminal()layout_mgs_run()mgs_drawing()terminals_for_railing()
- civilpy.structural.odot.headwall module
EllipticalHeadwallHEADWALLS_BY_DIAMETERHEADWALLS_CIRCULARHEADWALLS_CONCRETE_BY_DIAMETERHEADWALLS_CONCRETE_CIRCULARHEADWALLS_CONCRETE_ELLIPTICALHeadwallHeadwallInputHeadwallLayoutHeadwallLayout.base_thickness_ftHeadwallLayout.concrete_cyHeadwallLayout.cover_inHeadwallLayout.front_outlineHeadwallLayout.height_ftHeadwallLayout.inputsHeadwallLayout.notesHeadwallLayout.pipe_centerHeadwallLayout.pipe_diameter_ftHeadwallLayout.side_profileHeadwallLayout.tableHeadwallLayout.top_thickness_ftHeadwallLayout.width_ft
elliptical_headwall_for_rise()headwall_for_diameter()layout_headwall()
- civilpy.structural.odot.portable_barrier module
- civilpy.structural.odot.ps_i_beam module
DIAPHRAGM_FC_KSIFCI_RANGE_KSIFC_RANGE_KSIFWS_PSFMAX_BEAM_SPACING_FTPSIBeamLayoutPSIBeamSectionPSIBeamSection.area_in2PSIBeamSection.bottom_flange_width_inPSIBeamSection.depth_inPSIBeamSection.draped_requiredPSIBeamSection.i_in4PSIBeamSection.max_bottom_flange_strandsPSIBeamSection.namePSIBeamSection.sb_in3PSIBeamSection.shipping_strand_locationsPSIBeamSection.st_in3PSIBeamSection.strand_rowsPSIBeamSection.top_flange_width_inPSIBeamSection.vol_surf_ratioPSIBeamSection.web_inPSIBeamSection.weight_plfPSIBeamSection.yb_inPSIBeamSection.yt_in
PS_I_BEAM_SECTIONSSHIPPING_STRAND_BOND_FTSTRAND_AREA_IN2STRAND_DIAMETER_INSTRAND_FPU_KSIi_beam_diaphragm_stations_ft()layout_ps_i_beam()ps_i_beam_profile()ps_i_beam_section()strand_centroid_in()strand_grid()strand_pattern()
- civilpy.structural.odot.roadway_barrier module
BARRIER_END_SECTIONSBarrierEndSectionBarrierEndSection.body_length_ftBarrierEndSection.designationBarrierEndSection.end_core_width_inBarrierEndSection.end_height_inBarrierEndSection.end_taper_inBarrierEndSection.end_width_inBarrierEndSection.face_transition_ftBarrierEndSection.medianBarrierEndSection.notesBarrierEndSection.parentBarrierEndSection.scdBarrierEndSection.scd_dateBarrierEndSection.total_length_ftBarrierEndSection.vertical_run_in
BarrierEndSectionLayoutBarrierTransitionLayoutPIER_PROTECTION_WIDTH_INPIER_RUN_EACH_SIDE_FTROADWAY_BARRIERSRoadwayBarrierRoadwayBarrier.base_widthRoadwayBarrier.designationRoadwayBarrier.f_cRoadwayBarrier.foundationRoadwayBarrier.heightRoadwayBarrier.joint_spacing_ftRoadwayBarrier.materialRoadwayBarrier.nameRoadwayBarrier.notesRoadwayBarrier.scdRoadwayBarrier.scd_dateRoadwayBarrier.shapeRoadwayBarrier.slope_h_to_vRoadwayBarrier.top_width
RoadwayBarrierInputRoadwayBarrierLayoutSIGN_SUPPORT_RUN_FTSIGN_SUPPORT_WIDTH_RANGE_INTRANSITION_RACEWAYSTRANSITION_TAPER_FTbarrier_end_section()layout_barrier_end_section()layout_barrier_transition()layout_roadway_barrier()roadway_barrier()
- civilpy.structural.odot.roadway_portable_barrier module
CONNECTOR_BOLT_SPECCONNECTOR_END_DISTANCE_MIN_INPCB_GAP_MAX_INROADWAY_PORTABLE_BARRIERSSPACER_PLATE_INTHRIE_BEAM_ELEMENTTHRIE_BEAM_PCB_TRANSITIONSTOE_PLATE_INTRANSITION_50_TO_32ThrieBeamPCBTransitionTransitionSectionUSE_LIMITroadway_portable_barrier()thrie_beam_pcb_transition()thrie_beam_transition_notes()
- civilpy.structural.odot.rocker_bolster module
- civilpy.structural.odot.slab_bridge module
- civilpy.structural.odot.sleeper_slab module
- civilpy.structural.odot.strip_seal_joint module
- civilpy.structural.odot.strip_seal_joint_box_beam module
- civilpy.structural.odot.typical_abutment module
- civilpy.structural.odot.vandal_fence module
- Module contents
AbutmentInputAbutmentLayoutAnchorDowelDetailApproachSlabDesignApproachSlabDesign.a_bar_dimension_ftApproachSlabDesign.a_bar_length_ftApproachSlabDesign.a_bar_markApproachSlabDesign.a_bar_spacing_inApproachSlabDesign.b501_bottom_countApproachSlabDesign.b501_bottom_spacing_inApproachSlabDesign.b501_top_countApproachSlabDesign.c_bar_length_ftApproachSlabDesign.c_bar_markApproachSlabDesign.length_ftApproachSlabDesign.thickness_in
ApproachSlabInputApproachSlabLayoutBarMatBarrierEndSectionBarrierEndSection.body_length_ftBarrierEndSection.designationBarrierEndSection.end_core_width_inBarrierEndSection.end_height_inBarrierEndSection.end_taper_inBarrierEndSection.end_width_inBarrierEndSection.face_transition_ftBarrierEndSection.medianBarrierEndSection.notesBarrierEndSection.parentBarrierEndSection.scdBarrierEndSection.scd_dateBarrierEndSection.total_length_ftBarrierEndSection.vertical_run_in
BarrierEndSectionLayoutBarrierSegmentBarrierTransitionLayoutBearingDesignDataBearingPadBeveledLoadPlateBikewayRailingInputBikewayRailingLayoutBoxBeamDesignBoxBeamDesign.beam_typeBoxBeamDesign.bearing_typeBoxBeamDesign.boxBoxBeamDesign.camber_d0BoxBeamDesign.camber_d30BoxBeamDesign.deflectionBoxBeamDesign.depthBoxBeamDesign.e_beamBoxBeamDesign.e_compositeBoxBeamDesign.n_strandsBoxBeamDesign.spanBoxBeamDesign.stirrup_w_pairsBoxBeamDesign.stirrup_yBoxBeamDesign.stirrup_zBoxBeamDesign.stirrup_zone_xBoxBeamDesign.strands_2inBoxBeamDesign.strands_4inBoxBeamDesign.strands_6inBoxBeamDesign.width
BoxBeamDesignSpecBoxBeamJointInputBoxBeamJointLayoutBoxBeamRatingBoxSectionPropertiesBridgeRailingBridgeRailing.bar_sizesBridgeRailing.base_widthBridgeRailing.design_force_check()BridgeRailing.designationBridgeRailing.f_cBridgeRailing.f_yBridgeRailing.f_y_steelBridgeRailing.heightBridgeRailing.materialBridgeRailing.meets_minimum_height()BridgeRailing.nameBridgeRailing.notesBridgeRailing.post_shapeBridgeRailing.post_spacingBridgeRailing.rail_elementBridgeRailing.rail_height_above_inBridgeRailing.scdBridgeRailing.scd_dateBridgeRailing.section_areaBridgeRailing.segment_length_ftBridgeRailing.shapeBridgeRailing.test_levelBridgeRailing.test_level_load()BridgeRailing.top_widthBridgeRailing.transition_length_ftBridgeRailing.transition_volume_cyBridgeRailing.vertical_bar_spacingBridgeRailing.weight_per_ft
BridgeTerminalAssemblyBridgeTerminalAssembly.connectionBridgeTerminalAssembly.connects_toBridgeTerminalAssembly.curb_noteBridgeTerminalAssembly.designationBridgeTerminalAssembly.length_inBridgeTerminalAssembly.n_postsBridgeTerminalAssembly.notesBridgeTerminalAssembly.originBridgeTerminalAssembly.pay_itemBridgeTerminalAssembly.post_groupsBridgeTerminalAssembly.post_spacings_inBridgeTerminalAssembly.post_stations_in()BridgeTerminalAssembly.rail_elementsBridgeTerminalAssembly.scdBridgeTerminalAssembly.scd_dateBridgeTerminalAssembly.start_offset_in
BridgeTerminalLayoutCSBarRunCSSlabDesignContinuousSlabInputContinuousSlabLayoutCpaRebarMarkCurbTypeDeckDesignPolicyDeckDesignPolicy.bottom_coverDeckDesignPolicy.concrete_classDeckDesignPolicy.epoxy_coatedDeckDesignPolicy.exposure_factorDeckDesignPolicy.f_cDeckDesignPolicy.f_yDeckDesignPolicy.future_wearing_surface_ksfDeckDesignPolicy.live_loadDeckDesignPolicy.methodDeckDesignPolicy.monolithic_wearing_surfaceDeckDesignPolicy.top_cover
DripStripPlacementEdgeBarSpecEdgeBeamDesignEllipticalHeadwallFenceRunInputFenceRunLayoutFixedBearingFixedBearingLayoutFullHeightHeadwallDesignFullHeightHeadwallInputFullHeightHeadwallLayoutFullHeightHeadwallLayout.center_faceFullHeightHeadwallLayout.concrete_cyFullHeightHeadwallLayout.inputsFullHeightHeadwallLayout.notesFullHeightHeadwallLayout.skewFullHeightHeadwallLayout.skew_bucket_degFullHeightHeadwallLayout.steel_lbFullHeightHeadwallLayout.tableFullHeightHeadwallLayout.type_FullHeightHeadwallLayout.wing1FullHeightHeadwallLayout.wing2
HaunchHeadwallHeadwallSkewGroupLoadPlateLayoutMGSDrawingMGSRunLayoutMGSStandardMGSStandard.blockout_sectionMGSStandard.embedmentMGSStandard.long_post_lengthMGSStandard.post_bolt_diameterMGSStandard.post_spacing()MGSStandard.railMGSStandard.rail_heightMGSStandard.rail_height_tolerance_existingMGSStandard.rail_height_tolerance_newMGSStandard.rail_panel_lengthsMGSStandard.round_wood_embedmentMGSStandard.round_wood_post_lengthMGSStandard.scdMGSStandard.scd_dateMGSStandard.standard_post_lengthMGSStandard.transition_rate_in_per_ft
PSIBeamLayoutPSIBeamSectionPSIBeamSection.area_in2PSIBeamSection.bottom_flange_width_inPSIBeamSection.depth_inPSIBeamSection.draped_requiredPSIBeamSection.i_in4PSIBeamSection.max_bottom_flange_strandsPSIBeamSection.namePSIBeamSection.sb_in3PSIBeamSection.shipping_strand_locationsPSIBeamSection.st_in3PSIBeamSection.strand_rowsPSIBeamSection.top_flange_width_inPSIBeamSection.vol_surf_ratioPSIBeamSection.web_inPSIBeamSection.weight_plfPSIBeamSection.yb_inPSIBeamSection.yt_in
PierBarMarkPierInputPierLayoutPlateSpacingPostSectionPostSpacingRoadwayBarrierRoadwayBarrier.base_widthRoadwayBarrier.designationRoadwayBarrier.f_cRoadwayBarrier.foundationRoadwayBarrier.heightRoadwayBarrier.joint_spacing_ftRoadwayBarrier.materialRoadwayBarrier.nameRoadwayBarrier.notesRoadwayBarrier.scdRoadwayBarrier.scd_dateRoadwayBarrier.shapeRoadwayBarrier.slope_h_to_vRoadwayBarrier.top_width
RoadwayBarrierInputRoadwayBarrierLayoutRockerBolsterRockerBolsterLayoutShearKeyDetailSlabBarSpecSlabBridgeInputSlabBridgeLayoutSlabDesignSleeperBarRunSleeperInstallationSleeperSlabInputSleeperSlabLayoutStandardDeckDesignStandardDeckDesign.deck_thicknessStandardDeckDesign.effective_span_ftStandardDeckDesign.longitudinal_bottomStandardDeckDesign.longitudinal_topStandardDeckDesign.overhang_bar_sizeStandardDeckDesign.overhang_cutoffStandardDeckDesign.overhang_thicknessStandardDeckDesign.transverse_bottomStandardDeckDesign.transverse_top
SteelPostStripRunStripSealJointInputStripSealJointLayoutSupportAngleRunTerminalPostGroupThrieBeamPCBTransitionTieRodDetailTransitionSectionTypicalAbutmentInputTypicalAbutmentLayoutWingwallInputWingwallLayoutanchor_bar_mark()anchor_hole_stations_ft()approach_slab_design()barrier_end_section()barrier_run()bearing_pad()bearing_seat_dim_a_ft()bend_shape()box_beam_design()box_beam_rating()box_section_properties()box_void_dimensions()bridge_length_ft()bridge_terminal()bridge_terminal_assemblies()cpa_bend_shape()cpa_rebar_mark()cs_slab_design()curb_height_in()curb_profile_in()curb_type()designs_for_box()diaphragm_count()diaphragm_end_offset()diaphragm_stations_ft()drip_strip_runs()edge_beam_design()elliptical_headwall_for_rise()end_diaphragm_stations_ft()fixed_bearing()fixed_bearing_smallest_for_load()full_height_headwall_design()haunch_depth_at()headwall_for_diameter()i_beam_diaphragm_stations_ft()interior_span_ft()intermediate_diaphragm_stations_ft()joint_length_ft()lateral_clearance_in()layout_approach_slab()layout_barrier_end_section()layout_barrier_transition()layout_bikeway_railing()layout_box_beam_joint()layout_bridge_terminal()layout_capped_pile_abutment()layout_capped_pile_pier()layout_continuous_slab()layout_fence_run()layout_fixed_bearing()layout_full_height_headwall()layout_load_plate()layout_mgs_run()layout_ps_i_beam()layout_roadway_barrier()layout_rocker_bolster()layout_slab_bridge()layout_sleeper_slab()layout_strip_seal_joint()layout_typical_abutment()layout_wingwall()load_plate_bevel()m_bar_offset_in()mgs_drawing()minimum_deck_thickness()nearest_skew_bucket()overhang_thickness()pcb_profile_points_in()pier_bar()pier_length_ft()plate_spacing()post_section()ps_i_beam_profile()ps_i_beam_section()q_bend_height_ft()railing()railings_for_test_level()roadway_barrier()roadway_portable_barrier()rocker_bolster()s_bar_length_ft()secondary_longitudinal_reinforcement()slab_design()sleeper_installations()smallest_for_load()solid_diaphragm_block_in()solid_end_block_in()ss501_length_ft()ss502_count()ss502_length_ft()standard_deck_design()standard_hook_bar_length_ft()strand_centroid_in()strand_grid()strand_group_height_in()strand_pattern()strip_profile_in()structural_design_thickness()support_angle_lengths_in()terminals_for_railing()thrie_beam_pcb_transition()thrie_beam_transition_notes()top_bearing_plate_radius_in()top_bearing_web_radius_in()
- civilpy.structural.stm_topology package
- Submodules
- civilpy.structural.stm_topology.cost module
- civilpy.structural.stm_topology.design module
- civilpy.structural.stm_topology.extract module
- civilpy.structural.stm_topology.mesh module
- civilpy.structural.stm_topology.pipeline module
- civilpy.structural.stm_topology.problem module
DRegionProblemDRegionProblem.add_load()DRegionProblem.add_support()DRegionProblem.boundaryDRegionProblem.bounds()DRegionProblem.from_3dm()DRegionProblem.loadsDRegionProblem.materialDRegionProblem.rectangle()DRegionProblem.solidsDRegionProblem.solve()DRegionProblem.supportsDRegionProblem.thicknessDRegionProblem.voidsDRegionProblem.vol_frac
LoadMaterialSupport
- civilpy.structural.stm_topology.simp module
- Module contents
DRegionProblemDRegionProblem.add_load()DRegionProblem.add_support()DRegionProblem.boundaryDRegionProblem.bounds()DRegionProblem.from_3dm()DRegionProblem.loadsDRegionProblem.materialDRegionProblem.rectangle()DRegionProblem.solidsDRegionProblem.solve()DRegionProblem.supportsDRegionProblem.thicknessDRegionProblem.voidsDRegionProblem.vol_frac
DensityResultDepthCandidateGroundMeshLoadMaterialPierCapDesignSTMResultSupportestimate_cost()extract_truss()governing_strut_angle()is_stable()layout_optimize_truss()optimize_density()optimize_pier_cap()optimize_to_stm()refine_truss()resolve_prices()set_price_provider()
Submodules
civilpy.structural.abutment module
Cantilever retaining walls and stub / full-height abutments.
A RetainingWall carries the geometry of a conventional cantilever
wall (toe, stem, heel, footing) and computes the three external-stability
checks every wall and spread-footing abutment must satisfy – overturning,
sliding, and bearing – driving the earth pressures from the Rankine and
Coulomb coefficients already in civilpy.geotech.lateral_earth and the
contact-pressure distribution from
civilpy.geotech.shallow_foundation. The structural design of the
stem, toe, and heel as reinforced-concrete cantilevers reuses the AASHTO
LRFD resistance functions in
civilpy.structural.aashto.lrfd.concrete. A wingwall is handled as a
retaining wall taken at its average exposed height.
Units: feet, pounds, pcf (pressures psf), everything per running foot of wall; the structural helpers convert to the kip-inch-ksi LRFD convention. Moments and lever arms are measured about the bottom front corner of the toe. Angles in degrees.
- class civilpy.structural.abutment.RetainingWall(stem_height: float, stem_thickness: float, toe_length: float, heel_length: float, footing_thickness: float, backfill_gamma: float, backfill_phi: float, concrete_gamma: float = 150.0, surcharge: float = 0.0, backfill_slope: float = 0.0, base_friction_deg: float | None = None, base_cohesion: float = 0.0, embedment: float = 0.0, include_passive: bool = False, include_toe_soil: bool = True, method: Literal['rankine', 'coulomb'] = 'rankine', wall_friction_deg: float = 0.0)[source]
Bases:
objectCantilever retaining wall geometry and soil parameters.
stem_heightis the stem height above the footing,stem_thicknessits (rectangular) thickness;toe_lengthandheel_lengthare the footing projections in front of and behind the stem;footing_thicknessthe base slab depth.backfill_gamma/backfill_phidescribe the retained soil;surchargea uniform vertical surcharge (psf) over the heel;backfill_slopethe surface slope behind the wall (deg).base_friction_degthe soil-footing interface friction angle andbase_cohesionits adhesion (psf). Wheninclude_passivethe passive wedge over anembedment(ft) of soil on the toe is credited to sliding.- heel_design(q_avg: float, a_s: float, f_c: float = 4.0, f_y: float = 60.0, cover_in: float = 3.0, bar_dia_in: float = 1.0, load_factor: float = 1.35) dict[str, CheckResult][source]
Heel cantilever: net downward pressure (soil + surcharge + self-weight, less the upward bearing
q_avgpsf averaged under the heel) produces tension in the top.a_sis top steel per foot;q_avgis the contact pressure under the heel fromstability().
- method: Literal['rankine', 'coulomb'] = 'rankine'
"rankine"or"coulomb".- Type:
Lateral earth-pressure theory
- stability(fs_sliding: float = 1.5, fs_overturning: float = 2.0) StabilityResult[source]
Overturning, sliding, and bearing stability per foot of wall.
- stem_design(a_s: float, f_c: float = 4.0, f_y: float = 60.0, cover_in: float = 2.0, bar_dia_in: float = 1.0, eh: float = 1.5) dict[str, CheckResult][source]
Flexure and shear at the base of the stem, reusing the LRFD RC functions.
a_sis the vertical reinforcement in a 12-in strip (in^2/ft); the stem height drives the cantilever moment.
- toe_design(q_avg: float, a_s: float, f_c: float = 4.0, f_y: float = 60.0, cover_in: float = 3.0, bar_dia_in: float = 1.0, load_factor: float = 1.35) dict[str, CheckResult][source]
Toe cantilever: the upward bearing pressure
q_avg(psf, net of the toe self-weight) produces tension in the bottom.a_sis bottom steel per foot.
- property total_height: float
Height of the active-pressure plane through the heel, including the soil wedge above a sloping backfill.
- classmethod wingwall(high_height: float, low_height: float, **kwargs) RetainingWall[source]
Build a wingwall as a retaining wall taken at the average of its high and low exposed stem heights (a common simplification for the tapering wing of an abutment). Extra keyword arguments are passed through to
RetainingWall.
- class civilpy.structural.abutment.StabilityResult(sum_v: float, sum_h: float, resisting_moment: float, overturning_moment: float, eccentricity: float, base_width: float, contact: object, passive: float = 0.0, fs_required_sliding: float = 1.5, fs_required_overturning: float = 2.0, details: dict = <factory>)[source]
Bases:
objectExternal-stability summary for a retaining wall / abutment, per foot of wall.
civilpy.structural.beam_bending module
Beam analysis module for shear force, bending moment, and deflection diagrams.
- Sign conventions follow AISC Steel Construction Manual (Table 3-23):
Loads: positive = downward (gravity direction)
Shear V: positive = left portion of beam has upward resultant (left-face-up)
Moment M: positive = sagging (concave up, bottom-fiber tension)
Reactions: positive = upward
Deflection: negative = downward (shown below zero line)
All units are US customary: lengths in feet (ft), forces in kips, moments in kip·ft. Deflection calculations require E in ksi and I in in⁴; results are in inches.
Overhanging beams (AISC Table 3-23 Cases 24–28) are fully supported. The pinned and rolling supports may be placed anywhere within the beam span — they do not need to coincide with the beam ends. When a support is interior, the beam simply overhangs that support on one or both sides.
Examples
Simply supported beam (supports at ends):
>>> my_beam = Beam(9)
>>> my_beam.pinned_support = 0
>>> my_beam.rolling_support = 9
>>> my_beam.add_loads([PointLoadV(20, 4.5)])
>>> F_Ax, F_Ay, F_By = my_beam.get_reaction_forces()
>>> round(F_Ay, 4), round(F_By, 4)
(10.0, 10.0)
Overhanging beam — AISC Case 26 (P at free overhang end): Beam 0–12 ft, pin at x=0, roller at x=8, 10-kip load at x=12. Expected: R_A = −5 kips (down), R_B = 15 kips (up).
>>> ob = Beam(12)
>>> ob.pinned_support = 0
>>> ob.rolling_support = 8
>>> ob.add_loads([PointLoadV(10, 12)])
>>> F_Ax, F_Ay, F_By = ob.get_reaction_forces()
>>> round(F_Ay, 4), round(F_By, 4)
(-5.0, 15.0)
- class civilpy.structural.beam_bending.Beam(span: float = 10)[source]
Bases:
objectRepresents a one-dimensional beam that can take axial and tangential loads.
Through the method add_loads, a Beam object can accept a list of:
PointLoad objects, and/or
DistributedLoad objects.
Notes
Supports only statically determinate beams with exactly one pinned and one roller support.
Supports may be placed at any x-coordinate within the beam span. When a support is interior to the span the beam overhangs that support on one or both sides (AISC Table 3-23 Cases 24–28).
Units: lengths in ft, forces in kips, moments in kip·ft. Deflection requires E in ksi and I in in⁴; result is in inches.
- add_loads(loads: list)[source]
Apply an arbitrary list of (point- or distributed) loads to the beam.
- get_bending_moment()[source]
Alias for get_moment_function to maintain compatibility with older notebook cells.
- get_deflection_function(E_ksi, I_in4)[source]
Returns the symbolic deflection function δ(x) in inches.
- get_envelope(loads: list, step_size: float = 0.5)[source]
Calculates the shear and moment envelope for a moving load or set of loads.
- Parameters:
- Returns:
x_vec (ndarray) – The x-coordinates along the beam.
max_shear (ndarray) – Maximum shear at each x.
min_shear (ndarray) – Minimum shear at each x.
max_moment (ndarray) – Maximum moment at each x.
min_moment (ndarray) – Minimum moment at each x.
- get_reaction_forces()[source]
Calculates the reaction forces at the supports, given the applied loads.
The first and second values correspond to the horizontal and vertical forces of the pinned support. The third one is the vertical force at the rolling support.
- Returns:
(F_Ax, F_Ay, F_By) reaction force components for pinned (x,y) and rolling (y) supports respectively.
- Return type:
- property pinned_support
x-coordinate of the beam’s pinned support. Must be within the beam span.
- plot()[source]
Generates a single figure with 4 plots.
- The plots correspond to:
a schematic of the loaded beam
normal force diagram
shear force diagram
bending moment diagram
- Returns:
figure with the 4 subplots.
- Return type:
matplotlib.figure.Figure
- plot_all()[source]
Generates a figure with 4 subplots: beam diagram, shear, moment, deflection.
Parameters are obtained from the beam’s existing loads. Deflection is omitted (use plot() or call plot_deflection() separately with E and I values). Returns a figure with shear and moment only alongside the beam diagram.
- plot_beam_diagram(ax=None)[source]
Returns a schematic of the beam and all the loads applied on it.
- plot_bending_moment(ax=None)[source]
Returns a plot of the bending moment as a function of the x-coordinate.
- plot_envelope(x_vec, max_shear, min_shear, max_moment, min_moment, ax=None)[source]
Plots the shear and moment envelopes.
- plot_normal_force(ax=None)[source]
Returns a plot of the normal force as a function of the x-coordinate.
- class civilpy.structural.beam_bending.CantileverBeam(span: float = 10)[source]
Bases:
BeamA cantilever beam: fixed at x=0, free at x=span.
Loads and coordinates use the same conventions as Beam (kips, ft).
Example
>>> cb = CantileverBeam(10) >>> cb.add_loads([PointLoadV(-5, 10)]) # 5 kip downward at free end >>> V, M = cb.get_reaction_forces() >>> round(float(V), 4), round(float(M), 4) (5.0, 50.0)
- get_deflection_function(E_ksi, I_in4)[source]
Returns the symbolic deflection function δ(x) in inches.
Boundary conditions: δ(0) = 0, δ’(0) = 0 (fixed end).
- class civilpy.structural.beam_bending.ContinuousBeam(span: float = 10, intermediate_supports: list = None)[source]
Bases:
BeamA continuous beam with an arbitrary number of intermediate roller supports.
- Parameters:
- class civilpy.structural.beam_bending.DistributedLoadH(expr, span)[source]
Bases:
DistributedLoadHDistributed horizontal load (kips/ft) over a span interval (ft).
Examples
>>> wind_load = DistributedLoadH("0.5", (0, 20)) # 0.5 kips/ft over full span
- class civilpy.structural.beam_bending.DistributedLoadV(expr, span)[source]
Bases:
DistributedLoadVDistributed vertical load (kips/ft) over a span interval (ft). Positive = downward.
Examples
>>> snow_load = DistributedLoadV("2*x+1", (0, 10)) # downward, linearly varying, 0 to 10 ft
- class civilpy.structural.beam_bending.FixedFixedBeam(span: float = 10)[source]
Bases:
BeamA fixed-fixed beam: fixed at x=0, fixed at x=span.
Boundary conditions: - x = 0: slope = 0, deflection = 0 - x = span: slope = 0, deflection = 0
- get_deflection_function(E_ksi, I_in4)[source]
Returns the symbolic deflection function δ(x) in inches.
- get_reaction_forces()[source]
Calculates the reaction forces at the supports, given the applied loads.
The first and second values correspond to the horizontal and vertical forces of the pinned support. The third one is the vertical force at the rolling support.
- Returns:
(F_Ax, F_Ay, F_By) reaction force components for pinned (x,y) and rolling (y) supports respectively.
- Return type:
- class civilpy.structural.beam_bending.PointLoadH(force, coord)[source]
Bases:
PointLoadHHorizontal point load: (force in kips, coord in ft). Positive = rightward.
Examples
>>> external_force = PointLoadH(10, 9) # 10 kips to the right at x=9 ft >>> external_force PointLoadH(force=10, coord=9)
- class civilpy.structural.beam_bending.PointLoadV(force, coord)[source]
Bases:
PointLoadVVertical point load: (force in kips, coord in ft). Positive = downward.
Examples
>>> external_force = PointLoadV(30, 3) # 30 kips downwards at x=3 ft >>> external_force PointLoadV(force=30, coord=3)
- class civilpy.structural.beam_bending.PointTorque(torque, coord)[source]
Bases:
PointTorquePoint clockwise torque: (torque in kip·ft, coord in ft).
Examples
>>> applied_moment = PointTorque(30, 4) # 30 kip·ft clockwise at x=4 ft
- class civilpy.structural.beam_bending.ProppedCantileverBeam(span: float = 10)[source]
Bases:
BeamA propped cantilever beam: fixed at x=0, roller at x=span.
Boundary conditions: - x = 0: slope = 0, deflection = 0 - x = span: deflection = 0
Examples
>>> pcb = ProppedCantileverBeam(10) >>> pcb.add_loads([PointLoadV(5, 5)]) # 5 kip downward at midspan >>> R_A, V_A, M_A, R_B = pcb.get_reaction_forces()
- get_deflection_function(E_ksi, I_in4)[source]
Returns the symbolic deflection function δ(x) in inches. Boundary conditions: δ(0) = 0, δ’(0) = 0.
- get_reaction_forces()[source]
Calculates the reaction forces (F_Ax, V_A, M_A, R_B) for a propped cantilever.
- Returns:
- (F_Ax, V_A, M_A, R_B)
F_Ax: Horizontal reaction at fixed end.
V_A: Vertical reaction at fixed end (positive upward).
M_A: Moment at fixed end (positive counter-clockwise).
R_B: Vertical reaction at roller (positive upward).
- Return type:
- civilpy.structural.beam_bending.plot_beam_full(beam, E=29000, I=None, title='')[source]
2×2 grid: beam diagram, shear, moment, deflection (if I provided).
- Parameters:
beam (Beam or CantileverBeam) – A beam object with loads already applied.
E (float) – Modulus of elasticity in ksi. Default 29000 (steel).
I (float or Pint quantity, optional) – Moment of inertia in in⁴. When omitted the deflection panel is hidden.
title (str, optional) – Figure suptitle.
civilpy.structural.bim module
BrIM attribute schema: the per-component BIM record the Rhino “source of truth” model carries as user text, and that MIDAS / quantity estimating read back.
Every component gets a typed attribute set (not the blanket gdr.*):
shared
bim.type(girder / deck / parapet / bearing / load_plate / haunch / shear_stud / rebar / diaphragm) and a uniquebim.id;where the part is a standard detail,
bim.scd+bim.scd_year– the highest-value BIM keys, since the SCD implies most of the rest;a pay item (
pay.*) so quantities roll straight into an estimate;a material block (
mat.*): spec/grade/treatment for steel, f’c/class for concrete, coating/size for reinforcing.
Builders return a flat {key: str} dict ready to write to Rhino user text (or
IFC property sets). Values are strings so they survive the .3dm round-trip.
- class civilpy.structural.bim.CostEstimate(rows: dict[str, dict], total: float, unpriced: tuple[str, ...] = ())[source]
Bases:
objectA priced pay-item rollup.
rowsextends the quantity records ({"desc", "unit", "qty", "objects"}) withunit_priceandcost; items the price book doesn’t know are listed inunpriced(theircostisNone) and excluded fromtotal.
- civilpy.structural.bim.DEFAULT_UNIT_PRICES: dict[str, float] = {'507E10000': 75.0, '509E00100': 1.4, '509E00200': 1.6, '509E00300': 3.0, '511E12100': 950.0, '511E40000': 850.0, '512E10000': 1200.0, '513E10220': 2.25, '513E20000': 10.0, '515E10000': 22000.0, '515E20000': 30000.0, '515E30000': 1500.0, '516E10000': 2000.0, '526E10000': 120.0}
Planning-level unit prices ($ per pay-item unit) for the seed catalog — round numbers in the range of recent ODOT bid tabulations, meant to rank alternatives and sanity-check a budget, not to replace a district estimate. Override any entry (or add items) through the
pricesargument ofcost_estimate().
- civilpy.structural.bim.PAY_ITEMS: dict[str, PayItem] = {'507E10000': PayItem(code='507E10000', description='Steel piles HP, furnished and driven [CONFIRM]', unit='ft', category='507', level=1), '509E00100': PayItem(code='509E00100', description='Reinforcing steel, black [CONFIRM]', unit='lb', category='509', level=1), '509E00200': PayItem(code='509E00200', description='Epoxy coated reinforcing steel [CONFIRM]', unit='lb', category='509', level=1), '509E00300': PayItem(code='509E00300', description='GFRP deformed bars [CONFIRM]', unit='lb', category='509', level=1), '511E12100': PayItem(code='511E12100', description='Class QC2 concrete, superstructure (deck) [CONFIRM]', unit='cy', category='511', level=1), '511E40000': PayItem(code='511E40000', description='Class QC1 concrete, substructure [CONFIRM]', unit='cy', category='511', level=1), '512E10000': PayItem(code='512E10000', description='Concrete, parapet/railing [CONFIRM]', unit='cy', category='512', level=1), '513E10220': PayItem(code='513E10220', description='Structural steel members, Level 1', unit='lb', category='513', level=1), '513E20000': PayItem(code='513E20000', description='Shear connectors (welded studs)', unit='ea', category='513', level=1), '515E10000': PayItem(code='515E10000', description='Prestressed concrete box beam member [CONFIRM]', unit='ea', category='515', level=1), '515E20000': PayItem(code='515E20000', description='Prestressed concrete I-beam member [CONFIRM]', unit='ea', category='515', level=1), '515E30000': PayItem(code='515E30000', description='Intermediate diaphragms [CONFIRM]', unit='ea', category='515', level=1), '516E10000': PayItem(code='516E10000', description='Elastomeric bearing [CONFIRM]', unit='ea', category='516', level=1), '526E10000': PayItem(code='526E10000', description='Reinforced concrete approach slabs [CONFIRM]', unit='sy', category='526', level=1)}
Seed catalog. The two studs/steel items are confirmed from Dane; the concrete and reinforcing items are typical ODOT numbers flagged CONFIRM until verified against the current ODOT Construction & Material Specs item master.
- class civilpy.structural.bim.PayItem(code: str, description: str, unit: str, category: str, level: int = 1)[source]
Bases:
objectAn ODOT pay item.
levelis the estimating detail level;unitthe quantity unit (lb, ea, cy, ft, sf).
- civilpy.structural.bim.REBAR_PAY_ITEM = {'black': '509E00100', 'epoxy': '509E00200', 'gfrp': '509E00300'}
Reinforcing pay item by coating (see
PAY_ITEMS).
- civilpy.structural.bim.SUBSTRUCTURE_CONCRETE_TYPES = ('pier_cap', 'abutment_cap', 'beam_seat', 'column', 'footing', 'backwall', 'wingwall', 'foreslope_wall', 'cutoff_wall')
Substructure concrete component types (one Rhino layer each).
- civilpy.structural.bim.approach_slab_tags(bid: str, scd: str = 'AS-1-15', *, scd_year: str | int | None = None, length_ft: float, width_ft: float, thickness_in: float, skew_deg: float = 0.0, fc_psi: float = 4500.0, area_sy: float | None = None) dict[source]
Approach slab concrete. ITEM 526 measures the plan area (sy) and includes the slab reinforcing (anchor bars into the abutment are the exception — they measure under the ITEM 509 reinforcing items).
- civilpy.structural.bim.bearing_tags(bid: str, *, fixity: str, kind: str = 'elastomeric', plies: int | None = None, ply_thickness_in: float | None = None, total_thickness_in: float | None = None) dict[source]
- civilpy.structural.bim.box_beam_tags(bid: str, *, box: str, depth_in: float, beam_type: str, part: str, span_ft: float, scd: str | None = 'PSBD-1-25', scd_year: str | int | None = 2025, fc_psi: float = 6000.0, n_strands: int | None = None, concrete_cy: float | None = None, count: int | None = None) dict[source]
Prestressed box-beam member. The member is drawn as several
partprisms (top/bottom flange, webs) sharing a beam id prefix; exactly one part per beam carriescountso the 515 member item counts each beam once (strands, tie rods, and precast diaphragms are included in the member).
- civilpy.structural.bim.concrete_mat(fc_psi: float, cls: str = 'QC2') dict[source]
Concrete material block.
- civilpy.structural.bim.cost_estimate(quantities: dict[str, dict], prices: dict[str, float] | None = None) CostEstimate[source]
Price a quantity rollup (the
{item: {"desc", "unit", "qty", ...}}dict thatpay_item_quantities/read_bim_quantitiesproduce).Unit prices come from
DEFAULT_UNIT_PRICESupdated withprices; see the caveat there — these are planning numbers.
- civilpy.structural.bim.cross_frame_tags(bid: str, *, frame_type: str, member_shape: str, grade: str = '50', weight_lb: float | None = None) dict[source]
Intermediate cross-frame / steel diaphragm bay (build plan §3a). Its members are fabricated structural steel measured by weight into the 513 item like the girders.
- civilpy.structural.bim.deck_tags(bid: str, *, thickness_in: float, slope_pct: float, crown_offset_ft: float, fc_psi: float = 4500.0, cls: str = 'QC2', volume_cy: float | None = None) dict[source]
- civilpy.structural.bim.diaphragm_tags(bid: str, *, thickness_in: float, fc_psi: float = 4500.0, volume_cy: float | None = None, pay: bool = True, item: str = '511E12100', count: float | None = None) dict[source]
Concrete end/intermediate diaphragm. Integral and semi-integral end diaphragms are cast with (and move with) the superstructure, so their concrete measures into the superstructure item like the haunches; a box beam’s precast diaphragms are included in the member (
pay=False). A PS I-beam bridge’s intermediate diaphragms have their own 515 each-measured item (PSID-1-13 sheet 10) — passitem="515E30000"withcountinstead ofvolume_cy.
- civilpy.structural.bim.field_splice_tags(bid: str, *, bolt_count: int, grade: str = '50', weight_lb: float | None = None) dict[source]
Bolted field splice (§3a): the splice plates are structural steel by weight (513); the high-strength bolts are incidental, carried as a count.
- civilpy.structural.bim.girder_tags(bid: str, shape: str, *, grade: str = '50W', spec: str = 'ASTM A709', treatment: str = 'none', weight_lb: float | None = None) dict[source]
- civilpy.structural.bim.haunch_tags(bid: str, *, depth_in: float, width_in: float, fc_psi: float = 4500.0, volume_cy: float | None = None) dict[source]
Haunch concrete is conventionally measured with the superstructure (deck) concrete item, so a
volume_cyrolls into511E12100.
- civilpy.structural.bim.load_plate_tags(bid: str, *, thickness_in: float, grade: str = '50', spec: str = 'ASTM A709', weight_lb: float | None = None) dict[source]
- civilpy.structural.bim.parapet_tags(bid: str, scd: str, *, scd_year: str | int | None = None, height_in: float | None = None, fc_psi: float = 4500.0, length_ft: float | None = None, volume_cy: float | None = None) dict[source]
- civilpy.structural.bim.pile_tags(bid: str, *, shape: str, length_ft: float, grade: str = '50', spec: str = 'ASTM A572') dict[source]
Driven steel HP pile: pay quantity is the furnished+driven length (ft) below cutoff.
- civilpy.structural.bim.ps_i_beam_tags(bid: str, *, section: str, depth_in: float, span_ft: float, scd: str | None = 'PSID-1-13', scd_year: str | int | None = 2025, fc_psi: float = 5500.0, n_strands: int | None = None, n_debonded: int | None = None, concrete_cy: float | None = None, count: int | None = None) dict[source]
Prestressed I-beam member (PSID-1-13). One prism per beam; the member carries the 515 I-beam item (strands, embedded sole plates, and anchorage-zone steel are included in the member per sheet 10).
- civilpy.structural.bim.rebar_mat(size: int, coating: str = 'epoxy') dict[source]
Reinforcing material block.
coating= epoxy / GFRP / stainless / black;sizethe bar number (#), diameter in eighths of an inch. The tabulated weight is steel (C&MS 509); GFRP bars carry the spec reference instead (C&MS 705.28) since their weight is producer-specific.
- civilpy.structural.bim.rebar_tags(bid: str, *, size: int, coating: str = 'epoxy', mat: str = 'top', bend: str = 'straight', length_ft: float | None = None, scd: str | None = None) dict[source]
- civilpy.structural.bim.shear_stud_tags(bid: str, *, dia_in: float = 0.875, length_in: float = 6.0, count: int | None = None) dict[source]
- civilpy.structural.bim.steel_mat(spec: str = 'ASTM A709', grade: str = '50W', treatment: str = 'none') dict[source]
Structural-steel material block.
gradeis 36/50/50W/70;50Wand70Ware weathering.treatment= none / galvanized / painted.
- civilpy.structural.bim.stiffener_tags(bid: str, *, kind: str, thickness_in: float, grade: str = '50', weight_lb: float | None = None) dict[source]
Web/bearing/longitudinal stiffener plate (§3a).
kindistransverse/bearing/longitudinal; measured by weight into the 513 structural-steel item.
- civilpy.structural.bim.substructure_concrete_tags(btype: str, bid: str, *, fc_psi: float = 4000.0, cls: str = 'QC1', volume_cy: float | None = None, **dims: float) dict[source]
Tag block for a cast-in-place substructure concrete component.
btypeis one ofSUBSTRUCTURE_CONCRETE_TYPES; extradimskeywords flatten to"<btype>.<key>"string tags (e.g.depth_ft=5.0on apier_capbecomespier_cap.depth_ft). All substructure concrete measures into the one Class QC1 substructure item, the way haunches roll into the deck item.
civilpy.structural.bim_spec module
Storable parametric Spec records — the BIM system’s schema authority.
The queryable-BIM architecture stores every parameter needed to run the
civilpy.structural.aashto.lrfd checks from scratch as a JSON
document (Postgres JSONB on the application side). This module declares
those records once, as plain dataclasses of JSON-safe primitives:
every field carries its engineering metadata (
unit, bounds, enum options, the checks that consume it) indataclasses.field(metadata=)— one declaration drives validation, documentation, and the check-coverage test;record_to_dict()/record_from_dict()round-trip a record through a JSON document with no schema declared anywhere else (add a field here and the document simply carries an extra key);JSON is not type-checked on write, so
SpecRecord.validate()is the type guarantor — validate before save;the record reconstitutes the executed-design objects the geometry builders in
civilpy.structural.substructure_layoutconsume, so a record read back from storage can regenerate geometry, the tagged.3dmemit, and every check input without the session that authored it.
The first element modeled end-to-end was the single-column hammerhead
pier (HammerheadPierRecord); the Phase-6 breadth elements —
BentPierRecord, PileBentRecord,
SeatAbutmentRecord — follow the same pattern. Each element has
a *_CHECK_INPUTS coverage map tying every LRFD check that consumes
it to the record fields that feed it — the test suite walks the maps, so
a check added without a mapped input path (or a field renamed out from
under one) fails loudly instead of rotting silently.
Units follow the hub convention: layout-scale dimensions in feet
(_ft), section-scale in inches (_in), strengths in ksi.
- civilpy.structural.bim_spec.BENT_PIER_CHECK_INPUTS: dict[str, dict[str, tuple]] = {'4.5.3.2.2b': {'m_2': ('loads', 'larger end moment'), 'p_e': ('derived', ('column.b_in', 'column.h_in', 'column.diameter_in', 'column.height_ft', 'column.fixity', 'column.fc_ksi'), 'Euler load from cracked EI and effective length'), 'p_u': ('loads', 'factored axial')}, '5.10.8.2.1': {'d_b': ('derived', ('pier_cap.tie_bar_size',), 'bar diameter'), 'f_c': ('field', 'pier_cap.fc_ksi'), 'f_y': ('field', 'pier_cap.fy_ksi')}, '5.6.4.2': {'a_g': ('derived', ('column.b_in', 'column.h_in', 'column.diameter_in'), 'gross area'), 'a_st': ('field', 'column.bars_area_in2'), 'f_c': ('field', 'column.fc_ksi'), 'f_y': ('field', 'column.fy_ksi')}, '5.6.4.4': {'a_g': ('derived', ('column.b_in', 'column.h_in', 'column.diameter_in'), 'gross area'), 'a_st': ('field', 'column.bars_area_in2'), 'f_c': ('field', 'column.fc_ksi'), 'f_y': ('field', 'column.fy_ksi'), 'p_u': ('loads', 'factored axial from the cap reactions'), 'spiral': ('field', 'column.spiral')}, '5.6.4.5 check': {'b': ('field', 'column.b_in'), 'diameter': ('field', 'column.diameter_in'), 'f_c': ('field', 'column.fc_ksi'), 'f_y': ('field', 'column.fy_ksi'), 'h': ('field', 'column.h_in'), 'layers': ('derived', ('column.bars_area_in2', 'detailing.cover_in', 'column.h_in', 'column.diameter_in'), 'stem_rebar_layers()'), 'm_u': ('loads', 'factored (magnified) moment'), 'p_u': ('loads', 'factored axial'), 'spiral': ('field', 'column.spiral')}, '5.6.7': {'d_c': ('derived', ('detailing.cover_in', 'pier_cap.tie_bar_size'), 'cover + half bar diameter'), 'f_ss': ('loads', 'service tie-steel stress'), 'h': ('field', 'pier_cap.depth_ft')}, '5.7.2.5': {'a_v': ('derived', ('detailing.stirrup_size',), 'stirrup legs area'), 'b_v': ('field', 'pier_cap.thickness_ft'), 'f_c': ('field', 'pier_cap.fc_ksi'), 's': ('field', 'detailing.stirrup_spacing_in')}, '5.7.2.6': {'b_v': ('field', 'pier_cap.thickness_ft'), 'd_v': ('derived', ('pier_cap.depth_ft', 'detailing.cover_in', 'pier_cap.tie_bar_size'), 'effective shear depth from the section'), 'f_c': ('field', 'pier_cap.fc_ksi'), 'v_u': ('loads', 'factored shear at the section')}, '5.7.3.3': {'a_v': ('derived', ('detailing.stirrup_size',), 'stirrup legs area'), 'b_v': ('field', 'pier_cap.thickness_ft'), 'd_v': ('derived', ('pier_cap.depth_ft', 'detailing.cover_in', 'pier_cap.tie_bar_size'), 'effective shear depth from the section'), 'f_c': ('field', 'pier_cap.fc_ksi'), 'f_y': ('field', 'pier_cap.fy_ksi'), 's': ('field', 'detailing.stirrup_spacing_in'), 'v_u': ('loads', 'factored shear at the section')}, '5.8.2.4': {'a_st': ('derived', ('pier_cap.tie_bar_size', 'pier_cap.tie_bar_count'), 'bar area x count'), 'f_y': ('field', 'pier_cap.fy_ksi'), 'p_u': ('loads', 'governing tie force from the STM solve')}, '5.8.2.5': {'a_cn': ('derived', ('pier_cap.thickness_ft', 'detailing.seat_side_in'), 'node face area: seat side x min(cap width, seat side)'), 'f_c': ('field', 'pier_cap.fc_ksi'), 'p_u': ('loads', 'strut / node face force from the STM solve')}, '5.8.2.6': {'a_s_horizontal': ('derived', ('detailing.skin_bar_size',), 'two-face skin bar area'), 'a_s_vertical': ('derived', ('detailing.stirrup_size',), 'stirrup legs area'), 'b_w': ('field', 'pier_cap.thickness_ft'), 's_h': ('field', 'detailing.stirrup_spacing_in'), 's_v': ('field', 'detailing.skin_bar_spacing_in')}}
the hammerhead map with the stem paths renamed to the bent’s uniform
columnrecord (same checks, same physics).- Type:
Multi-column bent
- class civilpy.structural.bim_spec.BearingStiffenerRecord(plate_width_in: float, plate_thickness_in: float, pairs: int = 1, fy_ksi: float = 50.0, milled_to_bear: bool = True)[source]
Bases:
SpecRecordBearing stiffener plate pair at a support (6.10.11.2.3).
- class civilpy.structural.bim_spec.BentPierRecord(pier_cap: PierCapRecord, column: PierStemRecord, column_xs_ft: tuple[float, ...], footing: FootingRecord | None = None, detailing: CapDetailingRecord | None = None, standard: str | None = None, standard_year: int | None = None, provenance: Provenance | None = None)[source]
Bases:
ElementRecordOne multi-column bent pier: the cap envelope + governing tie (the bent cap’s tie is normally the bottom chord — set
pier_cap.tie_at_top=Falsewhen storing one), a uniform column section/steel, and the column centers along the cap. Per-column height/section variation is a later refinement; the uniform case is the standard-drawing bent.- BIM_TYPE = 'pier'
- SUBTYPE = 'bent'
- column: PierStemRecord
- detailing: CapDetailingRecord | None = None
- footing: FootingRecord | None = None
- pier_cap: PierCapRecord
- provenance: Provenance | None = None
- class civilpy.structural.bim_spec.BoxBeamBridgeRecord(box: str, span_ft: float, n_beams: int, skew_deg: float = 0.0, fc_psi: float = 6000.0, standard: str | None = None, standard_year: int | None = None, provenance: Provenance | None = None)[source]
Bases:
ElementRecordOne adjacent prestressed box-beam bridge on the ODOT standard designs (PSBDD-1-25 tables): the
BoxBridgeInputas a storable record. The strand pattern and section come from the design table for(box, span_ft)at emit time.- BIM_TYPE = 'bridge'
- SUBTYPE = 'box_beam'
- provenance: Provenance | None = None
- to_input()[source]
The
BoxBridgeInputthe emit engine consumes.
- class civilpy.structural.bim_spec.BridgeLayoutRecord(spans_ft: tuple[float, ...], girder_count: int = 2, girder_spacing_ft: float = 8.0, girder_label: str = 'W36X150', overhang_ft: float = 3.0, railing: str = 'SBR-1-20', grade: str = 'Grade 50', skew_deg: float = 0.0, design_haunch_in: float = 2.0, deck_thickness_in: float | None = None, deck_fc_ksi: float = 4.5, cross_slope_pct: float = 2.0, crown_offset_ft: float | None = None, composite: bool = True, standard: str | None = None, standard_year: int | None = None, provenance: Provenance | None = None)[source]
Bases:
ElementRecordThe bridge-level superstructure geometry as a storable record: the
BridgeInputa stored bridge reconstitutes to for the.3dmemit. This is the whole-bridge frame (spans, girder count/spacing, overhang, skew, deck) that the per-girder-lineSteelGirderRecorddetails hang off; the batch materializer reads it, callsto_bridge_input(), and runsgirder_bridge_emit— so the served model comes from an authored Spec, not a second geometry engine (build plan §2.4/§8). Every field mirrorsBridgeInputone-for-one, so the round-trip is faithful.- BIM_TYPE = 'bridge'
- SUBTYPE = 'layout'
- provenance: Provenance | None = None
- class civilpy.structural.bim_spec.CapDetailingRecord(cover_in: float = 3.0, stirrup_size: int = 5, stirrup_spacing_in: float = 12.0, skin_bar_size: int = 5, skin_bar_spacing_in: float = 12.0, seat_side_in: float = 27.0)[source]
Bases:
SpecRecordCap/stem detailing outside the STM schedule: covers, the shear stirrups the sectional checks run with, the D-region crack-control grid (5.8.2.6), and the bearing-seat plan side the node faces bear on.
- class civilpy.structural.bim_spec.CompositeRecord(stud_diameter_in: float = 0.875, studs_per_row: int = 3, stud_gauge_in: float = 3.0, pitch_in: float = 12.0, effective_width_in: float = 96.0, modular_ratio_n: float = 8.0, haunch_in: float = 2.0, deck_fc_ksi: float = 4.5)[source]
Bases:
SpecRecordThe shear-stud connection + composite deck properties – promotes what were emit-time stud arguments into stored fields so a bridge round-trips them. Consuming checks 6.10.10 (fatigue pitch, stud strength).
- class civilpy.structural.bim_spec.CrossFrameRecord(frame_type: str = 'K', member_shape: str = 'L4X4X1/2', member_length_ft: float = 6.0, spacing_ft: float = 20.0, stations_ft: tuple[float, ...] | None = None, connection_plate_thickness_in: float = 0.5, fy_ksi: float = 50.0)[source]
Bases:
SpecRecordAn intermediate cross-frame / steel diaphragm bay: type, member section, connection plate, and along-span spacing. The members run the ported tension/compression checks (6.8.2.1 / 6.9.4.1.1); the spacing feeds 6.7.4 when it ports and the LTB unbraced length today (6.10.8.2.3
l_b).- bay_stations(length_ft: float) tuple[float, ...][source]
The cross-frame line stations along a girder of
length_ft: the storedstations_ftwhen given (irregular spacing kept verbatim), else a uniform run atspacing_ft.
- class civilpy.structural.bim_spec.ElementRecord[source]
Bases:
SpecRecordBase for whole-element records: the
bim.type/subtypeidentity keys stamped into the flattened document, and the guarded round-trip every element shares.- BIM_TYPE = ''
- SUBTYPE = ''
- class civilpy.structural.bim_spec.FieldSpliceRecord(station_ft: float, flange_width_left_in: float, flange_width_right_in: float, flange_thickness_in: float, web_depth_in: float, web_thickness_left_in: float, web_thickness_right_in: float, flange_clearance_in: float = 1.0, splice_plate_thickness_in: float = 0.5, bolt_diameter_in: float = 0.875, bolt_rows: int = 2, bolt_cols: int = 3, bolt_fu_ksi: float = 120.0, plate_fy_ksi: float = 50.0, plate_fu_ksi: float = 65.0)[source]
Bases:
SpecRecordA bolted field splice on the girder (6.13.6 family): location, the flange/web splice-plate schedule as the checks size it, and the bolt group. Ties into the NSBA splice designer and the girder->splice
gdr.*contract.
- class civilpy.structural.bim_spec.FootingRecord(length_ft: float, width_ft: float, thickness_ft: float)[source]
Bases:
SpecRecordSpread-footing plan dims — a geotech deliverable, explicit inputs (mirrors
FootingSpec).
- civilpy.structural.bim_spec.GIRDER_CHECK_INPUTS: dict[str, dict[str, tuple]] = {'6.10.10.1.2': {'d_stud': ('field', 'composite.stud_diameter_in'), 'n_per_row': ('field', 'composite.studs_per_row'), 'pitch': ('field', 'composite.pitch_in'), 'shear_flow': ('loads', 'fatigue horizontal shear flow at the section')}, '6.10.10.2': {'d_stud': ('field', 'composite.stud_diameter_in'), 'n_cycles': ('loads', 'design fatigue cycles N (None = Fatigue I)')}, '6.10.10.3': {'d_stud': ('field', 'composite.stud_diameter_in'), 'flange_width_in': ('derived', ('section.label', 'section.top_flange_width_in'), 'top flange width'), 'gauge_in': ('field', 'composite.stud_gauge_in'), 'n_per_row': ('field', 'composite.studs_per_row')}, '6.10.10.4': {'d_stud': ('field', 'composite.stud_diameter_in'), 'e_c': ('derived', ('composite.deck_fc_ksi',), 'deck modulus from f_c (C5.4.2.4)'), 'f_c': ('field', 'composite.deck_fc_ksi')}, '6.10.11.1.2': {'b_f': ('derived', ('section.label', 'section.top_flange_width_in'), 'widest compression flange in the field section'), 'b_t': ('field', 'transverse_stiffener.plate_width_in'), 'd_web': ('derived', ('section.label', 'section.web_depth_in'), 'web depth'), 't_p': ('field', 'transverse_stiffener.plate_thickness_in')}, '6.10.11.1.3': {'b_t': ('field', 'transverse_stiffener.plate_width_in'), 'd_o': ('field', 'transverse_stiffener.spacing_in'), 'd_web': ('derived', ('section.label', 'section.web_depth_in'), 'web depth'), 'f_ys': ('field', 'transverse_stiffener.fy_ksi'), 'f_yw': ('field', 'section.fyw_ksi'), 'moment_of_inertia': ('derived', ('transverse_stiffener.plate_width_in', 'transverse_stiffener.plate_thickness_in', 'transverse_stiffener.single_sided'), 'I_t about the web face (single) or mid-thickness (pair)'), 't_p': ('field', 'transverse_stiffener.plate_thickness_in'), 't_w': ('derived', ('section.label', 'section.web_thickness_in'), 'web thickness')}, '6.10.11.2.2': {'b_t': ('field', 'bearing_stiffener.plate_width_in'), 'f_ys': ('field', 'bearing_stiffener.fy_ksi'), 't_p': ('field', 'bearing_stiffener.plate_thickness_in')}, '6.10.11.2.3': {'a_pn': ('derived', ('bearing_stiffener.plate_width_in', 'bearing_stiffener.plate_thickness_in', 'bearing_stiffener.pairs'), 'net projecting bearing area'), 'f_ys': ('field', 'bearing_stiffener.fy_ksi')}, '6.10.11.2.4': {'b_t': ('field', 'bearing_stiffener.plate_width_in'), 'd_web': ('derived', ('section.label', 'section.web_depth_in'), 'web depth'), 'f_ys': ('field', 'bearing_stiffener.fy_ksi'), 'p_u': ('loads', 'factored bearing reaction'), 'pairs': ('field', 'bearing_stiffener.pairs'), 't_p': ('field', 'bearing_stiffener.plate_thickness_in'), 't_w': ('derived', ('section.label', 'section.web_thickness_in'), 'web thickness')}, '6.10.11.3': {'d_o': ('derived', ('transverse_stiffener.spacing_in',), 'panel spacing (uniform default if no transverse stiffener)'), 'd_web': ('derived', ('section.label', 'section.web_depth_in'), 'web depth'), 'f_yc': ('field', 'section.fyc_ksi'), 'f_ys': ('field', 'longitudinal_stiffener.fy_ksi'), 'moment_of_inertia': ('derived', ('longitudinal_stiffener.plate_width_in', 'longitudinal_stiffener.plate_thickness_in'), 'I_l of the plate about the web face'), 'proj_width': ('field', 'longitudinal_stiffener.plate_width_in'), 'radius_of_gyration': ('derived', ('longitudinal_stiffener.plate_width_in', 'longitudinal_stiffener.plate_thickness_in'), 'r = sqrt(I_l/A) of the stiffener'), 't_s': ('field', 'longitudinal_stiffener.plate_thickness_in'), 't_w': ('derived', ('section.label', 'section.web_thickness_in'), 'web thickness')}, '6.10.8.1.1': {'f_bu': ('loads', 'factored flange stress'), 'f_l': ('loads', 'flange lateral bending stress'), 'f_nc': ('derived', ('section.label', 'section.top_flange_width_in', 'section.top_flange_thickness_in', 'cross_frame.spacing_ft'), 'Fnc from 6.10.8.2.1')}, '6.10.8.1.3': {'f_bu': ('loads', 'factored flange stress'), 'f_yf': ('field', 'section.fyc_ksi')}, '6.10.8.2.1': {'b_fc': ('derived', ('section.label', 'section.top_flange_width_in'), 'compression-flange width'), 'd_c': ('derived', ('section.label', 'section.web_depth_in'), 'depth of web in compression'), 'f_yc': ('field', 'section.fyc_ksi'), 'f_yw': ('field', 'section.fyw_ksi'), 'l_b': ('derived', ('cross_frame.stations_ft', 'cross_frame.spacing_ft'), 'governing unbraced length (max bay)'), 't_fc': ('derived', ('section.label', 'section.top_flange_thickness_in'), 'compression-flange thickness'), 't_w': ('derived', ('section.label', 'section.web_thickness_in'), 'web thickness')}, '6.10.8.2.2': {'b_fc': ('derived', ('section.label', 'section.top_flange_width_in'), 'compression-flange width from the catalog DB or plates'), 'f_yc': ('field', 'section.fyc_ksi'), 'f_yw': ('field', 'section.fyw_ksi'), 't_fc': ('derived', ('section.label', 'section.top_flange_thickness_in'), 'compression-flange thickness')}, '6.10.8.2.3': {'b_fc': ('derived', ('section.label', 'section.top_flange_width_in'), 'compression-flange width'), 'd_c': ('derived', ('section.label', 'section.web_depth_in'), 'depth of web in compression'), 'f_yc': ('field', 'section.fyc_ksi'), 'f_yw': ('field', 'section.fyw_ksi'), 'l_b': ('derived', ('cross_frame.stations_ft', 'cross_frame.spacing_ft'), 'governing unbraced length = max bay (CrossFrameRecord.max_bay_ft), from the per-bay stations or uniform spacing'), 't_fc': ('derived', ('section.label', 'section.top_flange_thickness_in'), 'compression-flange thickness'), 't_w': ('derived', ('section.label', 'section.web_thickness_in'), 'web thickness')}, '6.10.9': {'d_web': ('derived', ('section.label', 'section.web_depth_in'), 'web depth'), 'f_yw': ('field', 'section.fyw_ksi'), 't_w': ('derived', ('section.label', 'section.web_thickness_in'), 'web thickness')}, '6.13.5.3': {'a_vg': ('derived', ('splices.web_depth_in', 'splices.splice_plate_thickness_in'), 'gross shear area of the web splice plate'), 'a_vn': ('derived', ('splices.web_depth_in', 'splices.splice_plate_thickness_in', 'splices.bolt_diameter_in', 'splices.bolt_rows'), 'net shear area through the bolt line'), 'f_u': ('field', 'splices.plate_fu_ksi'), 'f_y': ('field', 'splices.plate_fy_ksi')}, '6.13.6.1.3b': {'flange_thickness': ('field', 'splices.flange_thickness_in'), 'flange_width_left': ('field', 'splices.flange_width_left_in'), 'flange_width_right': ('field', 'splices.flange_width_right_in'), 'web_thickness_left': ('field', 'splices.web_thickness_left_in'), 'web_thickness_right': ('field', 'splices.web_thickness_right_in')}, '6.13.6.1.3c': {'flange_clearance': ('field', 'splices.flange_clearance_in'), 'web_depth': ('field', 'splices.web_depth_in'), 'web_thickness': ('field', 'splices.web_thickness_left_in'), 'web_thickness_other': ('field', 'splices.web_thickness_right_in')}, '6.13.6.1.4': {'a_f': ('derived', ('splices.flange_width_left_in', 'splices.flange_thickness_in'), 'smaller-side flange area'), 'a_p': ('derived', ('splices.flange_width_left_in', 'splices.splice_plate_thickness_in'), 'splice plate area')}, '6.7.4.2.2': {'brace_stiffness': ('derived', ('cross_frame.member_shape', 'cross_frame.member_length_ft', 'cross_frame.connection_plate_thickness_in'), 'provided cross-frame torsional stiffness'), 'i_eff': ('derived', ('section.label', 'section.top_flange_width_in', 'section.top_flange_thickness_in'), 'effective lateral moment of inertia'), 'l_span': ('derived', ('length_ft',), 'girder span/length'), 'm_r': ('loads', 'required flexural strength Mr at the brace'), 'n_braces': ('derived', ('cross_frame.stations_ft', 'cross_frame.spacing_ft'), 'number of intermediate brace points')}, '6.8.2.1': {'a_g': ('derived', ('cross_frame.member_shape',), 'AISC member gross area'), 'f_y': ('field', 'cross_frame.fy_ksi')}, '6.9.4.1.1': {'a_g': ('derived', ('cross_frame.member_shape',), 'AISC member gross area'), 'f_y': ('field', 'cross_frame.fy_ksi'), 'kl_over_r': ('derived', ('cross_frame.member_shape', 'cross_frame.member_length_ft'), 'K*L/r_y from the section and work length')}, '6.9.4.2.1': {'b': ('derived', ('cross_frame.member_shape',), 'outstanding leg width from the AISC label'), 'f_y': ('field', 'cross_frame.fy_ksi'), 't': ('derived', ('cross_frame.member_shape',), 'leg thickness from the AISC label')}}
The check-coverage map for a steel girder line (build plan §3, applied to §3a). Aggregates every ported LRFD check that consumes some part of the girder and where its inputs live.
("loads", ...)marks a demand from the line-girder / grillage analysis, not a stored field. The coverage test asserts every required parameter of every listed check resolves to a path onSteelGirderRecord. Not-yet-ported checks (6.7.4 cross-frame spacing, 6.10.11.3 longitudinal stiffener) are deliberately absent – their fields are quantity space until the article lands.
- class civilpy.structural.bim_spec.GirderSectionRecord(label: str | None = None, web_depth_in: float | None = None, web_thickness_in: float | None = None, top_flange_width_in: float | None = None, top_flange_thickness_in: float | None = None, bot_flange_width_in: float | None = None, bot_flange_thickness_in: float | None = None, fyc_ksi: float = 50.0, fyw_ksi: float = 50.0)[source]
Bases:
SpecRecordThe girder cross-section the sectional checks run on: EITHER a cataloged rolled shape (
label) OR an explicit built-up plate section. This is the single controlling section (the design’s governing positive-moment / pier section); the whole variable-depth schedule lives inSteelGirderRecord.plates. Give exactly one representation – a label or the full set of plate dims – andresolve()returns the(d, t_w, b_fc, t_fc)the checks need from whichever is present.
- civilpy.structural.bim_spec.HAMMERHEAD_CHECK_INPUTS: dict[str, dict[str, tuple]] = {'4.5.3.2.2b': {'m_2': ('loads', 'larger end moment'), 'p_e': ('derived', ('pier_stem.b_in', 'pier_stem.h_in', 'pier_stem.diameter_in', 'pier_stem.height_ft', 'pier_stem.fixity', 'pier_stem.fc_ksi'), 'Euler load from cracked EI and effective length'), 'p_u': ('loads', 'factored axial')}, '5.10.8.2.1': {'d_b': ('derived', ('pier_cap.tie_bar_size',), 'bar diameter'), 'f_c': ('field', 'pier_cap.fc_ksi'), 'f_y': ('field', 'pier_cap.fy_ksi')}, '5.6.4.2': {'a_g': ('derived', ('pier_stem.b_in', 'pier_stem.h_in', 'pier_stem.diameter_in'), 'gross area'), 'a_st': ('field', 'pier_stem.bars_area_in2'), 'f_c': ('field', 'pier_stem.fc_ksi'), 'f_y': ('field', 'pier_stem.fy_ksi')}, '5.6.4.4': {'a_g': ('derived', ('pier_stem.b_in', 'pier_stem.h_in', 'pier_stem.diameter_in'), 'gross area'), 'a_st': ('field', 'pier_stem.bars_area_in2'), 'f_c': ('field', 'pier_stem.fc_ksi'), 'f_y': ('field', 'pier_stem.fy_ksi'), 'p_u': ('loads', 'factored axial from the cap reactions'), 'spiral': ('field', 'pier_stem.spiral')}, '5.6.4.5 check': {'b': ('field', 'pier_stem.b_in'), 'diameter': ('field', 'pier_stem.diameter_in'), 'f_c': ('field', 'pier_stem.fc_ksi'), 'f_y': ('field', 'pier_stem.fy_ksi'), 'h': ('field', 'pier_stem.h_in'), 'layers': ('derived', ('pier_stem.bars_area_in2', 'detailing.cover_in', 'pier_stem.h_in', 'pier_stem.diameter_in'), 'stem_rebar_layers()'), 'm_u': ('loads', 'factored (magnified) moment'), 'p_u': ('loads', 'factored axial'), 'spiral': ('field', 'pier_stem.spiral')}, '5.6.7': {'d_c': ('derived', ('detailing.cover_in', 'pier_cap.tie_bar_size'), 'cover + half bar diameter'), 'f_ss': ('loads', 'service tie-steel stress'), 'h': ('field', 'pier_cap.depth_ft')}, '5.7.2.5': {'a_v': ('derived', ('detailing.stirrup_size',), 'stirrup legs area'), 'b_v': ('field', 'pier_cap.thickness_ft'), 'f_c': ('field', 'pier_cap.fc_ksi'), 's': ('field', 'detailing.stirrup_spacing_in')}, '5.7.2.6': {'b_v': ('field', 'pier_cap.thickness_ft'), 'd_v': ('derived', ('pier_cap.depth_ft', 'detailing.cover_in', 'pier_cap.tie_bar_size'), 'effective shear depth from the section'), 'f_c': ('field', 'pier_cap.fc_ksi'), 'v_u': ('loads', 'factored shear at the section')}, '5.7.3.3': {'a_v': ('derived', ('detailing.stirrup_size',), 'stirrup legs area'), 'b_v': ('field', 'pier_cap.thickness_ft'), 'd_v': ('derived', ('pier_cap.depth_ft', 'detailing.cover_in', 'pier_cap.tie_bar_size'), 'effective shear depth from the section'), 'f_c': ('field', 'pier_cap.fc_ksi'), 'f_y': ('field', 'pier_cap.fy_ksi'), 's': ('field', 'detailing.stirrup_spacing_in'), 'v_u': ('loads', 'factored shear at the section')}, '5.8.2.4': {'a_st': ('derived', ('pier_cap.tie_bar_size', 'pier_cap.tie_bar_count'), 'bar area x count'), 'f_y': ('field', 'pier_cap.fy_ksi'), 'p_u': ('loads', 'governing tie force from the STM solve')}, '5.8.2.5': {'a_cn': ('derived', ('pier_cap.thickness_ft', 'detailing.seat_side_in'), 'node face area: seat side x min(cap width, seat side)'), 'f_c': ('field', 'pier_cap.fc_ksi'), 'p_u': ('loads', 'strut / node face force from the STM solve')}, '5.8.2.6': {'a_s_horizontal': ('derived', ('detailing.skin_bar_size',), 'two-face skin bar area'), 'a_s_vertical': ('derived', ('detailing.stirrup_size',), 'stirrup legs area'), 'b_w': ('field', 'pier_cap.thickness_ft'), 's_h': ('field', 'detailing.stirrup_spacing_in'), 's_v': ('field', 'detailing.skin_bar_spacing_in')}}
How each LRFD check consuming a hammerhead resolves its inputs. Values are
("field", "<dotted record path>")for a direct read,("derived", (<paths>...), "<how>")for a value computed from record fields, and("loads", "<what>")for demand-side inputs that come from the superstructure reactions / analysis, not the element record.tests/structural/test_bim_spec.pyasserts every required parameter of every listed check is mapped and every mapped path exists on the record schema — a gap is a failing test, i.e. the live backlog of missing parameters.
- class civilpy.structural.bim_spec.HammerheadPierRecord(pier_cap: PierCapRecord, pier_stem: PierStemRecord, footing: FootingRecord | None = None, detailing: CapDetailingRecord | None = None, standard: str | None = None, standard_year: int | None = None, provenance: Provenance | None = None)[source]
Bases:
ElementRecordOne hammerhead pier as a storable parametric record — the Phase-1 vertical slice of the queryable-BIM schema.
standard+standard_yearkey the standards-catalog defaults lookup;build()reconstitutes the geometry-builder spec so a record read back from storage regenerates placement, the tagged.3dmemit, and the check inputs inHAMMERHEAD_CHECK_INPUTS.- BIM_TYPE = 'pier'
identity keys stamped into the flattened document
- SUBTYPE = 'hammerhead'
- build(layout, unit, **frame_kw)[source]
Place the pier under
layout— the same contract as the specsassemble_substructure()consumes, so a stored record slots straight into the emit pipeline.
- detailing: CapDetailingRecord | None = None
- footing: FootingRecord | None = None
- pier_cap: PierCapRecord
- pier_stem: PierStemRecord
- provenance: Provenance | None = None
- class civilpy.structural.bim_spec.LongitudinalStiffenerRecord(plate_width_in: float, plate_thickness_in: float, location_from_top_flange_in: float, fy_ksi: float = 50.0)[source]
Bases:
SpecRecordLongitudinal web stiffener on a deep web. 6.10.11.3 is not yet in the ported library, so this is geometry/quantity space; its check-coverage entry lands when the article ports (the
WingwallRecordfallback).
- civilpy.structural.bim_spec.PILE_BENT_CHECK_INPUTS: dict[str, dict[str, tuple]] = {'5.10.8.2.1': {'d_b': ('derived', ('pier_cap.tie_bar_size',), 'bar diameter'), 'f_c': ('field', 'pier_cap.fc_ksi'), 'f_y': ('field', 'pier_cap.fy_ksi')}, '5.6.7': {'d_c': ('derived', ('detailing.cover_in', 'pier_cap.tie_bar_size'), 'cover + half bar diameter'), 'f_ss': ('loads', 'service tie-steel stress'), 'h': ('field', 'pier_cap.depth_ft')}, '5.7.2.5': {'a_v': ('derived', ('detailing.stirrup_size',), 'stirrup legs area'), 'b_v': ('field', 'pier_cap.thickness_ft'), 'f_c': ('field', 'pier_cap.fc_ksi'), 's': ('field', 'detailing.stirrup_spacing_in')}, '5.7.2.6': {'b_v': ('field', 'pier_cap.thickness_ft'), 'd_v': ('derived', ('pier_cap.depth_ft', 'detailing.cover_in', 'pier_cap.tie_bar_size'), 'effective shear depth from the section'), 'f_c': ('field', 'pier_cap.fc_ksi'), 'v_u': ('loads', 'factored shear at the section')}, '5.7.3.3': {'a_v': ('derived', ('detailing.stirrup_size',), 'stirrup legs area'), 'b_v': ('field', 'pier_cap.thickness_ft'), 'd_v': ('derived', ('pier_cap.depth_ft', 'detailing.cover_in', 'pier_cap.tie_bar_size'), 'effective shear depth from the section'), 'f_c': ('field', 'pier_cap.fc_ksi'), 'f_y': ('field', 'pier_cap.fy_ksi'), 's': ('field', 'detailing.stirrup_spacing_in'), 'v_u': ('loads', 'factored shear at the section')}, '5.8.2.4': {'a_st': ('derived', ('pier_cap.tie_bar_size', 'pier_cap.tie_bar_count'), 'bar area x count'), 'f_y': ('field', 'pier_cap.fy_ksi'), 'p_u': ('loads', 'governing tie force from the STM solve')}, '5.8.2.5': {'a_cn': ('derived', ('pier_cap.thickness_ft', 'detailing.seat_side_in'), 'node face area: seat side x min(cap width, seat side)'), 'f_c': ('field', 'pier_cap.fc_ksi'), 'p_u': ('loads', 'strut / node face force from the STM solve')}, '5.8.2.6': {'a_s_horizontal': ('derived', ('detailing.skin_bar_size',), 'two-face skin bar area'), 'a_s_vertical': ('derived', ('detailing.stirrup_size',), 'stirrup legs area'), 'b_w': ('field', 'pier_cap.thickness_ft'), 's_h': ('field', 'detailing.stirrup_spacing_in'), 's_v': ('field', 'detailing.skin_bar_spacing_in')}, '6.9.4.1.1': {'a_g': ('derived', ('piles.shape',), 'AISC HP section area'), 'f_y': ('field', 'piles.fy_ksi'), 'kl_over_r': ('derived', ('piles.shape', 'piles.unbraced_length_ft'), 'K*Lu/r_y from the section and exposed length'), 'p_u': ('loads', 'factored pile reaction')}}
the cap family plus the pile compression check.
- Type:
Pile bent
- class civilpy.structural.bim_spec.PierCapRecord(span_ft: float, depth_ft: float, thickness_ft: float, tie_bar_size: int, tie_bar_count: int, tie_at_top: bool = True, tip_depth_ft: float | None = None, fc_ksi: float = 4.0, fy_ksi: float = 60.0)[source]
Bases:
SpecRecordThe executed hammerhead cap design as parameters: overall envelope plus the governing STM tie’s bar schedule (the outputs of
optimize_pier_cap()that geometry and checks consume — the sweep itself is not stored).
- class civilpy.structural.bim_spec.PierStemRecord(height_ft: float, bars_area_in2: float, b_in: float | None = None, h_in: float | None = None, diameter_in: float | None = None, bar_size: int = 9, fc_ksi: float = 4.0, fy_ksi: float = 60.0, spiral: bool = False, fixity: str = 'fixed-fixed')[source]
Bases:
SpecRecordThe hammerhead stem (single column): section, clear height, and the longitudinal steel the executed design carries. Rectangular
b_in x h_in(balong the cap) or circulardiameter_in.
- class civilpy.structural.bim_spec.PileBentRecord(pier_cap: PierCapRecord, piles: PileRecord, detailing: CapDetailingRecord | None = None, standard: str | None = None, standard_year: int | None = None, provenance: Provenance | None = None)[source]
Bases:
ElementRecordOne capped-pile pier (pile bent): the cap directly on driven HP piles — the CPP-1-08 pattern. The cap design’s supports are the piles, so the governing tie is normally the bottom chord.
- BIM_TYPE = 'pier'
- SUBTYPE = 'pile_bent'
- detailing: CapDetailingRecord | None = None
- pier_cap: PierCapRecord
- piles: PileRecord
- provenance: Provenance | None = None
- class civilpy.structural.bim_spec.PileRecord(xs_ft: tuple[float, ...], shape: str = 'HP12X53', length_ft: float = 40.0, fy_ksi: float = 50.0, unbraced_length_ft: float = 0.0)[source]
Bases:
SpecRecordA driven HP-pile group under a cap: positions along the support line (girder-1 frame, same as the cap design’s supports), the AISC section, and the structural-check inputs. Pay length is the driven length from the geotech recommendation.
- class civilpy.structural.bim_spec.PlateSegmentRecord(x_start_ft: float, x_end_ft: float, web_depth_in: float, web_thickness_in: float, top_flange_width_in: float, top_flange_thickness_in: float, bot_flange_width_in: float, bot_flange_thickness_in: float)[source]
Bases:
SpecRecordOne station-ranged built-up girder segment: the web + flange plates over
[x_start_ft, x_end_ft]. Variable depth rides onweb_depth_invarying between segments; flange transitions on the flange plate dims. Geometry/quantity space for the mesh emit – the sectional checks run on the singleGirderSectionRecord.
- class civilpy.structural.bim_spec.Provenance(source: str = 'manual', field_sources: dict | None = None, doc_id: str | None = None, sheet: str | None = None)[source]
Bases:
SpecRecordWhere this element’s parameters came from.
sourceis the element-level tier;field_sourcesoverrides it per field (dotted record path -> tier) for manually-entered values;doc_id/sheetpoint into the plans document store whensource="plans"so a plan revision flags exactly which fields to re-verify.
- civilpy.structural.bim_spec.SCHEMA_VERSION = 1
Bump when a saved document’s meaning changes (not when a field is merely added — additive keys are the point of the JSON substrate).
- civilpy.structural.bim_spec.SEAT_ABUTMENT_CHECK_INPUTS: dict[str, dict[str, tuple]] = {'5.10.8.2.1': {'d_b': ('derived', ('cap.tie_bar_size',), 'bar diameter'), 'f_c': ('field', 'cap.fc_ksi'), 'f_y': ('field', 'cap.fy_ksi')}, '5.6.7': {'d_c': ('derived', ('detailing.cover_in', 'cap.tie_bar_size'), 'cover + half bar diameter'), 'f_ss': ('loads', 'service tie-steel stress'), 'h': ('field', 'cap.depth_ft')}, '5.7.2.5': {'a_v': ('derived', ('detailing.stirrup_size',), 'stirrup legs area'), 'b_v': ('field', 'cap.thickness_ft'), 'f_c': ('field', 'cap.fc_ksi'), 's': ('field', 'detailing.stirrup_spacing_in')}, '5.7.2.6': {'b_v': ('field', 'cap.thickness_ft'), 'd_v': ('derived', ('cap.depth_ft', 'detailing.cover_in', 'cap.tie_bar_size'), 'effective shear depth from the section'), 'f_c': ('field', 'cap.fc_ksi'), 'v_u': ('loads', 'factored shear at the section')}, '5.7.3.3': {'a_v': ('derived', ('detailing.stirrup_size',), 'stirrup legs area'), 'b_v': ('field', 'cap.thickness_ft'), 'd_v': ('derived', ('cap.depth_ft', 'detailing.cover_in', 'cap.tie_bar_size'), 'effective shear depth from the section'), 'f_c': ('field', 'cap.fc_ksi'), 'f_y': ('field', 'cap.fy_ksi'), 's': ('field', 'detailing.stirrup_spacing_in'), 'v_u': ('loads', 'factored shear at the section')}, '5.8.2.4': {'a_st': ('derived', ('cap.tie_bar_size', 'cap.tie_bar_count'), 'bar area x count'), 'f_y': ('field', 'cap.fy_ksi'), 'p_u': ('loads', 'governing tie force from the STM solve')}, '5.8.2.5': {'a_cn': ('derived', ('cap.thickness_ft', 'detailing.seat_side_in'), 'node face area: seat side x min(cap width, seat side)'), 'f_c': ('field', 'cap.fc_ksi'), 'p_u': ('loads', 'strut / node face force from the STM solve')}, '5.8.2.6': {'a_s_horizontal': ('derived', ('detailing.skin_bar_size',), 'two-face skin bar area'), 'a_s_vertical': ('derived', ('detailing.stirrup_size',), 'stirrup legs area'), 'b_w': ('field', 'cap.thickness_ft'), 's_h': ('field', 'detailing.stirrup_spacing_in'), 's_v': ('field', 'detailing.skin_bar_spacing_in')}, '6.9.4.1.1': {'a_g': ('derived', ('piles.shape',), 'AISC HP section area'), 'f_y': ('field', 'piles.fy_ksi'), 'kl_over_r': ('derived', ('piles.shape', 'piles.unbraced_length_ft'), 'K*Lu/r_y from the section and exposed length'), 'p_u': ('loads', 'factored pile reaction')}}
cap family (paths under
cap.) plus piles. The backwall / wingwall (Section 11) checks are not yet in the ported library — their fields are geometry/quantity space until they land (the §3 fallback), at which point they join this map.- Type:
Seat abutment
- civilpy.structural.bim_spec.SOURCES = ('brr', 'plans', 'manual', 'snbi', 'inferred')
Provenance tiers, element-level (decision 3 of the build plan): per-field overrides ride in
Provenance.field_sourcesonly for manually-entered values where a re-derivation trigger pays off.snbi= seeded from the inventory columns;inferred= resolved through the era-standards registry (the standard-default path) — both mark records the Tier A campaign populates without a plan in hand.
- class civilpy.structural.bim_spec.SeatAbutmentRecord(cap: PierCapRecord, piles: PileRecord, backwall_thickness_in: float = 18.0, wingwall: WingwallRecord | None = None, detailing: CapDetailingRecord | None = None, standard: str | None = None, standard_year: int | None = None, provenance: Provenance | None = None)[source]
Bases:
ElementRecordOne conventional capped-pile seat abutment: the cap on driven piles, the backwall up to the low deck edge, and optional wingwalls. The semi-integral and integral variants get their own subtypes when they land (their geometry builders already exist).
- BIM_TYPE = 'abutment'
- SUBTYPE = 'seat'
- cap: PierCapRecord
- detailing: CapDetailingRecord | None = None
- piles: PileRecord
- provenance: Provenance | None = None
- to_abutment_spec()[source]
The layout-side
AbutmentSpec(wingwall reconstituted as the 4-attribute wall the geometry builder reads).
- wingwall: WingwallRecord | None = None
- class civilpy.structural.bim_spec.SlabBridgeRecord(span_ft: int, width_ft: float, skew_deg: float = 0.0, edge_condition: str = 'over_the_side', standard: str | None = None, standard_year: int | None = None, provenance: Provenance | None = None)[source]
Bases:
ElementRecordOne single-span ODOT standard slab bridge (SB-1-24 series): the
SlabBridgeInputas a storable record. The slab thickness and the full A/B/M/N bar schedule come from the standard’s design table at emit time — they are the standard’s content, so they are never stored here (design rule: never copy what the era registry derives).- BIM_TYPE = 'bridge'
- SUBTYPE = 'slab'
- provenance: Provenance | None = None
- class civilpy.structural.bim_spec.SpecRecord[source]
Bases:
objectValidation mixin: the type guarantor in front of a schema-free JSON store. Checks every field against its annotation and its
spec_fieldmetadata (enum membership,gt/gebounds), recursing into nested records. Returns problem strings;validate(strict=True)raises instead.
- class civilpy.structural.bim_spec.SteelGirderRecord(section: GirderSectionRecord, length_ft: float, grade: str = 'Grade 50', plates: tuple[PlateSegmentRecord, ...] | None = None, composite: CompositeRecord | None = None, transverse_stiffener: TransverseStiffenerRecord | None = None, bearing_stiffener: BearingStiffenerRecord | None = None, longitudinal_stiffener: LongitudinalStiffenerRecord | None = None, cross_frame: CrossFrameRecord | None = None, splices: tuple[FieldSpliceRecord, ...] | None = None, standard: str | None = None, standard_year: int | None = None, provenance: Provenance | None = None)[source]
Bases:
ElementRecordOne steel girder line as a storable parametric record – the Phase-6a superstructure schema space.
sectionis the controlling section (a catalog label or a built-up plate section); the optional customization records give cross-frames, stiffeners, the composite block, and a field splice somewhere to live.platescarries the full variable-depth schedule for the mesh emit. The check inputs are inGIRDER_CHECK_INPUTS.- BIM_TYPE = 'girder'
- SUBTYPE = 'steel'
- bearing_stiffener: BearingStiffenerRecord | None = None
- composite: CompositeRecord | None = None
- cross_frame: CrossFrameRecord | None = None
- longitudinal_stiffener: LongitudinalStiffenerRecord | None = None
- plates: tuple[PlateSegmentRecord, ...] | None = None
- provenance: Provenance | None = None
- section: GirderSectionRecord
- splices: tuple[FieldSpliceRecord, ...] | None = None
- to_bridge_input(*, girder_count: int, girder_spacing_ft: float, overhang_ft: float, spans_ft=None, **overrides)[source]
Reconstitute the
BridgeInputthe superstructure emit consumes today. Only a catalogedsection.labelmaps into the current prismatic engine; a plate schedule and the stiffener/cross-frame/splice records are carried for the emit extension (§3a follow-on) and raise here until it lands.
- transverse_stiffener: TransverseStiffenerRecord | None = None
- class civilpy.structural.bim_spec.TransverseStiffenerRecord(plate_width_in: float, plate_thickness_in: float, spacing_in: float, single_sided: bool = False, fy_ksi: float = 50.0)[source]
Bases:
SpecRecordTransverse web stiffener plates on the governing shear zone: dims + the panel spacing
d_othat turns on 6.10.9’s stiffened-panel path. The record’s presence means a stiffened web (absent = the unstiffened default).
- class civilpy.structural.bim_spec.WingwallRecord(length_ft: float, stem_height_ft: float, stem_thickness_ft: float, base_width_ft: float, footing_thickness_ft: float)[source]
Bases:
SpecRecordWingwall panel dimensions — the executed
RetainingWallstem/footing as stored parameters, plus the run along the roadway. The Section-11 wall checks are not yet in the ported library, so this record is geometry/quantity space; its check-coverage entries land when they port (the §3 fallback: manual enumeration until the check exists).
- civilpy.structural.bim_spec.abutment_metrics(geom) dict[source]
Derived metrics for one placed abutment (
AbutmentGeometry) — same emit-time sidecar contract aspier_metrics().
- civilpy.structural.bim_spec.girder_metrics(record: SteelGirderRecord) dict[source]
Derived, JSON-safe metrics for the sidecar/generated columns: the query-hot superstructure numbers, computed from the record (not remeasured off the mesh) the way
pier_metricsis.
- civilpy.structural.bim_spec.pier_metrics(geom) dict[source]
Derived metrics for one placed pier (
PierGeometry), reported at emit time — civilpy knows the semantic element, so this is cheaper and safer than re-measuring meshes. The application side lands these next to the JSONB record and promotes the query-hot ones to generated columns.
- civilpy.structural.bim_spec.record_from_dict(cls, data: dict)[source]
Rebuild
clsfromrecord_to_dict()output (tuples restored, nested records recursed via the type hints — the single schema).
- civilpy.structural.bim_spec.record_paths(cls) set[str][source]
Every dotted field path reachable on a record class (Optionals unwrapped, nested records recursed) — the schema surface the coverage test resolves against.
- civilpy.structural.bim_spec.record_to_dict(rec) dict[source]
Flatten a record to JSON-safe primitives (nested dicts/lists).
- civilpy.structural.bim_spec.select_box(span_ft: float, *, composite: bool = True) str | None[source]
The shallowest PSBDD-1-25 box designation whose cataloged span range covers
span_ft(48-in widths; the standard’s selection logic), or None when no box reaches — spans are cataloged in 5-ft steps, so the lookup rounds up to the next step.
- civilpy.structural.bim_spec.spec_field(default=<dataclasses._MISSING_TYPE object>, *, unit: str | None = None, desc: str | None = None, enum: tuple | None = None, gt: float | None = None, ge: float | None = None, checks: tuple[str, ...]=())[source]
A dataclass field carrying its engineering metadata:
unit, bounds (gt/ge),enumoptions, and the article numbers of thechecksconsuming it. The metadata rides on the one field declaration — validation and the coverage test both read it here.
- civilpy.structural.bim_spec.stem_rebar_layers(record: HammerheadPierRecord, n_layers: int = 2)[source]
Break the stored stem steel into strain-compatibility layers for the P-M checks: half at each face,
coverfrom the extreme fibers (the conventional two-layer idealization of a ring).
civilpy.structural.box_beam_pipeline module
L1 verification of the ODOT standard box-beam designs.
The PSBDD-1-25 tables are pre-engineered designs; this module is the pure-Python gate that re-derives the governing checks for one design line — the box-beam analog of the steel line-girder envelope:
HL-93 demands from the same
girder_line_envelope()influence-line machinery the steel slice uses, distributed with the adjacent-box factors (LRFD 4.6.2.2.2b/3c, type “g” cross-section) — torsion constant from the thin-wall closed section;prestress per the tabulated strand pattern: elastic shortening (5.9.3.2.3a) + the approximate lump-sum time-dependent loss (5.9.3.3). The transfer check assumes every strand fully bonded at the transfer length — conservative for the longest catalog spans, where the standard drawing debonds strands near the ends (a D/C slightly over 1.0 on transfer tension there reproduces exactly the condition debonding exists to fix, not a defect in the tabulated design);
concrete stress checks at transfer (5.9.2.3.1) and service (5.9.2.3.2, Service III tension with the 0.8 live-load factor);
Strength I flexural resistance (5.6.3) against the factored envelope;
release/erection camber passed through from the design line (the sheet’s own tabulated values).
Units: kip / inch / ksi internally; spans and uniform loads enter in feet and klf. Simple spans only (the PSBD standard’s scope).
- class civilpy.structural.box_beam_pipeline.BoxBeamDeadLoads(beam: float = 0.0, deck: float = 0.0, barrier: float = 0.0, asphalt: float = 0.0, fws: float = 0.0, sources: dict = <factory>)[source]
Bases:
objectPer-beam uniform dead loads (klf) for an adjacent box-beam bridge, split into the LRFD load cases and traceable to the BDM article that sets each one.
dc1acts on the bare beam (the beam itself plus a wet CIP deck),dc2on the composite section (barriers, railings) anddwis the wearing-surface case that takes the LRFD 3.4.1 gamma of 1.50 rather than 1.25.
- class civilpy.structural.box_beam_pipeline.BoxBeamLineChecks(design: object, df_moment: float, df_shear: float, midspan_moments: dict = <factory>, losses: dict = <factory>, stresses: dict = <factory>, checks: dict = <factory>, camber_release_in: float = 0.0, camber_erection_in: float = 0.0)[source]
Bases:
objectEverything
box_beam_line_checks()derives for one interior beam of one PSBDD-1-25 design line. Moments in kip-ft; stresses in ksi (compression positive);checksvalues areCheckResult.
- class civilpy.structural.box_beam_pipeline.DeckLayout(n_lanes: int, lane_width_ft: float, shoulder_ft: tuple[float, float], barrier: str | None, barrier_width_ft: float, n_beams: int, beam_width_ft: float, deck_width_ft: float, roadway_ft: float, required_ft: float, spare_ft: float, lane_offsets_ft: tuple[float, ...], governing_loaded_lanes: int, exterior_lane_fraction: float)[source]
Bases:
objectA deck sized from the traffic it has to carry.
The designer states lanes, shoulders and railing; everything else – how wide the deck has to be, how many boxes that takes, and where the lanes have to sit to govern – follows.
- civilpy.structural.box_beam_pipeline.F_PJ_KSI = 202.5
jacking stress, low-relaxation (5.9.2.2)
- civilpy.structural.box_beam_pipeline.TOPPING_FC_KSI = 4.5
PSBD cast-in-place topping strength
- civilpy.structural.box_beam_pipeline.barrier_width_ft(barrier: str | None) float[source]
Base width of one railing run, feet, from the ODOT catalog (
BR-1 (36 in)is 18 in,SBR-1 (42 in)21 in, …).
- civilpy.structural.box_beam_pipeline.box_beam_dead_loads(box: str, span_ft: float, n_beams: int, *, barrier: str | None = 'BR-1 (36 in)', n_barriers: int = 2, barrier_klf: float | None = None, fws_ksf: float | None = None, asphalt_in: float | None = None, deck_in: float | None = None, composite: bool | None = None) BoxBeamDeadLoads[source]
The BDM’s dead loads for one interior beam, in klf.
The wearing surface is not a free choice – BDM 309.1 ties it to whether the beam is composite:
composite (a
CBdesign): a 6 in CIP deck, of which the top 1 in is the monolithic wearing surface that 309.1.A excludes from the composite section but not from the load. No asphalt.non-composite (a
Bdesign): no deck; a 3 in minimum asphalt concrete wearing surface (309.1.B), 8 in maximum (308.2.3.3), at the BDM 909.A unit weight of 145 pcf – not LRFD’s 140.
Both get the BDM 303.1.2 future wearing surface of 0.060 ksf on top, which the manual states unconditionally; pass
fws_ksf=0.0for the two cases that exempt it (temporary structures, BDM 501, and the dead load used for shop camber, BDM 308.2.2.1.f).The parapets come from the ODOT railing catalog:
barriernames a standard ("BR-1 (36 in)"= 0.441 klf per run,"SBR-1 (42 in)"= 0.613, …) andn_barriershow many runs the deck carries, so they are in the dead load by default rather than something a caller has to remember.barrier_klfoverrides with an explicit bridge total;barrier=Nonewith no override means no parapet at all. Either way the total is shared equally across the beams – the standard’s own assumption for adjacent units, and what BDM 308.2.2.1.f endorses for camber.Deck and wearing-surface loads are computed on each beam’s own 4 ft width, so a deck overhanging the fascia beams (up to 8 in per side, BDM 308.2.3.3.c) is not included – add it to
barrier_klf.
- civilpy.structural.box_beam_pipeline.box_beam_line_checks(box: str, span_ft: float, n_beams: int, *, strand_area_in2: float = 0.153, fci_ksi: float = 4.0, fc_ksi: float = 5.5, barrier_klf: float = 0.0, fws_klf: float = 0.0, humidity_pct: float = 70.0) BoxBeamLineChecks[source]
Re-derive the governing LRFD checks for one interior beam of a PSBDD-1-25 standard design.
barrier_klf/fws_klfare bridge-total railing and future wearing surface, shared equally across the beams (the standard’s own assumption for adjacent units).fci_ksi/fc_ksidefault to the low end of the sheet’s designer-selected ranges (conservative for the stress checks).
- civilpy.structural.box_beam_pipeline.box_torsion_constant_in4(depth_in: float, width_in: float = 48.0, web_in: float = 6.0, flange_in: float = 5.5) float[source]
St. Venant torsion constant of the closed box (thin-wall, C4.6.2.2.1-3):
J = 4 A0^2 / sum(s/t)with the shear-flow path on the wall midlines, webs and flanges at their own thicknesses. Bredt is conservative here – these walls are thick relative to the cell, and an exact St. Venant solve runs ~10-15% higher.
- civilpy.structural.box_beam_pipeline.design_lane_offsets_ft(deck_width_ft: float, *, barrier_width_ft: float = 1.75, lane_width_ft: float = 12.0, align: str = 'center') list[float][source]
Transverse centreline of each design lane, from the deck edge.
LRFD 3.6.1.1.1 fixes the count –
INT(clear roadway / 12 ft), so three lanes on the 40.5 ft clear roadway of an 11-beam bridge – but deliberately leaves the position open: 3.6.1.1.1 has the lanes placed wherever produces the extreme force effect, and on a 40.5 ft roadway three 12 ft lanes leave 4.5 ft of slack to slide them in.alignpicks the placement:"center"(default)lanes packed adjacent and centred, the usual starting point and the worst case for an interior beam;
"left"/"right"packed against one barrier – the case that governs the exterior beam, and the one a centred layout will never find.
Warning
Do not try to cover both by handing MIDAS extra candidate lane positions and letting the combination search choose. It treats lanes as independent and will happily load two that overlap, which is worse than the real governing case rather than equal to it. Run the placements as separate analyses and envelope them yourself.
- civilpy.structural.box_beam_pipeline.layout_deck(n_lanes: int, *, lane_width_ft: float = 12.0, shoulder_ft: float | tuple[float, float] = 8.0, barrier: str | None = 'BR-1 (36 in)', beam_width_ft: float = 4.0, skew_deg: float = 0.0, radius_ft: float | None = None) DeckLayout[source]
Size an adjacent box-beam deck from the traffic it carries.
n_lanesdesign lanes atlane_width_ft(LRFD 3.6.1.1.1 uses 12 ft), plusshoulder_fteach side, plus a railing run each side at its cataloged base width, rounded up to a whole number of boxes – you cannot buy two thirds of a beam.shoulder_ftis the designer’s, not the code’s: the BDM does not set shoulder widths, the L&D Manual does, by route class. The 8 ft default is ODOT’s usual mainline value and should be checked, not assumed.On a skewed bridge the beams stay square to the abutments and the deck simply runs longer, so the width is unaffected. A curved alignment is different: BDM 302.1 allows box beams only where the mid-ordinate is 6 in or less, and the chorded beams need that mid-ordinate added to the width, so
radius_ftwidens the deck byL^2 / 8Rand refuses the layout outright past the 6 in limit.
- civilpy.structural.box_beam_pipeline.structural_model_from_box(box: str, span_ft: float, n_beams: int | None = None, *, n_lanes: int | None = None, lane_width_ft: float = 12.0, shoulder_ft: float | tuple[float, float] = 8.0, ties: bool = True, dead_loads: bool = True, shear_keys: bool = True, deck: bool | None = None, lanes: bool = True, lane_offsets_ft: list[float] | None = None, barrier: str | None = 'BR-1 (36 in)', n_barriers: int = 2, barrier_klf: float | None = None, fws_klf: float | None = None, fws_ksf: float | None = None, asphalt_in: float | None = None, deck_in: float | None = None, fc_ksi: float = 5.5, skew_deg: float = 0.0, mesh_ft: float = 5.0)[source]
Build the
StructuralModelhub for an adjacent box-beam bridge — the MIDAS spoke: one line of beam elements per box, transverse ties at the diaphragm stations, the grouted shear key between each pair of boxes, the composite deck, and the BDM dead loads.Vertical datum: z = 0 is the top of the beams. Every section rides the default center-center offset, so each node line sits at its own section’s centroid — girders at
Yb - D, shear keys at their own centroid, the deck at its mid-surface — and the assembly lines up in a rendered view without any section offsets to keep in sync.- Geometry
mesh_ftsets the target longitudinal segment length. The diaphragm stations are always nodes; this subdivides between them, which both resolves the moving-load influence lines and gives the deck plates a sane aspect ratio. Passmesh_ft=0for the diaphragm stations alone.- Shear keys (
shear_keys) A longitudinal beam element line at each joint carrying the actual grout cross-section from
shear_key_shape()— about 48 in^2 on a 27 in box, 2 1/2 in wide, so it reads clearly in a non-hidden view instead of hiding inside the beams. It is tied to the boxes each side by the transverse ties, which are split at the key rather than run past it: that is the physical load path (tie rod through the diaphragm, grout key in vertical shear) and it needs no element type beyondBEAM. The key’s own longitudinal stiffness is ~0.25% of one box’s, so it does not meaningfully stiffen the span — it is there to be seen and to carry shear.- Deck (
deck, defaults to on for a composite design) The 5 in structural thickness of the BDM 308.2.3.3.c deck as plate elements at their mid-surface, rigid-linked to every girder node in all 6 DOF — full composite action, and the deck’s own concrete modulus rather than a transformed width. The 1 in monolithic wearing surface is deliberately not in the plates: BDM 309.1.A excludes it from the composite section. It is in the loads.
Warning
The deck plates carry stiffness only. Their weight is in the
DC1beam loads, where it belongs — the deck is wet when it is placed, so the bare beam carries it. Do not add a self-weight load case on top of this or the deck is counted twice. This is a single-stage idealization: for the transfer and service stress checks, which need the load on the section that was there at the time, usebox_beam_line_checks().- Dead loads (
dead_loads) From
box_beam_dead_loads()— see there for the BDM articles.fws_klfis accepted for backward compatibility as a bridge total in klf; preferfws_ksf, which defaults to the BDM 303.1.2 value of 0.060 ksf and needs no deck-width bookkeeping.
- civilpy.structural.box_beam_pipeline.worst_lane_placement(n_lanes: int, n_beams: int, *, lane_width_ft: float = 12.0, beam_width_ft: float = 4.0, barrier_width: float = 0.0, step_ft: float = 0.25) tuple[tuple[float, ...], int, float][source]
Where to put the lanes so the analysis finds the governing case.
LRFD 3.6.1.1.1 fixes how many design lanes there are but deliberately leaves where they go – they are placed wherever produces the extreme force effect, and a roadway wider than
n_lanes x 12 ftleaves slack to slide them in.This sweeps the lane group across that slack and scores each position with the rigid cross-section reaction of LRFD C4.6.2.2.2d –
R = N_L/N_b + x_ext * sum(e) / sum(x^2)– times the multiple presence factor of Table 3.6.1.1.2-1, over every number of loaded lanes. It returns the placement, the governing lane count, and the lanes the fascia beam carries there.The answer always comes out packed against one barrier, which is worth knowing because it means one analysis covers everything: with the lanes packed, the subset MIDAS loads for
N_L = 1is already the worst single-lane position, the subset forN_L = 2the worst pair, and so on. A centred layout – the obvious default, and what this used to do – cannot reach any of them, and understates the fascia beam.Do not instead hand MIDAS extra candidate positions and let its lane-combination search choose: it treats lanes as independent and will load two that overlap, which is not conservative but fictitious.
civilpy.structural.bridge_layout module
Parametric steel-girder bridge layout for the Rhino/Grasshopper front end.
Pure geometry/engineering layout — no Rhino imports — so the whole bridge
description is computable and testable outside Rhino. A Grasshopper
component (see Notebooks/Rhino Components) feeds user inputs into
layout_bridge() and turns the returned primitives into Rhino
geometry; every primitive carries the gdr.* user-text tags the
civilpy.structural.rhino_gdr reader (and therefore the MIDAS
pipeline) consumes, so a generated model is immediately analyzable.
Engineering content comes from the sibling modules and is not duplicated
here: deck thickness/mats from the ODOT BDM standard designs
(civilpy.structural.odot.deck_design), haunches per BDM 309.3.5,
section dimensions from civilpy.structural.steel.W(), barriers from
the civilpy.structural.odot.bridge_railing SCD catalog.
Conventions (matching the gdr contract): plan frame with X = stations
along the layout centerline, Y = transverse (girder 1 at the lowest Y),
Z = up. Plan coordinates and lengths in feet; section dimensions in
inches (names carry _in suffixes). Z = 0 is the TOP OF DECK; the
structure hangs below it. Skew is the angle (degrees) between a support
line and the perpendicular to the centerline — positive skew rotates
support lines counterclockwise in plan, so points at +Y shift toward +X.
Supports are parallel (constant skew) and deck plan edges stop at the end
support lines. The deck is crowned: the top surface peaks at the
roadway crown (crown_offset_ft, default mid-width) and falls at
cross_slope_pct to each side, and everything hung from it — soffit,
haunches, girder seats, bearing seats, and both rebar mats — follows that
surface. Z = 0 is the top of deck at the crown.
- class civilpy.structural.bridge_layout.BarrierRun(designation: 'str', edge: "Literal['left', 'right']", line: 'tuple[Point, Point]', height_in: 'float | None', base_width_in: 'float | None', weight_plf: 'float | None')[source]
Bases:
object- designation: str
ODOT railing designation (see
railing_by_scd()).
- class civilpy.structural.bridge_layout.BearingPoint(line_no: 'int', station_index: 'int', location: 'Point', fixity: "Literal['fixed', 'expansion']", tags: 'dict[str, str]')[source]
Bases:
object- fixity: Literal['fixed', 'expansion']
"fixed"or"expansion"(seedefault_fixity()).
- class civilpy.structural.bridge_layout.BridgeInput(spans_ft: tuple[float, ...], girder_count: int, girder_spacing_ft: float, girder_label: str, overhang_ft: float, railing: str = 'SBR-1-20', grade: str = 'Grade 50', skew_deg: float = 0.0, design_haunch_in: float = 2.0, deck_thickness_in: float | None = None, deck_fc_ksi: float = 4.5, cross_slope_pct: float = 2.0, crown_offset_ft: float | None = None, composite: bool = True)[source]
Bases:
objectEverything a Grasshopper slider panel specifies.
deck_thickness_inofNonemeans “use the ODOT standard design” (BDM Figure 309-3, which also fixes the rebar mats); giving a value switches to a custom deck and skips the standard-design table (the BDM 309.3.1 minimum is still enforced).railingis an SCD designation from thebridge_railingcatalog (e.g."SBR-1-20").- composite: bool = True
Whether the deck acts compositely with the girders (shear studs / rebar extending into the deck). Drives the deck-to-girder connection in the refined-grillage analysis model (
grillage_model_from_layout()): a composite deck is rigidly tied to the girders (full plane-section action); a non-composite deck bears on the girders vertically but is free to slip longitudinally. Both are used in practice, so the design engineer sets it – it materially changes the girder demands for steel and prestressed concrete superstructures.
- cross_slope_pct: float = 2.0
Deck cross slope, percent. The roadway crowns at
crown_offset_ftand falls this grade to each side, so the girder seat elevations vary across the width (they follow the crown/cross-slope, at full precision – not rounded).0.0is a flat deck.
- crown_offset_ft: float | None = None
Transverse offset (ft, girder 1 at 0) of the roadway crown / high point.
Nonecenters it on the girder group.
- girder_label: str
AISC W-shape label as cataloged in
civilpy.structural.steel, e.g."W36X150".
- grade: str = 'Grade 50'
"Grade 36","Grade 50","Grade 50W", or"Grade HPS70W".- Type:
Structural steel grade label (AASHTO M270)
- railing: str = 'SBR-1-20'
ODOT SCD railing designation, resolved through
railing_by_scd()(e.g."SBR-1-20","BR-1-13").
- class civilpy.structural.bridge_layout.BridgeLayout(inputs: ~civilpy.structural.bridge_layout.BridgeInput, section: ~civilpy.structural.bridge_layout.GirderSection, effective_span_ft: float, girders: tuple[~civilpy.structural.bridge_layout.GirderLine, ...], bearings: tuple[~civilpy.structural.bridge_layout.BearingPoint, ...], haunches: tuple[~civilpy.structural.bridge_layout.HaunchRun, ...], deck: ~civilpy.structural.bridge_layout.DeckSlab, barriers: tuple[~civilpy.structural.bridge_layout.BarrierRun, ...], standard_design: ~civilpy.structural.odot.deck_design.StandardDeckDesign | None, doc_tags: dict[str, str] = <factory>)[source]
Bases:
objectThe full generated bridge: geometry primitives plus the document tags (
doc_tags) the.3dmmust carry for the Python reader.- barriers: tuple[BarrierRun, ...]
- bearings: tuple[BearingPoint, ...]
- deck_profile_yz() tuple[tuple[float, float], ...][source]
Closed deck cross-section as
(y, z)pairs — the crowned top, the parallel soffit, and the thickened overhangs per BDM Figure 309-4: the overhang soffit runs atoverhang_thickness_inbelow the top (parallel to it) from the deck edge to the outboard top-flange tip of the fascia girder, where it steps up to the uniformthickness_insoffit. With the standardt + 2in overhang and a 2 in design haunch that step lands exactly at the flange-top / haunch-bottom plane, reproducing the figure. Order: along the top fromy_lotoy_hi(crown included when interior), then back along the soffit. A backend maps(y, z)into 3D at each bridge end (x = station + y*tan(skew)) and lofts the two profiles into the deck solid.
- deck_soffit_z(y: float) float[source]
Deck-soffit elevation (ft) at
y, parallel to the top surface (uniform slab; the overhang thickening past the fascia girders is additional and belongs todeck_profile_yz()).
- deck_top_z(y: float) float[source]
Top-of-deck elevation (ft) at transverse offset
y: 0 at the crown, falling atcross_slope_pctto each side.
- girders: tuple[GirderLine, ...]
- inputs: BridgeInput
- section: GirderSection
- standard_design: StandardDeckDesign | None
- class civilpy.structural.bridge_layout.DeckSlab(outline: tuple[tuple[float, float, float], tuple[float, float, float], tuple[float, float, float], tuple[float, float, float]], thickness_in: float, overhang_thickness_in: float, structural_thickness_in: float, rebar: tuple[RebarSet, ...])[source]
Bases:
objectPlan outline (counterclockwise, each corner at the local deck-top elevation of the crowned surface), thicknesses, and the mats.
outlinecorners run girder-1 start edge -> girder-N start edge -> girder-N end edge, honoring skew.
- class civilpy.structural.bridge_layout.GirderLine(line_no: 'int', start: 'Point', end: 'Point', tags: 'dict[str, str]')[source]
Bases:
object
- class civilpy.structural.bridge_layout.GirderSection(label: str, depth: float, flange_width: float, flange_thickness: float, web_thickness: float, fillet_k: float)[source]
Bases:
objectW-shape display dimensions (in), pulled from the AISC database.
- class civilpy.structural.bridge_layout.HaunchRun(line_no: int, start: tuple[float, float, float], end: tuple[float, float, float], width_in: float, depth_in: float)[source]
Bases:
objectOne haunch prism: the girder line at deck-soffit level plus the section (width x depth, in) to extrude along it.
The square cross-section is deliberate: BDM 309.3.5 (2020 Ed.) says “Detail the sides of the haunch as vertical and aligned with the edges of the top flange” — no sloped forming at any depth — with a 2 in minimum design haunch (see Figures 309-7/309-8).
- civilpy.structural.bridge_layout.NOMINAL_PARAPET_PLF_PER_FT = 130.0
Preliminary concrete-parapet weight per foot of barrier height, lb/ft/ft, used only when the SCD catalog has no tabulated
weight_per_ft. Calibrated so a 42 in single-slope (ODOT SBR-1) comes out ~455 plf; replace with the real barrier weight for final design.
- class civilpy.structural.bridge_layout.RebarSegment(points: tuple[tuple[float, float, float], ...], rebar_set: RebarSet)[source]
Bases:
objectOne physical bar: a polyline at the set’s depth below the local deck surface, so bars follow the crown/cross-slope.
pointshas two vertices for a straight bar and three when a transverse bar crosses the crown (the crank at the high point);start/endare the first and last vertices.
- class civilpy.structural.bridge_layout.RebarSet(name: str, direction: str, mat: str, size: int, spacing_in: float, depth_in: float, angle_deg: float = 0.0, extent: str = 'deck', overhang_cutoff_in: float | None = None)[source]
Bases:
objectOne family of deck bars for the generator to instantiate.
directionis"transverse"or"longitudinal";matis"top"/"bottom".depth_inis the bar centerline depth below the top of deck.angle_degrotates transverse bars in plan (0 = perpendicular to the centerline; the BDM 309.3.4.2 skew rule is applied by the layout).extentis"deck"for full-width/length mats or"overhang"for the additional overhang bars, which run from each deck edge tooverhang_cutoff_inbeyond the fascia girder centerline.
- civilpy.structural.bridge_layout.deck_rebar_segments(layout: BridgeLayout, side_cover_in: float = 2.0) list[RebarSegment][source]
Instantiate every deck bar (feet), clipped to the deck plan inset by
side_cover_inon the edges. The Grasshopper generator draws these directly; nothing here needs Rhino.Bars sit
depth_inbelow the local deck surface, so both mats follow the crown/cross-slope and stay inside the slab (a level bar would exit the soffit at the crown and the top surface at the edges). A transverse bar whose run crosses the crown is emitted as one 3-vertex polyline cranked at the high point.The deck plan is the skewed parallelogram
y in [y_lo, y_hi],u = x - y*tan(skew) in [0, L].
- civilpy.structural.bridge_layout.default_fixity(station_index: int, station_count: int) Literal['fixed', 'expansion'][source]
Bearing-fixity starting point, mirroring the C# plugin’s rule: a single span is fixed at its first bearing; a continuous unit at the interior support nearest mid-length; everything else expansion.
- civilpy.structural.bridge_layout.effective_span_ft(spacing_ft: float, section: GirderSection) float[source]
Effective deck span (ft) per LRFD 9.7.2.3 for slabs on steel girders: clear distance between flange tips plus the flange overhang (tip to web face) — i.e.
S - bf/2 - tw/2.
- civilpy.structural.bridge_layout.girder_line_loads(layout: BridgeLayout, girder_index: int) dict[source]
Uniform dead loads (klf, positive = downward) on one girder line.
girder_indexis 0-based (0 = first fascia). Returns{"dc1", "dc2", "dw"}: DC1 = girder self-weight + wet deck slab on the girder’s tributary width; DC2 = the barrier weight, on fascia girders only; DW = future wearing surface on the tributary width. These are the per-line loads the hub applies and the native line-girder envelope (girder_pipeline) consumes – preliminary magnitudes (no haunch/cross-slope), same basis asstructural_model_from_layout().
- civilpy.structural.bridge_layout.girder_section(label: str) GirderSection[source]
Resolve an AISC W label to display dimensions via
steel.W.
- civilpy.structural.bridge_layout.grillage_model_from_layout(layout: BridgeLayout, *, composite: bool | None = None, seg_target_ft: float | None = None, dead_loads: bool = True, girder_subset: list[int] | None = None)[source]
Build the refined-grillage hub: bare-steel girder beams plus a physical deck of plate elements, tied together with rigid links.
This is the “deck as plate, girder as frame” model MIDAS recommends when the full deck surface is needed (moving-load traffic lanes) – unlike
structural_model_from_layout(), which is a line-girder grid with no deck. The deck’s longitudinal stiffness lives entirely in the plate elements and the girders stay bare steel, so the deck stiffness is never double-counted (the trap of pairing a composite girder section with deck plates).The
compositetoggle (defaulting tolayout.inputs.composite) sets the deck-to-girder connection, mirroring whether the real bridge has shear studs / rebar extending into the deck:composite – a fully rigid link (all 6 DOF) at every deck node over a girder, so slab and girder share plane sections (transformed-section action emerges from the geometry).
non-composite – the deck bears on the girders vertically and is located transversely/rotationally, but longitudinal slip (DX) is free, so the slab adds no composite flexural stiffness to the girder; a single longitudinal anchor at the fixed-bearing line removes rigid-body drift.
seg_target_ftis the target longitudinal plate length (default: the girder spacing, giving roughly square plates); each span is divided into a whole number of segments nearest that target, always keeping a node on every support line. Dead loads (DC1/DC2/DW) are applied to the girders as instructural_model_from_layout(); the plates carry stiffness only (self-weight is not separately activated, so the slab weight is not double-counted).girder_subset(0-based, contiguous) builds only those girder lines and the deck they carry – a construction phase. The phase deck runs from the outer overhang (where a fascia girder is included) to the mid-bay closure joint where the phase is cut from the girders not yet built, so the phase’s inner girder carries a deck cantilever to that joint. This is the stage-1 (or stage-2) structure a closure-pour analysis needs – seecivilpy.structural.construction_staging.Returns the
StructuralModel(units kips/ft) withrigid_linkspopulated.
- civilpy.structural.bridge_layout.layout_bridge(inp: BridgeInput) BridgeLayout[source]
Generate the full bridge layout from the Grasshopper-level inputs.
Raises
ValueErrorwhen the inputs violate the ODOT standard-design assumptions (unless a customdeck_thickness_inis given) or are geometrically impossible.
- civilpy.structural.bridge_layout.railing_by_scd(scd: str) BridgeRailing[source]
First cataloged railing on the given SCD (e.g.
"SBR-1-20").
- civilpy.structural.bridge_layout.structural_model_from_layout(layout: BridgeLayout, *, diaphragms: bool = True, dead_loads: bool = True)[source]
Build the canonical
StructuralModelhub straight from aBridgeLayout– no Rhino.3dmround-trip.This is the faithful full-bridge analysis model the MIDAS spoke consumes (via
civilpy.structural.midas_models.midas_payloads()): every girder line is a continuous chain of beam elements broken at every support station, each carrying its resolved AISC section and steel grade; each bearing becomes a 6-DOF restraint (fixed or expansion) on its girder node; and, whendiaphragmsis set, a transverse beam ties adjacent girders at every support line. Contrast the equivalent-strip slab model – here the whole grid goes to MIDAS, so it can host traffic lanes for a moving-load run.With
dead_loadsthe three dead-load cases are applied as downward (negativeGZ) uniform beam loads on the girder elements:DC1(non-composite): girder self-weight + the wet deck slab on each girder’s tributary width,DC2(composite SDL): each barrier’s weight on its fascia girder,DW: future wearing surface (FWS_KSF) on the tributary width.
Haunch weight, cross-slope, and the exact wearing-surface extent (roadway only) are preliminary-level approximations; refine per BDM for final design.
Returns the
StructuralModel(units kips/ft).
civilpy.structural.bridge_type module
Preliminary bridge-type feasibility and selection advisor.
The first gate of the conceptual-design loop: given a span arrangement, say
which superstructure types are reasonable and steer the engineer away from
ones that are not. A 300 ft single span asked of a reinforced concrete slab
has to be redirected to a continuous steel plate girder (or a comparable
long-span type) before any detailing or analysis happens – that redirect is
the job of assess().
This is planning-level guidance, not a code check. The span ranges are practical/economical envelopes drawn from ODOT BDM Section 302 (structure type studies) and common AASHTO practice, not hard limits: a type near the edge of its range is flagged “marginal,” and the controlling span (the longest, and for simple spans the only span) is what a type is judged against. The engineer still runs the real design; civilpy only keeps the conceptual phase honest.
Pure Python (no numpy/heavy deps) so it runs anywhere the rest of the station/offset stack does, including Rhino’s bundled CPython.
Conventions: spans in feet, one entry per span in spans_ft (a single-span
bridge is [L]). A type is “continuous” if it carries negative moment over
interior supports; single-span or simple-span-made-continuous-for-live-load
types are marked accordingly.
- class civilpy.structural.bridge_type.BridgeType(key: str, name: str, material: str, min_span_ft: float, max_span_ft: float, simple_max_span_ft: float | None = None, needs_continuity: bool = False, note: str = '')[source]
Bases:
objectOne superstructure family and the span envelope it is economical in.
min_span_ft/max_span_ftbound a single span of this type;max_span_ftis the practical/economical ceiling, above which the type is infeasible (not merely uneconomical).needs_continuitymarks types that only reach their upper range as a continuous unit (so a single-span request is capped lower, atsimple_max_span_ft).
- civilpy.structural.bridge_type.CATALOG: tuple[BridgeType, ...] = (BridgeType(key='rc_slab', name='Reinforced concrete slab (single span)', material='concrete', min_span_ft=11.0, max_span_ft=40.0, simple_max_span_ft=40.0, needs_continuity=False, note='ODOT SB-1-24 covers 11-38 ft; reinforced slab gets uneconomical and heavy past ~40 ft.'), BridgeType(key='rc_slab_cont', name='Continuous reinforced concrete slab', material='concrete', min_span_ft=20.0, max_span_ft=60.0, simple_max_span_ft=40.0, needs_continuity=True, note='ODOT CS-1-24; negative moment over piers extends the slab to ~60 ft interior spans.'), BridgeType(key='adjacent_box', name='Prestressed adjacent box beams', material='prestressed', min_span_ft=20.0, max_span_ft=100.0, simple_max_span_ft=None, needs_continuity=False, note='Shallow, fast to erect; adjacent-box distribution + transverse post-tensioning.'), BridgeType(key='ps_i_girder', name='Prestressed I-girder, composite deck', material='prestressed', min_span_ft=40.0, max_span_ft=160.0, simple_max_span_ft=None, needs_continuity=False, note='AASHTO/ODOT I-beams; made continuous for live load over piers.'), BridgeType(key='steel_rolled', name='Steel rolled-beam, composite deck', material='steel', min_span_ft=40.0, max_span_ft=120.0, simple_max_span_ft=None, needs_continuity=False, note='Rolled W-shapes; economical single or continuous spans.'), BridgeType(key='steel_plate_girder', name='Continuous steel plate girder, composite deck', material='steel', min_span_ft=90.0, max_span_ft=400.0, simple_max_span_ft=180.0, needs_continuity=True, note='Welded plate girders with field splices and flange transitions; the workhorse long-span type.'))
Common ODOT superstructure types, ordered short-span to long-span. Ranges are conceptual-design envelopes (ODOT BDM 302 / AASHTO practice), deliberately generous at the top; see the module docstring.
- class civilpy.structural.bridge_type.Feasibility(type: BridgeType, verdict: str, controlling_span_ft: float, continuous: bool, reasons: tuple[str, ...] = ())[source]
Bases:
objectHow a single type scores against a span arrangement.
- type: BridgeType
- class civilpy.structural.bridge_type.Recommendation(type: BridgeType, spans_ft: tuple[float, ...], verdict: str, resplit: bool, reason: str)[source]
Bases:
objectA redirect target: a type and the span arrangement it wants.
spans_ftmay differ from what the engineer asked for – the whole point of the long-span redirect is to turn one over-long span into a continuous unit of several feasible spans.resplitis True when that happened.- type: BridgeType
- class civilpy.structural.bridge_type.TypeAssessment(requested: BridgeType, spans_ft: tuple[float, ...], result: Feasibility, recommended: tuple[Recommendation, ...] = ())[source]
Bases:
objectThe advisor’s answer for a requested type against a span arrangement.
allowedis True when the requested type is feasible (possibly marginal). When it is not,recommendedholds the redirect targets (type + span arrangement), best first.- recommended: tuple[Recommendation, ...] = ()
- requested: BridgeType
- result: Feasibility
- civilpy.structural.bridge_type.assess(requested_key: str, spans_ft, *, n_recommend: int = 3) TypeAssessment[source]
Assess a requested bridge type against a span arrangement and, when it is infeasible, recommend the best feasible alternatives – re-splitting an over-long single span into a continuous multi-span unit as needed.
- Parameters:
- Returns:
.allowed/.verdict/.summary()and, on a redirect,.recommended(Recommendationtargets, ranked best-first).- Return type:
Examples
>>> a = assess("rc_slab", [300.0]) >>> a.allowed False >>> [r.type.material for r in a.recommended][:1] ['steel'] >>> a.recommended[0].resplit # 300 ft becomes a continuous unit True >>> assess("rc_slab", [30.0]).allowed True
- civilpy.structural.bridge_type.evaluate_type(bt: BridgeType, spans_ft, *, marginal_band: float = 0.1) Feasibility[source]
Score one type against
spans_ft.A single-span arrangement (
len(spans_ft) == 1) is judged against the type’s simple ceiling; a multi-span arrangement lets continuous types use their full range. Withinmarginal_band(fraction) of either end of the range the verdict is “marginal” rather than “ok”.
- civilpy.structural.bridge_type.feasible_types(spans_ft, *, include_marginal: bool = True) list[Feasibility][source]
Every catalog type that is not infeasible for
spans_ft, ordered by how centered the controlling span sits in each type’s range (best first).
- civilpy.structural.bridge_type.get_type(key: str) BridgeType[source]
Look up a catalog type by key;
KeyErrorwith the valid keys if bad.
- civilpy.structural.bridge_type.recommend_for_length(total_ft: float, *, exclude: str | None = None, n: int = 3) list[Recommendation][source]
Best types for crossing
total_ft, re-splitting into continuous units where a single span will not do. Ranked fewest-spans then most-centered,okbeforemarginal.
civilpy.structural.cande module
Box-culvert soil-structure interaction through CANDE.
CANDE (Culvert ANalysis and DEsign, FHWA) solves the buried-structure
problem the AASHTO simplified methods approximate: incremental embankment
construction, nonlinear soil, and arching around the structure. This
module builds a CANDE Level-2 box-culvert model (the engine’s canned
box mesh), runs it through the cande-wrapper package, and returns member
force envelopes in the kip/ft units the civilpy.structural.aashto
checks expect — so CANDE supplies the demands and civilpy the capacities.
Install the engine with pip install civilpy[cande] (or
pip install cande-wrapper). Building the input file needs nothing
beyond civilpy:
>>> from civilpy.structural.cande import BoxCulvertModel, SoilMaterial
>>> model = BoxCulvertModel(span_ft=8, rise_ft=6, cover_ft=4,
... top_thickness_in=10, wall_thickness_in=8)
>>> print(model.to_cid().splitlines()[0].strip())
A-1!!ANALYS 2 0 1Box culvert 8 ft x 6 ft, 4 ft cover
The model is a half-mesh of the culvert (symmetric about midspan) with
14 beam elements on the slab/wall centerlines, three soil zones (in situ,
bedding, fill), and the fill placed in incremental lifts. span_ft
and rise_ft are centerline dimensions. Beam elements carry no
self-weight — add the structure dead load to the demands separately.
Results are per foot of culvert length: moments in kip-in/ft, thrust and shear in kip/ft, enveloped over all construction increments.
- class civilpy.structural.cande.BoxCulvertModel(span_ft: float, rise_ft: float, cover_ft: float, *, top_thickness_in: float, wall_thickness_in: float, bottom_thickness_in: float | None = None, f_c_ksi: float = 4.0, e_concrete_psi: float | None = None, nu_concrete: float = 0.17, fill: SoilMaterial | None = None, in_situ: SoilMaterial | None = None, bedding: SoilMaterial | None = None, trench_width_ft: float | None = None, bedding_depth_in: float = 12.0, load_steps: int | None = None, title: str | None = None)[source]
Bases:
objectA single-cell box culvert under earth fill, analyzed with the CANDE Level-2 box mesh.
span_ft/rise_ftare centerline dimensions;cover_ftthe fill above the top slab. Wall/slab stiffnesses come from gross concrete sections of the given thicknesses (e_concrete_psidefaults to 57000*sqrt(f’c) with f’c = 4 ksi).trench_width_ftswitches the mesh from embankment to trench installation. Fill is placed in incremental lifts; when the cover exceeds 1.5 times the rise the mesh is truncated and the remainder applied as surface pressure (handled by the engine; an extra load step is added automatically).- run(work_dir=None, prefix: str = 'culvert') BoxCulvertResults[source]
Run the model through the CANDE engine and return the parsed results. Requires the
cande-wrapperpackage; outputs land inwork_dir(a temporary directory by default).
- class civilpy.structural.cande.BoxCulvertResults(model: BoxCulvertModel, work_dir, prefix: str)[source]
Bases:
objectMember force envelopes parsed from a CANDE box-culvert run.
membersmaps"top_slab"/"wall"/"bottom_slab"toMemberForcesenveloped over every construction increment; corner nodes contribute to both adjacent members.stepsholds the raw nodal rows per increment for custom post-processing.
- class civilpy.structural.cande.MemberForces(moment: float = 0.0, thrust: float = 0.0, shear: float = 0.0, shear_concurrent_moment: float = 0.0)[source]
Bases:
objectForce envelope for one culvert member, per foot of length:
momentkip-in/ft,thrustkip/ft (compression positive),shearkip/ft, and the moment concurrent with the peak shear (for the Vu*de/Mu term of the slab shear check).
- class civilpy.structural.cande.SoilMaterial(density_pcf: float, model: str, name: str, e_psi: float = 0.0, nu: float = 0.0)[source]
Bases:
objectOne CANDE soil zone: linear-elastic or a canned Duncan/Selig hyperbolic model. Use the constructors rather than building directly.
- classmethod duncan_selig(name: str, density_pcf: float) SoilMaterial[source]
A canned Duncan/Selig soil (e.g.
"SW90"for gravelly sand at 90% standard Proctor).
civilpy.structural.concrete module
ACI 318-19 Chapter 17 - Anchoring to Concrete
All lengths in inches, forces in pounds (lb), stresses in psi.
- class civilpy.structural.concrete.AnchorBolts(f_c: float, h_a: float, d_a: float, h_ef: float, f_ya: float, f_uta: float, n_x: int = 1, n_y: int = 1, s_x: float = 0.0, s_y: float = 0.0, c_a1: float = inf, c_a2: float = inf, c_a_min: float | None = None, A_se_N: float | None = None, A_se_V: float | None = None, A_brg: float | None = None, e_h: float | None = None, lambda_a: float = 1.0, is_cracked: bool = True, has_supp_reinf: bool = False, is_ductile: bool = True, grout_pad: bool = False, e_N_prime: float = 0.0, e_V_prime: float = 0.0, A_Nc: float | None = None, A_Vc: float | None = None, N_ua: float = 0.0, V_ua: float = 0.0, shear_direction: Literal['perpendicular', 'parallel', 'away'] = 'perpendicular', anchor_type: Literal['cast_in', 'post_installed', 'adhesive'] = 'cast_in', tau_uncr: float | None = None, tau_cr: float | None = None, sdc: str = 'A', anchor_reinf_tension: float | None = None, anchor_reinf_shear: float | None = None, bolt_circle_radius: float | None = None, shaft_radius: float | None = None, coupler_depth: float | None = None, c_ac: float | None = None)[source]
Bases:
objectACI 318-19 Chapter 17 anchor design checks for cast-in and post-installed anchors.
All inputs in US customary units (inches, pounds, psi).
- Parameters:
f_c (float) – Specified concrete compressive strength (psi). Capped at 10,000 psi for cast-in anchors per ACI 318 Section 17.3.1.
h_a (float) – Thickness of concrete member measured parallel to anchor axis (in).
d_a (float) – Nominal anchor diameter (in).
h_ef (float) – Effective embedment depth (in).
f_ya (float) – Anchor yield strength (psi). e.g. ASTM F1554 Gr 36 → 36,000.
f_uta (float) – Anchor tensile strength (psi). e.g. ASTM F1554 Gr 36 → 58,000. Used value is capped at min(1.9*f_ya, 125,000) per ACI 318-19 17.6.1.2.
n_x (int) – Number of anchors in the x-direction (direction of applied shear).
n_y (int) – Number of anchors in the y-direction.
s_x (float) – Anchor spacing in x-direction (in). Use 0 if n_x == 1.
s_y (float) – Anchor spacing in y-direction (in). Use 0 if n_y == 1.
c_a1 (float) – Edge distance from anchor to free edge in direction of applied shear (in). Use a large value (e.g. 100) if no near edge in shear direction.
c_a2 (float) – Edge distance from anchor to free edge perpendicular to shear (in). Use a large value if no near edge perpendicular to shear.
c_a_min (float, optional) – Minimum edge distance in any direction (in). Defaults to min(c_a1, c_a2).
A_se_N (float, optional) – Effective cross-sectional area of anchor in tension (in²). Computed from UNC thread formula if not provided.
A_se_V (float, optional) – Effective cross-sectional area in shear (in²). Defaults to A_se_N.
A_brg (float, optional) – Net bearing area of anchor head (in²). Required for headed-bolt pullout per 17.6.3.2.2a. If None, pullout check uses the hooked-bolt formula with e_h, or is skipped.
e_h (float, optional) – Hook length (in) for J- or L-bolts; from inside of hook to bearing surface. Required for hooked-bolt pullout per 17.6.3.2.2b.
lambda_a (float) – Lightweight concrete modification factor per ACI 318 Table 17.2.4.1. Use 1.0 for normalweight concrete (default).
is_cracked (bool) – True if concrete is assumed cracked at service load levels (conservative default). False for uncracked.
has_supp_reinf (bool) – True if supplementary reinforcement conforming to ACI 318 17.5.2.1 is provided (Condition A → higher φ for concrete limit states).
is_ductile (bool) – True if anchor material meets ductility requirements (elongation ≥ 14%, reduction ≥ 30%). ASTM F1554 Gr 36 and Gr 55 qualify.
grout_pad (bool) – True if anchor is used with a built-up grout pad. Reduces V_sa by 0.80 per ACI 318 Section 17.7.1.2.1.
e_N_prime (float) – Eccentricity of resultant tensile force from centroid of anchor group (in). Use 0 for concentric loading.
e_V_prime (float) – Eccentricity of shear force from centroid of anchor group (in). Use 0 for concentric shear.
A_Nc (float, optional) – Projected concrete failure area for tension (in²). Computed automatically if not provided.
A_Vc (float, optional) – Projected concrete failure area for shear (in²). Computed automatically if not provided.
N_ua (float) – Total factored tensile demand on the anchor group (lb).
V_ua (float) – Total factored shear demand on the anchor group (lb).
shear_direction (str) – Direction of applied shear relative to the nearest edge: “perpendicular” (default), “parallel” (ACI 17.7.2.1(c) — doubles V_cb, sets psi_ed=1.0), or “away” (shear directed away from edge — no edge breakout check).
anchor_type (str) – “cast_in” (default), “post_installed”, or “adhesive”. Affects k_c in basic breakout (24 vs 17) and enables bond check.
tau_uncr (float, optional) – Characteristic bond stress in uncracked concrete (psi), from ACI 355.4 evaluation report. Required for adhesive bond strength check.
tau_cr (float, optional) – Characteristic bond stress in cracked concrete (psi). Required for adhesive bond strength check when is_cracked=True.
sdc (str) – Seismic Design Category (“A”–“F”). For SDC C–F, concrete failure mode design strengths are multiplied by 0.75 per ACI 318-19 Section 17.10.5.3.
anchor_reinf_tension (float, optional) – Design strength (φ*Rn, lb) of properly developed anchor reinforcement in tension per ACI 17.5.2.1. When provided, replaces concrete breakout in the tension limit-state check.
anchor_reinf_shear (float, optional) – Design strength (φ*Rn, lb) of anchor reinforcement in shear per ACI 17.5.2.1. When provided, replaces concrete breakout in the shear limit-state check.
bolt_circle_radius (float, optional) – Radius of the bolt circle (in) for circular anchor patterns (e.g. highway poles, luminaires, sign structures). When provided, _A_Nc and _A_Vc use annular/circular geometry instead of the rectangular array formulas. Use from_circular() to construct typical highway pole base patterns.
shaft_radius (float, optional) – Outer radius of a drilled shaft (in). When provided together with bolt_circle_radius, automatically sets c_a1 = shaft_radius − bolt_circle_radius (edge distance from bolt to shaft perimeter).
coupler_depth (float, optional) – Depth (in) of a heavy-hex coupling nut used to extend a damaged anchor rod via field repour. Per AISC Design Guide 1 and ACI 318-19 Section 17.10.5.3, the coupler must be frictionally isolated from the new concrete (e.g., with polyethylene tape or PVC sleeving) to preserve the anchor’s stretch length and maintain ductile behavior. If the coupler is NOT isolated, it acts as the bearing surface, reducing effective embedment: h_ef_eff = h_ef − coupler_depth.
c_ac (float, optional) – Critical edge distance for post-installed anchors (in). Used in the splitting factor ψ_cp,N per ACI 318-19 Eq. 17.6.2.6.1b. Defaults to 2.5·h_ef per ACI Table 17.9.5 if not provided. Only used when anchor_type=’post_installed’ and is_cracked=False.
- static bolt_demands_from_pole(n_bolts: int, bolt_circle_radius: float, M_u: float, P_u: float = 0.0, V_u: float = 0.0, T_u: float = 0.0) tuple[source]
Max individual bolt demands from global AASHTO pole reactions.
Uses the elastic circular bolt group method (AASHTO LTS-1 / AISC Design Guide 1). All inputs in consistent units (lb and in).
- Parameters:
n_bolts (int) – Number of anchor rods on the bolt circle.
bolt_circle_radius (float) – Radius from pole centroid to bolt centerlines (in).
M_u (float) – Factored overturning moment at base (lb·in).
P_u (float) – Factored axial load; positive = uplift/tension (lb).
V_u (float) – Factored in-plane shear at base (lb).
T_u (float) – Factored torsional moment at base (lb·in).
- Returns:
(N_ua_max, V_ua_max) – Maximum tension demand (lb) and shear demand (lb) on a single bolt.
- Return type:
Notes
Tension from moment: N_M = 2·M_u / (n·r) [elastic, Σy² = n·r²/2] Axial share: N_P = P_u / n Shear from V_u: V_v = V_u / n Shear from torsion: V_t = T_u / (n·r) [all bolts at equal radius] Final: N_ua_max = max(0, N_M + N_P) V_ua_max = V_v + V_t (conservative, additive)
- bond_strength_tension() AnchorCheckResult | None[source]
Bond strength of adhesive anchors in tension — ACI 318-19 Section 17.6.5.
Requires anchor_type=”adhesive” and tau_uncr (or tau_cr when is_cracked). Returns None for non-adhesive anchors or missing bond stress data.
N_ba = tau * π * d_a * h_ef (Eq. 17.6.5.2.1) c_Na = 10 * d_a * sqrt(tau / 1100) (Eq. 17.6.5.1.5, influence distance)
- breakout_strength_shear() AnchorCheckResult | None[source]
Concrete breakout strength of anchor group in shear — ACI 318-19 17.7.2.
shear_direction="perpendicular": standard edge breakout (17.7.2.1a/b). Corner condition (both c_a1 and c_a2 finite): automatically computes both edges and uses the minimum per ACI 17.7.2.1(d).shear_direction="parallel": doubles V_cb with psi_ed=1.0 (17.7.2.1(c)).shear_direction="away": returns None (no edge breakout).Returns None when c_a1 is infinite (no near edge in shear direction).
- breakout_strength_tension() AnchorCheckResult[source]
Concrete breakout strength of anchor group in tension.
Single anchor: Eq. 17.6.2.1a Anchor group: Eq. 17.6.2.1b (includes psi_ec_N)
- check_all() dict[source]
Run all applicable limit-state checks.
Returns a dict mapping check label to AnchorCheckResult. Interaction check is included only when both N_ua and V_ua are nonzero. Stretch length check is included for SDC C–F.
- classmethod from_circular(n_bolts: int, bolt_circle_radius: float, f_c: float, h_a: float, d_a: float, h_ef: float, f_ya: float, f_uta: float, M_u: float | None = None, P_u: float = 0.0, V_u: float | None = None, T_u: float = 0.0, **kwargs) AnchorBolts[source]
Constructor for circular bolt patterns — highway poles, luminaires, signs.
- Parameters:
n_bolts (int) – Total number of anchor rods on the bolt circle.
bolt_circle_radius (float) – Radius of the bolt circle (in), measured to bolt centerlines.
M_u (float, optional) – Factored overturning moment at pole base (lb·in). When provided, bolt demands are computed automatically via bolt_demands_from_pole().
P_u (float, optional) – Factored axial load; positive = uplift/tension (lb). Used with M_u.
V_u (float, optional) – Factored base shear at pole base (lb). When provided, shear demands are computed automatically via bolt_demands_from_pole().
T_u (float, optional) – Factored torsional moment at pole base (lb·in). Used with V_u.
**kwargs – Any additional AnchorBolts parameters (shaft_radius, sdc, N_ua, …). Explicit N_ua/V_ua in kwargs take precedence over computed values.
Notes
n is stored as n_y=n_bolts, n_x=1. Projected areas _A_Nc and _A_Vc use annular/circular geometry automatically when bolt_circle_radius is set. Supply shaft_radius to auto-derive c_a1 for drilled shaft applications.
- classmethod from_dxf(path: str, f_c: float, h_a: float, h_ef: float, f_ya: float, f_uta: float, bolts_layer: str = 'BOLTS', concrete_layer: str = 'CONCRETE', **kwargs) AnchorBolts[source]
Build an AnchorBolts instance by parsing geometry from a DXF file.
Reads two layers from the DXF modelspace:
bolts_layer(default"BOLTS") — CIRCLE entities whose centres define bolt locations and whose radius gives anchor_radius = d_a / 2.concrete_layer(default"CONCRETE") — one LWPOLYLINE (or POLYLINE) whose vertices define the concrete boundary. Edge distances c_a1 (shear direction) and c_a2 (perpendicular) are derived from the minimum distances from the bolt-group centroid to the boundary edges, classified by edge orientation.
Bolt pattern detection:
Circular — all bolts lie within 10 % of the same radius from the centroid. Delegates to
from_circular().Rectangular — grid spacing is inferred from the sorted unique x/y coordinates of the bolt positions.
- Parameters:
path (str) – Absolute or relative path to the
.dxffile.f_c (float) – Material and geometry properties not derivable from CAD.
h_a (float) – Material and geometry properties not derivable from CAD.
h_ef (float) – Material and geometry properties not derivable from CAD.
f_ya (float) – Material and geometry properties not derivable from CAD.
f_uta (float) – Material and geometry properties not derivable from CAD.
bolts_layer (str) – Layer names to query (case-sensitive, DXF convention is UPPERCASE).
concrete_layer (str) – Layer names to query (case-sensitive, DXF convention is UPPERCASE).
**kwargs – Additional
AnchorBoltskeyword arguments (N_ua, V_ua, sdc, …).
- Return type:
- governing_shear_strength() AnchorCheckResult[source]
Return the governing (minimum φSn) shear limit state.
- governing_tension_strength() AnchorCheckResult[source]
Return the governing (minimum φSn) tension limit state.
- plot(title: str = '', figsize: tuple = (8, 7))[source]
2D plan-view diagram of the anchor group.
Draws the concrete boundary, bolt locations, the 1.5·h_ef tension breakout zone (dashed red), and demand arrows for V_ua and N_ua.
- pryout_strength() AnchorCheckResult[source]
Concrete pryout strength of anchors in shear — ACI 318-19 Eq. 17.7.3.1.
V_cp = k_cp * N_cp (single anchor) V_cpg = k_cp * N_cpg (group)
k_cp = 1.0 for h_ef < 2.5 in, else 2.0. N_cp / N_cpg taken as concrete breakout strength in tension.
- pullout_strength() AnchorCheckResult | None[source]
Pullout strength of cast-in anchors — ACI 318-19 Section 17.6.3.
Returns None if insufficient geometry is provided (no A_brg or e_h).
For headed studs/bolts: N_p = 8 * A_brg * f_c’ (Eq. 17.6.3.2.2a) For J- or L-bolts: N_p = 0.9 * f_c’ * e_h * d_a (Eq. 17.6.3.2.2b) N_pn = psi_c,P * N_p (Eq. 17.6.3.1)
Note: Pullout is evaluated per anchor; total = n * phi * N_pn.
- side_face_blowout() AnchorCheckResult | None[source]
Side-face blowout strength of headed anchors — ACI 318-19 Section 17.6.4.
Applicable only when h_ef > 2.5 * c_a1 (deep anchor near edge). Returns None if condition not met or A_brg not provided.
N_sb = 160 * c_a1 * sqrt(A_brg) * lambda_a * sqrt(f_c’) (Eq. 17.6.4.1) Group: N_sbg = (1 + s/6/c_a1) * N_sb (Eq. 17.6.4.2, simplified)
- steel_shear_strength() AnchorCheckResult[source]
Steel strength of anchors in shear — ACI 318-19 Eq. 17.7.1.2b.
For cast-in headed bolts (threads may be in shear plane):
V_sa = 0.6 * A_se,V * f_uta (per anchor)
Grout pad reduces by 0.80 (Section 17.7.1.2.1).
- steel_tension_strength() AnchorCheckResult[source]
Steel strength of anchors in tension — ACI 318-19 Eq. 17.6.1.2.
N_sa = A_se,N * f_uta (per anchor) Total group design strength = n * phi * N_sa
- stretch_length_check() AnchorCheckResult | None[source]
Stretch length adequacy for seismic ductile designs — ACI 318-19 Section 17.10.5.3.
For SDC C–F where anchor yielding is relied upon for ductility, the frictionally isolated length must be ≥ 8·d_a. Returns None if not in seismic SDC C–F. coupler_depth is used as the isolated stretch length when provided; otherwise h_ef is used as a conservative proxy.
- tension_shear_interaction() AnchorCheckResult[source]
Tension-shear interaction — ACI 318-19 Section 17.8.
If N_ua/(φN_n) ≤ 0.2, shear governs and the full shear capacity is available. If V_ua/(φV_n) ≤ 0.2, tension governs and the full tension capacity is available. Otherwise (Eq. 17.8.3):
N_ua/(φN_n) + V_ua/(φV_n) ≤ 1.2
- class civilpy.structural.concrete.AnchorCheckResult(label: str, reference: str, N_n: float, V_n: float, phi: float, phi_Sn: float, demand: float, is_ratio: bool = False)[source]
Bases:
objectResult for a single limit-state check.
- class civilpy.structural.concrete.ShearLugCheck(f_c: float, h_a: float, n_sl: int, A_ef_sl: float, h_sl: float, c_a1: float = inf, c_a2: float = inf, lambda_a: float = 1.0, has_supp_reinf: bool = False, V_ua: float = 0.0, sdc: str = 'A')[source]
Bases:
objectACI 318-19 Section 17.11 shear lug design checks.
Shear lugs transfer in-plane shear by bearing against concrete rather than through anchor rod bending. Two limit states are checked:
Bearing strength of lug (17.11.2.1): V_brg,sl = n_sl × 1.7 × λ_a × f_c × A_ef,sl
Concrete breakout of lug (17.11.3.2.1): V_b,sl = 3.5 × λ_a × √f_c × A_ef,sl^0.5 × h_sl^1.5 Full breakout: V_cb,sl = (A_Vc,sl / A_Vco,sl) × psi_ed × V_b,sl
All lengths in inches, forces in pounds (lb), stresses in psi.
- Parameters:
f_c (float) – Specified concrete compressive strength (psi), capped at 10,000 psi.
h_a (float) – Member thickness in the direction of applied shear (in).
n_sl (int) – Number of shear lugs.
A_ef_sl (float) – Effective bearing area of a single shear lug (in²). This is the projected bearing face area perpendicular to applied shear, excluding any grout pocket area.
h_sl (float) – Height of shear lug above the concrete or grout surface (in).
c_a1 (float) – Edge distance from the lug to the nearest free edge in the direction of applied shear (in). Use float(‘inf’) if no near edge.
c_a2 (float) – Edge distance perpendicular to applied shear (in). Use float(‘inf’) if no near edge.
lambda_a (float) – Lightweight concrete factor (1.0 for normalweight).
has_supp_reinf (bool) – True if supplementary reinforcement is provided (Condition A, φ=0.75).
V_ua (float) – Factored shear demand on the lug group (lb).
sdc (str) – Seismic Design Category; applies 0.75 seismic reduction for SDC C–F.
- bearing_strength() AnchorCheckResult[source]
Bearing strength of shear lugs — ACI 318-19 Eq. 17.11.2.1.
V_brg,sl = n_sl × 1.7 × λ_a × f_c × A_ef,sl
- breakout_strength() AnchorCheckResult | None[source]
Concrete breakout strength of shear lug — ACI 318-19 Section 17.11.3.
V_b,sl = 3.5 × λ_a × √f_c × A_ef,sl^0.5 × h_sl^1.5 (Eq. 17.11.3.2.1) Returns None when c_a1 is infinite (no near edge).
civilpy.structural.construction_staging module
Staged / phased-construction helpers for the refined-grillage bridge model.
Maintenance-of-traffic (MOT) staged construction, deck replacement, and
widening all share the same moves this module supports on top of
civilpy.structural.bridge_layout.grillage_model_from_layout():
shift traffic onto a subset of the girders – during a phase, the open lanes are squeezed over the girder lines that are carrying traffic (
lanes_over_girders()), and the live/lane load is applied there;place an anchored portable concrete barrier as a temporary dead load at the work-zone edge (
PORTABLE_BARRIERS,add_barrier_dead_load());analyze a construction phase in isolation – a stage-1 (or stage-2) structure carrying only the girders built so far, via the
girder_subsetphase model, so a closure pour can be checked: the wet concrete of the closure strip (closure_pour_line_load_plf()) loads each half before it cures, and the differential deflection at the joint governs the pour geometry/timing.
Everything is applied as named load cases ("MOT-LANE", "PCB",
"CLOSURE", …) on the hub, so the phases go to MIDAS through the ordinary
civilpy.structural.midas_models.midas_payloads() path and can be combined
or run as construction stages there. Loads are placed on the girders by the
transverse lever rule (the standard line-girder distribution) – a
preliminary-level idealization; the deck plates still provide the real
transverse distribution in the solved grillage.
Units: transverse offsets and widths in feet (layout Y, girder 1 at
Y = 0); barrier/line weights in lb/ft (plf); beam loads on the hub in
kip/ft, negative GZ = downward (matching the DC/DW convention).
- civilpy.structural.construction_staging.LANE_LOAD_KLF = 0.64
HL-93 design lane load (AASHTO LRFD 3.6.1.2.4), kip/ft per 10 ft lane.
- civilpy.structural.construction_staging.MULTIPLE_PRESENCE = {1: 1.2, 2: 1.0, 3: 0.85}
AASHTO LRFD 3.6.1.1.2 multiple-presence factor m by number of loaded lanes.
- civilpy.structural.construction_staging.PORTABLE_BARRIERS: dict[str, PortableBarrier] = {'JERSEY-PORTABLE': PortableBarrier(designation='JERSEY-PORTABLE', height_in=32.0, weight_plf=400.0, anchored=False, note='unanchored precast Jersey segments'), 'PCB-32': PortableBarrier(designation='PCB-32', height_in=32.0, weight_plf=470.0, anchored=True, note='32 in F-shape portable concrete barrier, anchored'), 'PCB-42': PortableBarrier(designation='PCB-42', height_in=42.0, weight_plf=650.0, anchored=True, note='42 in tall single-slope portable barrier, anchored')}
Nominal portable-barrier catalog (confirm weights against the ODOT SCD).
- class civilpy.structural.construction_staging.PortableBarrier(designation: str, height_in: float, weight_plf: float, anchored: bool = True, note: str = '')[source]
Bases:
objectA temporary/portable concrete barrier used for work-zone separation.
weight_plfis the self-weight per foot of run (lb/ft);anchorednotes whether it is pinned/bolted to the deck (which changes overturning and the deck anchor design, not the gravity load this module applies). Values are nominal catalog figures – confirm against the governing ODOT SCD / shop drawing for a specific product.
- class civilpy.structural.construction_staging.RoadwayLanes(roadway_width_ft: float, lane_width_ft: float, cl_offset: float, n_lanes: int, curb_lines: tuple[float, float], lane_edges: tuple[float, ...], lane_centers: tuple[float, ...])[source]
Bases:
objectThe traffic-lane layout of the roadway, in the layout transverse frame (
Y, ft; girder 1 atY = 0).Built from a lane-width variable and the curb-to-curb roadway width:
cl_offsetis the roadway centerline,curb_linesthe two roadway edges,lane_edgesevery design-lane boundary (n_lanes + 1of them), andlane_centersthe design-lane centers. These are the lines a Rhino grip interface edits (drag the CL / lane edges) and the reference positions handed to MIDAS for the lane-load live load.n_lanesfollows AASHTO LRFD 3.6.1.1.1 (int(roadway_width / 12)).
- civilpy.structural.construction_staging.add_barrier_dead_load(model, layout, offset_ft: float, *, designation: str = 'PCB-32', case: str = 'PCB') dict[int, float][source]
Place an anchored portable concrete barrier as a dead load at
offset_ft(lever rule to the straddling girders). Returns the per-girder applied klf.
- civilpy.structural.construction_staging.add_closure_pour_load(model, layout, edge_girder_line: int, *, closure_width_ft: float = 2.0, share: float = 0.5, case: str = 'CLOSURE', wet_factor: float = 1.0) float[source]
Apply the wet closure-pour weight carried by one phase to that phase’s closure-edge girder line.
shareis the fraction of the strip weight this phase carries (0.5 when the joint is centered between the two phases). Applied to every element ofedge_girder_linein casecase. Returns the applied line load (kip/ft, negative = downward). The load sits on the deck cantilever outboard of the edge girder, so in a refined grillage it also induces a small torsion the plates carry – for the girder demand this vertical line load is the preliminary quantity.
- civilpy.structural.construction_staging.add_design_lane_load(model, layout, lanes: RoadwayLanes, *, n_loaded: int | None = None, case: str = 'LL-LANE', lane_load_klf: float = 0.64, apply_multiple_presence: bool = True) dict[int, float][source]
Apply the HL-93 lane load to
n_loadedof the design lanes (default: all), with the AASHTO multiple-presence factor.The lane load is placed at each loaded lane’s center (lever rule to the girders beneath) – the distributed lane-load portion of HL-93, the piece handed to MIDAS from the Rhino-defined lanes. Returns per-girder klf. The design truck/tandem and its longitudinal positioning for the governing per-support reaction are a MIDAS moving-load run (see the notebook note) – this static lane load is the preliminary live-load reaction.
- civilpy.structural.construction_staging.add_lane_load(model, layout, lane_centers: list[float], *, case: str = 'MOT-LANE', lane_load_klf: float = 0.64) dict[int, float][source]
Apply the HL-93 static lane load at each lane center (lever rule).
The AASHTO multiple-presence factor is not applied here (one or two staged lanes are usually
m = 1.0/1.0); scalelane_load_klfif a differentmgoverns. Returns the per-girder total applied klf.
- civilpy.structural.construction_staging.add_line_load_at_offset(model, layout, offset_ft: float, w_plf: float, *, case: str, downward: bool = True) dict[int, float][source]
Apply a longitudinal line load (
w_plflb/ft) at a transverseoffset_ftto the girders it sits between, by the lever rule.A load between girders k and k+1 splits to each in inverse proportion to its distance; a load outboard of a fascia girder goes fully to it (an overhang load’s cantilever moment is not added here – preliminary). Returns
{girder_line_no: applied_klf}(negative = downward), and adds the loads to every element of those girder lines in load casecase.
- civilpy.structural.construction_staging.closure_pour_line_load_plf(layout, *, closure_width_ft: float = 2.0, wet_factor: float = 1.0) float[source]
Self-weight (lb/ft of bridge length) of the wet closure-pour strip.
A staged deck is cast in phases with a longitudinal gap left between them; the closure pour fills that
closure_width_ftstrip last. Before it cures it is dead weight the already-built phases carry – half to each side.wet_factorscales for over-pour / screed surcharge. Uses the deck thickness and the concrete unit weight (CONCRETE_UNIT_WT_KCF).
- civilpy.structural.construction_staging.design_lanes(layout, *, roadway_width_ft: float, lane_width_ft: float = 12.0, cl_offset: float | None = None, n_lanes: int | None = None) RoadwayLanes[source]
Lay out design traffic lanes across the roadway from the lane width.
cl_offsetdefaults to the girder-group center;n_lanesdefaults toint(roadway_width_ft / 12)(AASHTO number of design lanes). Then_laneslanes of widthlane_width_ftare centered on the roadway centerline. Returns aRoadwayLanes.
- civilpy.structural.construction_staging.girder_elements(model, line_no: int) list[source]
The girder beam elements of one girder line (1-based
line_no).
- civilpy.structural.construction_staging.lanes_over_girders(layout, girder_lines: list[int], *, n_lanes: int | None = None, lane_width_ft: float = 12.0) list[float][source]
Transverse center offsets for design lanes shifted over
girder_lines.During a construction phase the open roadway is carried on a subset of the girders; this centers
n_lanesdesign lanes over the span of those girder lines (1-based). Withn_lanesomitted, as many 12 ft lanes as fit between the outermost ofgirder_linesare used (at least one). Returns the lane center offsets (ft,Y) – feed each toadd_lane_load()(static lane load) or use them as MIDAS traffic-line lane reference positions for a moving-load run.
- civilpy.structural.construction_staging.multiple_presence(n_lanes: int) float[source]
Multiple-presence factor
mforn_lanesloaded design lanes.
- civilpy.structural.construction_staging.portable_barrier(designation: str = 'PCB-32') PortableBarrier[source]
Look up a
PortableBarrierby designation.
civilpy.structural.continuous_beam module
Continuous-beam analysis for line girders (the offline substitute for a live MIDAS run when producing dead-load and moving-load moment envelopes).
A prismatic (or piecewise-EI) beam on simple supports is solved by the
direct-stiffness / slope-deflection method with the support rotations as the
only unknowns (every support pins the vertical translation). From the solved
member-end moments the support reactions follow, and the bending moment at any
section is then pure statics.
Moving-load envelopes come from influence lines: a unit load is walked
across the span and the moment at the section of interest recorded, giving an
InfluenceLine that the existing
hl93_effect / maximize_axle_train machinery turns into an HL-93 envelope.
Sign convention: downward loads positive; sagging bending moment positive.
Lengths in feet, loads in kips / kip-ft, ei in consistent units (its
absolute value cancels for statically-determinate reactions but sets the
distribution across an indeterminate beam, so only relative ei matters).
- class civilpy.structural.continuous_beam.ContinuousBeam(supports: list[float], ei: float = 1.0, _loads: list = <factory>)[source]
Bases:
objectA continuous beam defined by its ordered support positions (ft).
>>> b = ContinuousBeam([0.0, 20.0, 40.0]) # two 20-ft spans >>> b.add_udl(2.0) # 2 klf everywhere >>> round(b.moment_at(20.0), 3) # interior support: -wL^2/8 -100.0
- add_point(p: float, x: float) ContinuousBeam[source]
Add a point load
p(kip, down +) at stationx(ft).
- add_udl(w: float, x0: float | None = None, x1: float | None = None) ContinuousBeam[source]
Add a uniform load
w(kip/ft, down +) over[x0, x1](default the whole beam).
- deflection_diagram(e_ksi: float, i_in4: float, n: int = 1001, loads=None, xs=None)[source]
(stations, deflections)— deflection in inches, negative = downward, for a prismatic section (e_ksiin ksi,i_in4in in^4).The sagging-positive moment diagram (
neven samples, orxs) is integrated twice as curvatureM/EI; the rigid-body line is then removed by a least-squares fit through the support stations, so every support sits at (numerically) zero deflection.
- diagrams(xs=None, n: int = 201, loads=None)[source]
(stations, shears, moments)atxs(orneven stations).One reactions solve, then vectorized statics — the fast path for diagram sampling and moving-load envelopes. Shear and moment take their left-limit value at a support or point-load station.
- moment_at(x: float, loads=None) float[source]
Bending moment (kip-ft, sagging +) at station
xby statics from the solved reactions and the loads to the left ofx.
- moment_diagram(n: int = 201, loads=None)[source]
(stations, moments)samplednpoints along the beam.
- moment_influence_line(x_section: float, *, n: int = 201) InfluenceLine[source]
Influence line for the bending moment at
x_section: a unit downward load walked across the beam. Feed the result toInfluenceLine.hl93_effectfor the HL-93 live-load envelope.
- reactions(loads=None) list[float][source]
Support reactions (kip, up +) under the applied (or supplied) loads.
- shear_at(x: float, loads=None) float[source]
Shear (kip, left-limit) at station
xby statics: reactions minus applied loads to the left ofx.
- class civilpy.structural.continuous_beam.EnvelopeExtreme(value: float, station: float)[source]
Bases:
objectOne extreme of a moving-load envelope: its value and station (ft).
- class civilpy.structural.continuous_beam.MovingLoadEnvelope(stations: ndarray, moment_max: ndarray, moment_min: ndarray, shear_max: ndarray, shear_min: ndarray)[source]
Bases:
objectPer-station extreme shear and moment from stepping an axle train (plus any pattern-placed lane load) across a
ContinuousBeam.Arrays share
stations; conventions follow the beam (sagging moment positive, left-limit shear). These are 1D line-girder demands — distribution factors and impact are the caller’s business.- max_negative_moment() EnvelopeExtreme[source]
Most negative moment (hogging; 0 at midspan stations is normal).
- max_positive_moment() EnvelopeExtreme[source]
- max_shear() EnvelopeExtreme[source]
Largest absolute shear (signed value returned).
- class civilpy.structural.continuous_beam.UnitResponses(xs: ndarray, shear: ndarray, moment: ndarray)[source]
Bases:
objectThe unit-load response matrices of one beam configuration on one grid, reusable across every vehicle: build once with
from_beam(), thenenvelope()each truck against it (the per-configuration solve dominates; each envelope afterwards is a cheap gather-and-sum).- envelope(loads, positions, *, both_directions: bool = True, lane_klf: float = 0.0) MovingLoadEnvelope[source]
Envelope one axle train (see
moving_load_envelope()for the parameter semantics; placements step at the grid spacing).
- classmethod from_beam(beam: ContinuousBeam, step: float = 0.5)[source]
Solve the unit-load responses of
beamon a grid of spacingstep(ft, snapped so the supports land on grid points).
- civilpy.structural.continuous_beam.m_about_a_to_shear(w_tot, m_about_a, span)[source]
Simple-span left reaction share:
V_a = w_tot - m_about_a/span(the part of the load carried by the left end before the end-moment couple).
- civilpy.structural.continuous_beam.moving_load_envelope(beam: ContinuousBeam, loads, positions, *, step: float = 0.5, both_directions: bool = True, lane_klf: float = 0.0) MovingLoadEnvelope[source]
Step an axle train across
beamand envelope shear and moment at every station.loads(kip) sit atpositions(ft from the first axle) — pass a catalog truck as*vehicle.train()fromcivilpy.structural.aashto.vehicles. Axles off the beam contribute nothing (trains enter and leave the span).stepsets both the station grid and the train-placement increment; axle placements between grid points interpolate linearly between unit-load rows, so keepstepa divisor of the axle spacings where exactness at the peaks matters (the catalog trucks are all on 0.5-ft multiples).lane_klfadds a uniform lane load placed patterned per station: positive influence regions only for the maxima, negative only for the minima — the adverse-placement rule, computed from the same unit-load responses. Impact and distribution factors are deliberately not applied here.Enveloping several vehicles on the same beam? Build a
UnitResponsesonce and call itsenvelope()per truck — this function rebuilds the unit-load matrices every call.>>> b = ContinuousBeam([0.0, 100.0]) >>> env = moving_load_envelope(b, [8.0, 32.0, 32.0], [0.0, 14.0, 28.0]) >>> round(env.max_positive_moment().value, 1) # HS20, 100-ft span 1523.9
- civilpy.structural.continuous_beam.unit_response_matrices(beam: ContinuousBeam, xs)[source]
Shear and moment at every station of
xsfor a unit (1 kip) load at every position ofxs: two(len(xs), len(xs))arrays whose rowiis the beam’s response to the unit load atxs[i].Column
kread down the rows is therefore the influence line of the effect at stationxs[k]— one stiffness solve per row buys every truck, direction, and placement afterwards as a cheap gather-and-sum.
civilpy.structural.effective_length module
Column effective-length factors: the alignment-chart (nomograph) equations solved exactly, so no more reading K off a fuzzy chart.
g_a/g_b are the joint stiffness ratios sum(EI/L columns) /
sum(EI/L girders); use 10 for a pinned base and 1.0 for a fixed base
(the recommended practical values).
Examples
>>> round(k_factor(1.0, 1.0, sway=False), 2)
0.77
>>> round(k_factor(1.0, 1.0, sway=True), 2)
1.32
civilpy.structural.flexural_stress module
Elastic flexural stress distribution over a beam cross-section.
The classic sigma = M*y/I diagram: linear stress through the depth,
zero at the neutral axis, extreme-fiber values labeled. Works for any
section once its depth, neutral-axis location, and moment of inertia
are known (e.g. from civilpy.structural.section_properties).
Sagging (positive) moment puts the top fiber in compression, drawn to the left of the zero line, matching the hand-sketch convention.
Examples
>>> round(flexural_stress(1200.0, 800.0, 6.0), 3) # M=1200 k-in, I=800
9.0
>>> sigma_top, sigma_bot = extreme_fiber_stresses(1200.0, 800.0,
... c_top=8.0, c_bot=4.0)
>>> round(sigma_top, 2), round(sigma_bot, 2)
(-12.0, 6.0)
- civilpy.structural.flexural_stress.extreme_fiber_stresses(m_kip_in: float, i_in4: float, *, c_top: float, c_bot: float) tuple[float, float][source]
(sigma_top, sigma_bot) in ksi with the structural sign convention: compression negative. Positive (sagging) moment gives a negative top stress and positive bottom stress.
- civilpy.structural.flexural_stress.flexural_stress(m_kip_in: float, i_in4: float, y_in: float) float[source]
Bending stress magnitude M*y/I (ksi) at distance
y_infrom the neutral axis.
- civilpy.structural.flexural_stress.plot_flexural_stress(m_kip_in: float, i_in4: float, *, depth_in: float, y_bar_in: float, width_in: float | None = None, ax=None)[source]
Draw the linear elastic stress distribution through the depth.
y_bar_inlocates the neutral axis above the bottom fiber, so asymmetric sections (composite girders, tees) show their unequal extreme-fiber stresses. Whenwidth_inis given a rectangular section silhouette is sketched beside the diagram for scale. Compression plots left of the zero line (negative), tension right. Returns the figure.
civilpy.structural.girder_optimizer module
Rolled-girder + field-splice optimizer (stage B5 – “a better design”).
Sweeps candidate rolled shapes and, for each, (1) checks the girder carries the span’s governing factored moment (φ·Mp = φ·Fy·Zx, the compact-shape plastic moment – a conservative gate that ignores the extra composite capacity), and (2) auto-sizes the flange/web splice plates and designs the bolted field splice with the ODOT/NSBA method. Feasible shapes (girder OK and splice OK) are ranked by total cost – girder steel weight plus splice fabrication/erection – so the lightest adequate design surfaces, to compare against the as-built.
Cost model (all parameters overridable): steel at $/lb over the girder
length, plus per-bolt $fab fabrication and field_min erection minutes at
$/min. The absolute dollars are illustrative; the ranking is the point.
This is the splice-region optimizer; a full B5 also folds in Service II, fatigue, 6.10.2 proportion, and deflection limit states and lets the section vary between splice pieces – built on this same sweep.
- class civilpy.structural.girder_optimizer.GirderOption(shape: str, weight_lb_ft: float, steel_lb: float, total_bolts: int, steel_cost: float, splice_cost: float, total_cost: float, girder_ok: bool, splice_ok: bool)[source]
Bases:
objectOne candidate shape’s feasibility + cost.
- civilpy.structural.girder_optimizer.auto_splice_plates(left_label: str, right_label: str, grade: str = 'Grade 50', *, weld_size: float = 0.3125)[source]
Proportion the flange (
PlatePair) and web (WebPlate) splice plates for a shape pair using the NSBA sizers + the AISC geometry.
- civilpy.structural.girder_optimizer.cheapest_feasible(options)[source]
The lowest-cost fully-feasible
GirderOption, orNone.
- civilpy.structural.girder_optimizer.girder_plastic_moment(label: str, f_y: float = 50.0) float[source]
Plastic moment capacity
Mp = Fy*Zx(kip-ft) of a rolled shape.
- civilpy.structural.girder_optimizer.optimize_splice_shape(loads, candidates, *, length_ft: float, max_factored_moment: float, deck_thickness: float, deck_eff_width: float, rebar_area: float = 0.0, deck_fc: float = 4.0, grade: str = 'Grade 50', f_y: float = 50.0, bolt_dia: float = 0.875, steel_cost_per_lb: float = 1.5, bolt_fab: float = 20.0, field_min_per_bolt: float = 10.0, labor_per_min: float = 1.5, design_year: int = 2016)[source]
Sweep
candidates(uniform-shape girders) and return a list ofGirderOption, ranked feasible-first then bytotal_cost.loadsis the unfactored splice demand;max_factored_momentis the girder’s governing Strength I moment (from the envelope) used for the plastic-moment gate. Shapes whose flanges are too narrow to splice are skipped.
civilpy.structural.girder_pipeline module
Line-girder field-splice placement (stage G6).
Given a girder’s moving-load moment envelope (per load case, sampled along the span), decide how many field splices shipping length forces and where to put them: in the low-moment windows near the dead-load contraflexure points, subject to every field piece being no longer than the shippable length.
n_splices = ceil(L / L_ship) - 1
Each candidate is returned with the unfactored per-case demand set at its
station, ready to hand to the splice designer as a
SpliceLoads. The engineer accepts or
drags the gdr.kind=splice markers G8 then stamps into the .3dm.
This module is pure: it operates on sampled envelopes (from MIDAS via G5, or any line-girder analysis) and never touches a live session.
- class civilpy.structural.girder_pipeline.SpliceCandidate(station: float, factored_moment: float, loads: SpliceLoads)[source]
Bases:
objectA suggested field-splice location and the demand there.
- loads: SpliceLoads
- civilpy.structural.girder_pipeline.girder_line_envelope(supports, *, dc1_klf, dc2_klf=0.0, dw_klf=0.0, n_sections: int = 41, gdf: float = 1.0, im: float = 0.33, lane_klf: float = 0.64, il_samples: int = 201)[source]
Per-case moment envelope for one continuous girder line.
dc1_klf(non-composite dead load: steel + wet slab),dc2_klf(barrier/SDL), anddw_klf(future wearing surface) are uniform loads; the live load is the HL-93 moment envelope from an influence line at each section, scaled by the girder distribution factorgdf.Returns
(stations, moments)wheremomentshas thedc1/dc2/dw/ ll_pos/ll_negkeysplace_splices()consumes.
- civilpy.structural.girder_pipeline.hl93_pos_neg(il, *, im: float = 0.33, lane_klf: float = 0.64)[source]
Governing positive and negative HL-93 effects for one influence line.
Positive uses
InfluenceLine.hl93_effect()(truck/tandem placed for the maximum, lane load on the positive influence area). Negative mirrors it: the most-negative truck/tandem placement with the lane load on the negative area (a reasonable single-truck approximation of 3.6.1.3.1 – the two-truck negative-moment rule for interior supports is a refinement).
- civilpy.structural.girder_pipeline.n_field_splices(length_ft: float, ship_max_ft: float) int[source]
Number of field splices shipping length forces on a girder of
length_ftwhen the longest shippable piece isship_max_ft.
- civilpy.structural.girder_pipeline.place_splices(stations, moments, ship_max_ft, *, samples: int = 400)[source]
Suggest field-splice stations for one girder.
- Parameters:
stations (list[float]) – Increasing sample positions along the girder (ft), from 0 to L.
moments (dict[str, list[float]]) – Per-case moments (k-ft) aligned with
stations. Must include the keysdc1,dc2,dw,ll_pos,ll_neg.dc1(the non-composite dead load) locates the contraflexure the splice hides in.ship_max_ft (float) – Longest shippable field piece (
gdr.ship_max).samples (int) – Search resolution across each splice’s feasible shipping window.
- Returns:
n_field_splices(L, ship_max)candidates, ordered along the span, each at the lowest-magnitude Strength I station within its shipping-feasible window (contraflexure wins, since dead + live both pass through zero there).- Return type:
civilpy.structural.influence_lines module
Influence lines for statically determinate beams (overhangs allowed), with axle-train stepping for moving-load maxima — the classic line-girder approach, so ordinates can be compared against any rating software.
Conventions follow civilpy.structural.beam_bending: kips/ft, plots
accept ax=None and return the figure. Unit load is 1 kip downward;
sign conventions are the usual ones (positive reaction up, positive
moment sagging, shear by the left-segment rule).
Examples
>>> il = InfluenceLine.moment(span=20, section=10) # midspan moment
>>> round(il.eta(10), 4) # peak = L/4
5.0
>>> peak = il.maximize_axle_train([32, 32, 8], [0, 14, 28])
>>> round(peak.value, 1) # one axle fits the peak
160.0
>>> round(il.uniform_load_effect(0.64), 1) # 0.64 klf lane load
32.0
- class civilpy.structural.influence_lines.InfluenceLine(eta, length: float, label: str = '')[source]
Bases:
objectInfluence line for one effect (reaction, shear, or moment at a section) on a beam with a pin at
support_aand roller atsupport_b; the beam runs from 0 tolength(defaults tosupport_b), so overhangs come from supports inside the ends.- eta(x)[source]
Ordinate(s) at unit-load position(s)
x: a float for scalar input, an array for array input.
- hl93_effect(im: float = 0.33, lane_klf: float = 0.64, rear_spacings=(14.0, 30.0)) dict[source]
Governing HL-93 effect on this line (3.6.1.3): design truck (rear-axle spacing swept) with dynamic load allowance, plus lane load on the positive regions. Returns the pieces and the total.
- maximize_axle_train(loads, positions, step: float = 0.05, both_directions: bool = True, sign: float = 1.0) TrainResult[source]
Step an axle train across the beam and return the extreme effect.
loads(kips) sit atpositions(ft, from the first axle); axles off the beam contribute nothing.sign=-1finds the most negative effect instead.
- classmethod moment(span: float, section: float, support_a: float = 0.0, support_b: float | None = None, length: float | None = None)[source]
IL for bending moment at
section.
- plot(ax=None, n: int = 1001, axle_train=None)[source]
Plot the influence line (positive area shaded), optionally with an axle train drawn at its governing position.
- classmethod reaction(span: float, support: str = 'A', support_a: float = 0.0, support_b: float | None = None, length: float | None = None)[source]
IL for a vertical reaction.
spanis shorthand for supports at 0 andspan; or place them withsupport_a/support_b.
- classmethod shear(span: float, section: float, support_a: float = 0.0, support_b: float | None = None, length: float | None = None)[source]
IL for shear at
section(left-segment sign convention).
- classmethod two_span_moment(spans, section: float)[source]
IL for bending moment at
section(ft from A) of a two-span continuous beam.section = L1gives the classic negative-moment IL over the middle support.
- classmethod two_span_reaction(spans, support: str = 'B')[source]
IL for a reaction of a two-span continuous beam with supports A-B-C and (possibly unequal)
spans= (L1, L2). The middle reaction’s IL is the Müller-Breslau deflected shape of the beam with support B released.
- class civilpy.structural.influence_lines.TrainResult(value: float, position: float, reversed_train: bool)[source]
Bases:
objectExtreme effect of an axle train on an influence line.
- civilpy.structural.influence_lines.influence_line_from_midas(midas, element, cases, *, component='Moment-y', part='Part I', length=None, label='', unit=None)[source]
Assemble an
InfluenceLinefor one beam-element force effect from a set of unit-load analysis cases already solved in a MIDAS model.casesis an ordered iterable of(position_ft, load_case_name)pairs, one per unit-load (1 kip) position along the lane. This readsmidas.beam_forces([element], …)once for all the cases, takescomponent(e.g."Moment-y"/"Shear-z") at the givenpart("Part I"or"Part J") ofelementunder each case, and returns the interpolated influence line — ready forInfluenceLine.maximize_axle_train()permit runs.This does not create or modify load cases; it reads the response of unit cases you (or a generator) set up, so it stays side-effect free and works against whatever load-application scheme the model uses. Reactions and displacements follow the same pattern via
MidasCivil.result_table.
- civilpy.structural.influence_lines.influence_line_from_ordinates(positions, etas, length=None, label='')[source]
Build an
InfluenceLinefrom sampled ordinates.positions(ft, ascending) andetasare paired samples of an influence line obtained from any source — a MIDAS unit-load analysis, a finite-element model, or measured data — letting the full axle-train machinery (InfluenceLine.maximize_axle_train(),hl93_effect(),uniform_load_effect()) run permit and rating trucks against a real structure rather than only the closed-form prismatic cases.The line is reconstructed by linear interpolation between samples (
numpy.interp); load positions off the sampled range contribute zero. Accuracy is therefore limited by the sample spacing — and, because an influence line presumes linear superposition, by how linear the source structure is (see the walkthrough notes on nonlinear bridges).lengthdefaults to the largest position.
civilpy.structural.line_girder_tool module
Basic line-girder shear / moment / deflection tool.
The simplest useful form of a line-girder run, with no Rhino or MIDAS dependence: pick a handful of settings (barrier type, AASHTO vehicle, deck thickness, span arrangement, girder size/spacing), get back
the loads the tool derived from each selection (klf per girder, plus the AASHTO distribution factors), and
shear, moment, and deflection diagrams for an interior and an exterior girder — dead load exactly, live load as a stepped-vehicle / patterned-lane envelope.
The beam solver behind the diagrams is
ContinuousBeam; it and the
envelope logic here are verified against the AISC Steel Construction Manual
Table 3-23 beam cases in tests/structural/test_line_girder_tool.py
(cases 1, 29, 30, 43, 44) and tests/structural/test_beam_bending.py.
Simplifications, kept loud on purpose (this is a first-pass tool):
equal spans, prismatic girder, bare-steel
Ifor every deflection (no composite section);both barriers shared equally by all girders (a common DC2 practice);
HL-93 negative moment uses a single truck (the 90 % two-truck rule of 3.6.1.3.1 is a refinement this tool skips);
live-load deflection distributes
m x n_lanes / n_girdersper AASHTO 2.5.2.6.2, using the truck alone.
- civilpy.structural.line_girder_tool.BARRIER_CHOICES = ('BR-1 (36 in)', 'BR-1 (42 in)', 'SBR-3 (36 in)', 'SBR-1 (42 in)', 'SBR-2 (57 in median)', 'SBR-2 (57 in median, back-to-back)', 'TST-2 (three steel tube)')
Catalog barriers with a computable dead weight — the dropdown choices.
- class civilpy.structural.line_girder_tool.BridgeConfig(n_spans: int = 3, span_ft: float = 50.0, girder: str = 'W24X104', n_girders: int = 5, spacing_ft: float = 7.0, overhang_ft: float = 2.5, deck_t_in: float = 8.5, haunch_in: float = 1.0, barrier: str = 'SBR-1 (42 in)', vehicle: str = 'HL-93 (governing of truck/tandem + lane)', fws_ksf: float = 0.06, e_ksi: float = 29000.0)[source]
Bases:
objectOne dropdown’s worth of settings for the basic line-girder tool.
- barrier: str = 'SBR-1 (42 in)'
A key of
BARRIER_CHOICES(catalog railings with a computable dead weight).
- girder: str = 'W24X104'
AISC W-shape label from
civilpy.structural.steel.
- civilpy.structural.line_girder_tool.CONCRETE_KCF = 0.15
Reinforced-concrete unit weight (kcf) for deck and barrier dead load.
- class civilpy.structural.line_girder_tool.GirderDiagrams(loads: GirderLoads, stations: list[float], v_dead: ndarray, m_dead: ndarray, d_dead: ndarray, v_ll_pos: ndarray, v_ll_neg: ndarray, m_ll_pos: ndarray, m_ll_neg: ndarray, d_ll: ndarray)[source]
Bases:
objectSampled diagrams for one girder (kip / kip-ft / inches; station ft).
- property d_total
- loads: GirderLoads
- property m_total_neg
- property m_total_pos
- property v_total_neg
- property v_total_pos
- class civilpy.structural.line_girder_tool.GirderLoads(label: str, trib_ft: float, girder_klf: float, deck_klf: float, dc1_klf: float, barrier_klf: float, dc2_klf: float, dw_klf: float, df_moment: float, df_shear: float, df_deflection: float, im: float, lane_klf: float)[source]
Bases:
objectThe per-girder loads the tool derived from the user’s selections.
- class civilpy.structural.line_girder_tool.LineGirderResult(config: ~civilpy.structural.line_girder_tool.BridgeConfig, loads: dict[str, ~civilpy.structural.line_girder_tool.GirderLoads], girders: dict[str, ~civilpy.structural.line_girder_tool.GirderDiagrams] = <factory>)[source]
Bases:
objectEverything
analyze()produced: config, loads, and diagrams.- config: BridgeConfig
- girders: dict[str, GirderDiagrams]
- loads: dict[str, GirderLoads]
- civilpy.structural.line_girder_tool.VEHICLES = {'HL-93 (design tandem + lane)': {'im': 0.33, 'lane_klf': 0.64, 'trains': [([25.0, 25.0], [0.0, 4.0])]}, 'HL-93 (design truck + lane)': {'im': 0.33, 'lane_klf': 0.64, 'trains': [([8.0, 32.0, 32.0], [0.0, 14.0, 28.0]), ([8.0, 32.0, 32.0], [0.0, 14.0, 44.0])]}, 'HL-93 (governing of truck/tandem + lane)': {'im': 0.33, 'lane_klf': 0.64, 'trains': [([8.0, 32.0, 32.0], [0.0, 14.0, 28.0]), ([8.0, 32.0, 32.0], [0.0, 14.0, 44.0]), ([25.0, 25.0], [0.0, 4.0])]}, 'HS20-44 (truck only, Std. Spec. impact)': {'im': None, 'lane_klf': 0.0, 'trains': [([8.0, 32.0, 32.0], [0.0, 14.0, 28.0])]}}
Axle trains (loads kip, offsets ft) per selectable design vehicle. HL-93 rear-axle spacing varies 14-30 ft; stepping both bounding trains captures the governing spread for negative moment over interior supports.
- civilpy.structural.line_girder_tool.analyze(cfg: BridgeConfig, *, n: int = 401) LineGirderResult[source]
Run the basic line-girder tool for one configuration.
- civilpy.structural.line_girder_tool.barrier_weight_klf(designation: str) float[source]
Dead load of one barrier run (klf) from the ODOT railing catalog: gross concrete area x unit weight, else the catalog steel weight.
- civilpy.structural.line_girder_tool.compute_loads(cfg: BridgeConfig) dict[str, GirderLoads][source]
Loads + distribution factors for the interior and exterior girders.
- civilpy.structural.line_girder_tool.lane_load_envelope(supports, w_klf: float, *, n: int = 401, xs=None)[source]
Envelope of a uniform lane load placed on every combination of spans (the adverse-span patterning of AASHTO 3.6.1.3.1, done exhaustively — at most
2**n_spans - 1load cases). Same return keys asmoving_load_envelope()(without deflection).
- civilpy.structural.line_girder_tool.loads_table(result: LineGirderResult)[source]
The loads the tool associated with each selection, as a DataFrame (rows = load items, columns = interior / exterior girder).
- civilpy.structural.line_girder_tool.moving_load_envelope(supports, trains, *, n: int = 401, xs=None, step: float | None = None, e_ksi: float | None = None, i_in4: float | None = None)[source]
Step each axle train (both travel directions) across the beam and envelope shear, moment, and (optionally) deflection at
nstations.trainsis a list of(axle_loads_kip, offsets_ft)pairs; axles off the beam contribute nothing. Stations areneven samples (orxswhen given). Returns a dict withstationsand thev_max/v_min/m_max/m_minenvelopes (kip, kip-ft), plusd_min(inches, most-downward) whene_ksi/i_in4are given.
- civilpy.structural.line_girder_tool.plot_diagrams(result: LineGirderResult, figsize=(12, 9))[source]
Shear / moment / deflection, interior and exterior girder side by side.
Gray = dead load alone; colored band = dead + live-load envelope.
civilpy.structural.midas module
MIDAS Civil API request templates and helpers.
Prebuilt JSON payloads and helpers for the MIDAS Civil (MOTIVE) REST API:
requesting analysis-result tables (beam forces, reactions) and mapping
result cases, used by the model-interrogation tools in
civilpy.structural.midas_models.
- exception civilpy.structural.midas.MidasApiError(message, response=None)[source]
Bases:
RuntimeErrorRaised when the MIDAS API returns an HTTP or application error.
- class civilpy.structural.midas.MidasCivil(base_url=None, mapi_key=None, timeout=60, secrets_path=None, analysis_timeout=None, reconnect_retries=None, retry_backoff=None)[source]
Bases:
objectA connection to a running MIDAS Civil NX (or Gen NX) instance through the MIDAS API — the same API the MIDAS GH Rhino/Grasshopper extension uses, so anything sent from here lands in the connected session.
Three lines:
from civilpy.structural.midas import MidasCivil midas = MidasCivil() # key from ~/secrets.json MIDAS_API_KEY nodes = midas.nodes() # GET /db/NODE midas.put_nodes({"1": {"X": 0, "Y": 0, "Z": 0}}) # PUT /db/NODE
Coverage:
Model database — typed helpers for the common tables (nodes, elements, materials, sections, supports, static loads, units, groups, load combinations) plus generic
get_db()/put_db()/post_db()/delete_db()that reach every/db/*table in the MIDAS API manual.Document operations —
new(),open(),save(),save_as(),analyze(),import_file(),export_file().Results —
result_table()wrappingPOST /post/TABLE.
Unlike the module-level functions above (kept for backwards compatibility), this class holds no global state, supports custom base URLs (regional/local API servers), applies request timeouts, and raises
MidasApiErrorinstead of printing.- ANALYSIS_TIMEOUT = 600
Read timeout (s) for long operations — analysis, open, result tables — on large finite-element models (a 10k-element solve exceeds the default). Override per instance with
analysis_timeout=for very large models.
- DEFAULT_BASE_URL = 'https://moa-engineers.midasit.com:443/civil'
- RECONNECT_RETRIES = 2
Default retry policy for transport (connection) failures. A dropped Civil NX session is the failure that clusters mid-batch, so one or two backoff retries recover most blips without masking a real outage.
- RETRY_BACKOFF = 0.5
- TRIAGE_TABLES = ('NODE', 'ELEM', 'CONS', 'STLD', 'MVLD', 'LCOM-GEN', 'SECT')
Tables read to judge whether a model can be analyzed / rated.
- analyze()[source]
POST /doc/ANAL— run the analysis on the current model (long timeout; a large finite-element solve exceeds the default request timeout).
- beam_forces(elem_ids, load_case_names, components=('Elem', 'Load', 'Part', 'Axial', 'Shear-y', 'Shear-z', 'Moment-y', 'Moment-z'), unit=None, styles=None, validate=True)[source]
POST /post/TABLEBEAMFORCE in the request shape confirmed against live Civil NX: integer element ids inNODE_ELEMS["KEYS"]plusUNIT,STYLES, andPARTS— omitting any of those returns the"second query is wrong"HTTP 400.elem_idsare integer beam element ids;load_case_namesuse the result suffixes ("…(ST)"static,"…(MV:all)"moving). Returns the raw/post/TABLEJSON (flatten withcivilpy...()/brr.rating.parse_result_table).When
validateis set (default), the returned table is checked for duplicate column headers viaduplicate_columns()and a warning is issued if any are found — guarding against the component aliasing that produced identical Axial / Shear-z envelopes on some models.
- capability(summary=None)[source]
(can_analyze, can_rate_live_load, note)for the current model.A model needs geometry, supports, and at least one load case to solve; live-load rating additionally needs a moving-load case. Pass a cached
summarize()result assummaryto avoid a second round-trip.
- static duplicate_columns(response)[source]
Column headers that appear more than once in a
/post/TABLEresponse. A non-empty result flags a component→column aliasing problem (e.g.AxialandShear-zresolving to the same field, seen on some BEAMFORCE result shapes) — the values under aliased headers are unreliable and should be re-queried.
- open(path)[source]
POST /doc/OPEN— open an .mcb model file on the Civil NX machine (long timeout; large files take time to load).
- put_elements(assign)[source]
Create/update elements:
{id: {"TYPE": "BEAM", "MATL":, "SECT":, "NODE": [i, j]}}.
- put_materials(assign)[source]
Create/update materials:
{id: {"TYPE": "STEEL"|"CONC"|"USER", "NAME":, ...}}.
- put_supports(assign)[source]
Create/update supports:
{node_id: {"ITEMS": [{"ID": 1, "CONSTRAINT": "1110000"}]}}.
- request(method, command, body=None, timeout=None, retries=None)[source]
Send one request to the MIDAS API and return the parsed JSON.
commandis the endpoint path, e.g."/db/NODE"or"/doc/ANAL".timeoutoverrides the instance default for one call (used byanalyze(),open(), andresult_table()on large models).retriesoverridesreconnect_retriesfor one call (0 disables the backoff retry).Error handling distinguishes three failure modes so a batch caller can react sensibly:
MidasConnectionError— the API was unreachable (dropped session); retried with backoff first, then raised.MidasTimeoutError— the request outran its read timeout (a large solve); raised immediately, not retried.MidasLicenseError— the license tier rejected the model (HTTP 400, e.g. too many construction stages).
All three subclass
MidasApiError, as does a generic HTTP or{"error": ...}payload failure.
- result_table(table_name, table_type=None, components=None, node_elems=None, load_case_names=None, parts=None, unit=None, styles=None, timeout=None)[source]
POST /post/TABLE— extract an analysis results table.Example:
midas.result_table( "BeamForce", table_type="BEAMFORCE", components=["Elem", "Load", "Moment-y"], node_elems={"KEYS": ["1to20"]}, load_case_names=["DL(CB)"], )
For element-force tables, prefer
beam_forces(), which sends the request shape confirmed to work against live Civil NX.timeoutoverrides the request timeout for large result sets.
- set_units(force='KIPS', dist='FT', heat='BTU', temper='F')[source]
Set the model unit system (defaults: kips / feet).
- summarize()[source]
Counts of the current model’s structural ingredients.
One round-trip per
TRIAGE_TABLESentry; returns a dict withnodes,elems,elem_types,sect_types,supports,static_loads,moving_loadsandcombinations. Feed it tocapability()to decide whether a model is worth analyzing before spending a long solve on it.
- exception civilpy.structural.midas.MidasConnectionError(message, response=None)[source]
Bases:
MidasApiErrorThe MIDAS API could not be reached — Civil NX is closed, the session dropped, or the network blipped. Transport-level and typically transient, so
MidasCivil.request()retries it before giving up. SubclassesMidasApiErrorso existingexcept MidasApiErrorpaths still catch it.
- exception civilpy.structural.midas.MidasLicenseError(message, response=None)[source]
Bases:
MidasApiErrorThe connected Civil NX license tier rejected the operation — e.g. opening a model with more construction stages than a Plus license allows. Not retryable: skip the model rather than re-sending.
- exception civilpy.structural.midas.MidasTimeoutError(message, response=None)[source]
Bases:
MidasConnectionErrorA request outran its read timeout — usually a large finite-element solve or model open that needs a longer
analysis_timeoutrather than a retry (re-issuing it just doubles the wait).
- civilpy.structural.midas.column_values(rows, column, cast=<class 'float'>)[source]
Pull one column from parsed rows, dropping blanks and un-castable cells.
- civilpy.structural.midas.convert_node_units(from_units: str = None, to_units: str = None, in_place: bool = True) None[source]
Converts the existing nodes from one unit system to another, for instance if you import a dxf file that was drawn in feet, but your midas units were set to inches, :param from_units: :type from_units: str = The units the drawing is currently in ie ‘inches’ will grab them from midas if None :param to_units: :type to_units: str = The desired units to convert the drawing to, if None, will not run :param in_place: :type in_place: bool = Whether you want the function to push the data to the midas, or just return the updated values
- Returns:
None - Converts the units in the open model to the desired units
nodes - if in_place is False
- civilpy.structural.midas.envelope(rows, column, *, absolute=True)[source]
Largest value of
columnacross rows (by magnitude unless told not to).Returns
0.0for no usable values — the controlling demand of an empty set is nothing.
- civilpy.structural.midas.get_api_key(secrets_path=None)[source]
Retrieve the API key from the secrets.json file in the civilpy directory
- Parameters:
secrets_path (the path where the secrets.json file is located)
- Return type:
None - Sets a global variable for the module
- civilpy.structural.midas.get_elements()[source]
Retrieve all elements from the active MIDAS Civil model.
- Returns:
MIDAS API response containing element definitions keyed by element ID under
"ELEM".- Return type:
- civilpy.structural.midas.get_elements_by_material_index(material_index: int = None)[source]
Get the elements of a model by specifying a material index value :param material_index: :type material_index: int = Index of the material you want to sort by in str format
- Returns:
return_dict
- Return type:
dict = The elements of a model that match the material specified
- civilpy.structural.midas.get_elements_by_section_index(section_index: int = None)[source]
Returns a dictionary containing all elements of the midas model that match the section index
- Parameters:
section_index (int = The index value of the section you want to search the elements for)
- Returns:
return_dict – specified
- Return type:
A dictionary containing all elements of the midas model that match the section
- civilpy.structural.midas.get_materials()[source]
Retrieve all material definitions from the active MIDAS Civil model.
- Returns:
MIDAS API response containing material properties keyed by material ID under
"MATL".- Return type:
- civilpy.structural.midas.get_nodes()[source]
Retrieve all nodes from the active MIDAS Civil model.
- Returns:
MIDAS API response containing node coordinates keyed by node ID under
"NODE".- Return type:
- civilpy.structural.midas.get_sections()[source]
Retrieve all section definitions from the active MIDAS Civil model.
- Returns:
MIDAS API response containing section properties keyed by section ID under
"SECT".- Return type:
- civilpy.structural.midas.get_static_loads()[source]
Retrieve all static load case definitions from the active MIDAS Civil model.
- Returns:
MIDAS API response containing static load cases keyed by load case ID under
"STLD".- Return type:
- civilpy.structural.midas.get_supports()[source]
Retrieve all support (constraint) definitions from the active MIDAS Civil model.
- Returns:
MIDAS API response containing nodal boundary conditions keyed by node ID under
"CONS".- Return type:
- civilpy.structural.midas.get_units()[source]
Retrieve the current unit system settings from the active MIDAS Civil model.
- Returns:
MIDAS API response containing force, length, heat, and temperature unit settings.
- Return type:
- civilpy.structural.midas.midas_api(method, command, body=None)[source]
Make a request to the MIDAS API and return the response as a JSON object
- Parameters:
method (str - 'GET', 'PUT', 'POST' or 'DELETE')
command (str - The particular method within the API you want) – to target, such as ‘db/elem’ for the elements you want to access
body (dict - The body of the request you want to send to the api, usually contains) – the values you want MIDAS to update with
- Return type:
response.json - the response from the MIDAS API
- civilpy.structural.midas.parse_result_table(response, table_name=None)[source]
Flatten a MIDAS
POST /post/TABLEresponse into a list of row dicts.MIDAS returns
{<table>: {"HEAD": [col, ...], "DATA": [[val, ...], ...]}}. Whentable_nameis omitted the first block that carriesHEAD/DATAis used, soparse_result_table(resp)works without knowing the key. Cell values are left as MIDAS sent them (usually strings); cast withcolumn_values().
- civilpy.structural.midas.setup_output_directory(output_directory: str = '/builds/Dane/civilpy')[source]
Create an
outputsubdirectory inside output_directory if absent.- Parameters:
output_directory (str) – Parent directory path. Defaults to the current working directory.
Note
Prints a status message indicating whether the directory was found or created.
civilpy.structural.midas_models module
Payload builders for the bridge model types MIDAS Civil NX is built for.
These are pure functions: each returns the {id: {...}} assign
dictionaries the MIDAS API expects, so they can be unit-tested with no live
session and then pushed with
civilpy.structural.midas.MidasCivil.put_db(table, assign). They cover
the geometries a line-girder framing plan cannot express:
curved_girder_model()– horizontally curved girders, modelled as concentric chorded beam lines with optional transverse diaphragms (the grid action that makes a curved bridge behave is in the diaphragms).bifurcated_girder_model()– a girder line that splits at a gore node into diverging branches (ramp splits, Y-piers).abutment_connection()– the super-/substructure connection for integral (monolithic, moment-continuous via a rigid link) and semi-integral (girders on bearings, deck continuous) abutments.soil_spring_supports()– nodal foundation springs from the p-y / t-z / q-z stiffnesses incivilpy.geotech.lateral_pileandcivilpy.geotech.axial_load_transfer, so a pier or integral pile bent rests on soil rather than on fixed points.
Table schemas (NODE, ELEM, CONS, FRLS, ELNK, RIGD) follow the
/db/* manual pages. The point-spring table name is not yet verified
against a live release – soil_spring_supports() returns the body and
takes the table name as an argument; capture a real one per the live-capture
checklist before relying on it.
Units: geometry in the model’s length unit (feet in the civilpy/BrR default
KIPS/FT system); spring stiffnesses are passed through in model units – use
lb_per_in_to_kip_per_ft() to convert the geotech curves’ lb/in values.
Constraint/release flag strings are DX DY DZ RX RY RZ RW (‘1’ = fixed for
CONS, ‘1’ = released for FRLS), matching the rest of the API client.
- civilpy.structural.midas_models.BDM_908_DESIGN_DB = {'HL-93 tandem': ('AASHTO-LRFD', 'HL-93TDM'), 'HL-93 truck': ('AASHTO-LRFD', 'HL-93TRK'), 'HS20-44 lane': ('AASHTO-STD', 'HS20-44L'), 'HS20-44 truck': ('AASHTO-STD', 'HS20-44')}
The design loads ODOT BDM 908.2 specifies for inventory/operating ratings, as
{label: (STANDARD_CODE, VEHICLE_TYPE_NAME)}. Figures 908.2-1/-2 reproduce the standard AASHTO definitions unchanged (HS20 lane 0.640 klf with the 18 kip moment / 26 kip shear concentrated loads; HL-93 tandem 25 kip axles at 4 ft), so the DB entries apply.
- civilpy.structural.midas_models.BDM_908_LANE_LOADS = frozenset({'HS20-44 lane'})
Entries in
BDM_908_DESIGN_DBthat are a LANE load rather than a truck or tandem. BDM 924.4.C: “Dynamic load allowance shall only be applied to the truck or tandem portion of HL93 loading (dynamic load allowance shall not be provided to the lane portion).” These get IM = 0.
- civilpy.structural.midas_models.BDM_908_STANDARD_DB = {'2F1': ('OHDOT LOAD', 'OH Legal load 2F1'), '3F1': ('OHDOT LOAD', 'OH Legal load 3F1'), '4F1': ('OHDOT LOAD', 'OH Legal load 4F1'), '5C1': ('OHDOT LOAD', 'OH Legal load 5C1'), 'EV2': ('FAST ACT EV LOADS', 'Type EV2'), 'EV3': ('FAST ACT EV LOADS', 'Type EV3'), 'SU4': ('AASHTO LEGAL/PERMIT LOAD', 'AASHTO Posting load SU4'), 'SU5': ('AASHTO LEGAL/PERMIT LOAD', 'AASHTO Posting load SU5'), 'SU6': ('AASHTO LEGAL/PERMIT LOAD', 'AASHTO Posting load SU6'), 'SU7': ('AASHTO LEGAL/PERMIT LOAD', 'AASHTO Posting load SU7'), 'Type 3': ('AASHTO LEGAL/PERMIT LOAD', 'AASHTO Legal Type 3'), 'Type 3-3': ('AASHTO LEGAL/PERMIT LOAD', 'AASHTO Legal Type 3-3'), 'Type 3S2': ('AASHTO LEGAL/PERMIT LOAD', 'AASHTO Legal Type 3S2')}
Midas standard-DB entries for the ODOT BDM 908.3 rating vehicles, as
{civilpy name: (STANDARD_CODE, VEHICLE_TYPE_NAME)}. Strings are read back off a live model where dane added them through the UI (2026-07-28). Note the spaces: the online API manual renders these with all spaces stripped (“AASHTOLegalType3”, “OHDOTLOAD”), which is a rendering artifact – those forms are wrong and, because the API does not validate names, would store cleanly and then apply ZERO load silently. Always confirm nonzero envelopes after the first analysis.The two ODOT state permit loads (S-PL60T/S-PL65T) have no DB entry at all. Build those with
midas_vehicle_payload()– but note the IM caveat there.Warning
These map a BDM vehicle NAME to a Midas DB entry; they do not assert that Midas’s axle train matches the current BDM figure. Midas’s built-in definitions are a vendor transcription frozen at some past spec revision – their ODOT standard box sections are known to be out of date, and the same risk applies here. A DB vehicle also hides its axle data: the stored record carries only the type name, so the API cannot read back what Midas will actually apply. Verify each against
RATING_VEHICLES(which IS checked against BDM Figures 908.3-1..-5) before using the DB entry for a rating.
- civilpy.structural.midas_models.DUMMY_PROPS = {'DEN': 0.0, 'ELAST': 144.0, 'POISN': 0.3, 'THERMAL': 0.0}
A weightless, near-rigid-free material for dummy elements (traffic lane lines). They exist to carry a lane definition and must not attract load or add stiffness: 1 ksi and zero density.
- civilpy.structural.midas_models.ROD_STEEL_PROPS = {'DEN': 0.49, 'ELAST': 4176000.0, 'POISN': 0.3, 'THERMAL': 6.5e-06}
A709 Grade 50 is the default steel; tie rods are ASTM A307 Grade 307A. Same modulus and density – the grade matters for capacity, not for the stiffness this model carries.
- civilpy.structural.midas_models.STEEL_PROPS = {'DEN': 0.49, 'ELAST': 4176000.0, 'POISN': 0.3, 'THERMAL': 6.5e-06}
A709 Grade 50 steel in KIPS/FT model units (E in ksf, density in kcf) – the structural-steel property block both exporters share.
- civilpy.structural.midas_models.abutment_connection(kind: str, girder_end_nodes: Iterable[int], seat_node: int, *, bearing_stiffness: list[float] | None = None, link_start: int = 1) dict[source]
Super-/substructure connection at an abutment.
kind="integral"– girders are cast monolithically into the abutment cap: a rigid link (/db/RIGD) makesseat_nodethe master and every girder end a slave for all six DOF, so the connection is moment-continuous and thermal movement is taken by the (flexible) pile bent below – give that bentsoil_spring_supports(), not fixed bases.kind="semi-integral"– the deck/backwall is continuous but the girders bear on bearings at the seat: an elastic link (/db/ELNK,LINK="GEN") per girder carries vertical load stiffly and frees longitudinal translation and rotation.bearing_stiffnessis theSDRvector[kdx, kdy, kdz, krx, kry, krz]in model units; the default is a near-rigid vertical (kdz) bearing, soft elsewhere.Returns a dict of
/db/*assign bodies to merge into the model ({"RIGD": ...}or{"ELNK": ...}).
- civilpy.structural.midas_models.bifurcated_girder_model(stem_length: float, stem_segments: int, branches: list[dict], *, stem_offset: float = 0.0, matl: int = 1, sect: int = 1, node_start: int = 1, elem_start: int = 1) dict[source]
A girder line that runs straight for
stem_lengththen splits at a gore node into diverging branches.The stem runs along +X from
(0, stem_offset)to(stem_length, stem_offset)instem_segmentsbeams; the last stem node is the gore. Each entry inbranchesis{"length", "end_offset", "segments"}– a branch from the gore to(stem_length + length, end_offset), straight-line interpolated.Returns
{"NODE", "ELEM", "gore_node", "branch_end_nodes", "meta"}.
- civilpy.structural.midas_models.circular_curve_nodes(radius: float, central_angle_deg: float, n_segments: int, girder_offsets: Iterable[float], *, z: float = 0.0, node_start: int = 1) tuple[dict, dict][source]
Nodes for girders following a horizontal circular curve.
Each girder is a concentric arc: girder
gat radialoffsetrides radiusradius + offset(offset positive toward the outside of the curve). The arc sweepscentral_angle_deginn_segmentsequal steps; the curve centre is at the origin and station 0 lies on the +Y axis, soX = r*sin(theta)andY = r*cos(theta).Returns
(assign, grid)whereassignis the/db/NODEbody{id: {"X", "Y", "Z"}}andgridmaps(girder_index, station_index) -> node_idfor wiring elements.
- civilpy.structural.midas_models.concrete_elastic_modulus_ksf(fc_psi: float, unit_wt_pcf: float = 145.0) float[source]
Concrete elastic modulus in kips/ft^2 from
f'c(psi).AASHTO LRFD 5.4.2.4 / ACI 318:
Ec = 33000 * K1 * (wc/1000)^1.5 * sqrt(f'c)withwcin kcf andf'cin ksi givesEcin ksi; convert to ksf (x144) for a model whose DIST unit is feet. Forf'c= 4500 psi,wc= 145 pcf this is ~3952 ksi, the value MIDAS should hold for an SB-1-24 slab – not the ~29000 ksi steel modulusmidas_payloadsotherwise defaults to.
- civilpy.structural.midas_models.concrete_material_block(name: str = 'Class-S-4500', *, matl_id: int = 1, fc_psi: float = 4500.0, unit_wt_pcf: float = 145.0, length_unit: str = 'ft') dict[source]
A
/db/MATLUSER concrete material withEcfromconcrete_elastic_modulus_ksf().ELASTandDENare carried in the model’s own length unit – ksf and kcf whenlength_unitis"ft", ksi and kip/in^3 when it is"in". MIDAS applies one length unit model-wide, so a material block built for feet inside an inch model overstatesEcby 144x and the density by 1728x, with nothing in the response to say so: pass the hub’sunits.length.
- civilpy.structural.midas_models.constraint_assign(cons_by_id: dict[int, str]) dict[source]
Wrap
{node_id: "DX..RW" flag string}into the/db/CONSbody{node_id: {"ITEMS": [{"ID": 1, "CONSTRAINT": flags}]}}.
- civilpy.structural.midas_models.curved_girder_model(radius: float, central_angle_deg: float, n_segments: int, girder_offsets: Iterable[float], *, matl: int = 1, sect: int = 1, diaphragm_sect: int | None = None, node_start: int = 1, elem_start: int = 1) dict[source]
A horizontally curved multi-girder model: concentric chorded girder lines plus, when
diaphragm_sectis given, a transverse diaphragm between adjacent girders at every station.Returns
{"NODE": ..., "ELEM": ..., "grid": ..., "meta": ...}whereNODE/ELEMare/db/*assign bodies. Chorded straight beams approximate the curve (standard practice); refinen_segmentsuntil the chord offset is acceptable.
- civilpy.structural.midas_models.hub_section_material_blocks(model, *, sect_start: int = 1, matl_start: int = 1, default_grade: str = 'Grade 50', length_unit: str | None = None, db_name: str | None = 'AISC10(US)') dict[source]
Assign a real
SECTper distinct AISC shape and aMATLper distinct grade from the hub’s elements (stage G5 – replaces the single placeholder SECT/MATL thatmidas_payloadsemits by default).Every shape is sent as a reference into MIDAS’s own
db_namesection database (default"AISC10(US)") rather than a re-entered set of dimensions – seerolled_i_section_block(). Passdb_name=Noneif the model’s shapes are built-up/historic sections with no library entry;length_unit(defaulting to the model’s ownunits.length) only matters in that fallback, to keep section dimensions consistent with theNODEcoordinates and theUNITtable’s DIST.Returns
{"SECT", "MATL", "sect_by_shape", "matl_by_grade", "elem_assign"}whereelem_assignmaps eachElement.idto its(sect_id, matl_id)so the caller can wire real sections onto elements. Elements with nosectionlabel getsect_id=None(keep the placeholder for those).
- civilpy.structural.midas_models.lb_per_in_to_kip_per_ft(k_lb_per_in: float) float[source]
Convert a spring constant from lb/in (the geotech curve unit) to kip/ft (the KIPS/FT model unit):
k[kip/ft] = k[lb/in] * 12 / 1000.
- civilpy.structural.midas_models.load_oh_vehicles(client, *, im_percent: float = 33.0, include_design: bool = True, use_standard_db: bool = False, replace: bool = False) dict[source]
Push every vehicle ODOT BDM Section 908 specifies into a live model.
Loads exactly the BDM set and nothing else – the ten commercial legal vehicles and two emergency vehicles of 908.3, the two state permit loads of 908.3.3, and (with
include_design) the 908.2 inventory/operating design loads. Vehicles Midas offers but the BDM does not call for – the AASHTO National Rating Load, the H15/H20/HS15/ HS25/AML standard set, Ohio 4F1 – are deliberately excluded.- Parameters:
client – A live
MidasCivil.im_percent –
Dynamic load allowance written onto standard-DB records, per BDM 924.4.A (33% for all non-buried bridges). Two rules from that article are applied automatically: lane loads get IM = 0 (924.4.C,
BDM_908_LANE_LOADS), and the state permit loads get IM = 0 (924.4.E allows the allowance to be ignored for permit loads under controlled conditions). The rest of 924.4 is bridge-specific and must be handled by the caller: 15% for fatigue (B), no IM on wood components (D), and the buried- structure reduction IM = 33(1 - 0.125*DE) (F).The value is silently discarded on user-defined records – see
midas_vehicle_payload()– so whenuse_standard_dbis False the returned"im_applied"is False and the allowance must be applied downstream.include_design – Also load the 908.2 HL-93 / HS20 design loads.
use_standard_db – Reference Midas’s built-in definitions instead of writing explicit axle trains. Default False: the built-ins are a vendor transcription frozen at some past spec revision (Midas’s ODOT standard box sections are demonstrably out of date), a DB record stores only its type name so the API cannot read back what will actually be applied, and an unresolved name applies ZERO load silently. The user-defined axle trains come from
RATING_VEHICLES, which is checked against BDM Figures 908.3-1..-5. The two permit loads have no DB entry and are always user-defined.replace – Delete every vehicle already in the model first.
- Returns:
{"pushed": {id: label}, "im_applied": bool, "user_defined": [...], "standard_db": [...]}.- Return type:
- civilpy.structural.midas_models.midas_payloads(model: StructuralModel, *, node_start: int = 1, elem_start: int = 1, material_name: str = 'A709-50', db_name: str | None = 'AISC10(US)') dict[source]
Serialize a
StructuralModelto MIDASPUT /db/*assign bodies – the Rhino -> Midas payload step.Pure (no live session): returns
{table: assign}in send orderUNIT, MATL, SECT, NODE, ELEM, CONS, STLD, CNLD. The hub’s stable string ids are mapped to the 1-based integer ids MIDAS uses (node insertion order); full 6-DOF restraints become the 7-charCONSflag string viaRestraint.to_constraint_string(), sofix_z/rx/ry/rzreach MIDAS intact. Loads are grouped intoSTLDcases with their nodal forces and moments inCNLD.Every element gets a real
SECTper distinct AISC shape label (hub_section_material_blocks()) and a realMATLper distinct grade; an element with no shape label falls back to the placeholder square section (still real steel material) so it always references a valid section. By default eachSECTreferences the shape directly out of MIDAS’s owndb_namedatabase ("AISC10(US)") instead of re-entering dimensions; passdb_name=Nonefor built-up/historic shapes with no library entry.Note
The
CNLDconcentrated-nodal-load layout follows the API manual but is unverified against a live Civil NX release – check the send report’s errors first when debugging (same caveat as themidas_modelsbuilders).
- civilpy.structural.midas_models.midas_standard_vehicle(type_name: str, *, name: str | None = None, standard_code: str = 'AASHTO-LRFD', im_percent: float = 33.0) dict[source]
A
/db/mvhlstandard-DB vehicle record.Verified live 2026-07-27:
STANDARD_CODEis REQUIRED for the DB reference to resolve – without it the record stores cleanly but the vehicle applies zero load, silently. LRFD DB names include “HL-93TRK”, “HL-93TDM”, “HS20-FTG”;standard_code“OHDOT LOAD” reaches the Ohio legal/permit set (“OH Legal load 5C1”, 2F1, 3F1, 4F1 – the BDM’s EV vehicles are absent, usemidas_vehicle_payload()for those).
- civilpy.structural.midas_models.midas_vehicle_payload(vehicle, *, lane_load_klf: float | None = None, plm_kip: float = 0.0, plv_kip: float = 0.0, im_percent: float = 0.0, length_unit: str = 'in') dict[source]
A
/db/mvhluser-defined Truck/Lane vehicle record from aRatingVehicle– the way to run vehicles Midas’s DB lacks (ODOT’s FAST-Act EV2/EV3, custom permit trains).Schema verified live 2026-07-27: axles ride a
LOAD_ITEMSarray of{POINT_LOAD, POINT_DIST}(distance to the NEXT axle; kips and model length units); the lane component and concentrated moment/shear loads (PLM/PLV) sit inVEH_DEFAULT.lane_load_klfdefaults to the vehicle’s own definition.length_unitmust match the model’sUNITDIST – axle spacings and the lane load are carried in it, so a truck built for inches and pushed to a model in feet is 12x too long, stores without complaint, and simply produces the wrong envelope. Read it off the model withmodel_length_unit()rather than assuming.Warning
im_percentis silently discarded for user-defined vehicles. Verified live 2026-07-28: a standard-DB record (midas_standard_vehicle()) storesVEH_DEFAULT.DYN_LOAD_ALLOWANCE, but on aTruck/Laneuser vehicle Midas drops the field entirely – it is absent from the record on read-back, i.e. IM = 0. Apply the dynamic load allowance downstream instead (moving-load-case scale factor, or the load combination), or the live load effect will be low by the full IM. The parameter is kept so the axle loads stay nominal and auditable against the source figure.
- civilpy.structural.midas_models.model_length_unit(client, default: str = 'in') str[source]
The live model’s
UNITtable DIST, lower-cased ("in","ft", …).Axle spacings, section coordinates, material moduli and densities are all carried in this one unit, and nothing in an API response says which it is – a vehicle built in feet and pushed to an inch model stores cleanly and then runs a truck 12x too short. Read it, don’t assume it.
- civilpy.structural.midas_models.placeholder_section_block(*, sect_id: int = 1, side_ft: float = 1.0, name: str = 'PLACEHOLDER-1ft-SQ') dict[source]
A solid-square
/db/SECTplaceholder so exported elements reference a valid section. Swap to the real shape inside Civil NX for flexure – the equal-area square keeps axial stiffness honest meanwhile (same convention asTrussBridge).
- civilpy.structural.midas_models.push_midas(model: StructuralModel, midas=None, **client_kwargs) dict[source]
Send a hub to a live Civil NX session (built from
~/secrets.jsonwhenmidasis not given). Pushes eachmidas_payloads()table in order, keeps going on errors, and returns{table: {"sent": n} | {"error": msg}}– the same report shape asTrussBridge.to_midas.
- civilpy.structural.midas_models.rolled_i_section_block(shape_label: str, *, sect_id: int = 1, length_unit: str = 'in', db_name: str | None = 'AISC10(US)') dict[source]
A
/db/SECTbody for a rolled I/H shape.By default this references the shape directly out of MIDAS’s own section database (
db_name="AISC10(US)",DATATYPE=1) rather than re-entering dimensions as a user-input section:shape_label(e.g."W24X104") must match a name in that database, and MIDAS – not civilpy – is then the source of truth for the section properties used in analysis. This is the correct choice for standard rolled shapes; civilpy’s ownsteel.Wdimensions are not sent at all in this path. Confirmed against a live Civil NX round-trip:GET /db/SECTechoed back exactly thisSECT_BEFOREshape for a section entered as “DB/Shape -> AISC10(US) -> W24X104” in the Civil NX UI.Pass
db_name=Nonefor a built-up or historic shape with no library entry – this falls back to a user-input (DATATYPE=2) section with dimensions pulled fromsteel.W(the AISC database module), the previous default behavior.length_unitis a Pint unit string ("in","ft", …) and must match the model’s own ``UNIT`` table DIST in that fallback – MIDAS applies one length unit to every geometric quantity in the model, section dimensions included, so a mismatch silently scales the section by the conversion factor (inches sent while the model is in feet gives a 12x oversized, self-intersecting section). Callers building a full model payload should pass the model’s ownunits.length(seehub_section_material_blocks()); the"in"default is only for standalone use.
- civilpy.structural.midas_models.soil_spring_supports(node_springs: dict[int, list[float]], *, table: str = 'SPRING', spring_type: str = 'LINEAR') dict[source]
Nodal foundation springs from per-node stiffness vectors.
node_springsmapsnode_id -> [kdx, kdy, kdz, krx, kry, krz]in model units (convert the geotech curves’ lb/in values withlb_per_in_to_kip_per_ft()). Typicallykdx/kdycome from the p-y secant modulus (civilpy.geotech.lateral_pile),kdzfrom the t-z springs and the q-z tip spring (civilpy.geotech.axial_load_transfer).Returns the assign body
{node_id: {"ITEMS": [{"ID": 1, "TYPE": ..., "SDR": [...]}]}}. The MIDAS point-spring table name is unverified – pass the live-confirmedtabletoMidasCivil.put_db(table, body)(see the live-capture checklist).
- civilpy.structural.midas_models.solid_rect_section_block(width: float, height: float, *, sect_id: int = 1, name: str | None = None) dict[source]
A solid-rectangle (
SB)/db/SECTbody,widthxheightin the model’s length unit.This is the honest section for an equivalent-strip slab beam (a 1 ft wide x thickness deep concrete strip), where an AISC I-shape reference makes no sense. Dimension order matches the placeholder square:
vSIZE = [H, B]is MIDAS’s [depth, width] for the SB shape.
- civilpy.structural.midas_models.solid_round_section_block(diameter: float, *, sect_id: int = 1, name: str | None = None) dict[source]
A solid-round (
SR)/db/SECTbody – the honest section for a tie rod or anchor dowel, which is a bar, not a rectangle.
- civilpy.structural.midas_models.steel_material_block(name: str = 'A709-50', *, matl_id: int = 1, props: dict | None = None, length_unit: str = 'ft') dict[source]
The
/db/MATLassign body for a USER-defined steel material.STEEL_PROPSis stated in KIPS/FT;length_unitrescalesELAST(force/length^2) andDEN(force/length^3) into the model’s own length unit – seeconcrete_material_block()for why that matters.
- civilpy.structural.midas_models.thickness_block(t_ft: float, *, thik_id: int = 1, name: str | None = None) dict[source]
A
/db/THIKvalue-thickness body (t_ftin the model length unit), the plate-element analogue of a beamSECT. A plateELEMreferences this id through its ``SECT`` field (verified against a live Civil NX release: the plate body is{"TYPE":"PLATE","MATL":m,"SECT":<thik id>, "NODE":[n1..n4,0,0,0,0],"ANGLE":0,"STYPE":1}).
civilpy.structural.mohrs_circle module
Mohr’s circle for plane stress: principal stresses, maximum shear, and the classic circle diagram with the rotated-element companion sketch.
Follows the same conventions as civilpy.structural.beam_bending:
plot methods accept an optional ax (a new figure is created when
omitted) and return the matplotlib figure so they drop straight into
Jupyter notebooks. Stresses are sign-convention standard: tension
positive, and a positive tau_xy acts upward on the right (+x) face.
The shear axis is drawn positive-down so clockwise rotation of the
physical element matches counterclockwise travel around the circle, the
way it’s taught.
Examples
>>> mc = MohrsCircle(sigma_x=80, sigma_y=20, tau_xy=30)
>>> round(mc.sigma_1, 2), round(mc.sigma_2, 2)
(92.43, 7.57)
>>> round(mc.tau_max, 2)
42.43
>>> round(mc.theta_p_deg, 2)
22.5
>>> sx, sy, txy = mc.stresses_at(22.5)
>>> round(sx, 2), round(txy, 6)
(92.43, 0.0)
- class civilpy.structural.mohrs_circle.MohrsCircle(sigma_x: float, sigma_y: float, tau_xy: float, units: str = 'ksi')[source]
Bases:
objectPlane-stress state and its Mohr’s circle.
Parameters are the normal stresses on the x and y faces and the shear
tau_xy, in any consistent stress unit (the unit label used on the plots can be set withunits).- plot(ax=None, show_out_of_plane: bool = False, theta_deg: float | None = None)[source]
Draw the Mohr’s circle with the X/Y stress points, principal stresses, and maximum shear labeled.
show_out_of_planeadds the two sigma_3 = 0 circles governing the absolute maximum shear;theta_degmarks the stress point for an element rotated by that angle. Returns the figure.
- plot_element(theta_deg: float = 0.0, ax=None)[source]
Sketch the square stress element rotated
theta_degwith its normal- and shear-stress arrows labeled. Returns the figure.
- stresses_at(theta_deg: float) tuple[float, float, float][source]
Stress components on an element rotated
theta_degcounterclockwise: (sigma_x’, sigma_y’, tau_x’y’).
- property tau_abs_max: float
Absolute maximum shear including the out-of-plane circles (plane stress, sigma_3 = 0).
civilpy.structural.moment_distribution module
Hardy Cross moment distribution for prismatic continuous beams, with the iteration table (the part they make you do by hand) and the final moment diagram.
Sign convention: counterclockwise end moments positive (the standard moment-distribution convention); the plotted bending-moment diagram is converted to the sagging-positive beam convention.
Examples
Two equal 20-ft spans, 2 klf everywhere, pinned ends:
>>> md = MomentDistribution()
>>> md.add_span(length=20, w=2.0)
>>> md.add_span(length=20, w=2.0)
>>> moments = md.solve()
>>> round(-moments[1][0], 1) # support moment = wL^2/8 = 100 (hogging)
100.0
- class civilpy.structural.moment_distribution.MomentDistribution(left_fixed: bool = False, right_fixed: bool = False)[source]
Bases:
objectContinuous prismatic beam on simple supports (ends optionally fixed), loaded with a uniform load and/or one point load per span.
- add_span(length: float, ei: float = 1.0, w: float = 0.0, point_load: tuple[float, float] | None = None)[source]
wis a uniform load (klf, downward positive);point_load= (P kips, a ft from the span’s left end).
- moment_at(span_index: int, x: float) float[source]
Sagging-positive bending moment at
xft along a span, superposing the simple-span moment and the end-moment gradient.
civilpy.structural.pier module
Multi-column pier (bent) substructure.
A PierCap is the cap beam spanning the columns: it carries the
girder bearing reactions as point loads plus its own self weight and is
analyzed as a continuous beam on the columns (rigid vertical supports) with
a direct-stiffness solver, giving the design shear / moment envelope and the
axial load delivered to each column. Its flexural and shear design reuse
the AASHTO LRFD reinforced-concrete resistance functions.
A MultiColumnBent puts the cap on top of PierColumn
columns, distributes the vertical reactions and a lateral force among the
columns, and checks each column with the P-M interaction and
moment-magnification routines already in
civilpy.structural.aashto.lrfd.columns – a native build, not an
RC-Pier automation.
Units: kip, inch, ksi (matching the LRFD column/concrete checks). Seismic demand is out of scope here; supply the lateral force from the governing non-seismic combination.
- class civilpy.structural.pier.BeamSolution(x: ndarray, moment: ndarray, shear: ndarray, reactions: dict)[source]
Bases:
objectContinuous-beam results: nodal coordinates and the moment / shear diagrams plus the support reactions.
- class civilpy.structural.pier.BentResult(column_axials: list[float], column_shears: list[float], column_moments: list[float], cap_solution: BeamSolution, pm_checks: list[CheckResult])[source]
Bases:
objectPer-column demands and checks for a multi-column bent.
- cap_solution: BeamSolution
- pm_checks: list[CheckResult]
- class civilpy.structural.pier.MultiColumnBent(cap: PierCap, columns: list[PierColumn], lateral_force: float = 0.0, column_dead_load: float | list[float] = 0.0)[source]
Bases:
objectA pier cap on multiple columns, with vertical girder reactions and a lateral force distributed to the columns by their stiffness.
- analyze() BentResult[source]
Distribute loads to the columns and run each column’s P-M check (with the moment magnified for slenderness).
- class civilpy.structural.pier.PierCap(length: float, width: float, depth: float, column_positions: list[float], loads: list[~civilpy.structural.pier.PointLoad] = <factory>, f_c: float = 4.0, f_y: float = 60.0, unit_weight: float = 8.680555555555556e-05, e_concrete: float | None = None)[source]
Bases:
objectPier cap beam.
length(in),width/depththe cross section (in),f_c/f_y(ksi).column_positionsare the support x-locations;loadsthe girder bearing reactions;unit_ weightthe concrete weight (kci, kip/in^3) for self weight.- analyze(n_per_span: int = 12) BeamSolution[source]
Continuous-beam analysis for the design force envelope and the column reactions.
- flexure_check(a_s: float, cover: float = 3.0, bar_dia: float = 1.128, solution: BeamSolution | None = None) CheckResult[source]
Flexural check at the maximum cap moment, reusing
rc_rectangular_flexural_resistance().a_sis the tension steel (in^2).
- shear_check(a_v: float = 0.0, s: float = 12.0, cover: float = 3.0, bar_dia: float = 1.128, solution: BeamSolution | None = None) CheckResult[source]
Shear check at the maximum cap shear, reusing
rc_shear_resistance().
- class civilpy.structural.pier.PierColumn(height: float, layers: list[RebarLayer], f_c: float = 4.0, f_y: float = 60.0, b: float | None = None, h: float | None = None, diameter: float | None = None, spiral: bool = False, fixity: Literal['fixed-fixed', 'fixed-free'] = 'fixed-fixed')[source]
Bases:
objectOne bent column.
height(in) is the clear height; the section is rectangularbxhor circulardiameter(in) withlayersof longitudinal bars;f_c/f_y(ksi).fixityis"fixed-fixed"(cap restrains the top against rotation) or"fixed-free"(cantilever).- axial_resistance(p_u: float | None = None) CheckResult[source]
- fixity: Literal['fixed-fixed', 'fixed-free'] = 'fixed-fixed'
"fixed-fixed"(cap restrains rotation) or"fixed-free"(cantilever).- Type:
End restraint for slenderness
- lateral_moment(shear: float) float[source]
Column moment (kip-in) from a column shear
shear(kip): V*L/2 fixed-fixed, V*L cantilever.
- lateral_stiffness(e_concrete: float) float[source]
Translational stiffness of the column top (kip/in): 12EI/L^3 for a fixed-fixed column, 3EI/L^3 for a cantilever.
- layers: list[RebarLayer]
- magnified_moment(p_u: float, m_u: float, k_factor: float = 1.2, e_concrete: float | None = None) tuple[float, CheckResult][source]
Slenderness-magnified moment (kip-in). The critical buckling load uses the cracked-section
EI = 0.25*Ec*Ig(5.6.4.3) and the effective lengthk_factor*height. Returns the magnified moment and the magnifier CheckResult.
- pm_check(p_u: float, m_u: float) CheckResult[source]
P-M interaction adequacy at factored
p_u(kip) andm_u(kip-in), via the strain-compatibility diagram.
- class civilpy.structural.pier.PointLoad(x: float, p: float)[source]
Bases:
objectA downward point load
p(kip) at positionx(in) along the cap, measured from the left end.
- civilpy.structural.pier.solve_continuous_beam(length: float, ei: float, supports: list[float], point_loads: list[PointLoad] | None = None, udl: float = 0.0, n_per_span: int = 12) BeamSolution[source]
Solve a continuous beam of span
length(in) and stiffnessei(kip-in^2) on rigid verticalsupports(x positions, in), under downwardpoint_loads(kip) and a uniform loadudl(kip/in).The beam is discretized between breakpoints (supports, load points, ends), every segment subdivided into
n_per_spanelements so nodal moments capture the in-span peaks. Returns the moment / shear diagrams and the support reactions.
civilpy.structural.placement module
Placement and multi-representation contract for bridge components (A3).
This module defines the base classes for bridge components that can be placed on an Alignment and Terrain, and emit multiple representations (Rhino/MIDAS).
- class civilpy.structural.placement.Bridge(alignment: Alignment, terrain: Terrain | None = None)[source]
Bases:
BridgeComponentA collection of bridge components forming a complete structure.
- add_component(component: BridgeComponent, station: float, offset: float = 0.0)[source]
- structural_model() StructuralModel[source]
Returns the analysis hub representation (StructuralModel).
- class civilpy.structural.placement.BridgeComponent(*args, **kwargs)[source]
Bases:
ProtocolProtocol for a bridge component that can be placed in a CDE.
- structural_model() StructuralModel[source]
Returns the analysis hub representation (StructuralModel).
- class civilpy.structural.placement.PlacedComponent(component: BridgeComponent, placement: Placement)[source]
Bases:
objectWraps a component with its placement information.
- property frame
Returns the coordinate frame at the placement (origin, tangent, normal).
- structural_hub() StructuralModel[source]
Returns the structural model spoke for this component.
- class civilpy.structural.placement.Placement(alignment: Alignment, station: float, offset: float = 0.0, terrain: Terrain | None = None)[source]
Bases:
objectDefines where a component sits relative to the site’s project geometry.
- property elevation
Returns the elevation at this placement (alignment profile or terrain).
- property point
Returns the 3D point in global coordinates.
civilpy.structural.project_spec module
Storable project records — the project-object schema authority.
The historical-record system stores one “project object” per construction
contract: a thin ProjectRecord parent (PID, the SFNs it touches,
where and when it sold) composed with fat, per-discipline component
records. The keystone component is EraStandardsRecord — the
standards in force when the plan was sold (CMS spec year, the SCD/plan-
insert/standard-bridge-drawing sheets listed on the title sheet, and the
design-manual editions) — because a sparse historic bridge resolves any
field it does not explicitly know through the era registry, and is
marked as such via Provenance.
Everything follows civilpy.structural.bim_spec exactly: plain
dataclasses of JSON-safe primitives, per-field engineering metadata via
spec_field(), strict
validate() in front of the
schema-free JSONB store, and record_to_dict() round-trips. The application side (snbi_ui) stores the
flattened document plus a few promoted, indexed columns — the same
Bridge/OhioBridge split the BIM phases use.
The similarity pipeline consumes ProjectRecord.features() — a flat
dict of JSON scalars (numbers + categorical strings) versioned by
FEATURES_VERSION, fed to the EllisPCA-pattern engine whose
OneHotEncoder handles the categoricals. Stored vectors are
reproducible: same record + same version → same features.
MOT / real-estate / survey start deliberately thin (the fields a title sheet, MOT notes, and an RE summary reliably yield) and sharpen as the plan-corpus extractors populate them; adding a field is an additive JSON key, never a migration.
- class civilpy.structural.project_spec.EraStandardsRecord(spec_year: int | None = None, scds: tuple[StandardRef, ...] = (), plan_inserts: tuple[StandardRef, ...] = (), sbd_set: tuple[StandardRef, ...] = (), bdm_edition: str | None = None, ld_vol1_edition: str | None = None, ld_vol2_edition: str | None = None, ld_vol3_edition: str | None = None)[source]
Bases:
SpecRecordThe standards regime a project was designed and sold under.
For plan-holding projects this is read off the title sheet, not inferred: the sheet lists the SCDs with revision dates and the CMS spec year. For plan-less historic bridges the era registry supplies the set in force at
spec_year/ the letting date, and every field resolved that way is provenance-markedstandard-default.Manual editions are free-text labels validated against the hand-curated edition registry on the application side (a dozen rows per manual, not a schema concern here).
- plan_inserts: tuple[StandardRef, ...] = ()
- sbd_set: tuple[StandardRef, ...] = ()
- scds: tuple[StandardRef, ...] = ()
- civilpy.structural.project_spec.FEATURES_VERSION = 1
Bump when the meaning of a stored feature changes (not when one is added) — consumers persist vectors keyed by this.
- class civilpy.structural.project_spec.MOTRecord(scheme: str | None = None, n_phases: int | None = None, detour_length_mi: float | None = None, temp_structure: bool = False, barrier_types: tuple[str, ...] = (), lanes_maintained: int | None = None, min_lane_width_ft: float | None = None)[source]
Bases:
SpecRecordMaintenance-of-traffic, at the complexity level MOT notes yield: the scheme, phase count, and what is maintained through the work.
- class civilpy.structural.project_spec.ProjectRecord(pid: str, sfns: tuple[str, ...] = (), district: int | None = None, county: str | None = None, route: str | None = None, letting_date: str | None = None, work_category: str | None = None, era: EraStandardsRecord | None = None, mot: MOTRecord | None = None, real_estate: RERecord | None = None, survey: SurveyRecord | None = None, provenance: Provenance | None = None)[source]
Bases:
ElementRecordOne construction project (a PID / contract sale): the thin parent over the era-standards keystone and the per-discipline components. Bridge components live as the existing per-SFN element records (
BridgeLayoutRecordand friends), joined to this record throughsfns— a project does not restate them.- BIM_TYPE = 'project'
- SUBTYPE = 'record'
- era: EraStandardsRecord | None = None
- features() dict[source]
The flat feature dict the PCA→kNN similarity pipeline consumes: JSON scalars only — numbers for numerics, strings for categoricals (the pipeline’s
OneHotEncoderexpands them), None for genuinely unknown. Keyed byFEATURES_VERSIONso persisted vectors are reproducible.
- provenance: Provenance | None = None
- survey: SurveyRecord | None = None
- class civilpy.structural.project_spec.RERecord(n_parcels: int | None = None, n_takes_fee: int | None = None, n_takes_permanent_easement: int | None = None, n_takes_temporary_easement: int | None = None, n_utility_relocations: int | None = None, n_relocations_residential: int | None = None)[source]
Bases:
SpecRecordReal estate, as the RE summary tabulates it: parcel counts by take type and the utility-relocation load.
- class civilpy.structural.project_spec.StandardRef(code: str, date: str | None = None)[source]
Bases:
SpecRecordOne standard sheet in force on the project: the drawing code as printed on the title sheet (
CSB-1-55,PSID-1-99…) and the revision date printed beside it.date=Nonemeans the title sheet listed the code without a date (or it has not been read yet) — the catalog join resolves it.
- class civilpy.structural.project_spec.SurveyRecord(horizontal_datum: str | None = None, vertical_datum: str | None = None, n_control_monuments: int | None = None, row_monumented: bool | None = None)[source]
Bases:
SpecRecordSurvey control as the title/schematic sheets state it — the datum era matters for georeferencing old plans.
civilpy.structural.ps_i_beam_pipeline module
L1 design + verification of ODOT PSID-1-13 prestressed I-beam lines.
The box-beam slice verifies pre-engineered PSBDD table designs; PSID-1-13
has no companion design-data sheet, so this module both designs the
strand pattern on the sheet’s permissible grid
(civilpy.structural.odot.ps_i_beam.strand_grid()) and re-derives
the governing checks for one interior beam line — the prestressed-I
analog of civilpy.structural.box_beam_pipeline:
HL-93 demands from the same
girder_line_envelope()machinery, distributed with the type-k I-girder factors (LRFD 4.6.2.2.2b/3a,Kgfrom the composite girder/deck geometry);a straight, fully-bonded strand pattern chosen as the smallest even count that passes Service III tension (5.9.2.3.2b) and Strength I flexure (5.6.3) — sheet 10’s 0.6 in Grade 270 low-relaxation strand at 0.217 in^2, jacked to 0.75 f_pu;
elastic shortening (5.9.3.2.3a) + approximate lump-sum time-dependent losses (5.9.3.3);
concrete stresses at transfer (5.9.2.3.1, at the 60-diameter transfer length and midspan) and service (5.9.2.3.2, composite section for loads applied after the deck cures). When the fully-bonded pattern overstresses the beam end at transfer, the designer debonds end strands (outermost bottom-row strands first — the standard’s own remedy, sheet 10 detail item 4) in pairs up to the 5.9.4.3.3 cap (45 % of the total, the 8th-Edition-and-later limit; pass 0.25 for pre-2018 designs); the end check then runs on the bonded subset at its transfer length while midspan keeps the full pattern. Only when the cap can’t fix the end — or midspan release compression itself is over, the true long-span limit at ODOT’s 5.0 ksi
f'ciceiling — does the result carry adebond_noteflag / failed check;release camber (elastic: prestress hog minus self-weight sag at
E_ci).
The WF web locations the sheet marks “must be draped if utilized” are never used by the straight-pattern designer. Units: kip / inch / ksi internally; spans, spacings and uniform loads enter in feet and klf. Simple spans only.
- class civilpy.structural.ps_i_beam_pipeline.CompositeSection(b_eff_in: float, n_deck_beam: float, t_struct_in: float, haunch_in: float, area_in2: float, ybc_in: float, i_in4: float, sbc_in3: float, stc_beam_in3: float)[source]
Bases:
objectTransformed composite section of one interior beam and its deck tributary width. Inches; the deck is transformed into beam concrete by the deck/beam modular ratio. The haunch locates the slab vertically but its area is neglected (conservative on the moduli).
- civilpy.structural.ps_i_beam_pipeline.DECK_FC_KSI = 4.5
ODOT Class QC2 deck concrete
- civilpy.structural.ps_i_beam_pipeline.F_PJ_KSI = 202.5
jacking stress, low-relaxation (5.9.2.2)
- class civilpy.structural.ps_i_beam_pipeline.PSIBeamDesign(section: object, span_ft: float, spacing_ft: float, n_strands: int, pattern: tuple = (), ybar_in: float = 0.0, e_in: float = 0.0, fci_ksi: float = 4.0, fc_ksi: float = 5.5, n_debonded: int = 0)[source]
Bases:
objectA designed strand pattern for one PSID-1-13 beam line.
- class civilpy.structural.ps_i_beam_pipeline.PSIBeamLineChecks(design: PSIBeamDesign, composite: CompositeSection, df_moment: float, df_shear: float, midspan_moments: dict = <factory>, losses: dict = <factory>, stresses: dict = <factory>, checks: dict = <factory>, camber_release_in: float = 0.0, debond_note: str = '')[source]
Bases:
objectEverything
ps_i_beam_line_checks()derives for one interior beam line. Moments in kip-ft; stresses in ksi (compression positive);checksvalues areCheckResult.- composite: CompositeSection
- design: PSIBeamDesign
- civilpy.structural.ps_i_beam_pipeline.composite_section(name: str, spacing_ft: float, *, t_struct_in: float, haunch_in: float, fc_beam_ksi: float, fc_deck_ksi: float = 4.5) CompositeSection[source]
Transformed composite properties for an interior line: effective flange width = beam spacing (4.6.2.6.1), structural deck thickness only (the wearing course carries as DW, not stiffness).
- civilpy.structural.ps_i_beam_pipeline.ps_i_beam_line_checks(name: str, span_ft: float, n_beams: int, *, spacing_ft: float, deck_t_in: float = 8.5, sacrificial_in: float = 1.0, haunch_in: float = 2.0, n_strands: int | None = None, fci_ksi: float = 4.0, fc_ksi: float = 5.5, barrier_klf: float = 0.0, fws_klf: float | None = None, humidity_pct: float = 70.0, max_debond_fraction: float = 0.45) PSIBeamLineChecks[source]
Design (or verify, when
n_strandsis given) one interior PSID-1-13 beam line and re-derive its governing LRFD checks.deck_t_inis the full slab;sacrificial_in(the ODOT monodeck wearing course) is excluded from the composite stiffness but its weight stays in DC1.barrier_klf/fws_klfare bridge-total, shared equally across the beams;fws_klfdefaults to the sheet 10 design loading (60 psf across the deck).fci_ksi/fc_ksidefault to the low end of the sheet 10 designer-selected ranges (conservative for the stress checks).
- civilpy.structural.ps_i_beam_pipeline.structural_model_from_ps_i(name: str, span_ft: float, n_beams: int, *, spacing_ft: float, deck_t_in: float = 8.5, haunch_in: float = 2.0, diaphragms: bool = True, dead_loads: bool = True, barrier_klf: float = 0.0, fws_klf: float | None = None)[source]
Build the
StructuralModelhub for a prestressed I-beam bridge — one line of beam elements per girder broken at the sheet 5 intermediate- diaphragm stations, transverse diaphragm elements there, a pin + roller per line, and DC1/DC2/DW beam loads matchingps_i_beam_line_checks()exactly.Elements carry the PSID section name as their section label plus the published constants in metadata (
section.area_in2/section.i_in4) for a value-type section on the MAPI side.fws_klfis bridge-total; it defaults to the sheet 10 design loading (60 psf overn_beams * spacing_ft).
civilpy.structural.rhino_barrier module
Traffic barriers, bridge railings, portable concrete barriers, lane
markings, and their reinforcement for the girder-line model (stage G7,
the barrier & markings companion to civilpy.structural.rhino_deck).
Where rhino_deck puts a single generic parapet on
each deck edge, this module renders the actual ODOT standard section for a
chosen catalog entry (civilpy.structural.odot.bridge_railing):
concrete parapets in their true shape family – the New Jersey safety shape (vertical toe, lower/upper batter), the single-slope face, or the symmetric F-shape of a freestanding portable concrete barrier (PCB);
steel post-and-beam railings (TST-1 / TST-2) as a low concrete curb carrying vertical posts and horizontal rail tubes;
placeable on the two deck edges, in the median, or along any picked transverse line, so a construction-phase PCB run or a median SBR-2 is authored the same way as an edge parapet.
Each concrete barrier is drawn with a rebar cage – the vertical face bars at
the catalog vertical_bar_spacing plus longitudinal bars – as bar-centerline
curves on the Deck::Rebar layer, tagged gdr.kind=rebar. A companion
build_lane_lines() paints the traffic lane markings (a solid edge line
each side, dashed lane dividers between the 12 ft lanes) as flat strips on the
deck top.
Everything is display geometry tagged gdr.kind=barrier | rebar | lane_line so
the girder reader ignores it; the barrier’s real contribution to the analysis
model is its dc2 line load, carried in the tags exactly as the deck’s parapet
is. rhino3dm is an optional dependency imported lazily.
- civilpy.structural.rhino_barrier.BAR_DIAMETERS_IN: dict[int, float] = {3: 0.375, 4: 0.5, 5: 0.625, 6: 0.75, 7: 0.875, 8: 1.0, 9: 1.128, 10: 1.27, 11: 1.41, 14: 1.693, 18: 2.257}
Standard reinforcing-bar nominal diameters (in), keyed by bar number.
- class civilpy.structural.rhino_barrier.BarrierModel(designation: str, shape_family: str, height_in: float, length_ft: float, n_placements: int, dc2_klf_each: float, n_barrier: int, n_steel: int, n_rebar: int, material: str = '')[source]
Bases:
objectSummary of a generated barrier run: what was placed and the dead load it hands the analysis model. Lengths in the units named; loads in klf.
- civilpy.structural.rhino_barrier.CONCRETE_PCF = 150.0
Reinforced-concrete unit weight (pcf) for barrier dead load.
- civilpy.structural.rhino_barrier.DEFAULT_BARRIER = 'BR-1 (36 in)'
Default barrier designation (36 in New Jersey, TL-4).
- civilpy.structural.rhino_barrier.DEFAULT_COVER_IN = 2.0
Default clear cover to the reinforcing, inches.
- civilpy.structural.rhino_barrier.DEFAULT_OVERHANG_FT = 3.5
Default deck overhang beyond each exterior girder line (ft) – where the edge barriers land when placements are not given explicitly.
- class civilpy.structural.rhino_barrier.LaneLineModel(n_lanes: int, lane_width_ft: float, usable_width_ft: float, n_edge_lines: int, n_divider_lines: int, n_objects: int)[source]
Bases:
objectSummary of generated lane markings.
- civilpy.structural.rhino_barrier.bar_diameter_in(bar_no: int) float[source]
Nominal diameter (in) of a standard
#bar_noreinforcing bar.
- civilpy.structural.rhino_barrier.barrier_dc2_klf(designation: str, *, concrete_pcf: float = 150.0) float[source]
Dead load of one barrier run (klf): the gross concrete
section_areax unit weight for a concrete section, else the steelweight_per_ft.
- civilpy.structural.rhino_barrier.barrier_profile(r, height_ft: float, side: int) list[tuple[float, float]][source]
Cross-section of a concrete barrier as
(offset, z)vertices (ft),offsetmeasured transversely from the placement line andzfrom the deck top.sideis+1/-1for an edge barrier whose back face is on the line and body toward +Y / -Y, or0for a freestanding barrier built symmetric about the line (a PCB or median section).The vertex order winds around the section; the extruder fans its end caps from the centroid, so mild non-convexity (the NJ / F-shape break) is fine.
- civilpy.structural.rhino_barrier.barrier_rebar_curves(prof, y_ref, x0, x1, z0, *, cover_ft, spacing_ft, bar_size, long_spacing_ft)[source]
Bar centerlines for a concrete barrier’s cage: a vertical face bar every
spacing_ftalong X tracing the section inset bycover_ft, plus longitudinal bars everylong_spacing_ftup the inset face. Returns(vertical_pts, longitudinal_pts)– each a list of 3D point lists.
- civilpy.structural.rhino_barrier.build_barriers(source, *, out_path, designation: str = 'BR-1 (36 in)', placements=None, overhang_ft: float = 3.5, deck_top_z_ft: float = 0.0, height_in: float | None = None, rebar: bool = True, cover_in: float = 2.0, long_spacing_in: float = 12.0, concrete_pcf: float = 150.0, unit_system=None) BarrierModel[source]
Render the ODOT standard section for
designationand write it toout_path(a.3dmtheDeckBarriercommand imports).sourceis aGirderBridgeor a path to a girder.3dm.placementsselects where the barrier goes:None– one on each deck edge (y_extent+/-overhang_ft), back face outward;"median"– one freestanding barrier on the bridge centerline;a list of
(y_ft, side)– explicit transverse offsets,sidebeing+1/-1for an edge barrier facing inward / outward or0for a freestanding (symmetric) section such as a PCB.
height_inoverrides the catalog height. Whenrebaris true a bar cage is drawn on theRebarlayer for each concrete barrier (skipped for a steel railing, whose reinforcement lives in its curb detail). Returns aBarrierModel.
- civilpy.structural.rhino_barrier.build_lane_lines(source, *, out_path, n_lanes: int | None = None, lane_width_ft: float = 12.0, line_width_in: float = 4.0, edge_offset_ft: float = 1.0, overhang_ft: float = 3.5, barrier_base_in: float = 18.0, deck_top_z_ft: float = 0.0, dash_len_ft: float = 10.0, gap_len_ft: float = 30.0, unit_system=None) LaneLineModel[source]
Paint traffic lane markings on the deck top and write them to
out_path(a.3dmtheDeckLaneLinescommand imports): a solid edge lineedge_offset_ftinside each barrier face and dashed lane dividers everylane_width_ftbetween them.n_lanesdefaults to the usable width divided bylane_width_ft(rounded). Returns aLaneLineModel.
- civilpy.structural.rhino_barrier.deck_rebar_curves(x0, x1, y0, y1, z_bot, z_top, *, cover_ft, transverse_spacing_ft, longitudinal_spacing_ft)[source]
Top and bottom reinforcing mats for a deck slab spanning
[y0, y1]over[x0, x1]betweenz_botandz_top. Returns a list of(form, points)bar centerlines: transverse bars (run in Y at each X station) and longitudinal bars (run in X at each Y station), in both the top and bottom mat, inset bycover_ft.
- civilpy.structural.rhino_barrier.read_barrier_model(path)[source]
Read the
gdr.kind=barrier | rebar | lane_lineobjects back from a barrier/markings.3dm: a list of dicts withkind,id, and anattrsmap of the kind’sgdr.<kind>.*tags (numeric values asfloat). Round-tripsbuild_barriers()/build_lane_lines().
- civilpy.structural.rhino_barrier.shape_family(r) str[source]
Classify a catalog railing into a geometry family:
"new jersey","single slope","portable"(PCB F-shape),"combination"(full-height concrete barrier + steel tube pedestrian rail on top, e.g. BR-2-15),"steel tube"(post-and-beam on a low curb, e.g. TST-1/TST-2), or"trapezoid"(generic concrete).
civilpy.structural.rhino_bchw module
BrIM emit for ODOT concrete headwalls on precast box culverts (BCHW).
Builds the complete culvert-end assembly the Design Data sheets
(1/6-6/6) tabulate — resolved through
design_headwall():
both wingwalls, placed per headwall type (Type A: both at 45 deg from the culvert centerline; Type B: wall #1 at 45 deg + wall #2 straight along the roadway line, per tabulated skew; Type C: both parallel to the roadway), tops on the sheet’s 2:1 backslope (level for Type C) down to the 2 ft minimum tip;
the foreslope wall — the 6 in / 1’-6” panel that sits on top of the box, leaving the barrel opening clear;
an optional precast box stub (display only — the box itself is a separate ASTM C1433 design) so the opening reads as a culvert;
footings under each wingwall (with the 4’-0” extension past the tip) and the culvert footing strip across the opening, each with its cutoff wall at the stream edge;
the sheet’s reinforcing series as bar centerlines: “X” verticals and “Y” footing dowels at the tabulated size/spacing on the stream face, the #5 @ 18 near-face/horizontal mats, “V”/”W” footing mats and “Z” cutoff bars per the footing design number, and the foreslope-wall bars.
Every object lands on a Culvert::* layer with bim.* /
pay.* / mat.* user text. Quantities come from the sheet
tables, not from the drawn geometry: concrete solids carry their
tabulated cy (whole-assembly values split across the parts) and the
tabulated reinforcing lbs ride on per-part schedule markers, so
pay_item_quantities() reproduces the
sheet’s estimated quantities exactly. Individual bars are visual and
carry rebar.* metadata but no pay block (they would double-count the
schedule markers).
Frame: culvert axis = +x (the barrel runs into +x), headwall face through the origin, z = 0 at the top of footing, y = 0 on the culvert centerline. A Type B/C roadway skew rotates the face line by theta about z.
The record draws through the BCHW Grasshopper component,
Notebooks/Rhino Components/draw_bim_emit.py, and
emit_to_3dm().
- class civilpy.structural.rhino_bchw.BchwEmit(design: ~civilpy.structural.odot.box_culvert_headwall.HeadwallDesign, objects: tuple[~civilpy.structural.rhino_bim.EmitObject, ...], doc_tags: dict[str, str] = <factory>)[source]
Bases:
objectOne culvert-end headwall assembly — duck-compatible with
emit_to_json()andemit_to_3dm().- design: HeadwallDesign
- objects: tuple[EmitObject, ...]
- civilpy.structural.rhino_bchw.bchw_emit(inp: HeadwallInput, *, box_stub_ft: float = 4.0, rebar: bool = True) BchwEmit[source]
Tagged BrIM geometry for one culvert-end headwall assembly, resolved from the Design Data tables (see the module docstring).
box_stub_ftdraws that much display-only precast box behind the headwall (0 skips it);rebar=Falseskips the bar centerlines (the tabulated schedule markers, and therefore the quantities, stay either way).
civilpy.structural.rhino_bim module
BrIM emit layer for the parametric steel-girder bridge.
Turns a BridgeLayout into
tagged, transport-neutral geometry records — the full “source of truth”
model the Rhino document carries: girder solids with true k-fillets, the
crowned deck solid, haunches, welded shear studs, both deck rebar mats,
parapets, bearings, and load plates, each stamped with its
civilpy.structural.bim attribute set (bim.type/bim.id,
bim.scd/bim.scd_year where the part is a standard detail, a
pay.* pay-item block, and a mat.* material block).
The point of the neutral record (same architecture as
rhino_slab) is that the engineering content is
described exactly once; a backend only decides how to draw a prism or a
cylinder and how to stamp a user string, never where a stud goes. Known
backends:
Notebooks/Rhino Components/draw_bim_emit.py— live-document driver (run inside Rhino 8, from the ScriptEditor or over MCP) that consumesemit_to_json().The
odot_bridge_generator_ghpython.pyGrasshopper component shares the samelayout_bridge()layout for its preview geometry.
Coordinates are feet (the hub convention): X = stations along the
layout centerline, Y = transverse (girder 1 at y = 0), Z = 0 at the top
of deck at the crown. Section dimensions in the tags stay in inches
(_in suffixes).
Geometry record kinds
prismA closed planar loop (
points, unrepeated) extruded alongvector. Every solid here is a prism: the bridge is prismatic along its length, so the deck (skew-cut ends), girders (square-cut ends), haunches, parapets, bearings, and plates all emit this way.polylineOpen polyline through
points(rebar; girder centerlines).cylinderpoints = (base, tip)plusradius_ft(shear studs).pointA marker. The
bim.type = bridgemarker carries the bridge-wide parameters, because standalonerhino3dmcannot write or read the RhinoDoc string table (the same contractrhino_slabfollows); a live-document backend additionally mirrors them intodoc.Stringsfor therhino_gdrreader.
The gdr.* analysis contract is preserved untouched: girder
centerlines and bearing points still carry their gdr.* tags (plus a
bim.id back-reference), so a document drawn from this emit remains
readable by civilpy.structural.rhino_gdr and the MIDAS pipeline.
- civilpy.structural.rhino_bim.BEARING_PLY_IN = 0.6
internal elastomer ply thickness
- civilpy.structural.rhino_bim.BEARING_SIDE_IN = 20.0
square elastomeric pad plan side
- class civilpy.structural.rhino_bim.BridgeEmit(inputs: BridgeInput, layout: BridgeLayout, objects: tuple[EmitObject, ...], doc_tags: dict[str, str])[source]
Bases:
objectEverything a backend needs to draw one steel-girder BrIM model.
- inputs: BridgeInput
- layout: BridgeLayout
- objects: tuple[EmitObject, ...]
- of_type(bim_type: str) tuple[EmitObject, ...][source]
The emitted objects whose
bim.typematches.
- class civilpy.structural.rhino_bim.EmitObject(kind: str, layer: str, points: tuple[tuple[float, float, float], ...], tags: dict[str, str] = <factory>, vector: tuple[float, float, float] | None = None, radius_ft: float | None = None)[source]
Bases:
objectOne drawable object, independent of any Rhino API (see the module docstring for the
kindvocabulary).- KINDS = ('prism', 'polyline', 'cylinder', 'point')
- civilpy.structural.rhino_bim.PLATE_SIDE_IN = 21.0
load plate plan side
- civilpy.structural.rhino_bim.SBR1_EMBED_REDUCTION_IN = 1.5
embedment = deck thickness - this
- civilpy.structural.rhino_bim.SBR1_LEG_IN = 12.0
horizontal deck-lap leg of Y601/Y602
- civilpy.structural.rhino_bim.SBR1_TOP_BAR_DROP_IN = 3.0
X401 pair below the rail top
- civilpy.structural.rhino_bim.SBR1_Y601_TOP_BEND_IN = 5.25
Y601 top bend toward the back face
- civilpy.structural.rhino_bim.SBR1_Y602_HOOK_OVER_IN = 7.0
over toward traffic …
- Type:
top hairpin
- civilpy.structural.rhino_bim.STEEL_UNIT_WT_PCF = 490.0
structural steel unit weight, lb/ft^3
- civilpy.structural.rhino_bim.STUDS_PER_ROW = 3
studs across the top flange per row
- civilpy.structural.rhino_bim.STUD_DIA_IN = 0.875
welded stud diameter, 7/8 in typical
- civilpy.structural.rhino_bim.STUD_GAUGE_IN = 3.0
transverse c/c between studs in a row
- civilpy.structural.rhino_bim.STUD_LENGTH_IN = 6.0
stud length — embeds ~4 in into an 8.5 in deck
- civilpy.structural.rhino_bim.STUD_PITCH_IN = 24.0
longitudinal row pitch (preliminary)
- civilpy.structural.rhino_bim.SUB_FC_PSI = 4000.0
Class QC1 substructure concrete
- class civilpy.structural.rhino_bim.SubRebarSpec(stirrup_size: int = 5, stirrup_spacing_in: float = 12.0, column_bar_size: int = 9, column_tie_size: int = 4, column_tie_spacing_in: float = 12.0, wall_bar_size: int = 5, wall_bar_spacing_in: float = 12.0, cover_in: float = 3.0, wall_cover_in: float = 2.0, coating: str = 'epoxy')[source]
Bases:
objectSubstructure reinforcing parameters that the executed designs do not size themselves. The cap’s main (tie) steel comes from the STM bar schedule on the placed
CapBeamand the column verticals from the bent’sRebarLayerarea — everything here is the detailing around them: the stirrups the cap shear check was run with, the bar size the column steel area is broken into, its ties, and the nominal two-face wall mats (temperature/shrinkage-level; final wall design replaces them).
- civilpy.structural.rhino_bim.add_girder_details(emit: BridgeEmit, record, *, apply_to=None) BridgeEmit[source]
A new
BridgeEmitwith oneSteelGirderRecord’s §3a customizations (cross-frames, transverse/bearing/longitudinal stiffeners, and a field splice) appended as tagged geometry — mirroringadd_substructure(), so the merged emit still round-trips throughemit_to_json(),read_bim_tags(), and the quantity rollup.Placement uses the emit’s solved girder lines;
apply_torestricts it to thoseline_no(default: every line). The girder depth comes from the record’s controlling section (catalog or plate). Only the details present on the record are emitted.
- civilpy.structural.rhino_bim.add_substructure(emit: BridgeEmit, sub, *, fc_psi: float = 4000.0, rebar: SubRebarSpec | None = SubRebarSpec(stirrup_size=5, stirrup_spacing_in=12.0, column_bar_size=9, column_tie_size=4, column_tie_spacing_in=12.0, wall_bar_size=5, wall_bar_spacing_in=12.0, cover_in=3.0, wall_cover_in=2.0, coating='epoxy')) BridgeEmit[source]
A new
BridgeEmitwith the substructure appended to the superstructure emit — same doc tags, so the merged record still round-trips throughemit_to_json(),read_bim_tags(), and the quantity rollup.
- civilpy.structural.rhino_bim.emit_to_3dm(emit: BridgeEmit, path, *, version: int = 7, mesh: bool = False) dict[str, int][source]
Bake the emit straight into a
.3dmwith standalonerhino3dm— no Rhino session, no MCP server, no network: the notebook-only path to a real Rhino file. Prisms become capped extrusion breps, studs cylinder breps, rebar/centerlines polyline curves, markers points, on the same colored layer taxonomy as the live-document driver, each object stamped with its full user-text tags. The bridge-wide record rides on thebim.type = bridgemarker (standalonerhino3dmhas no document string table — see thepointrecord kind in the module docstring), soread_bim_tags()/read_bim_quantities()round-trip from the saved file alone. Returns per-layer object counts.mesh=Trueroutes every solid (prisms and stud cylinders) through closed-mesh construction instead of breps — the web-viewer flavor: headlessrhino3dmcannot tessellate breps, so three’s3DMLoaderrenders a brep file empty, while meshes arrive shaded. Tag stamping and read-back are identical in both flavors.Notebooks/Rhino Components/draw_bim_emit.pyremains the live-document twin (same JSON payload viaemit_to_json()) when a running Rhino 8 should host the model instead of a file.
- civilpy.structural.rhino_bim.emit_to_json(emit: BridgeEmit) str[source]
Serialize the emit for
draw_bim_emit.py(a pure-Rhino script — no civilpy import needed inside Rhino, so a stale civilpy in Rhino’s site environment cannot skew the geometry). Layer colors ride along so every backend paints the shared taxonomy identically.
- civilpy.structural.rhino_bim.girder_bridge_emit(inp: BridgeInput, *, scd_year: int | str = 2020, stud_pitch_in: float = 24.0, studs_per_row: int = 3, stud_dia_in: float = 0.875, stud_length_in: float = 6.0, side_cover_in: float = 2.0, rebar_coating: str = 'epoxy', integral_supports: tuple[int, ...] = ()) BridgeEmit[source]
Build the tagged BrIM geometry for one steel-girder bridge.
Shear studs are emitted only for a
compositelayout — they are the physical composite connection, mirroring the toggle the analysis models use.integral_supportslists support-line indices with integral abutments: no load plate / bearing pad is drawn there (the girder ends are cast into the end diaphragm) while the taggedgdr.*support point stays for the analysis reader. Raises whateverlayout_bridge()raises for inputs that violate the ODOT standard-design assumptions.
- civilpy.structural.rhino_bim.i_profile_wh(section, arc_pts: int = 4) list[tuple[float, float]][source]
Closed W-shape outline as
(w, h)pairs in inches,wacross the flange (0 at the web centerline),hup from the bottom face. The web-to-flange k-fillets (no square re-entrant corners) are tessellated witharc_ptspoints each; a section without a catalogedfillet_kfalls back to square corners.
- civilpy.structural.rhino_bim.pay_item_quantities(emit: BridgeEmit) dict[str, dict][source]
Quantity rollup straight from an emit (see
_tag_quantities()).
- civilpy.structural.rhino_bim.read_bim_estimate(path, prices: dict[str, float] | None = None, *, include_hidden: bool = False)[source]
Priced pay-item rollup straight from a saved BrIM
.3dm— the quantity read-back run throughcivilpy.structural.bim.cost_estimate(). Unit prices default to the planning-levelDEFAULT_UNIT_PRICESbook; passpricesto override with project numbers.
- civilpy.structural.rhino_bim.read_bim_quantities(path, *, include_hidden: bool = False) dict[str, dict][source]
Pay-item rollup for a saved BrIM
.3dm(read-back + estimate).
- civilpy.structural.rhino_bim.read_bim_tags(path, *, include_hidden: bool = False) dict[source]
Read a BrIM-tagged
.3dmback: every object carryingbim.typereturns its full user-text dict, and thebim.type = bridgemarker’s tags come back as the bridge-wide record.Components on hidden layers (directly, or under a hidden parent — e.g. a
Legacytree kept for reference) are skipped, so the rollup counts exactly what the Rhino document displays; passinclude_hidden=Trueto read everything. The bridge marker is document metadata and comes back regardless.This is the round-trip half of the source-of-truth contract: the saved Rhino document alone carries enough attributes to regenerate the estimate (and, through the preserved
gdr.*tags, the analysis model) without the session that drew it. Returns{"bridge": {...}, "components": [tags, ...]}.
- civilpy.structural.rhino_bim.stm_overlay_emit(model, geom, layout) tuple[EmitObject, ...][source]
The solved cap strut-and-tie model drawn in place on the
Substructure::STMlayers (ties red, struts blue) — the analysis overlay merged into the main document instead of a separate.3dm.modelis the solvedStrutAndTieModelfrom the cap’sPierCapDesign(its 2D frame: x along the cap in the girder-1-origin coordinates the reactions were given in, y up from the cap bottom);geomthe placed pier/abutment geometry it belongs to. Members carrystm.kind/stm.force_kipand no pay item, so every estimate rollup ignores them.
- civilpy.structural.rhino_bim.substructure_emit(sub, *, fc_psi: float = 4000.0, rebar: SubRebarSpec | None = SubRebarSpec(stirrup_size=5, stirrup_spacing_in=12.0, column_bar_size=9, column_tie_size=4, column_tie_spacing_in=12.0, wall_bar_size=5, wall_bar_spacing_in=12.0, cover_in=3.0, wall_cover_in=2.0, coating='epoxy')) tuple[EmitObject, ...][source]
Tagged BrIM geometry for a placed
SubstructureLayout: pier caps / columns / footings and abutment caps / piles / backwalls / wingwalls on theSubstructure::*layers, with the stepped beam seats under every bearing stack. All cast-in-place concrete measures into the Class QC1 substructure item; piles into the steel-pile item (ft).rebaradds the reinforcing cage: cap main steel from the placed STM tie schedule, column verticals from the bent’s steel area, and the detailing inSubRebarSpec(passNoneto skip).
civilpy.structural.rhino_box_beam module
Prestressed concrete adjacent box-beam bridges in Rhino (stage G9, the
precast-girder companion to the steel line-girder pipeline in
civilpy.structural.rhino_gdr).
Box beams are a different structural system from the steel-girder pipeline:
full-length precast members that sit side by side (spacing = beam width, no
gap) rather than field-spliced curves needing shape transitions. This module
lays out a straight, zero-skew box-beam bridge from three inputs – a
standard box designation (BOX_DESIGNATIONS,
e.g. "CB27-48"), a span, and a beam count – and writes the geometry a
BoxBeamLines Rhino command imports:
one girder-line curve per beam (
gdr.kind=girder,gdr.family=box) so the line-girder-style downstream tooling can still find the framing;the true box-beam solid, built as four wall meshes (top/bottom flange, two webs) using the wall thickness read off PSBD-1-25 sheet 3 (
BOX_WALL_THICKNESS_IN) – a hollow tube, not a solid rectangle, though the void corners are drawn square rather than filleted (cosmetic simplification, same category as the steel pipeline’s display W-shapes);a schematic prestressing tendon centerline per active strand row (2 in / 4 in / 6 in above the soffit, from the PSBDD-1-25 strand pattern) run the full beam length – straight, since box beams debond rather than harp; a full per-strand model is a possible future refinement;
transverse diaphragms at the PSBD-1-25 sheet 4/6 count and (for multiples) evenly-spaced stations between the end offsets (
diaphragm_stations_ft());tie rods at each diaphragm station, at the PSBD-1-25 tie-rod height (
TIE_ROD);a bearing pad footprint at each beam end, sized from the design’s standard pad (
BEARING_PADS);an optional composite topping slab, when the box’s design table calls for one, at the standard structural thickness plus wearing surface.
Skewed layouts are not yet supported (skew_deg stays 0); the PSBD-1-25
skew rules for diaphragm/tie-rod offset (sheet 2/6) are a follow-up. This is
display geometry plus engineering tags, same as the deck/barrier modules; it
does not yet wire a box-beam bridge into the MIDAS payload builder (a
different structural idealization from the line-girder model).
rhino3dm is an optional dependency imported lazily.
- class civilpy.structural.rhino_box_beam.BoxBeamBridgeModel(box: str, beam_type: str, depth_in: float, width_in: float, span_ft: float, n_beams: int, bridge_width_ft: float, n_strands: int, bearing_type: str, composite: bool, self_weight_klf_per_beam: float, n_diaphragms: int, n_beam_solids: int, n_tendons: int, n_tie_rods: int, n_bearing_pads: int, n_diaphragm_objects: int, n_slab: int = 0)[source]
Bases:
objectSummary of a generated box-beam bridge: what was placed and its engineering payload. Lengths in the units named; loads in klf.
- civilpy.structural.rhino_box_beam.CONCRETE_PCF = 150.0
Concrete unit weight (pcf) for the box-beam and topping-slab self-weight.
- civilpy.structural.rhino_box_beam.DIAPHRAGM_THICKNESS_IN = 8.0
Cosmetic diaphragm thickness – PSBD-1-25 gives the count and end offset but not a thickness in the section carried here; this is a display value only, not sourced from the drawing.
- civilpy.structural.rhino_box_beam.build_box_beams(*, out_path, box: str, span_ft: float, n_beams: int, x0_ft: float = 0.0, y0_ft: float = 0.0, concrete_pcf: float = 150.0, unit_system=None) BoxBeamBridgeModel[source]
Lay out a straight, zero-skew adjacent box-beam bridge and write it to
out_path(a.3dmtheBoxBeamLinescommand imports).boxis a standard designation fromBOX_DESIGNATIONS(e.g."CB27-48", composite; or"B27-48", non-composite);span_ftmust be one of that box’s cataloged spans (box_beam_design()raises aKeyErrornaming the valid spans otherwise). Beams are placed edge to edge starting at(x0_ft, y0_ft), each spanning the full width (no gap, per “adjacent box beam”). Returns aBoxBeamBridgeModel.
- civilpy.structural.rhino_box_beam.read_box_beam_model(path)[source]
Read the
gdr.kind=girder | box_beam | tendon | diaphragm | tie_rod | bearing_pad | deckobjects back from a box-beam.3dm: a list of dicts withkind,id, and anattrsmap of everygdr.*tag on the object (numeric values asfloatwhere they parse). Round-tripsbuild_box_beams()and mirrors what theBoxBeamLinesimporter carries.
civilpy.structural.rhino_box_bim module
BrIM emit layer for prestressed adjacent box-beam bridges.
The rhino_box_beam legacy writer ported to the
transport-neutral emit architecture: the same
PSBD-1-25 / PSBDD-1-25 standard-design content —
box members as four wall prisms each (hollow tube, square-drawn void corners), every part tagged
bim.type = box_beamwith the beam id; one part per beam carries the 515 member pay item (strands, tie rods, and precast diaphragms are included in the member);a schematic strand-row polyline per active row of the PSBDD-1-25 pattern (2 / 4 / 6 in above the soffit — straight, boxes debond rather than harp);
transverse diaphragms and tie rods at the standard stations;
standard bearing pads under each beam end (516 item);
the composite topping slab when the design line calls for one (511 superstructure concrete);
the
gdr.*girder centerline per beam (gdr.family = box) so theBoxBeamLinesimporter and line-girder tooling still work.
Drawn by the same draw_bim_emit.py driver and read back by the same
read_bim_quantities() as the steel
slice. Coordinates are feet: X spans 0..span, Y across the beams
(beam 1 edge at y = 0), Z = 0 at the box soffit.
Skewed layouts are not yet supported (the PSBD-1-25 sheet 2/6 skew rules for diaphragm/tie-rod offsets are the follow-up), and no railing is emitted — the barrier family on boxes differs from the deck-girder SBR standard.
- class civilpy.structural.rhino_box_bim.BoxBridgeInput(box: str, span_ft: float, n_beams: int, skew_deg: float = 0.0, fc_psi: float = 6000.0)[source]
Bases:
objectOne adjacent box-beam bridge on the ODOT standard designs.
boxis a PSBDD-1-25 designation ("CB27-48"composite /"B27-48"non-composite);span_ftmust be one of that box’s cataloged spans. Beams sit edge to edge (adjacent), beam 1 aty = 0.- box: str
"CB27-48"composite /"B27-48"non-composite – a key of the PSBD span tables incivilpy.structural.odot.box_beam_design.- Type:
PSBDD-1-25 box designation
- civilpy.structural.rhino_box_bim.DIAPHRAGM_THICKNESS_IN = 8.0
Display thickness for the precast diaphragms (PSBD-1-25 gives count and stations but no thickness in the carried section) — cosmetic only.
- civilpy.structural.rhino_box_bim.box_beam_bridge_emit(inp: BoxBridgeInput, *, concrete_pcf: float = 150.0) BridgeEmit[source]
Build the tagged BrIM geometry for one adjacent box-beam bridge.
Raises
KeyError(naming the valid spans) whenspan_ftis not a cataloged design forbox, andValueErrorfor a skewed layout (not yet supported).
civilpy.structural.rhino_deck module
Deck slab, parapets, and railing geometry for the girder-line model
(stage G6, the deck companion to civilpy.structural.rhino_gdr).
Given a girder-line bridge (the tagged .3dm the GirderLines / GirderShape
commands author, read by read_girder_model()),
this builds the riding surface the girders carry:
a deck slab spanning the full bridge length across all girder lines plus an overhang on each side;
a parapet swept along each deck edge, its cross-section taken from the ODOT bridge-railing catalog (
civilpy.structural.odot.bridge_railing); andan optional railing (a steel top rail) atop each edge.
The geometry is written to a companion .3dm the GirderDeck command imports
(the same write-back handoff the splice pipeline uses), tagged gdr.kind=deck |
parapet | railing so the girder reader — which only consumes girder and
support — ignores it, while the deck’s engineering payload (deck thickness,
the per-girder deck DC1 and per-parapet DC2 line loads) rides along in the tags
for the analysis model to pick up.
The slab and parapet shapes are display geometry (like the cosmetic girder
sections); the loads they carry are the real contribution to the MIDAS model.
rhino3dm is an optional dependency imported lazily.
- civilpy.structural.rhino_deck.CONCRETE_PCF = 150.0
Reinforced-concrete unit weight (pcf) for deck and parapet dead load.
- civilpy.structural.rhino_deck.DEFAULT_DECK_T_IN = 8.5
Default deck slab thickness (in) when the model carries no
gdr.deck_t.
- civilpy.structural.rhino_deck.DEFAULT_OVERHANG_FT = 3.5
Default deck overhang beyond each exterior girder line (ft).
- civilpy.structural.rhino_deck.DEFAULT_PARAPET = 'BR-1 (36 in)'
Default parapet designation (36 in New Jersey, TL-4).
- class civilpy.structural.rhino_deck.DeckModel(deck_t_in: float, width_ft: float, length_ft: float, overhang_ft: float, girder_spacing_ft: float, n_girder_lines: int, deck_dc1_klf_interior: float, parapet: str, parapet_height_in: float, parapet_dc2_klf_each: float, railing: str | None, railing_dc2_klf_each: float, n_deck: int, n_parapet: int, n_railing: int, n_rebar: int = 0)[source]
Bases:
objectSummary of a generated deck: geometry counts plus the dead-load quantities the analysis model needs. Lengths in the units named; loads in kips/ft (klf).
- civilpy.structural.rhino_deck.build_deck(source, *, out_path, deck_t_in: float | None = None, overhang_ft: float = 3.5, haunch_in: float = 0.0, deck_bottom_z_ft: float = 0.0, parapet: str = 'BR-1 (36 in)', railing: str | None = None, railing_height_in: float = 42.0, rebar: bool = False, rebar_cover_in: float = 2.5, rebar_transverse_spacing_in: float = 6.0, rebar_longitudinal_spacing_in: float = 12.0, concrete_pcf: float = 150.0, unit_system=None) DeckModel[source]
Generate the deck slab, edge parapets, and optional railing for a girder model and write them to
out_path(a.3dmtheGirderDeckcommand imports).sourceis aGirderBridgeor a path to a girder.3dm.deck_t_indefaults to the model’sgdr.deck_tand thenDEFAULT_DECK_T_IN. The deck spans the full girder length and the full transverse girder spread plusoverhang_fton each side; its bottom sits atdeck_bottom_z_ft+haunch_inabove the girder-line plane (cosmetic — the girder line is the analysis reference). Returns aDeckModelwith the geometry counts and the DC1/DC2 dead loads.
- civilpy.structural.rhino_deck.parapet_dc2_klf(designation: str, *, concrete_pcf: float = 150.0)[source]
Dead load of one parapet/railing run (klf) from the catalog: the gross concrete
section_area× unit weight for a concrete barrier, else the steelweight_per_ft. Returns 0.0 when the catalog states neither.
- civilpy.structural.rhino_deck.read_deck_model(path)[source]
Read the
gdr.kind=deck | parapet | railingobjects back from a deck.3dm: a list of dicts withkind,id, and anattrsmap of the kind’sgdr.<kind>.*tags (numeric values asfloat). Round-tripsbuild_deck()and mirrors what theGirderDeckimporter carries.
civilpy.structural.rhino_gdr module
Rhino 3D -> girder-line model (the gdr.* reader, stage G4).
Parses a tagged .3dm authored by the C# GirderLines / GirderShape /
GirderBearing commands into the canonical
StructuralModel hub, following the
girder / field-splice contract in docs/Rhino Design Philosophy.md:
Geometry carries what is spatial; tags carry what is scalar.
Girder lines are curves tagged
gdr.kind=girderwithgdr.shape(AISC label, e.g.W24X104),gdr.grade(defaultGrade 50), andgdr.line(girder number). A polyline yields oneElementper segment (a continuous-span chain); each element carries the resolved AISC section label and the grade.Bearings are points tagged
gdr.kind=supportwithgdr.fixity=fixed|expansionandgdr.line; they become 6-DOFRestraints on the nearest node of their girder line.Bridge parameters live in document user text (
gdr.deck_t,gdr.deck_weff,gdr.deck_fc,gdr.ship_max,gdr.bolt_*) and are returned as aGirderBridgealongside the hub.
Plane convention: PLAN – X = stations along the bridge, Y = transverse,
Z = up; lengths in feet (ModelUnitSystem honored on read, mirroring
rhino_stm). rhino3dm is an optional dependency imported lazily.
- class civilpy.structural.rhino_gdr.GirderBridge(model: StructuralModel, deck_t: float | None = None, deck_weff: float | None = None, deck_fc: float = 4.0, ship_max: float = 100.0, bolt_dia: float = 0.875, bolt_spec: str = 'A325', bolt_hole: str = 'oversize', bolt_class: str = 'C', girder_lines: dict[str, list[str]]=<factory>)[source]
Bases:
objectThe canonical hub plus the document-level bridge parameters the girder / splice pipeline needs (deck composite section + bolt standard). Missing
deck_t/deck_weffareNone(a loud warning is issued on read); the rest fall back to the ODOT BDM 308.2.2.1.j defaults.- model: StructuralModel
- class civilpy.structural.rhino_gdr.SpliceMarker(point: tuple, design: object, line: str = '', id: str = '')[source]
Bases:
objectA designed splice to write back: its station point (feet, PLAN), the
SpliceDesign, thegdr.lineit belongs to, and an optional persistentgdr.id(minted if empty, same identity rule as the C# authored tags).
- civilpy.structural.rhino_gdr.grade_fy_fu(grade: str) tuple[float, float][source]
(Fy, Fu) ksi for a
gdr.gradename; warns + defaults to Grade 50.
- civilpy.structural.rhino_gdr.read_girder_model(path, *, tol=0.5) GirderBridge[source]
Read a tagged Rhino
.3dminto aGirderBridge(the canonical hub + bridge parameters). OneElementchain pergdr.line;Element.sectionis the resolved AISC label,Element.materialthe grade name. Bearings become 6-DOF restraints on the nearest node of their girder line.
- civilpy.structural.rhino_gdr.read_splice_results(path)[source]
Read
gdr.kind=splicemarkers back from a.3dm: a list of dicts withpoint(feet),line,id,status,summary,checks(parsed into[article, check, actual, allowable, verdict]records), andattrs– thegdr.splice.*smart-node attribute set with the prefix stripped and numeric values converted tofloat(e.g.attrs["tf.bolts"],attrs["bolt_dia"]). Round-tripswrite_splice_results()and mirrors what C# reads; display geometry (nogdr.kind) is ignored by contract.
- civilpy.structural.rhino_gdr.resolve_shape(label: str)[source]
Look up an AISC W-shape by (already-normalized) label via
steel.W; returns the section orNone(with a warning) if it does not resolve. Import is local so the reader works without the steel db warmed.
- civilpy.structural.rhino_gdr.splice_attribute_tags(design, inp=None) dict[source]
Build the
gdr.splice.*smart-node attribute tags from aSpliceDesign.These ride on the
gdr.kind=splicemarker point next to the status/summary/checks write-back, so the marker carries the designed splice the way a bearing carries its fixity – readable in Rhino’s object properties and by the C#GirderSplicedialog. All lengths are inches; bolt counts are per side of the joint.Marker-level keys:
bolt_spec,bolt_dia,hole_type,hole_dia,layout(inline;staggeredreserved – the designer lays out straight rows),gap(girder end gap),method.Per-component keys under
tf./bf./web.:bolts,rowsxcols(the per-side grid),pitch(along the load path),gage(across it),edge,end, and the plate stack –plate_t/plate_w/plate_lwhereplate_lalways runs along the girder axis andplate_wacross it (transverse for flange plates, vertical for the paired web plates). Flanges add the two inner plates (inner_t/inner_w) and the joint-straddling spacingspitch_joint(flange, longitudinal) /gage_web(across the web); the web addsgage_joint(its across-the-gap spacing).inpis theSpliceInput; it defaults todesign.spec(attached bydesign_splice).
- civilpy.structural.rhino_gdr.splice_writeback_tags(design) dict[source]
Build the
gdr.status/gdr.summary/gdr.checkswrite-back tags (stage G8) from aSpliceDesign.gdr.checksis newline-separatedarticle|check|actual|allowable|verdictrecords – one row per limit state – the format the C#GirderSplicecommand renders (NG rows red).
- civilpy.structural.rhino_gdr.write_splice_results(out_path, markers, *, unit_system=None, display=True)[source]
Author
gdr.kind=splicesmart-node marker points into a new.3dmfor the C#GirderSplicecommand.Each marker carries the G8 check write-back (
gdr.status/gdr.summary/gdr.checks), a persistentgdr.id, and – when the design retains itsSpliceInput(design.spec, set bydesign_splice) – the fullgdr.splice.*attribute set: bolt spec and hole size, per-component bolt grids and spacings, and the three plate stacks (seesplice_attribute_tags()). Withdisplay=Truetrue-scale plate boxes and bolt-axis lines are baked next to each marker on theSplice Displaylayer; they carry nogdr.kindso they stay invisible to Python readers, and the C# importer carries them across for viewing only.markersis a list ofSpliceMarker; geometry is written inunit_system(default feet). Returns the number of markers written.
civilpy.structural.rhino_layers module
Shared Rhino layer taxonomy for civilpy’s .3dm writers.
Every civilpy module that writes a .3dm (rhino_gdr, rhino_deck,
rhino_barrier, rhino_box_beam, and the ODOT SCD Grasshopper
components) should bake into these layers instead of inventing its own
flat names, so a file civilpy writes and the C# RhinoODOTExtension
plugin reads (or vice versa) resolve to the same nested layer instead of
silently duplicating it under a differently-spelled one.
Deck / Superstructure / Substructure are authoritative and pinned to the
C# side – transcribed byte-for-byte from
ODOT Rhino Plugin/Core/Gdr.cs (RhinoODOTExtension repo,
commit 34f7051, “GirderDeck command + Deck/Superstructure/Substructure
layer groups”). This mirrors NBIS/SNBI’s three inspection groups (deck,
superstructure, substructure condition ratings), so the layer tree reads
the way a bridge is actually inspected and the same grouping can carry
straight through to Midas/BrR/AssetWise/IFC element groups later –
that alignment is the point of a shared layer taxonomy, not an
afterthought.
Culvert and Site are civilpy-side PROPOSED additions (not yet in
Gdr.cs) for the ODOT SCD standard-drawing components that are neither
deck, superstructure, nor substructure: headwalls/box culverts (Culvert)
and off-structure roadway items like approach slabs, portable barrier, and
drainage strips (Site). See the dated “PROPOSED” section in
docs/Rhino Design Philosophy.md for the reconciliation request; do not
treat these two as frozen until the C# side signs off, same as every other
gdr.* contract addition.
- civilpy.structural.rhino_layers.DEFAULT_COLORS: dict[str, tuple[int, int, int, int]] = {'Culvert': (130, 150, 150, 255), 'Culvert::Box': (170, 170, 175, 255), 'Culvert::Footings': (110, 110, 115, 255), 'Culvert::Foreslope Walls': (150, 150, 155, 255), 'Culvert::Rebar': (60, 120, 200, 255), 'Culvert::Wingwalls': (145, 135, 120, 255), 'Deck': (170, 170, 175, 255), 'Deck::Approach Slab': (173, 216, 230, 255), 'Deck::Approach Slab::Rebar': (255, 140, 0, 255), 'Deck::Bridge Deck': (170, 170, 175, 255), 'Deck::Lane Markings': (245, 225, 70, 255), 'Deck::Rebar': (60, 120, 200, 255), 'Deck::Traffic Barriers': (150, 150, 155, 255), 'Site': (150, 140, 120, 255), 'Substructure': (120, 90, 70, 255), 'Substructure::Backwalls': (150, 140, 125, 255), 'Substructure::Beam Seats': (170, 160, 145, 255), 'Substructure::Caps': (155, 145, 130, 255), 'Substructure::Columns': (140, 130, 115, 255), 'Substructure::Footings': (120, 110, 95, 255), 'Substructure::Piles': (70, 70, 80, 255), 'Substructure::Rebar': (60, 120, 200, 255), 'Substructure::STM': (150, 150, 150, 255), 'Substructure::STM::Struts': (40, 80, 200, 255), 'Substructure::STM::Ties': (200, 40, 40, 255), 'Substructure::Wingwalls': (145, 135, 120, 255), 'Superstructure': (40, 40, 40, 255), 'Superstructure::Bearings': (0, 110, 200, 255), 'Superstructure::Box Beams': (140, 140, 145, 255), 'Superstructure::Diaphragms': (100, 100, 180, 255), 'Superstructure::Display': (110, 125, 140, 255), 'Superstructure::Girders': (40, 40, 40, 255), 'Superstructure::Haunches': (200, 200, 190, 255), 'Superstructure::Load Plates': (90, 90, 100, 255), 'Superstructure::Shear Studs': (200, 120, 40, 255), 'Superstructure::Splices': (200, 30, 30, 255), 'Superstructure::Tendons': (200, 160, 20, 255), 'Superstructure::Tie Rods': (180, 60, 60, 255)}
Default RGBA colors, keyed by full layer path – kept alongside the path constants so every writer paints the same layer the same color.
- civilpy.structural.rhino_layers.LAYER_APPROACH_SLAB = 'Deck::Approach Slab'
PROPOSED leaves for the AS-1-15 approach slab emit (rhino_approach_slab): human-readable hierarchy under Deck (the slab rides with the roadway surface), rebar on its own toggleable/estimable sub-layer.
- civilpy.structural.rhino_layers.LAYER_CULVERT_BOX = 'Culvert::Box'
PROPOSED leaves for the BCHW headwall/wingwall emit (rhino_bchw).
- civilpy.structural.rhino_layers.LAYER_HAUNCHES = 'Superstructure::Haunches'
concrete haunches, welded shear studs, and bearing load plates get their own layers under Superstructure so the composite-connection and bearing hardware can be toggled/estimated independently of the girders.
- Type:
PROPOSED leaves for the BrIM source-of-truth model (rhino_bim)
- civilpy.structural.rhino_layers.LAYER_SUBSTRUCTURE = 'Substructure'
Reserved – no elements are generated yet (matches Gdr.cs’s own comment).
- civilpy.structural.rhino_layers.LAYER_SUB_CAPS = 'Substructure::Caps'
PROPOSED leaves under the (reserved) Substructure group, mirroring the Superstructure taxonomy one component per layer: cap beams, the stepped beam seats on top of them, columns, footings, driven piles, abutment backwalls and wingwalls, the substructure reinforcing cage, and an STM layer holding the strut-and-tie analysis overlay (non-pay — excluded from every estimate rollup).
- civilpy.structural.rhino_layers.ensure_layer(f, full_path: str, color: tuple = None) int[source]
Ensure a nested
"Group::Leaf"layer exists in an offlinerhino3dm.File3dm, creating any missing parents – the Python-side mirror of the plugin’sStmDocument.EnsureLayer(same “walk the::-separated path, create what’s missing” logic, since standalonerhino3dmhas noFindByFullPath, only parent-scopedFindName). Returns the leaf layer’s index.colordefaults toDEFAULT_COLORS[full_path]if not given and the path is one of the constants above.
civilpy.structural.rhino_ps_i_bim module
BrIM emit layer for prestressed I-beam bridges (PSID-1-13).
The prestressed-I counterpart of rhino_box_bim:
the executed design line from
ps_i_beam_line_checks()
drives the drawn geometry — the strand rows are the designed pattern,
not a free parameter.
each beam as one true-profile I-prism (
ps_i_beam_profile()— tapered flanges, no fillet radii), taggedbim.type = ps_i_beamcarrying the 515 member pay item (strands, embedded sole plates, and anchorage steel are included in the member per sheet 10);a schematic strand-row polyline per occupied row of the designed pattern, with the row’s end-debonded count in its tags;
2 in design haunches over each top flange (BDM 309.3.5) and the CIP deck slab (511 superstructure concrete);
cast-in-place intermediate diaphragms at the sheet 5 stations (midspan up to 80 ft, quarter points beyond), one 515 intermediate- diaphragm count per station-bay;
elastomeric bearing pads under each beam end (516 item);
the
gdr.*girder centerline per beam (gdr.family = ps_i) so the line-girder tooling reads the model back.
Drawn by the same draw_bim_emit.py driver and read back by the same
read_bim_quantities() as the steel
and box slices. Coordinates are feet: X spans 0..span, Y across the
beams (beam 1 at y = overhang), Z = 0 at the beam soffit.
Like the box slice: skew is not yet supported, the deck is drawn flat (no crown), and no railing is emitted here — place one with the barrier tooling. End/pier diaphragms belong to the substructure phase.
- civilpy.structural.rhino_ps_i_bim.DIAPHRAGM_THICKNESS_IN = 10.0
Display thickness for the CIP intermediate diaphragms (the project plans size them; sheet 9 details the connection) — cosmetic only.
- class civilpy.structural.rhino_ps_i_bim.PSIBridgeInput(section: str, span_ft: float, n_beams: int, spacing_ft: float, overhang_ft: float = 2.5, deck_t_in: float = 8.5, haunch_in: float = 2.0, skew_deg: float = 0.0, fci_ksi: float = 4.0, fc_ksi: float = 5.5, barrier_klf: float = 0.0, n_strands: int | None = None)[source]
Bases:
objectOne prestressed I-beam bridge on the PSID-1-13 standard.
sectionis a PSID-1-13 section name ("WF48-49","AASHTO Type 3", …). Beam 1’s centerline sits aty = overhang_ft; the deck edges runoverhang_ftbeyond each exterior beam centerline.
- civilpy.structural.rhino_ps_i_bim.ps_i_bridge_emit(inp: PSIBridgeInput, *, checks: PSIBeamLineChecks | None = None, concrete_pcf: float = 150.0) BridgeEmit[source]
Build the tagged BrIM geometry for one prestressed I-beam bridge.
checksshort-circuits the design step with an already-executedps_i_beam_line_checks()result (it must match the input’s section/span/spacing); otherwise the line is designed here. RaisesValueErrorfor a skewed layout (not yet supported) or when no passing pattern exists.
civilpy.structural.rhino_slab module
Rhino emit layer for ODOT single span slab bridges (SB-1-24).
Turns the pure layout in civilpy.structural.odot.slab_bridge into
tagged, transport-neutral geometry records (EmitObject), then
bakes them through whichever backend the caller has:
write_slab_bridge()– offlinerhino3dm.File3dm, no Rhino needed.The
SB-1-24Grasshopper component – buildsRhino.Geometryfrom the same records inside Rhino 8.Any live-document driver (an MCP agent, a plugin command) – consumes the same records.
The point of the neutral record is that SB-1-24’s engineering content is described exactly once. A backend only decides how to draw a closed polyline and how to stamp a user-string; it never decides where a bar goes.
Coordinates in the records are feet, matching the hub’s Units and the
gdr./stm. tag contracts. Backends scale to the document’s unit system.
Winding contract
EmitObject.points for a solid is a closed plan outline wound
counter-clockwise viewed from +Z, so its plane normal is +Z and
extrude_ft (negative) drives the solid down from the top of slab at
z = 0. This is load-bearing, not cosmetic: layout_slab_bridge returns its
outline wound clockwise, whose plane normal is -Z, and feeding that straight
into Extrusion.Create(crv, -thickness, True) extrudes the slab upward
into z > 0 while the bar mats stay at z < 0 – the “rebar appears below the
concrete deck” defect noted in the component’s own TODO block. _ccw()
normalizes the winding here so no backend can reintroduce it.
User-text contract (slab. namespace)
Mirrors gdr./stm.: a slab.kind discriminator marks structural
objects, and geometry without it is cosmetic display that readers ignore by
contract. See read_slab_bridge() for the round trip.
tag |
meaning |
|---|---|
|
|
|
source standard drawing, |
|
stable GUID, survives a Rhino -> hub -> Rhino trip |
|
bar mark |
|
bar size (imperial eighths, e.g. |
|
nominal bar diameter, in (ASTM A615) |
|
nominal bar area, in^2 (ASTM A615) |
|
|
|
bar length, ft (quantity take-off) |
|
|
|
slab thickness, in |
|
concrete strength, psi |
|
ODOT CMS pay item, only when supplied by caller |
Warning
slab.pay_item is never guessed. ODOT CMS item numbers are contractual
and civilpy does not ship a table of them; pass pay_items= to stamp
them, or the tag is simply omitted.
- civilpy.structural.rhino_slab.BAR_AREA_IN2: dict[int, float] = {3: 0.11, 4: 0.2, 5: 0.31, 6: 0.44, 7: 0.6, 8: 0.79, 9: 1.0, 10: 1.27, 11: 1.56}
Nominal bar area (in^2) by imperial bar size, ASTM A615.
- civilpy.structural.rhino_slab.BAR_DIAMETER_IN: dict[int, float] = {3: 0.375, 4: 0.5, 5: 0.625, 6: 0.75, 7: 0.875, 8: 1.0, 9: 1.128, 10: 1.27, 11: 1.41}
Nominal bar diameter (in) by imperial bar size, ASTM A615.
- civilpy.structural.rhino_slab.BAR_MAT: dict[str, str] = {'A': 'bottom', 'B': 'top', 'M': 'bottom', 'N': 'top'}
Which mat each SB-1-24 longitudinal bar mark belongs to (sheet 1).
- class civilpy.structural.rhino_slab.EmitObject(kind: str, layer: str, points: tuple[tuple[float, float, float], ...], tags: dict[str, str] = <factory>, extrude_ft: float | None = None, closed: bool = False)[source]
Bases:
objectOne drawable object, independent of any Rhino API.
kindis"solid"(extrudepointsalongextrude_ft) or"curve"(polyline throughpoints).tagsare the user strings to stamp verbatim, already prefixed withTAG.- KINDS = ('solid', 'curve', 'point')
"solid"|"curve"|"point"
- class civilpy.structural.rhino_slab.SlabEmit(inputs: SlabBridgeInput, layout: SlabBridgeLayout, objects: tuple[EmitObject, ...], doc_tags: dict[str, str])[source]
Bases:
objectEverything a backend needs to draw one SB-1-24 slab bridge.
- inputs: SlabBridgeInput
- layout: SlabBridgeLayout
- objects: tuple[EmitObject, ...]
- of_kind(kind: str) tuple[EmitObject, ...][source]
The emitted objects whose
slab.kindmatches (""= display).
- civilpy.structural.rhino_slab.push_slab_to_midas(inp: SlabBridgeInput, *, level: str = 'L1', midas=None, **client_kwargs) dict[source]
Push an SB-1-24 slab to a live MIDAS Civil NX session and report per table.
Builds
slab_midas_payloads(), then PUTs each table in send order, continuing past individual table errors (same report shape aspush_midas()). Pass aMidasCivil, or let it build one from~/secrets.json.
- civilpy.structural.rhino_slab.read_slab_bridge(path) dict[source]
Read a
slab.-tagged.3dmback into{"doc", "slab", "rebar"}.docis the document-level tag dict;slabandrebarare lists of{"points", "tags"}with numeric tag values cast tofloatand points converted to feet. Untagged (cosmetic) geometry is skipped by contract, the same ruleread_splice_results()follows.
- civilpy.structural.rhino_slab.rebar_quantities(emit: SlabEmit) dict[str, dict][source]
Bar count, total length (ft), and weight (lb) per mark – the take-off the
slab.diameter_in/slab.length_fttags exist to support.Steel unit weight is 490 lb/ft^3, so a bar’s weight is
area_in2 / 144 * 490 * length_ft.
- civilpy.structural.rhino_slab.slab_emit(inp: SlabBridgeInput, *, pay_items: dict[str, str] | None = None) SlabEmit[source]
Build the tagged, transport-neutral geometry for one slab bridge.
pay_itemsoptionally maps"concrete"and"rebar"to ODOT CMS item strings; absent keys leaveslab.pay_itemunstamped rather than guessed.Raises whatever
layout_slab_bridge()raises for an untabulated span, bad edge condition, or excessive skew.
- civilpy.structural.rhino_slab.slab_input_from_doc_tags(doc_tags: dict) SlabBridgeInput[source]
Rebuild a
SlabBridgeInputfrom theslab.document tags read off a.3dm(or authored by the GH component).This is the Rhino -> analysis handoff: the user-text attributes carry enough to regenerate the design, so an engineer who traced/authored a slab in Rhino can drive MIDAS from those tags alone. Values may arrive as floats (from
read_slab_bridge()) or strings (raw user text); both work.
- civilpy.structural.rhino_slab.slab_midas_payloads(inp: SlabBridgeInput, *, level: str = 'L1') dict[source]
MIDAS
/db/*payloads for an SB-1-24 slab, with concrete material and a solid-rectangle strip section.Starts from the generic hub serialization (
midas_payloads()) for NODE/ELEM/CONS/BEAM, then overrides the steel-centric MATL/SECT the hub emits by default: the material becomes concrete withEcfromf'c(concrete_material_block()) and the section becomes the equivalent-strip rectangle – 1 ft wide (the L1 loads are already scaled to a 1 ft strip by the1/Edistribution factor) by the slab thickness (solid_rect_section_block()).
- civilpy.structural.rhino_slab.write_slab_bridge(out_path, inp: SlabBridgeInput, *, pay_items: dict[str, str] | None = None, unit_system=None) SlabEmit[source]
Bake one SB-1-24 slab bridge into a new
.3dmand return itsSlabEmit.Geometry is written in
unit_system(default feet, so points round-trip 1:1 throughread_slab_bridge()). Objects land on the sharedDeck::Bridge Deck/Deck::Rebarlayers fromcivilpy.structural.rhino_layers– not a per-drawingDeck::SB-1-24layer – so a file civilpy writes and the C# plugin reads resolve to the same nested layer.
civilpy.structural.rhino_stm module
Rhino 3D <-> strut-and-tie interchange.
Bridges a Rhino .3dm model and
StrutAndTieModel using the convention documented in
docs/Rhino Design Philosophy.md:
Geometry carries what is spatial; tags carry what is scalar.
Members are curves tagged
stm.kind=member(an untagged curve falls back to a member, so quick sketches still import).Supports are block instances (
STM_Pin/STM_Roller_V/STM_Roller_H/STM_Fixed) at a node; the block name sets the common fixity andstm.fix_*user text overrides it. Full 6-DOF is recorded for future frame/Midas use; the 2D STM solver consumes only the in-plane translations.Loads are
STM_Loadarrow blocks (or plain tagged lines). The arrow’s orientation is the force direction;stm.kipscarries the magnitude.
Nodes are not authored explicitly – they are derived by snapping member endpoints together within a tolerance and auto-labeling A, B, C… ordered bottom-to-top then left-to-right (so labels read like an elevation).
The model is drawn flat in a world plane (the FHWA examples use XZ, a front
elevation). plane="auto" detects it; X/Z map to the analysis
x/y with gravity along -Z.
rhino3dm is an optional dependency (pip install civilpy[rhino]); it is
imported lazily so the rest of civilpy.structural works without it.
- civilpy.structural.rhino_stm.build_template(path, *, version=7)[source]
Write a starter
.3dm(units feet) holding the STM layers, ready to draw into.The symbol block definitions (pin / rollers / fixed / load arrow) are intentionally not baked in here: rhino3dm 8.x’s
InstanceDefinitions.Addcorrupts memory probabilistically, so block creation is left to the in-Rhino authoring tools (src/civilpy/structural/rhino_scripts/ stm_authoring.py), where RhinoCommon creates them reliably on first use. The reader (model_from_3dm()) understands those Rhino-authored block symbols as well as the tagged points/lines this package writes.
- civilpy.structural.rhino_stm.model_from_3dm(path, *, plane='auto', tol=0.05, as_model=False)[source]
Read a tagged Rhino
.3dmfile into a structural model.By default returns a 2D
StrutAndTieModel(backward compatible) – now produced as a thin projection of the canonical hub rather than via a second parser. Passas_model=Trueto get the richer 3D / 6-DOFStructuralModelhub instead (equivalent toread_structural_model()).- Parameters:
path (str) – Path to the
.3dmfile.plane ({"auto", "XY", "XZ", "YZ"}) – World plane the model is drawn in.
"auto"infers it from the geometry (the near-constant axis is the normal).tol (float) – Snapping tolerance (model units) for collapsing coincident endpoints into nodes and matching supports/loads to them.
as_model (bool) – When
Truereturn the fullStructuralModelhub instead of the projected 2DStrutAndTieModel.
- civilpy.structural.rhino_stm.model_to_3dm(model, path, *, plane='XZ', arrow_len=1.0, version=7)[source]
Author a
StrutAndTieModelas a tagged.3dmfile.Members are tagged lines, supports are tagged points (
stm.supportplus thestm.fix_*flags), and loads are tagged arrow lines drawn from the node in the force direction with the magnitude instm.kips. This keeps the writer to the rock-solidAddLine/AddPointpaths; the richer symbol blocks live in the template (build_template()) and are inserted by the in-Rhino authoring tools, and the reader understands both representations.arrow_lenis the drawn length of load arrows in model units (purely visual; magnitude comes from the tag).
- civilpy.structural.rhino_stm.problem_from_3dm(path, *, plane='auto', nu=0.2)[source]
Read a tagged Rhino
.3dmauthored with the region workflow into aDRegionProblem.The file holds one closed
stm.kind=regioncurve (the concrete D-region) carryingstm.thickness(ft) andstm.fc(ksi), optionallystm.E/stm.nu/stm.vol_frac; optionalstm.kind=void/stm.kind=solidinner curves; and the same supports and loads as the drawn-truss workflow, optionally with anstm.bearing(ft) width. Seedocs/Rhino Design Philosophy.md(§”Two front ends”).
- civilpy.structural.rhino_stm.read_structural_model(path, *, plane='auto', tol=0.05)[source]
Read a tagged Rhino
.3dminto the canonicalStructuralModelhub.Where
model_from_3dm()returns the lossy 2DStrutAndTieModel, this preserves everything the MIDAS and IFC adapters need: full 3D node coordinates, the complete 6-DOF restraint (stm.fix_*– sofix_z/fix_rx/fix_ry/fix_rzsurvive, not just the in-plane pair the truss solver uses), the support preset, stable object ids, and the full 3D load vectors. This is stage S2 of the package-coherence track indocs/Rhino Design Philosophy.md.- Parameters:
path (str) – Path to the
.3dmfile.plane ({"auto", "XY", "XZ", "YZ"}) – World plane the model is drawn in, used only to cluster coincident endpoints into nodes;
"auto"infers it from the geometry. The hub keeps the genuine 3D coordinates regardless.tol (float) – Snapping tolerance (model units) for collapsing coincident endpoints into nodes and matching supports/loads to them.
civilpy.structural.section_properties module
Built-up plate cross-section property calculator.
CrossSection accumulates rectangular plates (bottom to top) and computes
area, centroid, moments of inertia, and section moduli for the composite
shape — useful for plate girders and other built-up members.
- class civilpy.structural.section_properties.CrossSection(label, dimensions=None, shape=None, y=None, axis='strong')[source]
Bases:
objectA class for defining a built-up section by repeatedly adding plate dimensions.
This class allows users to define a built-up cross-section by calling the instance and passing in the dimensions of each plate making up the section. The plates are assumed to be entered in order from the bottom to the top, and all shapes are assumed to be rectangles that do not overlap.
- Parameters:
- Returns:
An instance of the CrossSection object.
- Return type:
- __init__(label, dimensions=None, shape=None, y=None, axis='strong')[source]
Initialise a built-up cross-section with its first plate or shape.
- Parameters:
label (str) – Identifier for this plate (e.g.
"bottom flange").dimensions (tuple[float, float], optional) –
(width, height)of a rectangular plate in consistent units. Required when shape isNone.shape (W, optional) – A steel wide-flange shape object from
civilpy.structural.steel. When supplied, dimensions is derived from the shape’s flange width and depth.y (float, optional) – Distance from the datum to the centroid of this plate. Defaults to half the plate height (i.e. the plate sits with its bottom at y = 0).
axis (str) –
'strong'(default) to use I_x and depth, or'weak'to use I_y and flange width for rolled shapes.
- append_value(dimensions=None, y=None, shape=None, axis=None)[source]
Add a plate or rolled shape to the cross-section and update properties.
Four dispatch paths are supported:
Rectangular plate stacked on top (
y=None,shape=None): plate sits directly above the current top of section.Rolled shape at explicit y (
shapeprovided,yprovided): uses I_x or I_y depending on axis.Rolled shape stacked on top (
shapeprovided,y=None): depth/flange width derived from shape; stacked on current top.Rectangular plate at explicit y (
shape=None,yprovided): plate placed with centroid at y.
- Parameters:
Note
_calc_gen_properties()is called automatically after each plate is added.
- civilpy.structural.section_properties.get_bar_section_properties(d)[source]
Return (I, S, r) for a solid circular bar about its centroidal axis.
- Parameters:
d – Diameter of the bar.
- Returns:
(I, S, r) — moment of inertia, section modulus, radius of gyration.
- Return type:
- civilpy.structural.section_properties.get_hollow_oval_section_properties(a, b, c, d)[source]
Return (I, S, r) for a hollow ellipse (elliptical tube) about its centroidal axis.
- Parameters:
a – Outer semi-axis height.
b – Outer semi-axis width.
c – Inner semi-axis height.
d – Inner semi-axis width.
- Returns:
(I, S, r) — moment of inertia, section modulus, radius of gyration.
- Return type:
- civilpy.structural.section_properties.get_oval_section_properties(b, a)[source]
Return (I, S, r) for a solid ellipse about its centroidal axis.
- Parameters:
b – Semi-axis width (horizontal half-width).
a – Semi-axis height (vertical half-height).
- Returns:
(I, S, r) — moment of inertia, section modulus, radius of gyration.
- Return type:
- civilpy.structural.section_properties.get_pipe_section_properties(D, d)[source]
Return (I, S, r) for a hollow circular pipe about its centroidal axis.
- Parameters:
D – Outer diameter.
d – Inner diameter.
- Returns:
(I, S, r) — moment of inertia, section modulus, radius of gyration.
- Return type:
- civilpy.structural.section_properties.get_rectangular_section_properties(b, d)[source]
Return (I, S, r) for a rectangle about its neutral axis (centroid).
- Parameters:
b – Width of the rectangle.
d – Height of the rectangle.
- Returns:
(I, S, r) — moment of inertia, section modulus, radius of gyration.
- Return type:
- civilpy.structural.section_properties.get_rectangular_section_properties_baseline(b, d)[source]
Return (I, S, r) for a rectangle about its baseline (bottom edge).
- Parameters:
b – Width of the rectangle.
d – Height of the rectangle.
- Returns:
(I, S, r) — moment of inertia, section modulus, radius of gyration.
- Return type:
- civilpy.structural.section_properties.get_triangular_section_properties(b, d)[source]
Return (I, S, r) for a triangle about its neutral axis.
- Parameters:
b – Base width of the triangle.
d – Height of the triangle.
- Returns:
(I, S, r) — moment of inertia, section modulus, radius of gyration.
- Return type:
- civilpy.structural.section_properties.get_triangular_section_properties_baseline(b, d)[source]
Return (I, S, r) for a triangle about its baseline.
- Parameters:
b – Base width of the triangle.
d – Height of the triangle.
- Returns:
(I, S, r) — moment of inertia, section modulus, radius of gyration.
- Return type:
civilpy.structural.shear_flow module
Transverse shear stress distribution tau = VQ/(I*b) over a built-up section of stacked rectangles, with the classic distribution plot.
Examples
>>> sec = ShearSection([Plate(width=12, height=2, y_bottom=0),
... Plate(width=1, height=20, y_bottom=2),
... Plate(width=12, height=2, y_bottom=22)])
>>> round(sec.y_bar, 2)
12.0
>>> tau_max = sec.tau(50, sec.y_bar) # web stress at the NA
>>> tau_web_top = sec.tau(50, 21.99)
>>> tau_max > tau_web_top
True
- class civilpy.structural.shear_flow.Plate(width, height, y_bottom)[source]
Bases:
PlateA rectangle in the cross-section:
widthxheightwith its bottom edge aty_bottom(in).
- class civilpy.structural.shear_flow.ShearSection(plates: list[Plate])[source]
Bases:
objectStacked-rectangle section for VQ/(I*b) shear stress.
- plot(v: float, ax=None, n: int = 400)[source]
Plot tau(y) beside the section outline; flange/web jumps from the width change show as the textbook discontinuities.
civilpy.structural.steel module
AISC steel-shape database lookup and section classes.
Loads the AISC shapes database (current and historic editions, bundled as
CSV under structural/res) and exposes SteelSection-style classes
whose attributes are Pint quantities (SteelSection("W36X150").I_x,
.S_x, .weight, …), plus bolt-weight reference data for detailing.
- class civilpy.structural.steel.BoltMaterial(designation: str, f_y: float, f_u: float, f_v: float = 48)[source]
Bases:
objectStructural bolt material specification.
- Parameters:
designation (str) – ASTM bolt designation, e.g.
'A325','A490','F3125GrA325'.f_y (float) – Proof load / yield strength, ksi.
f_u (float) – Minimum tensile strength, ksi.
f_v (float) – Nominal shear strength (ASD allowable or LRFD nominal), ksi. Defaults to
48(A325 threads excluded from shear plane).
Example
>>> b = BoltMaterial('A325', f_y=92, f_u=120, f_v=48) >>> b.f_u 120 kip/inch²
- class civilpy.structural.steel.C(label)[source]
Bases:
SteelSectionClass to provide more specific attributes and functions related to designing with steel C s. Splitting values into multiple classes allows dropping of empty values in the database.
>>> t = C("C15X50") >>> t.weight 50.0 pound/foot
- class civilpy.structural.steel.HP(label)[source]
Bases:
SteelSectionClass to provide more specific attributes and functions related to designing with steel M s. Splitting values into multiple classes allows dropping of empty values in the database.
>>> t = HP("HP18X204") >>> t.weight 204.0 pound/foot
- class civilpy.structural.steel.HSS(label)[source]
Bases:
SteelSectionClass to provide more specific attributes and functions related to designing with steel HSS sections. Splitting values into multiple classes allows dropping of empty values in the database.
>>> t = HSS("HSS20X20X.500") >>> t.weight 130.52 pound/foot
- class civilpy.structural.steel.HistoricSteelSection(label, designation=None)[source]
Bases:
objectMain Steel Section Class, the goal is to make the attributes of various steel sections easily accessible in various python scripts.
- __init__(label, designation=None)[source]
Initialize a historic steel section from the AISC historic shapes database.
- Parameters:
- clean_user_input(user_input)[source]
Eliminates value not found errors by removing lower case letters and spaces
>>> t = SteelSection("W 44X335") >>> print(t.clean_user_input('W 44X335')) W44X335 >>> print(t.clean_user_input('w40x294')) W40X294 >>> t.id 'W40X294'
- Returns:
cleaned input
- get_historical_shape(designation=None)[source]
Searches AISC Steel database table for member label passed to it, returns values from table if it finds a match, or prints an error if it doesn’t
>>> t = SteelSection("W36X150") >>> t.aisc_value['Type'].values[0] 'W'
>>> t = SteelSection("2L8X4X5/8LLBB") >>> t.aisc_value['W'].values[0] 48.4
- Parameters:
self
- Returns:
dataframe of raw values from AISC Shape Table
- class civilpy.structural.steel.L(label)[source]
Bases:
SteelSectionClass to provide more specific attributes and functions related to designing with steel L s. Splitting values into multiple classes allows dropping of empty vain the database.
>>> t = L("L12x12x1-3/8") >>> t.weight 105.0 pound/foot
- class civilpy.structural.steel.M(label)[source]
Bases:
SteelSectionClass to provide more specific attributes and functions related to designing with steel M s. Splitting values into multiple classes allows dropping of empty values in the database.
>>> t = M("M10X9") >>> t.weight 9.0 pound/foot
- class civilpy.structural.steel.MC(label)[source]
Bases:
CClass to provide more specific attributes and functions related to designing with steel MC s. Splitting values into multiple classes allows dropping of empty values in the database.
>>> t = MC("MC18X58") >>> t.weight 58.0 pound/foot
- class civilpy.structural.steel.MT(label)[source]
Bases:
SteelSectionClass to provide more specific attributes and functions related to designing with steel WT s. Splitting values into multiple classes allows dropping of empty values in the database.
>>> t = MT("MT5x4") >>> t.weight 4.0 pound/foot
- class civilpy.structural.steel.Pipe(label)[source]
Bases:
SteelSectionClass to provide more specific attributes and functions related to designing with steel Pipe Sections. Splitting values into multiple classes allows dropping of empty values in the database.
>>> t = Pipe("Pipe10SCH140") >>> t.weight 104.0 pound/foot
- class civilpy.structural.steel.Rebar(bar_number: int, grade: int = 60)[source]
Bases:
objectStandard deformed reinforcing bar per ASTM A615 / A706.
Provides nominal diameter, cross-sectional area, and unit weight for standard US rebar sizes #2 through #18. All dimensional properties carry Pint units.
- Parameters:
- Raises:
ValueError – If bar_number is not a standard ASTM size.
Example
>>> bar = Rebar(5) >>> float(bar.area.magnitude) 0.31 >>> float(bar.weight.magnitude) 1.043 >>> bar.diameter 0.625 inch
>>> bar = Rebar(8, grade=60) >>> bar.bar_number 8
- class civilpy.structural.steel.S(label)[source]
Bases:
SteelSectionClass to provide more specific attributes and functions related to designing with steel M s. Splitting values into multiple classes allows dropping of empty values in the database.
>>> t = S("S24X121") >>> t.weight 121.0 pound/foot
- class civilpy.structural.steel.ST(label)[source]
Bases:
WTClass to provide more specific attributes and functions related to designing with steel ST s. Splitting values into multiple classes allows dropping of empty values in the database.
>>> t = ST("ST10x48") >>> t.weight 48.0 pound/foot
- class civilpy.structural.steel.SteelMaterial(designation: str, f_y: float, f_u: float, e_s: float = 29000)[source]
Bases:
objectStructural steel material specification.
Stores yield strength, tensile strength, and modulus of elasticity for common ASTM structural steel grades. Properties carry Pint units.
- Parameters:
Example
>>> m = SteelMaterial('A36', f_y=36, f_u=58) >>> m.f_y 36 kip/inch²
- class civilpy.structural.steel.SteelSection(label)[source]
Bases:
objectMain Steel Section Class, the goal is to make the attributes of various steel sections easily accessible in various python scripts.
- __init__(label)[source]
Initialize a SteelSection from an AISC shape label.
Looks up the label in the AISC Steel Construction Manual shape table and populates standard section properties with Pint units attached.
- Parameters:
label (str) – AISC standard nomenclature, e.g.
"W36X150","w40x294","2L8X4X5/8LLBB". Case and spaces are normalized automatically.- Raises:
KeyError – If the label is not found in the AISC shape database.
Example
>>> s = SteelSection("W36X150") >>> float(s.I_x.magnitude) 9040.0
- clean_user_input(user_input)[source]
Eliminates value not found errors by removing lower case letters and spaces
>>> t = SteelSection("W 44X335") >>> print(t.clean_user_input('W 44X335')) W44X335 >>> print(t.clean_user_input('w40x294')) W40X294 >>> t.id 'W40X294'
- Returns:
cleaned input
- get_shape()[source]
Searches AISC Steel database table for member label passed to it, returns values from table if it finds a match, or prints an error if it doesn’t
>>> t = SteelSection("W36X150") >>> t.aisc_value['Type'].values[0] 'W'
>>> t = SteelSection("2L8X4X5/8LLBB") >>> t.aisc_value['W'].values[0] 48.4
- Parameters:
self
- Returns:
dataframe of raw values from AISC Shape Table
- class civilpy.structural.steel.TwoL(label)[source]
Bases:
SteelSectionClass to provide more specific attributes and functions related to designing with steel 2L s. Splitting values into multiple classes allows dropping of empty values in the database.
>>> t = TwoL("2L10X10X1-1/4") >>> t.weight 48.0 pound/foot
- class civilpy.structural.steel.W(label)[source]
Bases:
SteelSectionClass to provide more specific attributes and functions related to designing with steel W s. Splitting values into multiple classes allows dropping of empty values in the database.
>>> t = W("W36X150") >>> t.weight 150.0 pound/foot
- class civilpy.structural.steel.WF(label, designation=None)[source]
Bases:
HistoricSteelSectionHistoric wide-flange section (pre-AISC W-shape standardization).
Used for evaluating existing bridges and structures built with pre-1970 steel sections. Extends
HistoricSteelSectionwith dimensional attributes (depth, flange width, web thickness, etc.) from the AISC historic shapes table.Example
>>> t = WF("18WF96", '18WF_B18b') >>> t.weight 96.0 force_pound/foot
- class civilpy.structural.steel.WT(label)[source]
Bases:
SteelSectionClass to provide more specific attributes and functions related to designing with steel WT s. Splitting values into multiple classes allows dropping of empty values in the database.
>>> t = WT("WT22x145") >>> t.weight 145.0 pound/foot
- civilpy.structural.steel.get_bolt_weights(length: float, diameter: float, no_of_washers: int) float[source]
Function to get the bolt weights from the A325_bolt_weights dictionary and calculate the weight per bolt if it’s not contained in the table.
Original table from: https://www.portlandbolt.com/print/?table=7587
The A325_bolt_weights dictionary reports the weight indexed by bolt length and diameter, and reports the value in lbs/100 bolts. This function simplifies that by reducing the value to that of a single bolt.
If bolt weights are not contained in the dictionary, the function will utilize the following formula to calculate the weight per bolt, if no_of_washers is greater than 0, it will include the weight of the washers in the calculation with the following values:
- Per inch Adder (dia):
0.5: 5.5 0.625: 8.6 0.75: 12.4 0.875: 16.9 1.0: 22.1 1.125: 28 1.25: 34.4 1.375: 42.5 1.5: 49.7
- F436 Round Washers
0.5: 2.1 0.625: 3.6 0.75: 4.8 0.875: 7 1.0: 9.4 1.125: 11.3 1.25: 13.8 1.375: 16.8 1.5: 20
Weight in table includes the weight of nuts.
civilpy.structural.structural_model module
The canonical structural model: a pure, IFC-aligned interchange hub.
It is the in-memory analogue of a tagged
.3dm / an IFC file: a discretized structure (nodes, elements, restraints,
loads, results) carrying 3D geometry, full 6-DOF restraints, multiple load
cases, and stable ids – everything the lossy 2D
StrutAndTieModel throws away.
It holds no solver. By design the package’s three analysis paradigms
(pin-jointed method of joints, continuous-beam/frame relaxation, external FEM)
solve different math and stay separate; what they share is this data. The
model is the hub; rhino_stm, the MIDAS serializer, .3dm write-back, the
future IFC adapter, and the capacity calculators are all spokes off it
(composition, not inheritance).
The vocabulary mirrors the IFC 4.3 IfcStructuralAnalysisDomain so a future
from_ifc / to_ifc adapter is a direct mapping (see the entity table in
the design doc):
hub type |
IFC 4.3 entity |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
Units are carried as labels (default kips / ft, matching the rest of
civilpy.structural and the Rhino tag contract); actual unit conversion is an
adapter concern (e.g. midas.convert_node_units already uses the
civilpy.general.units pint registry), so the hub stays dependency-light.
Examples
>>> m = StructuralModel()
>>> a = m.add_node(0, 0, 0, label="A")
>>> b = m.add_node(27, 0, 0, label="B")
>>> _ = m.add_element(a.id, b.id)
>>> m.add_restraint(a.id, preset="pin").to_constraint_string()
'1100000'
>>> m.add_restraint(b.id, preset="roller-v").to_2d()
(False, True)
>>> _ = m.add_load(b.id, fy=-600)
>>> len(m.nodes), len(m.elements), len(m.loads)
(2, 1, 1)
>>> m.check() # no structural errors in this little model
[]
- class civilpy.structural.structural_model.BeamLoad(element_id: str, w_start: float, w_end: float | None = None, case: str = 'default', direction: Literal['GX', 'GY', 'GZ']='GZ', id: str = <factory>)[source]
Bases:
objectA distributed load on a beam element.
- civilpy.structural.structural_model.DOF_NAMES = ('x', 'y', 'z', 'rx', 'ry', 'rz')
Translational + rotational degrees of freedom, in the order used everywhere in the package – it matches the
stm.fix_*tag suffixes and the first six characters of the MIDASCONSconstraint stringDX DY DZ RX RY RZ RW.
- class civilpy.structural.structural_model.Element(node_a: str, node_b: str, role: str = 'member', member_type: str = 'auto', midas_type: str = 'TRUSS', section: str | None = None, material: str | None = None, id: str = <factory>, nodes: list[str] = <factory>, metadata: dict = <factory>)[source]
Bases:
objectA member between nodes – IFC
IfcStructuralCurveMemberorIfcStructuralSurfaceMember.roleis the typed-component taxonomy (e.g.member,top_chord,diagonal) that later drives capacity-check routing and the MIDAS element type.member_typeis thestm.memberhint:auto(default; the solver classifies by sign) or a forcedtie/strut.midas_typeis the export element type (TRUSS/BEAM/TENS/PLATE).
- class civilpy.structural.structural_model.Load(node_id: str, fx: float = 0.0, fy: float = 0.0, fz: float = 0.0, mx: float = 0.0, my: float = 0.0, mz: float = 0.0, case: str = 'default', id: str = <factory>)[source]
Bases:
objectA nodal force/moment – IFC
IfcStructuralLoadSingleForceapplied via a point action. Full 3D force (and optional moment) vector;casenames theLoadCaseit belongs to (default"default").
- class civilpy.structural.structural_model.LoadCase(name: str, description: str = '', factor: float = 1.0)[source]
Bases:
objectA named load case – IFC
IfcStructuralLoadCase.
- civilpy.structural.structural_model.MEMBER_TYPES = ('auto', 'tie', 'strut', 'beam')
autois the never-written default;tie/strutare optional author overrides).- Type:
stm.memberhint values (contract
- class civilpy.structural.structural_model.Node(x: float, y: float, z: float = 0.0, label: str | None = None, id: str = <factory>)[source]
Bases:
objectA connection point – IFC
IfcStructuralPointConnection.idis stable (defaults to a uuid);labelis the human name an importer derives (A, B, C…). Coordinates are full 3D.
- class civilpy.structural.structural_model.Restraint(node_id: str, fix_x: bool = False, fix_y: bool = False, fix_z: bool = False, fix_rx: bool = False, fix_ry: bool = False, fix_rz: bool = False, preset: str | None = None)[source]
Bases:
objectA nodal boundary condition – IFC
IfcBoundaryNodeCondition.Full 6-DOF booleans (
True= fixed). Built directly or from aSUPPORT_PRESETSname viafrom_preset().- classmethod from_preset(node_id: str, preset: str) Restraint[source]
Build a restraint from a friendly preset (
pin/roller-v/roller-h/fixed/custom).
- class civilpy.structural.structural_model.Result(case: str = 'default', element_forces: dict[str, float]=<factory>, reactions: dict[str, tuple[float, float, float, float, float, float]]=<factory>)[source]
Bases:
objectSolved results for one load case – IFC
IfcStructuralResultGroup.element_forcesmaps element id -> axial force (tension positive, the package convention).reactionsmaps node id -> a 6-tuple inDOF_NAMESorder.
- class civilpy.structural.structural_model.RigidLink(master: str, slaves: list[str], dof: str = '111111', id: str = <factory>)[source]
Bases:
objectA rigid (master-slave) constraint – IFC
IfcRelConnectsStructuralMemberwith an infinitely stiff connection.masteris the retained node; every node inslavesis tied to it in the DOFs flagged indof(a 6-charDX DY DZ RX RY RZstring,'1'= constrained). This is the deck-to-girder tie of a refined grillage: a fully rigid link ("111111") makes the slab act compositely with the girder (studs/rebar into the deck); constraining vertical/transverse only and freeing longitudinal slip models a non-composite interface. Exports to the MIDAS/db/RIGDtable.
- civilpy.structural.structural_model.SUPPORT_PRESETS = {'custom': {}, 'fixed': {'fix_rz': True, 'fix_x': True, 'fix_y': True}, 'pin': {'fix_x': True, 'fix_y': True}, 'roller-h': {'fix_x': True}, 'roller-v': {'fix_y': True}}
Friendly support presets, expressed in the 2D analysis-plane axes the
stm.supportcontract uses (x = in-plane horizontal, y = in-plane vertical, z = out-of-plane). An adapter that knows the drawing plane maps these to global 3D DOF; the 2D STM solver consumesfix_x/fix_ydirectly.customleaves everything free for explicitfix_*flags.
- class civilpy.structural.structural_model.StructuralModel(nodes: dict[str, ~civilpy.structural.structural_model.Node]=<factory>, elements: dict[str, ~civilpy.structural.structural_model.Element]=<factory>, restraints: dict[str, ~civilpy.structural.structural_model.Restraint]=<factory>, loads: list[Load] = <factory>, beam_loads: list[BeamLoad] = <factory>, rigid_links: list[RigidLink] = <factory>, load_cases: dict[str, ~civilpy.structural.structural_model.LoadCase]=<factory>, results: dict[str, ~civilpy.structural.structural_model.Result]=<factory>, units: Units = <factory>, metadata: dict = <factory>)[source]
Bases:
objectCanonical structural model – IFC
IfcStructuralAnalysisModel.A pure data container plus light add/lookup helpers. No analysis lives here; engines and adapters consume or produce it. Built incrementally:
>>> m = StructuralModel() >>> n = m.add_node(0, 0, label="A") >>> m.add_restraint(n.id, preset="pin").to_2d() (True, True)
- add_beam_load(element_id: str, w_start: float, w_end: float | None = None, *, case: str = 'default', direction: Literal['GX', 'GY', 'GZ'] = 'GZ', id: str | None = None) BeamLoad[source]
- add_element(node_a: str, node_b: str, *, role: str = 'member', member_type: str = 'auto', midas_type: str = 'TRUSS', section: str | None = None, material: str | None = None, nodes: list[str] | None = None, id: str | None = None) Element[source]
- add_load(node_id: str, *, fx: float = 0.0, fy: float = 0.0, fz: float = 0.0, mx: float = 0.0, my: float = 0.0, mz: float = 0.0, case: str = 'default', id: str | None = None) Load[source]
- add_node(x: float, y: float, z: float = 0.0, *, label: str | None = None, id: str | None = None) Node[source]
- add_restraint(node_id: str, *, preset: str | None = None, **flags: bool) Restraint[source]
Restrain a node, by
presetand/or explicitfix_*flags. Explicit flags override the preset (matching the tag contract).
- add_rigid_link(master: str, slaves: list[str], *, dof: str = '111111', id: str | None = None) RigidLink[source]
Tie
slavestomasterrigidly in the DOFs flagged indof(6-charDX DY DZ RX RY RZ). Used for the deck-to-girder composite connection in a refined grillage (seeRigidLink).
- check() list[str][source]
Return a list of structural-integrity problems (empty = clean).
Pure, geometry-level checks an importer can run before handing the model to a solver – they name the offending object, unlike the generic determinacy error the solver raises later. Flags: elements pointing at missing nodes, restraints/loads on missing nodes, zero-length elements, a model with no restraints, and nodes that no element touches.
- metadata: dict
free-form model-level tags a builder wants to hand downstream (traffic lane names and offsets, the dead-load breakdown, …) – the model-level counterpart of
Element.metadata.
- class civilpy.structural.structural_model.Units(force: str = 'kips', length: str = 'ft')[source]
Bases:
objectUnit-system labels carried with a model (not a converter).
Defaults match the package convention and the Rhino tag contract (kips / feet). Conversion between systems is an adapter responsibility – keep the hub free of a numeric-units dependency.
civilpy.structural.strut_and_tie module
Strut-and-tie modeling: build a truss idealization of a D-region, solve member forces by the method of joints, and draw the classic STM diagram (struts dashed, ties solid, forces labeled).
Conventions match civilpy.structural.beam_bending: kips and feet
(or any consistent unit pair), plot methods take an optional ax and
return the figure for Jupyter use. Tension is positive, so positive
member forces are ties and negative are struts.
The AASHTO LRFD capacity side (tie strength 5.8.2.4, node crushing
5.8.2.5, crack-control reinforcement 5.8.2.6) lives in
civilpy.structural.aashto.lrfd.stm and takes the solved forces
directly.
Examples
Deep beam idealized as a triangular truss — supports 8 ft apart, load at midspan top, 4 ft deep:
>>> stm = StrutAndTieModel()
>>> stm.add_node("A", 0, 0)
>>> stm.add_node("B", 8, 0)
>>> stm.add_node("C", 4, 4)
>>> stm.add_member("A", "C")
>>> stm.add_member("B", "C")
>>> stm.add_member("A", "B")
>>> stm.add_support("A", fix_x=True, fix_y=True)
>>> stm.add_support("B", fix_y=True)
>>> stm.add_load("C", fy=-100)
>>> forces = stm.solve()
>>> round(forces[("A", "B")], 2) # bottom chord is a tie (+)
50.0
>>> round(forces[("A", "C")], 2) # diagonals are struts (-)
-70.71
- class civilpy.structural.strut_and_tie.StrutAndTieModel(E: float = 1.0)[source]
Bases:
objectA pin-jointed truss idealization of a disturbed region.
- add_load(node: str, fx: float = 0.0, fy: float = 0.0)[source]
Applied joint load (kips); fy negative = downward.
- add_member(node_a: str, node_b: str, area: float = 1.0, member_type: str = 'auto')[source]
Add a truss member.
area(cross-sectional area) only affects the indeterminate direct-stiffness solver — it sets the relative member stiffness that picks the load path; the determinate method of joints ignores it.member_typeis thestm.memberhint:auto(classified by the solved sign) or a forcedtie/strut.
- classify(member: tuple[str, str]) str | None[source]
Classify a member as
"tie"(tension) or"strut"(compression). A forcedstm.memberoverride wins; otherwise the solved sign decides (Noneuntilsolve()has run).
- degree_of_indeterminacy() int[source]
members + reactions - 2*nodes. Zero = statically determinate, positive = indeterminate (needs the direct-stiffness solver), negative = a mechanism (unstable).
- diagnose() list[str][source]
Actionable pre-solve checks that name the offending node/member.
These checks run before a solve and point at the fixable cause – a dangling node, a load with no load path, a support on an undefined node, missing supports, and an under-constrained (mechanism) model. A statically indeterminate model is reported informationally only: it is solved by the direct-stiffness method (
solve()), not an error. Returns an empty list for a clean, determinate model.
- classmethod from_3dm(path, **kwargs)[source]
Build a model from a tagged Rhino
.3dmfile. Thin wrapper overcivilpy.structural.rhino_stm.model_from_3dm()(needs the optionalrhino3dmdependency).
- classmethod from_structural_model(hub, *, plane='auto')[source]
Project a canonical
StructuralModelhub down to this 2D model (default elevation maps X->x, Z->y). The inverse ofto_structural_model().The hub’s full 6-DOF restraints collapse to the in-plane
(fix_x, fix_y)the truss solver consumes, and 3D load vectors are projected onto the plane.plane="auto"infers the drawing plane from the node coordinates. This letsfrom_3dmparse once into the hub and project, instead of carrying a second parser.
- plot(ax=None, force_units: str = 'kips', show_reactions: bool = True)[source]
Draw the strut-and-tie diagram: ties solid red, struts dashed blue with width scaled to force magnitude, joint loads as green arrows, supports as triangles. Solves first if needed. Returns the figure.
- solve(method: Literal['auto', 'joints', 'stiffness'] = 'auto') dict[tuple[str, str], float][source]
Solve member forces and reactions.
method="auto"uses the method of joints when the model is statically determinate and the direct-stiffness method (DSM) when it is indeterminate; pass"joints"or"stiffness"to force one. Returns{(a, b): force}with tension positive (tie) and compression negative (strut); reactions land inself.reactions.The DSM result depends on the member
areas(relative stiffness picks the load path among the redundant members). Usesolve_fully_stressed()to converge areas onto the forces.
- solve_fully_stressed(iterations: int = 25, tol: float = 0.0001, min_ratio: float = 0.001) dict[tuple[str, str], float][source]
Resize members in proportion to their force and re-solve until the load path stabilizes (fully-stressed design).
For an indeterminate truss the force distribution depends on the relative member areas; setting each area proportional to
|force|and iterating drives the truss toward a uniform-stress load path — the natural strut-and-tie idealization.min_ratiofloors the area (relative to the largest) so lightly loaded members keep the truss stable instead of vanishing. Determinate models are returned unchanged after a single solve.
- to_3dm(path, **kwargs)[source]
Write this model to a tagged
.3dmfile (members as lines, supports as symbol blocks, loads as arrows).
- to_structural_model(*, plane='XZ')[source]
Lift this 2D model into the canonical
StructuralModelhub (the inverse offrom_structural_model()).In-plane coordinates and forces are placed in the chosen world
plane; the 2D(fix_x, fix_y)fixity widens to a 6-DOF restraint with the remaining DOF free. Solved member forces and reactions, if present, are carried across into aResult.
civilpy.structural.substructure module
Substructure reactions from a solved bridge grillage – the hand-off from superstructure analysis to pier/abutment and foundation design.
A refined-grillage superstructure (from
civilpy.structural.bridge_layout.grillage_model_from_layout()) sits on
bearings at every support line. Those bearing reactions, summed across the
girders of each support line and factored into the AASHTO load combinations,
are exactly the loads a substructure unit (abutment or pier) and its foundation
are designed for. This module pulls the per-node reactions MIDAS solved, bins
them onto their substructure unit by station, and applies the combinations.
Sign/units: reactions in the model’s units (kips, ft) as MIDAS reports them –
FZ up-positive vertical, FX longitudinal (along stations), FY
transverse, MX/MY/MZ the moments. The vertical FZ and the horizontals
size the bearings and the pier; the total FZ per unit sizes the foundation.
- civilpy.structural.substructure.DEFAULT_COMBOS = {'Service I': {'DC': 1.0, 'DW': 1.0, 'LL': 1.0}, 'Strength I': {'DC': 1.25, 'DW': 1.5, 'LL': 1.75}}
Vertical AASHTO LRFD strength/service combinations (max load factors).
- civilpy.structural.substructure.LOAD_TYPE = {'CLOSURE': 'DC', 'DC1': 'DC', 'DC2': 'DC', 'DW': 'DW', 'LL-LANE': 'LL', 'MOT-LANE': 'LL', 'PCB': 'DC'}
Map a load-case name to its AASHTO load type (for the combinations).
- class civilpy.structural.substructure.SubstructureUnit(index: int, name: str, station_ft: float)[source]
Bases:
objectOne support line: an abutment or pier the superstructure bears on.
- civilpy.structural.substructure.design_reactions(grouped: dict[str, dict[str, tuple]], *, combos: dict[str, dict[str, float]] | None = None) dict[str, dict[str, tuple]][source]
Apply the AASHTO load combinations to grouped per-unit reactions.
Returns
{unit_name: {combo_name: (FX..MZ)}}– the factored reactions each substructure unit and foundation is designed for. Cases are mapped to DC/DW/LL byLOAD_TYPE; a case with an unknown name is treated as DC.
- civilpy.structural.substructure.fetch_support_reactions(midas, model, cases: list[str], *, suffix: str = '(ST)') dict[str, dict[int, tuple]][source]
Pull the support reactions for each static
casefrom a solved MIDAS model:{case: {midas_node_id: (FX, FY, FZ, MX, MY, MZ)}}.suffixis the result-case suffix MIDAS appends ("(ST)"static,"(CB)"combination). CallMidasCivil.analyze()first.
- civilpy.structural.substructure.group_support_reactions(model, layout, reactions_by_case: dict[str, dict[int, tuple]]) dict[str, dict[str, tuple]][source]
Sum per-node reactions onto their substructure unit, per case.
reactions_by_caseis{case: {midas_node_id: 6-tuple}}(fromfetch_support_reactions()). Each node is binned to the nearest support line by its station (X). Returns{unit_name: {case: (FX..MZ) sum}}.
- civilpy.structural.substructure.restrained_node_midas_ids(model) list[int][source]
MIDAS ids of the restrained (support) nodes.
- civilpy.structural.substructure.substructure_reaction_report(midas, model, layout, cases: list[str], *, combos: dict[str, dict[str, float]] | None = None) dict[source]
Solve-side convenience: fetch, group, and combine in one call on a solved MIDAS model. Returns
{"by_case": grouped, "design": design_reactions, "units": [...]}.
- civilpy.structural.substructure.substructure_units(layout) list[SubstructureUnit][source]
The abutments and piers of
layout, one per support line.
civilpy.structural.substructure_layout module
Substructure geometry placement: the executed design becomes the model.
substructure_from_layout() closes the loop the substructure design
notebook opens. The superstructure hands its factored reactions to
optimize_pier_cap() (pier and
abutment caps), MultiColumnBent (columns),
and RetainingWall (wingwalls); this
module reads the dimensions out of those design objects — never free
parameters — and places them under the bridge in the layout’s coordinate
frame, mirroring how BridgeInput
drives the superstructure.
Placement conventions (all feet, the hub frame: X = stations along the centerline, Y transverse with girder 1 at y = 0, Z = 0 at top of deck at the crown):
Each support line runs along the skew: plan direction
u = (sin(skew), cos(skew)), so a cap’s local coordinatesis the distance along the cap withs = 0at girder 1 — the same frame theload_xs/column_xsfed tooptimize_pier_capare measured in.The cap top is a level plane set one minimum seat below the lowest bearing-stack bottom on that support line; each girder then gets a beam seat block making up its own stack height, so the seats step across the width following the deck cross slope.
The cap is centered on the girder group: its length comes from the design (
PierCapDesign.span— girders plus the sweep’s edge distance), so the start offset is recovered as(width_along_cap - span) / 2without re-entering the edge parameter.
Everything here is a plain geometry record; civilpy.structural
.rhino_bim turns it into tagged emit objects on the Substructure::*
layers.
- class civilpy.structural.substructure_layout.AbutmentGeometry(unit: SubstructureUnit, cap: CapBeam, seats: tuple[BeamSeat, ...], piles: tuple[PileGeometry, ...], backwall: WallPanel | None = None, wingwalls: tuple[WallPanel, ...] = (), kind: Literal['seat', 'semi-integral', 'integral'] = 'seat', diaphragm: WallPanel | None = None)[source]
Bases:
objectOne abutment.
kindis"seat"(bearings on a stepped-seat cap),"semi-integral"(seat cap plus an end diaphragm that moves with the superstructure), or"integral"(a full-height end diaphragm on a single pile row — no bearings, soseatsis empty).- kind: Literal['seat', 'semi-integral', 'integral'] = 'seat'
Abutment configuration this geometry represents.
- piles: tuple[PileGeometry, ...]
- unit: SubstructureUnit
- class civilpy.structural.substructure_layout.AbutmentSpec(pile_xs_ft: tuple[float, ...], pile_shape: str = 'HP10X42', pile_length_ft: float = 40.0, backwall_thickness_in: float = 18.0, wingwall: object | None = None, wingwall_length_ft: float = 0.0)[source]
Bases:
objectCapped-pile abutment parameters that live outside the cap design: the pile layout the cap STM was solved on (
pile_xs_ftin the same girder-1-origin frame as itscolumn_xs), the driven length from the geotech recommendation, and the wingwall design.wingwallis the executedRetainingWallwhose stem/footing dimensions the wingwall panels are read from;wingwall_length_ftits run along the roadway.- pile_shape: str = 'HP10X42'
AISC HP label from
civilpy.structural.steel, e.g."HP10X42".
- class civilpy.structural.substructure_layout.BeamSeat(girder_line: int, center: tuple[float, float, float], side_in: float, height_in: float)[source]
Bases:
objectOne stepped bearing seat: a square pedestal from the cap top up to the bottom of that girder’s bearing stack.
- class civilpy.structural.substructure_layout.BentPierSpec(cap_design: object, bent: object, footing: FootingSpec | None = None)[source]
Bases:
objectMulti-column bent: cap from
cap_design, columns frombent(seepier_geometry()).- build(layout, unit, **frame_kw) PierGeometry[source]
- footing: FootingSpec | None = None
- class civilpy.structural.substructure_layout.CapBeam(origin: tuple[float, float, float], axis: tuple[float, float, float], length_ft: float, width_ft: float, depth_ft: float, tie_bar_size: int | None = None, tie_bar_count: int | None = None, tie_z_frac: float | None = None, soffit_profile: tuple[tuple[float, float], ...] | None = None)[source]
Bases:
objectA cap beam along a (possibly skewed) support line.
originis the cap-top centerline point ats = s0;axisthe unit vector along the cap.tie_bar_*carry the governing STM tie’s bar schedule for the rebar emit andtie_z_fracits height in the cap (0 = soffit, 1 = top — a hammerhead’s governing tie is the top chord); None when no design was attached.soffit_profilemakes the cap non-prismatic:(s_rel, depth_ft)breakpoints from the cap start, linearly interpolated — the top stays level and the soffit steps/tapers (hammerhead cantilevers).Noneis the constant-depth cap.
- class civilpy.structural.substructure_layout.ColumnGeometry(center: tuple[float, float], z_top: float, z_bot: float, diameter_in: float | None = None, b_in: float | None = None, h_in: float | None = None, bars_area_in2: float = 0.0)[source]
Bases:
objectOne pier column, cap soffit to footing top. Circular when
diameter_inis set, else rectangularb_in x h_in(balong the cap axis).
- civilpy.structural.substructure_layout.DEFAULT_BEARING_STACK_IN = 4.5
Bearing-stack height (in) under a girder bottom flange — load plate plus elastomeric pad, matching the
rhino_bimhardware defaults (1.5 in plate + 5 x 0.6 in plies).
- class civilpy.structural.substructure_layout.FootingGeometry(center: tuple[float, float], z_top: float, length_ft: float, width_ft: float, thickness_ft: float, axis: tuple[float, float, float])[source]
Bases:
objectSpread/pile-cap footing under one column, aligned with the cap axes (
length_ftalong the cap).
- class civilpy.structural.substructure_layout.FootingSpec(length_ft: float, width_ft: float, thickness_ft: float)[source]
Bases:
objectPer-column footing plan dims (a geotech deliverable — no civilpy footing designer exists yet, so these stay explicit inputs).
- class civilpy.structural.substructure_layout.HammerheadSpec(cap_design: object, column: object, tip_depth_ft: float | None = None, footing: FootingSpec | None = None)[source]
Bases:
objectSingle-column hammerhead pier (see
hammerhead_geometry()).columnis the executedPierColumn.- build(layout, unit, **frame_kw) PierGeometry[source]
- footing: FootingSpec | None = None
- class civilpy.structural.substructure_layout.IntegralAbutmentSpec(pile_xs_ft: tuple[float, ...], pile_shape: str = 'HP10X42', pile_length_ft: float = 40.0, diaphragm_thickness_in: float = 36.0, embed_below_girder_ft: float = 1.0, end_extension_ft: float = 2.0, pile_embed_in: float = 24.0, wingwall: object | None = None, wingwall_length_ft: float = 0.0)[source]
Bases:
objectIntegral abutment: a full-height end diaphragm cast around the girder ends on a single row of piles — no bearings. The diaphragm depth is derived from the layout (high deck edge down to
embed_below_girder_ftunder the girder bottom flange), not a free parameter; piles embedpile_embed_in(2 ft typical) into it.- build(layout, unit, **_frame_kw) AbutmentGeometry[source]
- pile_shape: str = 'HP10X42'
AISC HP label from
civilpy.structural.steel, e.g."HP10X42".
- civilpy.structural.substructure_layout.PILE_EMBED_IN = 12.0
pile head embedment into a capped-pile cap
- class civilpy.structural.substructure_layout.PierGeometry(unit: SubstructureUnit, cap: CapBeam, seats: tuple[BeamSeat, ...], columns: tuple[ColumnGeometry, ...] = (), footings: tuple[FootingGeometry, ...] = (), piles: tuple[PileGeometry, ...] = ())[source]
Bases:
objectOne pier: a multi-column bent carries
columns(+ optionalfootings); a capped-pile bent carriespilesinstead.- columns: tuple[ColumnGeometry, ...] = ()
- footings: tuple[FootingGeometry, ...] = ()
- piles: tuple[PileGeometry, ...] = ()
- unit: SubstructureUnit
- class civilpy.structural.substructure_layout.PileBentSpec(cap_design: object, pile_xs_ft: tuple[float, ...], pile_shape: str = 'HP12X53', pile_length_ft: float = 40.0)[source]
Bases:
objectCapped-pile pier (see
pile_bent_geometry()).- build(layout, unit, **frame_kw) PierGeometry[source]
- pile_shape: str = 'HP12X53'
AISC HP label from
civilpy.structural.steel(CPP-1-08 default"HP12X53").
- class civilpy.structural.substructure_layout.PileGeometry(head: tuple[float, float, float], shape: str, length_ft: float)[source]
Bases:
objectOne driven HP pile.
headis the butt at the embedment plane inside the cap; the pay length runs below the cutoff.
- civilpy.structural.substructure_layout.SEAT_MIN_IN = 3.0
minimum beam-seat (pedestal) height
- civilpy.structural.substructure_layout.SEAT_SIDE_IN = 27.0
21 in load plate + 3 in edges
- Type:
seat plan side
- class civilpy.structural.substructure_layout.SeatAbutmentSpec(cap_design: object, spec: AbutmentSpec)[source]
Bases:
objectConventional seat abutment: the Phase-4
AbutmentSpecplus its cap design, buildable per unit.- build(layout, unit, **frame_kw) AbutmentGeometry[source]
- spec: AbutmentSpec
- class civilpy.structural.substructure_layout.SemiIntegralAbutmentSpec(cap_design: object, spec: AbutmentSpec, diaphragm_thickness_in: float = 30.0)[source]
Bases:
objectSeat abutment plus the superstructure-borne end diaphragm (see
semi_integral_abutment_geometry()).- build(layout, unit, **frame_kw) AbutmentGeometry[source]
- spec: AbutmentSpec
- class civilpy.structural.substructure_layout.SubstructureLayout(layout: object, abutments: tuple[AbutmentGeometry, ...], piers: tuple[PierGeometry, ...])[source]
Bases:
objectEvery substructure unit of one bridge, placed under its layout.
- abutments: tuple[AbutmentGeometry, ...]
- piers: tuple[PierGeometry, ...]
- class civilpy.structural.substructure_layout.WallPanel(origin: tuple[float, float, float], axis: tuple[float, float, float], length_ft: float, thickness_ft: float, height_ft: float)[source]
Bases:
objectA rectangular wall run (backwall or wingwall stem/footing).
originis the bottom-centerline start point; the panel extendslength_ftalongaxis,thickness_ftcentered on the line,height_ftup.
- civilpy.structural.substructure_layout.abutment_geometry(layout, unit: SubstructureUnit, cap_design, spec: AbutmentSpec, *, bearing_stack_in: float = 4.5, seat_min_in: float = 3.0, seat_side_in: float = 27.0, pile_embed_in: float = 12.0) AbutmentGeometry[source]
Place one capped-pile abutment: the cap from its
cap_design(anoptimize_pier_cap()run with the piles as supports), the piles fromspec, the backwall from the cap top to the low deck edge, and wingwall stem+footing panels from the executedRetainingWall.
- civilpy.structural.substructure_layout.assemble_substructure(layout, assignments: dict, *, bearing_stack_in: float = 4.5, seat_min_in: float = 3.0, seat_side_in: float = 27.0) SubstructureLayout[source]
Place a substructure that mixes unit types.
assignmentsmaps a support-line index (0 at the start abutment) to its typed spec (BentPierSpec,PileBentSpec,SeatAbutmentSpec, …); the string keys"pier"and"abutment"supply defaults for unassigned units of that role.
- civilpy.structural.substructure_layout.hammerhead_geometry(layout, unit: SubstructureUnit, cap_design, column, *, tip_depth_ft: float | None = None, footing: FootingSpec | None = None, bearing_stack_in: float = 4.5, seat_min_in: float = 3.0, seat_side_in: float = 27.0) PierGeometry[source]
Place one hammerhead pier: the cap from
cap_design(anoptimize_pier_cap()run with a single column support — the cantilever D-region is exactly what the STM checks, and its governing tie lands in the top chord, which the rebar emit follows), the stem fromcolumn(aPierColumnfor section/height/steel).tip_depth_fttapers the soffit linearly from the full design depth at the column faces to this depth at the cantilever tips (Nonekeeps the cap prismatic). The STM was solved on the full- depth rectangle, so the tie schedule carries over; the taper is the conventional weight/formwork refinement outside the nodal zones.
- civilpy.structural.substructure_layout.integral_abutment_geometry(layout, unit: SubstructureUnit, spec: IntegralAbutmentSpec) AbutmentGeometry[source]
Place one integral abutment (see
IntegralAbutmentSpec). The superstructure emit must skip the bearing stack at this support line (girder_bridge_emit(..., integral_supports=...)); thegdr.*support points stay for the analysis reader.
- civilpy.structural.substructure_layout.pier_geometry(layout, unit: SubstructureUnit, cap_design, bent, *, footing: FootingSpec | None = None, bearing_stack_in: float = 4.5, seat_min_in: float = 3.0, seat_side_in: float = 27.0) PierGeometry[source]
Place one pier from its executed designs: the cap from
cap_design(PierCapDesign), the columns frombent(MultiColumnBent, whosecap.column_positionsare inches from the left end of the cap).
- civilpy.structural.substructure_layout.pile_bent_geometry(layout, unit: SubstructureUnit, cap_design, pile_xs_ft, *, pile_shape: str = 'HP12X53', pile_length_ft: float = 40.0, bearing_stack_in: float = 4.5, seat_min_in: float = 3.0, seat_side_in: float = 27.0, pile_embed_in: float = 12.0) PierGeometry[source]
Place one capped-pile pier (pile bent): the cap from
cap_design(anoptimize_pier_cap()run with the piles as supports, same as the abutment cap) directly on driven piles atpile_xs_ft— the CPP-1-08 pattern generalized off the continuous-slab sheet, whoseHP12X53default the pile shape keeps (civilpy.structural.odot.capped_pile_piercarries the SCD’s own limits for the standard-drawing case).
- civilpy.structural.substructure_layout.semi_integral_abutment_geometry(layout, unit: SubstructureUnit, cap_design, spec: AbutmentSpec, *, diaphragm_thickness_in: float = 30.0, bearing_stack_in: float = 4.5, seat_min_in: float = 3.0, seat_side_in: float = 27.0, pile_embed_in: float = 12.0) AbutmentGeometry[source]
Place one semi-integral abutment: the seat abutment (cap on piles, stepped seats, bearings) with the backwall replaced by an end diaphragm that encases the girder ends and moves with the superstructure. The diaphragm runs the cap length, offset inward (toward the span) so its back face sits over the cap’s back edge, from the bearing plane up to the high deck edge (drawn level; the crown-following top is the same refinement flagged for backwalls).
- civilpy.structural.substructure_layout.substructure_from_layout(layout, *, pier_cap, pier_bent, abutment_cap, abutment: AbutmentSpec, footing: FootingSpec | None = None, bearing_stack_in: float = 4.5, seat_min_in: float = 3.0, seat_side_in: float = 27.0) SubstructureLayout[source]
Place the full substructure under
layoutfrom the executed designs: every pier getspier_cap+pier_bentand every abutment getsabutment_cap+abutment(one design reused across identical units, the way the notebook designs them — pass the per-unit builders directly for units that differ).
civilpy.structural.truss module
Plane truss analysis by the method of joints, with the force diagram.
This is the general-statics face of
civilpy.structural.strut_and_tie.StrutAndTieModel — identical
solver and plotting (tension red/solid, compression blue/dashed), plus
member-length and axial-stress helpers for sizing.
Examples
>>> t = Truss()
>>> t.add_node("A", 0, 0)
>>> t.add_node("B", 12, 0)
>>> t.add_node("C", 6, 8)
>>> t.add_member("A", "B")
>>> t.add_member("A", "C")
>>> t.add_member("B", "C")
>>> t.add_support("A", fix_x=True, fix_y=True)
>>> t.add_support("B", fix_y=True)
>>> t.add_load("C", fx=10)
>>> f = t.solve()
>>> round(t.member_lengths()[("A", "C")], 0)
10.0
- class civilpy.structural.truss.Truss(E: float = 1.0)[source]
Bases:
StrutAndTieModelPin-jointed plane truss; see the base class for the full API.
civilpy.structural.truss_builder module
Object-oriented truss bridge builder: typed components, semantic deck and lane definitions, plane-truss analysis, AASHTO capacity checks, and direct MIDAS Civil NX API export.
The builder generates a complete truss bridge from panel geometry that
may be fully non-uniform — every panel length and every panel-point
height is independent, so real (often asymmetric) bridges can be modeled
exactly. Components are typed (TopChord, Diagonal,
Floorbeam, Stringer, …) so loads, capacity checks,
and the exported analysis model all know what each piece is.
The load path is resolved semantically: a Deck (with an
optional centerline offset for asymmetric roadways) sheds area load to
the stringer lines by tributary width, stringers deliver reactions to
the floorbeams, and each floorbeam distributes to the two truss planes
by statics — so an off-center deck correctly loads one truss harder than
the other. LaneLine objects place vehicle lanes by transverse
offset; on export they become MIDAS traffic line lanes riding the
nearest stringer elements with the exact eccentricity.
Conventions match civilpy.structural.beam_bending: kips and feet,
plot methods take an optional ax and return the figure.
Examples
A 100-ft through Pratt truss with non-uniform panels and a polygonal (Parker-style) top chord:
>>> bridge = TrussBridge(panel_lengths_ft=[22, 28, 28, 22],
... heights_ft=[None, 24, 27, 24, None],
... width_ft=18.0)
>>> bridge.nodes["U2"]
(50.0, 27.0)
>>> _ = bridge.set_deck(Deck(width_ft=16.0, thickness_in=8.0))
>>> forces = bridge.solve()
>>> forces[("L1", "L2")] > 0 # bottom chord in tension
True
>>> forces[("U1", "U2")] < 0 # top chord in compression
True
>>> r = bridge.reactions()
>>> total = bridge.deck_area_load_ksf() * 16.0 * 100.0
>>> abs(sum(v[1] for v in r.values()) - total / 2.0) < 1e-6
True
- class civilpy.structural.truss_builder.BottomChord(start: str, end: str, section: TrussSection | None = None, midas_type: str | None = None)[source]
Bases:
Member- midas_type = 'BEAM'
- role = 'Bottom Chord'
- class civilpy.structural.truss_builder.Counter(start: str, end: str, section: TrussSection | None = None, midas_type: str | None = None)[source]
Bases:
DiagonalTension-only counter diagonal (TENS element on export). Counters make the plane truss statically indeterminate, so add them to MIDAS-bound models only.
- midas_type = 'TENS'
- role = 'Counter'
- class civilpy.structural.truss_builder.Deck(width_ft: float, thickness_in: float = 8.0, unit_weight_pcf: float = 150.0, wearing_surface_psf: float = 0.0, offset_ft: float = 0.0)[source]
Bases:
objectSemantic deck definition: geometry plus the loads it generates.
offset_ftshifts the deck centerline transversely (positive toward the +y truss plane) so asymmetric roadways shed more load to one truss. Deck self weight is DC;wearing_surface_psfis DW.
- class civilpy.structural.truss_builder.Diagonal(start: str, end: str, section: TrussSection | None = None, midas_type: str | None = None)[source]
Bases:
Member- role = 'Diagonal'
- class civilpy.structural.truss_builder.EndPost(start: str, end: str, section: TrussSection | None = None, midas_type: str | None = None)[source]
Bases:
Member- midas_type = 'BEAM'
- role = 'End Post'
- class civilpy.structural.truss_builder.Floorbeam(point: str, section: TrussSection | None = None)[source]
Bases:
MemberTransverse floor member at a panel point, spanning between the two truss planes.
start/endname the deck-level panel-point node; the export expands it across the bridge width.- midas_type = 'BEAM'
- role = 'Floorbeam'
- class civilpy.structural.truss_builder.Hanger(start: str, end: str, section: TrussSection | None = None, midas_type: str | None = None)[source]
Bases:
Vertical- role = 'Hanger'
- class civilpy.structural.truss_builder.LaneLine(name: str, offset_ft: float, width_ft: float = 12.0, wheel_space_ft: float = 6.0)[source]
Bases:
objectA vehicle lane line at a transverse offset from the bridge centerline (feet, positive toward the +y truss plane).
- class civilpy.structural.truss_builder.LateralBrace(start: str, end: str, section: TrussSection | None = None, midas_type: str | None = None)[source]
Bases:
MemberCross-frame/lateral member added manually between any two 3-D node names (use the prime suffix for far-plane nodes, e.g.
LateralBrace("U1", "U2'")).- role = 'Lateral Brace'
- class civilpy.structural.truss_builder.Member(start: str, end: str, section: TrussSection | None = None, midas_type: str | None = None)[source]
Bases:
objectA typed truss-bridge component between two named nodes.
expectedrecords the gravity-load behavior the type implies (tension/compression/flexure) and routesTrussBridge. capacity_checks()to the matching AASHTO check.midas_typeis the element type used on API export (BEAM, TRUSS, or TENS for tension-only members such as counters).- midas_type = 'TRUSS'
- role = 'Member'
- class civilpy.structural.truss_builder.Stringer(offset_ft: float, section: TrussSection | None = None)[source]
Bases:
MemberA longitudinal stringer line at a transverse offset, bearing on every floorbeam. One object represents the whole line; the export breaks it into one element per panel.
- midas_type = 'BEAM'
- role = 'Stringer'
- class civilpy.structural.truss_builder.Strut(point: str, section: TrussSection | None = None)[source]
Bases:
MemberTransverse strut tying the two truss planes at a panel point away from the deck (e.g. top struts on a through truss).
- midas_type = 'BEAM'
- role = 'Strut'
- class civilpy.structural.truss_builder.TopChord(start: str, end: str, section: TrussSection | None = None, midas_type: str | None = None)[source]
Bases:
Member- midas_type = 'BEAM'
- role = 'Top Chord'
- class civilpy.structural.truss_builder.TrussBridge(panel_lengths_ft, height_ft: float | None = None, *, heights_ft=None, width_ft: float = 20.0, pattern: str | None = 'pratt', deck_level: str = 'bottom')[source]
Bases:
objectBuild an analysis-ready truss bridge from panel geometry.
- Parameters:
panel_lengths_ft (list of float) – Length of every panel — non-uniform spacing is fully supported.
height_ft (float, optional) – Uniform truss depth. For a through truss (
deck_level= "bottom") the end panel points get no top node, producing inclined end posts; for a deck truss the top chord runs full length.heights_ft (list of float or None, optional) – Explicit depth at every panel point (
len(panel_lengths_ft) + 1entries) for polygonal and asymmetric chords;Noneor0means no top node there. Overridesheight_ft.width_ft (float) – Center-to-center spacing of the two truss planes.
pattern ({"pratt", "howe", "warren", None}) – Web layout generated automatically.
Nonebuilds chords and end posts only, for fully custom webs viaadd()."warren"(with verticals) needs an even panel count.deck_level ({"bottom", "top"}) – Chord carrying the floor system — through truss or deck truss.
- add(member: Member)[source]
Add a custom typed member. Plane members (chords, web) use 2-D node names;
LateralBracemay cross planes with the prime suffix.
- add_panel_load(node: str, p_kips: float)[source]
Extra vertical load (positive down, kips) applied to a panel point of each truss plane — e.g. utilities or sidewalk.
- assign_section(member_type: type, section: TrussSection)[source]
Assign a section to every member of a typed class (subclasses included):
bridge.assign_section(Diagonal, TrussSection(...)).
- capacity_checks(case: str = 'total', k_factor: float = 1.0, design_year: int | None = None) dict[str, object][source]
AASHTO LRFD axial checks for every plane member with a section: tension members through 6.8.2.1 (yield/rupture) and compression members through 6.9.4.1.1 (column buckling with KL/r from the member’s own length). Returns
{member.name: CheckResult}; members without sections are skipped.
- deck_area_load_ksf(case: str = 'total') float[source]
Deck area load in ksf:
"dc"(self weight),"dw"(wearing surface), or"total".
- floorbeam_point_loads(index: int, case: str = 'total') dict[float, float][source]
Concentrated loads (kips, positive down) the stringers drop on floorbeam
index, keyed by transverse offset. Each stringer span is simply supported between floorbeams, so a floorbeam takes half of each adjacent panel.
- laterals: list[LateralBrace]
- member_forces(case: str = 'total') dict[Member, float][source]
Governing (largest-magnitude) force per typed member across both truss planes.
- midas_payloads(node_start: int = 1, elem_start: int = 1) dict[str, dict][source]
Build every
PUT /db/*Assign body for the full 3-D model.Returns
{table: assign}in send order: UNIT, MATL, SECT, NODE, ELEM, CONS, GRUP (structure groups named by component type), STLD (DC/DW cases), BMLD (deck load on the stringers), and LLAN (traffic line lanes riding the nearest stringer line).Load paths stay semantic in the export: deck weight lands on stringer elements as beam loads, lanes reference stringer elements with their true eccentricity, and every element belongs to a group named for its component type. Field layouts for GRUP/STLD/BMLD follow the MIDAS API manual but are unverified against a live Civil NX session — check the send report’s errors first when debugging.
- panel_point_loads(plane: str = 'near', case: str = 'total') dict[str, float][source]
Vertical load (kips, positive down) at each deck-level panel point of one truss plane, from floorbeam statics.
planeis"near"(y = -width/2) or"far"(+width/2); an off-center deck loads the planes unequally.
- plane_truss(plane: str = 'near', case: str = 'total') Truss[source]
The 2-D
Trussfor one plane, loaded at the deck-level panel points; pin at L0, roller at the far bottom chord end.
- plot_3d(ax=None, show_lanes: bool = True, elev: float = 22.0, azim: float = -60.0)[source]
Isometric wireframe of the complete 3-D model, drawn from the same node/element tables
midas_payloads()exports — so the picture is exactly what ato_midas()call will build: both truss planes, floorbeams (split at stringer crossings), stringers, struts, and any laterals, colored by component type, with lane lines and the deck outline overlaid. Returns the figure.
- plot_cross_section(ax=None)[source]
Transverse section at a floorbeam: truss planes, floorbeam, stringers, deck, and lane positions. Returns the figure.
- plot_elevation(ax=None)[source]
Elevation of the truss colored by member type, with the deck level and panel points marked. Returns the figure.
- plot_forces(plane: str = 'near', case: str = 'total', ax=None)[source]
Solved member-force diagram (tension red, compression blue) for one truss plane; see
Truss.plot().
- set_deck(deck: Deck, stringer_offsets_ft=None, max_stringer_spacing_ft: float = 8.0)[source]
Attach the deck and lay out stringer lines.
Without explicit offsets, stringers are spaced evenly across the deck width (edge lines at the deck edges) at no more than
max_stringer_spacing_ft.
- solve(plane: str = 'near', case: str = 'total') dict[tuple[str, str], float][source]
Member forces (kips, tension positive) for one truss plane.
- stringer_line_loads(case: str = 'total') dict[float, float][source]
Uniform load on each stringer line (klf, positive down) from the deck by tributary width, keyed by stringer offset.
- to_midas(midas=None, **client_kwargs) dict[source]
Send the model to a live Civil NX session through
civilpy.structural.midas.MidasCivil(created from~/secrets.jsonwhen not given). Sends each table in order and keeps going on errors; returns{table: {"sent": n} | {"error": message}}so a single bad table is visible without losing the rest of the model.
- class civilpy.structural.truss_builder.TrussSection(name: str, area_in2: float, fy_ksi: float = 50.0, fu_ksi: float = 65.0, r_in: float | None = None, net_area_in2: float | None = None, shear_lag_u: float = 1.0)[source]
Bases:
objectAxial-member section for capacity checks and export.
r_inis the governing radius of gyration (needed only for compression checks);net_area_in2enables the rupture check on tension members.
civilpy.structural.wood module
Wood structural design per NDS (National Design Specification) 2024.
Provides section property lookups, NDS adjustment factor calculations, member design checks (bending, shear, compression, tension, deflection), timber bridge design per AASHTO LRFD, and bolted connection design.
References
AWC, National Design Specification (NDS) for Wood Construction, 2024.
Breyer, D. et al., Design of Wood Structures – ASD/LRFD, 8th Ed.
AASHTO LRFD Bridge Design Specifications, 9th Ed., Section 8 & 9.
USDA Forest Service, Timber Bridge Design, Construction, Inspection and Maintenance (LTRC/LTAP).
- class civilpy.structural.wood.AdjustmentFactors(section, method='ASD')[source]
Bases:
objectNDS adjustment factor calculator.
Computes all applicable adjustment factors for a wood member and returns adjusted design values. Works with both sawn lumber and glulam sections.
- Parameters:
section – A LumberSection or GlulamSection instance.
method – “ASD” or “LRFD”. Default “ASD”.
- CF(prop='Fb')[source]
Size factor for sawn lumber per NDS Table 4.3.1.
Returns 1.0 for glulam or Beams & Stringers / Posts & Timbers.
- CL(lu, load_type='uniform')[source]
Beam stability factor per NDS 3.3.3.
- Parameters:
lu – Unbraced length (Pint length quantity). Use 0 if fully braced.
load_type – Loading condition for effective length calculation.
- CM(prop, wet_service=False)[source]
Wet service factor per NDS Table 4.3.3 / 5.3.3.
- Parameters:
prop – Property name (“Fb”, “Ft”, “Fv”, “Fc_perp”, “Fc”, “E”, “Emin”).
wet_service – True if in-service moisture content > 19%.
- CV(L=None)[source]
Volume factor for glulam per NDS 5.3.6.
CV = (21/L)^(1/x) * (12/d)^(1/x) * (5.125/b)^(1/x) where x = 10 for Western Species, x = 10 for Southern Pine.
- Parameters:
L – Span length in feet (Pint length). Required for glulam.
- Cb(bearing_length)[source]
Bearing area factor per NDS 3.10.4.
- Parameters:
bearing_length – Length of bearing along grain (Pint length).
- Cp(le, axis='strong')[source]
Column stability factor per NDS 3.7.1.
- Parameters:
le – Effective column length (Pint length quantity).
axis – “strong” or “weak” buckling axis.
- Cr(repetitive=False)[source]
Repetitive member factor. 1.15 if 3+ members <= 12 in. deep, spaced <= 24 in. o.c.
- Ct(temperature_f, prop, wet_service=False)[source]
Temperature factor per NDS Table 2.3.3.
- Parameters:
temperature_f – Maximum sustained temperature in deg F.
prop – Property name.
wet_service – True if wet service conditions.
- adjusted_value(prop, load_duration='normal', wet_service=False, temperature_f=100.0, incised=False, repetitive=False, lu=None, le_col=None, bearing_length=None, flat_use=False, span_length=None, load_type='uniform')[source]
Compute the fully adjusted design value F’ for a given property.
- Parameters:
prop – Property name (“Fb”, “Ft”, “Fv”, “Fc_perp”, “Fc”, “E”, “Emin”).
load_duration – Load duration case name.
wet_service – True if MC > 19%.
temperature_f – Max sustained temperature (deg F).
incised – True if incised for treatment.
repetitive – True if repetitive member.
lu – Unbraced length for CL (bending only).
le_col – Effective column length for Cp (compression only).
bearing_length – Bearing length for Cb (Fc_perp only).
flat_use – True if loaded on flat face.
span_length – Span length for CV (glulam bending only).
load_type – Loading type for CL calculation.
- Returns:
{“value”: adjusted Pint quantity, “factors”: dict of factor names->values}
- Return type:
- class civilpy.structural.wood.BoltConnection(bolt_diameter, main_thickness, side_thickness, main_species_gravity, side_species_gravity=None, side_material='wood', loading='single_shear', angle_to_grain=0)[source]
Bases:
objectBolted connection design per NDS Chapter 12.
Computes reference lateral design values using the yield mode equations and applies NDS adjustment factors.
- Parameters:
bolt_diameter – Bolt diameter (Pint length, e.g. 0.75*units(“in”)).
main_thickness – Main member thickness (Pint length).
side_thickness – Side member thickness (Pint length).
main_species_gravity – Specific gravity of main member.
side_species_gravity – Specific gravity of side member (use 1.0 for steel).
side_material – “wood” or “steel”.
loading – “single_shear” or “double_shear”.
angle_to_grain – Angle between force and grain direction (degrees). 0 = parallel, 90 = perpendicular.
- C_delta(end_distance, edge_distance, bolt_spacing, row_spacing=None)[source]
Geometry factor per NDS 12.5.
Checks minimum end distance, edge distance, and spacing requirements. Returns 1.0 if all minimums met, reduced value if distances are between minimum and full.
- Parameters:
end_distance – Distance from bolt center to end of member (Pint length).
edge_distance – Distance from bolt center to edge (Pint length).
bolt_spacing – Center-to-center spacing between bolts in a row (Pint length).
row_spacing – Spacing between rows (Pint length). Optional.
- Returns:
Geometry factor (0 to 1.0).
- Return type:
- Cg(num_bolts_in_row, Am=None, As=None)[source]
Group action factor per NDS 10.3.6 Table 10.3.6A.
Simplified approach: for 2 bolts in a row, Cg ~ 1.0. For more bolts, Cg decreases.
- Parameters:
num_bolts_in_row – Number of fasteners in a row.
Am – Area of main member (in^2). Defaults from section.
As – Area of side member (in^2). Defaults to Am/2.
- Returns:
Group action factor.
- Return type:
- Z_prime(num_bolts=1, num_bolts_in_row=1, load_duration='normal', wet_service=False, temperature_f=100.0, end_distance=None, edge_distance=None, bolt_spacing=None)[source]
Adjusted total connection design value Z’.
Z’ = Z * n * CD * CM * Ct * Cg * C_delta
- Parameters:
num_bolts – Total number of bolts.
num_bolts_in_row – Bolts per row (for Cg).
load_duration – Load duration case.
wet_service – True if wet service.
temperature_f – Max sustained temperature.
end_distance – End distance (Pint length).
edge_distance – Edge distance (Pint length).
bolt_spacing – In-row spacing (Pint length).
- Returns:
dict with Z_prime, Z_ref, factors.
- class civilpy.structural.wood.GlulamSection(width, depth, combination=None, species=None)[source]
Bases:
objectGlued laminated timber section with NDS reference design values.
Can be constructed either from a size label or separate width/depth:
>>> beam = GlulamSection("6.75x30", "24F-V4", "Douglas Fir") >>> beam = GlulamSection(6.75, 30, "24F-V4", "Douglas Fir")
- Parameters:
width – Actual width in inches (e.g. 5.125, 6.75, 8.75, 10.75), or a size label string such as “6.75x30” (in which case the remaining arguments shift left).
depth – Actual depth in inches.
combination – Glulam combination symbol (e.g. “24F-V4”, “20F-V7”).
species – Species group (e.g. “Douglas Fir”, “Southern Pine”).
Example
>>> beam = GlulamSection(6.75, 30, "24F-V4", "Douglas Fir") >>> float(beam.Fb_pos.magnitude) 2400.0
- class civilpy.structural.wood.LumberSection(nominal_width, nominal_depth, species=None, grade=None)[source]
Bases:
objectSawn lumber section with NDS reference design values.
Looks up actual dimensions from the lumber sizes table and reference design values from the NDS Supplement tables.
Can be constructed either from a size label (like steel sections, e.g.
W("W36X150")) or from separate width/depth values:>>> beam = LumberSection("6x12", "Douglas Fir-Larch", "No. 1") >>> beam = LumberSection(6, 12, "Douglas Fir-Larch", "No. 1")
- Parameters:
nominal_width – Nominal width in inches (e.g. 2, 4, 6, 8), or a size label string such as “6x12” (in which case the remaining arguments shift left:
LumberSection("6x12", species, grade)).nominal_depth – Nominal depth in inches (e.g. 4, 6, 8, 10, 12).
species – Species group (e.g. “Douglas Fir-Larch”, “Southern Pine”).
grade – Lumber grade (e.g. “Select Structural”, “No. 1”, “No. 2”).
Example
>>> beam = LumberSection(6, 12, "Douglas Fir-Larch", "No. 1") >>> float(beam.b.magnitude) 5.5 >>> float(beam.d.magnitude) 11.25 >>> joist = LumberSection("2x10", "Douglas Fir-Larch", "No. 2") >>> float(joist.d.magnitude) 9.25
- class civilpy.structural.wood.TimberBridgeDeck(deck_type, span, thickness, species, grade, wearing_surface='none')[source]
Bases:
objectTimber bridge deck design per AASHTO LRFD Section 9.9.
- Parameters:
deck_type – “nail_laminated”, “spike_laminated”, “glulam”, “stress_laminated”, or “plank”.
span – Clear span of deck panel (Pint length).
thickness – Deck lamination depth (Pint length).
species – Wood species group.
grade – Lumber grade.
wearing_surface – Key from WEARING_SURFACE_WEIGHTS or “none”.
- check_deck()[source]
Full deck adequacy check under AASHTO HL-93 wheel load.
Uses a 16-kip wheel load (half of 32-kip axle) distributed over the effective width.
- Returns:
dict with bending, shear, and deflection check results.
- dead_load_per_ft()[source]
Dead load on a 1-ft-wide strip (lbf/ft).
- Returns:
Pint force/length quantity.
- wheel_load_distribution_width(tire_contact_width=None)[source]
Effective width for wheel load distribution per AASHTO 4.6.2.1.3.
- For transverse nail-laminated decks:
E = 2.0 * t + 40 (in.) (approximate, perpendicular to span)
- Parameters:
tire_contact_width – Tire contact length (Pint length). Defaults to 20 in. per AASHTO 3.6.1.2.5.
- Returns:
Effective distribution width.
- Return type:
Pint length quantity
- class civilpy.structural.wood.TimberStringer(section, span, spacing, num_lanes=1, deck_type='nail_laminated')[source]
Bases:
objectTimber bridge stringer design per AASHTO LRFD.
- Parameters:
section – LumberSection or GlulamSection for the stringer.
span – Stringer span (Pint length).
spacing – Stringer center-to-center spacing (Pint length).
num_lanes – Number of design lanes.
deck_type – Type of deck supported.
- dynamic_load_allowance()[source]
Dynamic load allowance (IM) per AASHTO 3.6.2.3.
Returns 0.33 for timber bridges (same as other materials). Note: Some agencies reduce IM for timber. AASHTO does not distinguish by material.
- live_load_distribution_factor(num_stringers=5)[source]
Live load distribution factor for moment per AASHTO Table 4.6.2.2.2a-1.
Simplified method for timber beam/slab bridges. For one design lane loaded: g = S/D where S = spacing (ft), D depends on bridge type.
- Parameters:
num_stringers – Total number of stringers.
- Returns:
Distribution factor (lanes per beam).
- Return type:
- live_load_distribution_factor_shear(num_stringers=5)[source]
Live load distribution factor for shear.
Conservatively uses the moment distribution factor (AASHTO allows this simplification for timber bridges).
- rating_factor(DC_moment, DW_moment, LL_moment, capacity_moment, gamma_DC=1.25, gamma_DW=1.5, gamma_LL=1.75, phi_c=1.0, phi_s=1.0)[source]
Load rating factor per AASHTO MBE Eq. 6A.4.2.1-1.
- RF = (phi_c * phi_s * C - gamma_DC * DC - gamma_DW * DW) /
(gamma_LL * LL * (1 + IM))
- Parameters:
DC_moment – Dead load moment (Pint moment).
DW_moment – Wearing surface moment (Pint moment).
LL_moment – Live load moment per stringer (Pint moment, without IM).
capacity_moment – Member capacity M = Fb’ * Sx (Pint moment).
gamma_DC – DC load factor.
gamma_DW – DW load factor.
gamma_LL – LL load factor.
phi_c – Condition factor (1.0, 0.85, or 0.65).
phi_s – System factor (1.0 for most timber).
- Returns:
dict with RF, numerator, denominator, and status.
- class civilpy.structural.wood.WoodMemberCheck(section, method='ASD', wet_service=False, temperature_f=100.0, incised=False, repetitive=False, load_duration='normal')[source]
Bases:
objectComplete NDS member design check with calc-sheet output.
Orchestrates adjustment factor computation and demand/capacity checks for all stress types. Each check returns a dict of intermediate values suitable for display in a Jupyter notebook.
- Parameters:
section – LumberSection or GlulamSection.
method – “ASD” or “LRFD”.
wet_service – True if in-service MC > 19%.
temperature_f – Maximum sustained temperature (deg F).
incised – True if incised for preservative treatment.
repetitive – True if repetitive member conditions apply.
load_duration – Governing load duration case.
- check_bearing(R, bearing_length, bearing_width=None)[source]
Compression perpendicular to grain (bearing) check.
- Parameters:
R – Applied bearing reaction (Pint force).
bearing_length – Length of bearing along grain (Pint length).
bearing_width – Width of bearing (Pint length). Defaults to section width.
- Returns:
dict with fc_perp, Fc_perp_prime, ratio, status, factors.
- check_bending(M, lu=None, load_type='uniform', span_length=None)[source]
Bending stress check.
- Parameters:
M – Applied moment (Pint moment quantity, e.g. ft*lbf or in*lbf).
lu – Unbraced length (Pint length). 0 or None = fully braced.
load_type – Loading type for effective length.
span_length – Span for CV (glulam only).
- Returns:
dict with fb, Fb_prime, ratio, status, factors.
- check_combined_bending_compression(P, M, le_strong, le_weak=None, lu=None, load_type='uniform')[source]
Combined bending and axial compression per NDS Eq. 3.9-3.
(fc/Fc’)^2 + fb / (Fb’ * (1 - fc/FcE)) <= 1.0
- Parameters:
P – Axial compression (Pint force).
M – Bending moment (Pint moment).
le_strong – Effective column length, strong axis.
le_weak – Effective column length, weak axis.
lu – Unbraced length for bending.
load_type – Loading type for CL.
- Returns:
dict with interaction ratio and components.
- check_compression(P, le_strong, le_weak=None)[source]
Compression parallel to grain check.
- Parameters:
P – Applied axial compression (Pint force).
le_strong – Effective length about strong axis (Pint length).
le_weak – Effective length about weak axis (defaults to le_strong).
- Returns:
dict with fc, Fc_prime, ratio, status, Cp, le_d, factors.
- check_deflection(w, L, limit='L/360', I_eff=None)[source]
Deflection check for uniform load on simple span:
delta = 5*w*L^4 / (384*E'*I)
- Parameters:
w – Uniform load (Pint force/length, e.g. lbf/ft).
L – Span length (Pint length).
limit – Deflection limit as string (e.g. “L/360”, “L/240”, “L/425”).
I_eff – Override moment of inertia. Defaults to section Ix.
- Returns:
dict with delta, delta_limit, ratio, status.
- check_shear(V)[source]
Shear stress check (fv = 3V/2A per NDS 3.4.2).
- Parameters:
V – Applied shear force (Pint force quantity).
- Returns:
dict with fv, Fv_prime, ratio, status, factors.
- civilpy.structural.wood.beam_stability_factor(Emin_prime, Fb_star, le, d, b)[source]
CL per NDS 3.3.3 with Pint quantities.
- Parameters:
Emin_prime – Adjusted E’min (Pint stress).
Fb_star – Fb * (all factors except CL and CV) (Pint stress).
le – Effective unbraced length (Pint length).
d – Depth (Pint length).
b – Width (Pint length).
- Returns:
- Return type:
- civilpy.structural.wood.beam_stability_factor_from_ratio(FbE, Fb_star)[source]
CL per NDS 3.3.3.8.
Same form as Cp but with c=0.95 for beams.
- Parameters:
FbE – Critical buckling stress (numeric, psi).
Fb_star – Fb times all factors except CL and CV (numeric, psi).
- Returns:
Beam stability factor CL.
- Return type:
- civilpy.structural.wood.column_stability_factor(FcE, Fc_star, c=0.8)[source]
Cp per NDS Eq. 3.7-1.
- Parameters:
FcE – Euler critical buckling stress (numeric, psi).
Fc_star – Fc multiplied by all adjustment factors except Cp (numeric, psi).
c – 0.8 for sawn lumber, 0.9 for glulam, 0.85 for round timber.
- Returns:
Column stability factor Cp.
- Return type:
- civilpy.structural.wood.effective_beam_length(lu, load_type='uniform')[source]
Effective unbraced length le for lateral-torsional buckling.
Per NDS Table 3.3.3.
- Parameters:
lu – Unbraced length (Pint length quantity).
load_type – One of “uniform”, “concentrated_center”, “concentrated_quarter”, “equal_end_moments”, “cantilever_uniform”, “cantilever_point”.
- Returns:
Pint length quantity.
- civilpy.structural.wood.euler_critical_buckling_stress(Emin_prime, le, d)[source]
FcE per NDS 3.7.1.5 with Pint quantities.
- Parameters:
Emin_prime – Adjusted minimum modulus of elasticity (Pint stress).
le – Effective column length (Pint length).
d – Member dimension in buckling plane (Pint length).
- Returns:
Pint stress quantity.
- civilpy.structural.wood.euler_critical_buckling_stress_val(Emin_prime, le_in, d_in)[source]
FcE = 0.822 * E’min / (le/d)^2 per NDS 3.7.1.5.
All arguments are raw numeric values in psi / inches.
- civilpy.structural.wood.list_available_grades(species=None)[source]
Return available grades, optionally filtered by species.
Module contents
Structural engineering package — the most developed part of CivilPy.
Highlights:
SteelSectionlookups from the AISC shapes database (steel) and built-up plate sections (section_properties).AASHTO LRFD design/rating in
civilpy.structural.aashto(design vehicles, distribution factors, steel/concrete/prestressed/timber resistance, splices, columns, railing — seeaashto.lrfd).AREMA railroad design in
civilpy.structural.arema.Strut-and-tie tools: solver (
strut_and_tie), Rhino authoring (rhino_stm), and the topology-optimization pipeline (civilpy.structural.stm_topology).Bridge substructure designers (
abutment,pier), ODOT standard-drawing designers (civilpy.structural.odot), classic analysis tools (influence lines, moment distribution, shear flow, Mohr’s circle), and interfaces to MIDAS Civil and CANDE.