1use super::*;
4
5#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
8pub struct NodalLoad {
9 pub node: NodeId,
10 pub values: [f64; 6],
11 pub name: String,
14 pub source: LoadSource,
17}
18
19#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
22pub enum LoadSource {
23 #[default]
25 Manual,
26 Auto,
29}
30
31impl LoadSource {
32 pub fn is_auto(self) -> bool {
34 matches!(self, LoadSource::Auto)
35 }
36}
37
38impl NodalLoad {
39 pub fn manual(node: NodeId, values: [f64; 6]) -> Self {
41 Self {
42 node,
43 values,
44 name: String::new(),
45 source: LoadSource::Manual,
46 }
47 }
48
49 pub fn auto(node: NodeId, values: [f64; 6]) -> Self {
51 Self {
52 node,
53 values,
54 name: String::new(),
55 source: LoadSource::Auto,
56 }
57 }
58}
59
60#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
63pub enum MemberLoadKind {
64 Point { a: f64, p: f64 },
66 Distributed { a: f64, b: f64, w1: f64, w2: f64 },
69}
70
71#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
74pub struct MemberLoad {
75 pub elem: ElemId,
76 pub dir: [f64; 3],
77 pub kind: MemberLoadKind,
78 pub name: String,
80 pub source: LoadSource,
82}
83
84impl MemberLoad {
85 pub fn manual(elem: ElemId, dir: [f64; 3], kind: MemberLoadKind) -> Self {
87 Self {
88 elem,
89 dir,
90 kind,
91 name: String::new(),
92 source: LoadSource::Manual,
93 }
94 }
95
96 pub fn auto(elem: ElemId, dir: [f64; 3], kind: MemberLoadKind) -> Self {
98 Self {
99 elem,
100 dir,
101 kind,
102 name: String::new(),
103 source: LoadSource::Auto,
104 }
105 }
106}
107
108#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
113pub enum LoadCaseKind {
114 Dead,
116 Live,
118 LiveSeismic,
120 Snow,
122 Wind,
124 Seismic,
126 #[default]
127 Other,
128}
129
130impl LoadCaseKind {
131 pub fn is_long_term(&self) -> bool {
137 matches!(
138 self,
139 LoadCaseKind::Dead | LoadCaseKind::Live | LoadCaseKind::Snow | LoadCaseKind::Other
140 )
141 }
142}
143
144#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
145pub struct LoadCase {
146 pub id: LoadCaseId,
147 pub name: String,
148 pub nodal: Vec<NodalLoad>,
149 #[serde(default)]
151 pub member: Vec<MemberLoad>,
152 #[serde(default)]
154 pub kind: LoadCaseKind,
155}
156
157impl LoadCase {
158 pub fn manual_nodal(&self) -> impl Iterator<Item = (usize, &NodalLoad)> {
161 self.nodal
162 .iter()
163 .enumerate()
164 .filter(|(_, nl)| !nl.source.is_auto())
165 }
166
167 pub fn manual_member(&self) -> impl Iterator<Item = (usize, &MemberLoad)> {
169 self.member
170 .iter()
171 .enumerate()
172 .filter(|(_, ml)| !ml.source.is_auto())
173 }
174
175 pub fn replace_auto_loads(&mut self, auto_nodal: Vec<NodalLoad>, auto_member: Vec<MemberLoad>) {
181 self.nodal.retain(|nl| !nl.source.is_auto());
182 self.nodal.extend(auto_nodal.into_iter().map(|mut nl| {
183 nl.source = LoadSource::Auto;
184 nl
185 }));
186 self.member.retain(|ml| !ml.source.is_auto());
187 self.member.extend(auto_member.into_iter().map(|mut ml| {
188 ml.source = LoadSource::Auto;
189 ml
190 }));
191 }
192
193 pub fn auto_loads_match(&self, auto_nodal: &[NodalLoad], auto_member: &[MemberLoad]) -> bool {
196 self.nodal
197 .iter()
198 .filter(|nl| nl.source.is_auto())
199 .eq(auto_nodal.iter())
200 && self
201 .member
202 .iter()
203 .filter(|ml| ml.source.is_auto())
204 .eq(auto_member.iter())
205 }
206}
207
208pub const DL_CASE_NAME: &str = "DL";
213
214pub const LL_FRAME_CASE_NAME: &str = "LL(架構用)";
217
218pub const LL_SEISMIC_CASE_NAME: &str = "LL(地震用)";
221
222pub const EX_CASE_NAME: &str = "EX";
225
226pub const EY_CASE_NAME: &str = "EY";
228
229pub fn default_load_cases() -> Vec<LoadCase> {
234 let make = |i: u32, name: &str, kind: LoadCaseKind| LoadCase {
235 id: LoadCaseId(i),
236 name: name.to_string(),
237 nodal: Vec::new(),
238 member: Vec::new(),
239 kind,
240 };
241 vec![
242 make(0, DL_CASE_NAME, LoadCaseKind::Dead),
243 make(1, LL_FRAME_CASE_NAME, LoadCaseKind::Live),
244 make(2, LL_SEISMIC_CASE_NAME, LoadCaseKind::LiveSeismic),
245 make(3, EX_CASE_NAME, LoadCaseKind::Seismic),
246 make(4, EY_CASE_NAME, LoadCaseKind::Seismic),
247 ]
248}
249
250#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
251pub struct LoadCombination {
252 pub name: String,
253 pub terms: Vec<(LoadCaseId, f64)>,
254}
255
256pub fn default_combinations() -> Vec<LoadCombination> {
271 crate::load_combo::standard_combinations(&crate::load_combo::ComboInput {
273 dl: LoadCaseId(0),
274 ll: LoadCaseId(1),
275 seismic_x: Some(LoadCaseId(3)),
276 seismic_y: Some(LoadCaseId(4)),
277 snow: None,
278 heavy_snow_zone: false,
279 snow_factors: None,
280 })
281}
282
283#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
288pub struct DamperSpec {
289 pub elem: ElemId,
290 pub device_weight: f64,
293 pub device_length: f64,
295 pub support_area: f64,
297}
298
299#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
301pub enum KBraceWeightRule {
302 #[default]
304 InternalNodes,
305 BaseNodesOnly,
307}
308
309#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
312pub struct LoadCfg {
313 pub steel_weight_factor: f64,
316 pub extra_line_weight: Vec<(ElemId, f64)>,
318 #[serde(default)]
322 pub finish_area_weight: Vec<(ElemId, f64)>,
323 #[serde(default)]
326 pub dampers: Vec<DamperSpec>,
327 #[serde(default)]
329 pub k_brace_rule: KBraceWeightRule,
330 #[serde(default)]
333 pub live_load_reduction: bool,
334}
335
336impl Default for LoadCfg {
337 fn default() -> Self {
338 Self {
339 steel_weight_factor: 1.0,
340 extra_line_weight: Vec::new(),
341 finish_area_weight: Vec::new(),
342 dampers: Vec::new(),
343 k_brace_rule: KBraceWeightRule::default(),
344 live_load_reduction: false,
345 }
346 }
347}
348
349impl LoadCfg {
350 pub fn effective_steel_factor(&self) -> f64 {
352 if self.steel_weight_factor > 0.0 {
353 self.steel_weight_factor
354 } else {
355 1.0
356 }
357 }
358}