1use super::*;
7
8#[derive(Clone, Debug, Default, PartialEq)]
12pub struct ElemAttrs {
13 pub wall: Option<WallAttr>,
14 pub steel_design: Option<SteelDesignAttr>,
15 pub brb: Option<BrbAttr>,
16 pub pca: Option<PcaBeamAttr>,
17 pub isolator: Option<IsolatorAttr>,
18 pub hysteresis: Option<MemberHysteresisAttr>,
19 pub damper: Option<DamperAttr>,
20 pub detail: Option<MemberDetailAttr>,
21}
22
23#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
24pub struct Model {
25 pub nodes: Vec<Node>,
26 pub elements: Vec<ElementData>,
27 pub sections: Vec<Section>,
28 pub materials: Vec<Material>,
29 pub stories: Vec<Story>,
30 #[serde(default)]
33 pub axes: Vec<AxisGroup>,
34 pub floor_regions: Vec<FloorRegion>,
35 pub constraints: Vec<Constraint>,
36 pub load_cases: Vec<LoadCase>,
37 pub combinations: Vec<LoadCombination>,
38 #[serde(default)]
40 pub vibration_cases: Vec<VibrationCase>,
41 #[serde(default)]
43 pub lumped_vibration_cases: Vec<LumpedVibrationCase>,
44 #[serde(default)]
47 pub generated_masters: Vec<NodeId>,
48 #[serde(default)]
52 pub mass_method: MassMethod,
53 #[serde(default)]
56 pub slab_thickness: f64,
57 #[serde(default)]
59 pub load_cfg: Option<LoadCfg>,
60 #[serde(default)]
62 pub wall_attrs: Vec<WallAttr>,
63 #[serde(default)]
67 pub multi_opening_mode: MultiOpeningMode,
68 #[serde(default)]
70 pub stress_cfg: StressAnalysisCfg,
71 #[serde(default)]
74 pub steel_design_attrs: Vec<SteelDesignAttr>,
75 #[serde(default)]
78 pub brb_attrs: Vec<BrbAttr>,
79 #[serde(default)]
81 pub pca_attrs: Vec<PcaBeamAttr>,
82 #[serde(default)]
84 pub isolator_attrs: Vec<IsolatorAttr>,
85 #[serde(default)]
88 pub member_hysteresis_attrs: Vec<MemberHysteresisAttr>,
89 #[serde(default)]
91 pub damper_attrs: Vec<DamperAttr>,
92 #[serde(default)]
96 pub member_detail_attrs: Vec<MemberDetailAttr>,
97 #[serde(default)]
99 pub unassigned_joists: Vec<SecondaryMember>,
100 #[serde(default)]
102 pub unassigned_posts: Vec<SecondaryMember>,
103 #[serde(default)]
109 pub beam_groups: Vec<Vec<ElemId>>,
110 #[serde(default)]
120 pub damper_defs: Vec<DamperDef>,
121 #[serde(default)]
124 pub beam_torsion: BeamTorsionMode,
125 #[serde(default)]
129 pub panel_zone: PanelZoneMode,
130 #[serde(default)]
132 pub wall_regions: Vec<WallRegion>,
133 #[serde(default)]
139 pub slabs: Vec<Slab>,
140 #[serde(default)]
146 pub wall_plates: Vec<WallPlate>,
147 #[serde(skip)]
148 pub dof_map: crate::dof::DofMap,
149}
150
151fn check_id_consistency<T>(
154 items: &[T],
155 coll: &str,
156 id_name: &str,
157 index_of: impl Fn(&T) -> usize,
158 raw_of: impl Fn(&T) -> u32,
159) -> Result<(), crate::error::CoreError> {
160 use crate::error::CoreError;
161 for (i, item) in items.iter().enumerate() {
162 if index_of(item) != i {
163 return Err(CoreError::IndexMismatch(format!(
164 "{coll}[{i}] has {id_name}({})",
165 raw_of(item)
166 )));
167 }
168 }
169 let mut seen = std::collections::HashSet::new();
170 for item in items {
171 if !seen.insert(index_of(item)) {
172 return Err(CoreError::DuplicateId(format!(
173 "{id_name}({})",
174 raw_of(item)
175 )));
176 }
177 }
178 Ok(())
179}
180
181fn slab_node_refs(slab: &Slab) -> Vec<NodeId> {
185 match &slab.shape {
186 SlabShape::Enclosed { boundary } => boundary.clone(),
187 SlabShape::Attached { anchor, .. } => match anchor {
188 RegionAnchor::Line { nodes, .. } => nodes.to_vec(),
189 RegionAnchor::Point(n) => vec![*n],
190 RegionAnchor::FloorRegion { .. } => Vec::new(),
193 },
194 }
195}
196
197fn wall_plate_node_refs(plate: &WallPlate) -> Vec<NodeId> {
203 match &plate.shape {
204 WallPlateShape::Enclosed { boundary } => boundary.clone(),
205 WallPlateShape::Attached { anchor, .. } => match anchor {
206 RegionAnchor::Line { nodes, .. } => nodes.to_vec(),
207 RegionAnchor::FloorRegion { nodes, .. } => nodes.to_vec(),
208 RegionAnchor::Point(_) => Vec::new(),
209 },
210 }
211}
212
213impl Model {
214 pub fn validate(&self) -> Result<(), crate::error::CoreError> {
215 use crate::error::CoreError;
216
217 check_id_consistency(&self.nodes, "nodes", "NodeId", |n| n.id.index(), |n| n.id.0)?;
218
219 for (i, elem) in self.elements.iter().enumerate() {
220 if elem.id.index() != i {
221 return Err(CoreError::IndexMismatch(format!(
222 "elements[{}] has ElemId({})",
223 i, elem.id.0
224 )));
225 }
226 }
227
228 let mut seen_elems = std::collections::HashSet::new();
229 for elem in &self.elements {
230 if !seen_elems.insert(elem.id) {
231 return Err(CoreError::DuplicateId(format!("ElemId({})", elem.id.0)));
232 }
233 for &nid in &elem.nodes {
234 if nid.index() >= self.nodes.len() || self.nodes[nid.index()].id != nid {
235 return Err(CoreError::DanglingRef(format!(
236 "Elem {} -> Node {}",
237 elem.id.0, nid.0
238 )));
239 }
240 }
241 if let Some(sid) = elem.section {
242 if sid.index() >= self.sections.len() || self.sections[sid.index()].id != sid {
243 return Err(CoreError::DanglingRef(format!(
244 "Elem {} -> Section {}",
245 elem.id.0, sid.0
246 )));
247 }
248 }
249 }
250
251 for sec in &self.sections {
253 for (role, mid) in [
254 ("Material", sec.material),
255 ("RebarMaterial", sec.rebar_material),
256 ("ShearRebarMaterial", sec.shear_rebar_material),
257 ("SteelMaterial", sec.steel_material),
258 ] {
259 if let Some(mid) = mid {
260 if mid.index() >= self.materials.len() || self.materials[mid.index()].id != mid
261 {
262 return Err(CoreError::DanglingRef(format!(
263 "Section {} -> {role} {}",
264 sec.id.0, mid.0
265 )));
266 }
267 }
268 }
269 }
270
271 check_id_consistency(
272 &self.stories,
273 "stories",
274 "StoryId",
275 |s| s.id.index(),
276 |s| s.id.0,
277 )?;
278 for pair in self.stories.windows(2) {
282 if pair[1].elevation < pair[0].elevation {
283 return Err(CoreError::DanglingRef(format!(
284 "Story {} ({}) の標高 {} が直下の Story {} ({}) の標高 {} より低い(階は標高の昇順に並べる)",
285 pair[1].id.0, pair[1].name, pair[1].elevation,
286 pair[0].id.0, pair[0].name, pair[0].elevation,
287 )));
288 }
289 }
290 for group in &self.axes {
293 for axis in &group.axes {
294 for &nid in &axis.nodes {
295 if nid.index() >= self.nodes.len() || self.nodes[nid.index()].id != nid {
296 return Err(CoreError::DanglingRef(format!(
297 "Axis {}/{} -> Node {}",
298 group.name, axis.name, nid.0
299 )));
300 }
301 }
302 }
303 }
304 check_id_consistency(
305 &self.floor_regions,
306 "floor_regions",
307 "FloorRegionId",
308 |s| s.id.index(),
309 |s| s.id.0,
310 )?;
311 for region in &self.floor_regions {
313 for &nid in ®ion.boundary {
314 if nid.index() >= self.nodes.len() || self.nodes[nid.index()].id != nid {
315 return Err(CoreError::DanglingRef(format!(
316 "FloorRegion {} -> Node {}",
317 region.id.0, nid.0
318 )));
319 }
320 }
321 for &sid in ®ion.slab_ids {
322 if sid.index() >= self.slabs.len() || self.slabs[sid.index()].id != sid {
323 return Err(CoreError::DanglingRef(format!(
324 "FloorRegion {} -> Slab {}",
325 region.id.0, sid.0
326 )));
327 }
328 }
329 }
330 {
333 let mut seen: std::collections::HashSet<Vec<u32>> = std::collections::HashSet::new();
334 for region in &self.floor_regions {
335 if region.boundary.is_empty() {
336 continue;
337 }
338 let mut key: Vec<u32> = region.boundary.iter().map(|n| n.0).collect();
339 key.sort_unstable();
340 key.dedup();
341 if !seen.insert(key) {
342 return Err(CoreError::DuplicateId(format!(
343 "FloorRegion {} は他の領域と同じ境界を持つ",
344 region.id.0
345 )));
346 }
347 }
348 }
349 check_id_consistency(&self.slabs, "slabs", "SlabId", |s| s.id.index(), |s| s.id.0)?;
351 check_id_consistency(
354 &self.wall_plates,
355 "wall_plates",
356 "WallPlateId",
357 |p| p.id.index(),
358 |p| p.id.0,
359 )?;
360 check_id_consistency(
361 &self.wall_regions,
362 "wall_regions",
363 "WallRegionId",
364 |r| r.id.index(),
365 |r| r.id.0,
366 )?;
367 {
368 let mut owner: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
369 for region in &self.wall_regions {
370 let mut seen_here = std::collections::HashSet::new();
371 for &pid in ®ion.wall_plate_ids {
372 if !seen_here.insert(pid) {
373 return Err(CoreError::DuplicateId(format!(
374 "WallRegion {} wall_plate_ids has WallPlateId({})",
375 region.id.0, pid.0
376 )));
377 }
378 if pid.index() >= self.wall_plates.len()
379 || self.wall_plates[pid.index()].id != pid
380 {
381 return Err(CoreError::DanglingRef(format!(
382 "WallRegion {} -> WallPlate {}",
383 region.id.0, pid.0
384 )));
385 }
386 if let Some(&other) = owner.get(&pid.0) {
387 if other != region.id.0 {
388 return Err(CoreError::DuplicateId(format!(
389 "WallPlate {} は複数の WallRegion({}・{})から参照されている",
390 pid.0, other, region.id.0
391 )));
392 }
393 }
394 owner.insert(pid.0, region.id.0);
395 }
396 }
397 }
398 for region in &self.wall_regions {
400 for &nid in ®ion.boundary {
401 if nid.index() >= self.nodes.len() || self.nodes[nid.index()].id != nid {
402 return Err(CoreError::DanglingRef(format!(
403 "WallRegion {} -> Node {}",
404 region.id.0, nid.0
405 )));
406 }
407 }
408 }
409 {
411 let mut seen: std::collections::HashSet<Vec<u32>> = std::collections::HashSet::new();
412 for region in &self.wall_regions {
413 if region.boundary.is_empty() {
414 continue;
415 }
416 let mut key: Vec<u32> = region.boundary.iter().map(|n| n.0).collect();
417 key.sort_unstable();
418 key.dedup();
419 if !seen.insert(key) {
420 return Err(CoreError::DuplicateId(format!(
421 "WallRegion {} は他の領域と同じ境界を持つ",
422 region.id.0
423 )));
424 }
425 }
426 }
427 {
428 let mut owner: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
429 for region in &self.floor_regions {
430 let mut seen_here = std::collections::HashSet::new();
431 for &sid in ®ion.slab_ids {
432 if !seen_here.insert(sid) {
433 return Err(CoreError::DuplicateId(format!(
434 "FloorRegion {} slab_ids has SlabId({})",
435 region.id.0, sid.0
436 )));
437 }
438 if let Some(&other) = owner.get(&sid.0) {
439 if other != region.id.0 {
440 return Err(CoreError::DuplicateId(format!(
441 "Slab {} は複数の FloorRegion({}・{})から参照されている",
442 sid.0, other, region.id.0
443 )));
444 }
445 }
446 owner.insert(sid.0, region.id.0);
447 }
448 }
449 }
450 for slab in &self.slabs {
452 for nid in slab_node_refs(slab) {
453 if nid.index() >= self.nodes.len() || self.nodes[nid.index()].id != nid {
454 return Err(CoreError::DanglingRef(format!(
455 "Slab {} -> Node {}",
456 slab.id.0, nid.0
457 )));
458 }
459 }
460 if let Some(sid) = slab.section() {
461 if sid.index() >= self.sections.len() || self.sections[sid.index()].id != sid {
462 return Err(CoreError::DanglingRef(format!(
463 "Slab {} -> Section {}",
464 slab.id.0, sid.0
465 )));
466 }
467 }
468 }
469 for plate in &self.wall_plates {
471 for nid in wall_plate_node_refs(plate) {
472 if nid.index() >= self.nodes.len() || self.nodes[nid.index()].id != nid {
473 return Err(CoreError::DanglingRef(format!(
474 "WallPlate {} -> Node {}",
475 plate.id.0, nid.0
476 )));
477 }
478 }
479 if let Some(sid) = plate.section {
480 if sid.index() >= self.sections.len() || self.sections[sid.index()].id != sid {
481 return Err(CoreError::DanglingRef(format!(
482 "WallPlate {} -> Section {}",
483 plate.id.0, sid.0
484 )));
485 }
486 }
487 }
492 check_id_consistency(
493 &self.sections,
494 "sections",
495 "SectionId",
496 |s| s.id.index(),
497 |s| s.id.0,
498 )?;
499 check_id_consistency(
500 &self.materials,
501 "materials",
502 "MaterialId",
503 |m| m.id.index(),
504 |m| m.id.0,
505 )?;
506
507 for (ri, region) in self.floor_regions.iter().enumerate() {
509 for (ji, sm) in region.secondary_joists.iter().enumerate() {
510 Self::validate_secondary_member(
511 sm,
512 &format!("FloorRegion {ri} secondary_joists[{ji}]"),
513 &self.nodes,
514 &self.sections,
515 )?;
516 if sm.kind != SecondaryMemberKind::Joist {
517 return Err(CoreError::DanglingRef(format!(
518 "FloorRegion {} secondary_joists[{ji}] は Joist でない",
519 region.id.0
520 )));
521 }
522 }
523 }
524 for (i, sm) in self.unassigned_joists.iter().enumerate() {
525 Self::validate_secondary_member(
526 sm,
527 &format!("unassigned_joists[{i}]"),
528 &self.nodes,
529 &self.sections,
530 )?;
531 if sm.kind != SecondaryMemberKind::Joist {
532 return Err(CoreError::DanglingRef(format!(
533 "unassigned_joists[{i}] は Joist でない"
534 )));
535 }
536 }
537 for (ri, region) in self.wall_regions.iter().enumerate() {
538 for (pi, sm) in region.posts.iter().enumerate() {
539 Self::validate_secondary_member(
540 sm,
541 &format!("WallRegion {ri} posts[{pi}]"),
542 &self.nodes,
543 &self.sections,
544 )?;
545 if sm.kind != SecondaryMemberKind::Post {
546 return Err(CoreError::DanglingRef(format!(
547 "WallRegion {} posts[{pi}] は Post でない",
548 region.id.0
549 )));
550 }
551 }
552 }
553 for (i, sm) in self.unassigned_posts.iter().enumerate() {
554 Self::validate_secondary_member(
555 sm,
556 &format!("unassigned_posts[{i}]"),
557 &self.nodes,
558 &self.sections,
559 )?;
560 if sm.kind != SecondaryMemberKind::Post {
561 return Err(CoreError::DanglingRef(format!(
562 "unassigned_posts[{i}] は Post でない"
563 )));
564 }
565 }
566
567 {
569 use std::collections::HashSet;
570 let mut seen = HashSet::new();
571 for sm in self.joists().chain(self.posts()) {
572 let a = sm.nodes[0].0.min(sm.nodes[1].0);
573 let b = sm.nodes[0].0.max(sm.nodes[1].0);
574 if !seen.insert((sm.kind, a, b)) {
575 return Err(CoreError::DanglingRef(format!(
576 "二次部材が重複しています({:?} 節点 {}-{})",
577 sm.kind, a, b
578 )));
579 }
580 }
581 }
582
583 for slab in &self.slabs {
588 if let SlabShape::Attached {
589 anchor: RegionAnchor::Line { span, .. },
590 ..
591 } = &slab.shape
592 {
593 if !crate::model::span_is_valid(*span) {
594 return Err(CoreError::DanglingRef(format!(
595 "Slab {} の取付き線の区間 span が不正(0.0 <= t_i < t_j <= 1.0 であること)",
596 slab.id.0
597 )));
598 }
599 }
600 }
601 for plate in &self.wall_plates {
604 if let WallPlateShape::Attached {
605 anchor: RegionAnchor::Line { span, .. },
606 ..
607 } = &plate.shape
608 {
609 if !crate::model::span_is_valid(*span) {
610 return Err(CoreError::DanglingRef(format!(
611 "WallPlate {} の取付き線の区間 span が不正(0.0 <= t_i < t_j <= 1.0 であること)",
612 plate.id.0
613 )));
614 }
615 }
616 }
617 for plate in &self.wall_plates {
623 if let WallPlateShape::Attached {
624 anchor,
625 extent: None,
626 } = &plate.shape
627 {
628 if !matches!(anchor, RegionAnchor::FloorRegion { .. }) {
629 return Err(CoreError::DanglingRef(format!(
630 "WallPlate {} は立ち上がり高さが未指定(階高いっぱい)だが、\
631 取付き先が床領域ではない。未指定を許すのは自立壁だけで、\
632 取付き線に取り付く全高の壁は囲まれた壁版として入力する",
633 plate.id.0
634 )));
635 }
636 }
637 }
638
639 {
642 let mut seen: std::collections::HashSet<Vec<u32>> = std::collections::HashSet::new();
643 for slab in &self.slabs {
644 let SlabShape::Enclosed { boundary } = &slab.shape else {
645 continue; };
647 if boundary.is_empty() {
648 continue;
649 }
650 let mut key: Vec<u32> = boundary.iter().map(|n| n.0).collect();
651 key.sort_unstable();
652 key.dedup();
653 if !seen.insert(key) {
654 return Err(CoreError::DuplicateId(format!(
655 "Slab {} は他の床板と同じ境界を持つ",
656 slab.id.0
657 )));
658 }
659 }
660 }
661 {
663 let mut seen: std::collections::HashSet<Vec<u32>> = std::collections::HashSet::new();
664 for plate in &self.wall_plates {
665 let WallPlateShape::Enclosed { boundary } = &plate.shape else {
666 continue; };
668 if boundary.is_empty() {
669 continue;
670 }
671 let mut key: Vec<u32> = boundary.iter().map(|n| n.0).collect();
672 key.sort_unstable();
673 key.dedup();
674 if !seen.insert(key) {
675 return Err(CoreError::DuplicateId(format!(
676 "WallPlate {} は他の壁版と同じ境界を持つ",
677 plate.id.0
678 )));
679 }
680 }
681 }
682
683 for (gi, group) in self.beam_groups.iter().enumerate() {
686 for &eid in group {
687 if eid.index() >= self.elements.len() || self.elements[eid.index()].id != eid {
688 return Err(CoreError::DanglingRef(format!(
689 "BeamGroup {} -> Elem {}",
690 gi, eid.0
691 )));
692 }
693 }
694 }
695
696 Ok(())
697 }
698
699 pub fn node(&self, id: NodeId) -> Option<&Node> {
708 match self.nodes.get(id.index()) {
709 Some(n) if n.id == id => Some(n),
710 _ => self.nodes.iter().find(|n| n.id == id),
711 }
712 }
713
714 pub fn element(&self, id: ElemId) -> Option<&ElementData> {
717 match self.elements.get(id.index()) {
718 Some(e) if e.id == id => Some(e),
719 _ => self.elements.iter().find(|e| e.id == id),
720 }
721 }
722
723 pub fn section(&self, id: SectionId) -> Option<&Section> {
731 match self.sections.get(id.index()) {
732 Some(s) if s.id == id => Some(s),
733 _ => self.sections.iter().find(|s| s.id == id),
734 }
735 }
736
737 pub fn node_in_use(&self, id: NodeId) -> bool {
740 self.elements.iter().any(|e| e.nodes.contains(&id))
741 || self.node_referenced_outside_elements(id)
742 }
743
744 pub fn node_in_use_excluding_elem(&self, id: NodeId, excl: ElemId) -> bool {
748 self.elements
749 .iter()
750 .any(|e| e.id != excl && e.nodes.contains(&id))
751 || self.node_referenced_outside_elements(id)
752 }
753
754 fn node_referenced_outside_elements(&self, id: NodeId) -> bool {
762 self.stories.iter().any(|s| s.node_ids.contains(&id))
763 || self.node_referenced_by_regions_or_plates(id)
764 }
765
766 pub(crate) fn node_referenced_by_regions_or_plates(&self, id: NodeId) -> bool {
782 self.load_cases
783 .iter()
784 .any(|lc| lc.nodal.iter().any(|nl| nl.node == id))
785 || self.floor_regions.iter().any(|r| r.boundary.contains(&id))
786 || self.slabs.iter().any(|sl| slab_node_refs(sl).contains(&id))
787 || self.wall_regions.iter().any(|r| r.boundary.contains(&id))
788 || self
789 .wall_plates
790 .iter()
791 .any(|p| wall_plate_node_refs(p).contains(&id))
792 || self
793 .joists()
794 .chain(self.posts())
795 .any(|sm| sm.nodes.contains(&id))
796 || self.constraints.iter().any(|c| match c {
797 Constraint::RigidDiaphragm { master, slaves, .. } => {
798 *master == id || slaves.contains(&id)
799 }
800 Constraint::Mpc { master, terms } => {
801 *master == id || terms.iter().any(|(n, _, _)| *n == id)
802 }
803 Constraint::RigidLink { master, slaves, .. } => {
804 *master == id || slaves.contains(&id)
805 }
806 })
807 }
808
809 pub fn support_isolator_ends(&self, elem: ElemId) -> Option<(NodeId, NodeId)> {
818 let e = self.elements.get(elem.index()).filter(|e| e.id == elem)?;
819 if e.kind != ElementKind::Isolator || e.nodes.len() != 2 {
820 return None;
821 }
822 let (n0, n1) = (e.nodes[0], e.nodes[1]);
823 let node0 = self.node(n0)?;
824 let node1 = self.node(n1)?;
825 if node0.coord != node1.coord {
826 return None;
827 }
828 let is_isolated_ground = |id: NodeId, restraint: crate::dof::Dof6Mask| {
829 restraint == crate::dof::Dof6Mask::FIXED && !self.node_in_use_excluding_elem(id, elem)
830 };
831 if is_isolated_ground(n0, node0.restraint) {
832 Some((n1, n0))
833 } else if is_isolated_ground(n1, node1.restraint) {
834 Some((n0, n1))
835 } else {
836 None
837 }
838 }
839
840 pub fn eq_ignoring_dofmap(&self, other: &Self) -> bool {
841 self.nodes == other.nodes
842 && self.elements == other.elements
843 && self.sections == other.sections
844 && self.materials == other.materials
845 && self.stories == other.stories
846 && self.floor_regions == other.floor_regions
847 && self.slabs == other.slabs
848 && self.constraints == other.constraints
849 && self.load_cases == other.load_cases
850 && self.combinations == other.combinations
851 && self.vibration_cases == other.vibration_cases
852 && self.lumped_vibration_cases == other.lumped_vibration_cases
853 && self.generated_masters == other.generated_masters
854 && self.mass_method == other.mass_method
855 && self.load_cfg == other.load_cfg
856 && self.wall_attrs == other.wall_attrs
857 && self.wall_plates == other.wall_plates
858 && self.stress_cfg == other.stress_cfg
859 && self.steel_design_attrs == other.steel_design_attrs
860 && self.brb_attrs == other.brb_attrs
861 && self.pca_attrs == other.pca_attrs
862 && self.unassigned_joists == other.unassigned_joists
863 && self.unassigned_posts == other.unassigned_posts
864 && self.axes == other.axes
865 && self.beam_groups == other.beam_groups
866 && self.isolator_attrs == other.isolator_attrs
867 && self.member_hysteresis_attrs == other.member_hysteresis_attrs
868 && self.damper_attrs == other.damper_attrs
869 && self.damper_defs == other.damper_defs
870 && self.member_detail_attrs == other.member_detail_attrs
871 && self.beam_torsion == other.beam_torsion
872 && self.panel_zone == other.panel_zone
873 && self.wall_regions == other.wall_regions
874 }
875
876 pub fn damper_props(&self, elem: ElemId) -> Option<DamperProps> {
878 self.damper_attrs
879 .iter()
880 .find(|a| a.elem == elem)
881 .map(|a| a.props)
882 }
883
884 pub fn set_damper_props(
887 &mut self,
888 elem: ElemId,
889 props: Option<DamperProps>,
890 ) -> Option<DamperProps> {
891 let old = self.damper_props(elem);
892 self.damper_attrs.retain(|a| a.elem != elem);
893 if let Some(p) = props {
894 self.damper_attrs.push(DamperAttr { elem, props: p });
895 }
896 old
897 }
898
899 pub fn visit_node_ids(&mut self, mut f: impl FnMut(&mut NodeId)) {
908 for node in &mut self.nodes {
909 f(&mut node.id);
910 }
911 for id in &mut self.generated_masters {
912 f(id);
913 }
914 for elem in &mut self.elements {
915 for n in &mut elem.nodes {
916 f(n);
917 }
918 }
919 for story in &mut self.stories {
920 for n in &mut story.node_ids {
921 f(n);
922 }
923 }
924 for group in &mut self.axes {
925 for axis in &mut group.axes {
926 for n in &mut axis.nodes {
927 f(n);
928 }
929 }
930 }
931 for region in &mut self.floor_regions {
932 for n in &mut region.boundary {
933 f(n);
934 }
935 for sm in &mut region.secondary_joists {
936 for n in &mut sm.nodes {
937 f(n);
938 }
939 }
940 }
941 for slab in &mut self.slabs {
942 match &mut slab.shape {
943 SlabShape::Enclosed { boundary } => {
944 for n in boundary {
945 f(n);
946 }
947 }
948 SlabShape::Attached { anchor, .. } => match anchor {
949 RegionAnchor::Line { nodes, .. } => {
950 for n in nodes {
951 f(n);
952 }
953 }
954 RegionAnchor::Point(n) => f(n),
955 RegionAnchor::FloorRegion { .. } => {}
957 },
958 }
959 }
960 for plate in &mut self.wall_plates {
961 match &mut plate.shape {
962 WallPlateShape::Enclosed { boundary } => {
963 for n in boundary {
964 f(n);
965 }
966 }
967 WallPlateShape::Attached { anchor, .. } => match anchor {
968 RegionAnchor::Line { nodes, .. } => {
969 for n in nodes {
970 f(n);
971 }
972 }
973 RegionAnchor::FloorRegion { nodes, .. } => {
974 for n in nodes {
975 f(n);
976 }
977 }
978 RegionAnchor::Point(_) => {}
980 },
981 }
982 }
983 for region in &mut self.wall_regions {
984 for n in &mut region.boundary {
985 f(n);
986 }
987 for sm in &mut region.posts {
988 for n in &mut sm.nodes {
989 f(n);
990 }
991 }
992 }
993 for sm in &mut self.unassigned_joists {
994 for n in &mut sm.nodes {
995 f(n);
996 }
997 }
998 for sm in &mut self.unassigned_posts {
999 for n in &mut sm.nodes {
1000 f(n);
1001 }
1002 }
1003 for c in &mut self.constraints {
1004 match c {
1005 Constraint::RigidDiaphragm { master, slaves, .. }
1006 | Constraint::RigidLink { master, slaves, .. } => {
1007 f(master);
1008 for s in slaves {
1009 f(s);
1010 }
1011 }
1012 Constraint::Mpc { master, terms } => {
1013 f(master);
1014 for (n, _, _) in terms {
1015 f(n);
1016 }
1017 }
1018 }
1019 }
1020 for lc in &mut self.load_cases {
1021 for nl in &mut lc.nodal {
1022 f(&mut nl.node);
1023 }
1024 }
1025 }
1026
1027 pub fn visit_story_ids(&mut self, mut f: impl FnMut(&mut StoryId)) {
1033 for story in &mut self.stories {
1034 f(&mut story.id);
1035 }
1036 for node in &mut self.nodes {
1037 if let Some(sid) = &mut node.story {
1038 f(sid);
1039 }
1040 }
1041 for c in &mut self.constraints {
1042 if let Constraint::RigidDiaphragm { story, .. } = c {
1043 f(story);
1044 }
1045 }
1046 }
1047
1048 pub fn visit_section_ids(&mut self, mut f: impl FnMut(&mut crate::ids::SectionId)) {
1051 for sec in &mut self.sections {
1052 f(&mut sec.id);
1053 }
1054 for elem in &mut self.elements {
1055 if let Some(sid) = &mut elem.section {
1056 f(sid);
1057 }
1058 }
1059 for region in &mut self.floor_regions {
1060 for sm in &mut region.secondary_joists {
1061 if let Some(sid) = &mut sm.section {
1062 f(sid);
1063 }
1064 }
1065 }
1066 for slab in &mut self.slabs {
1067 if let Some(sid) = &mut slab.plate.section {
1068 f(sid);
1069 }
1070 }
1071 for plate in &mut self.wall_plates {
1072 if let Some(sid) = &mut plate.section {
1073 f(sid);
1074 }
1075 }
1076 for sm in self
1077 .unassigned_joists
1078 .iter_mut()
1079 .chain(self.unassigned_posts.iter_mut())
1080 {
1081 if let Some(sid) = &mut sm.section {
1082 f(sid);
1083 }
1084 }
1085 for region in &mut self.wall_regions {
1086 for sm in &mut region.posts {
1087 if let Some(sid) = &mut sm.section {
1088 f(sid);
1089 }
1090 }
1091 }
1092 }
1093
1094 pub fn visit_material_ids(&mut self, mut f: impl FnMut(&mut crate::ids::MaterialId)) {
1097 for mat in &mut self.materials {
1098 f(&mut mat.id);
1099 }
1100 for sec in &mut self.sections {
1102 for mid in [
1103 &mut sec.material,
1104 &mut sec.rebar_material,
1105 &mut sec.shear_rebar_material,
1106 &mut sec.steel_material,
1107 ]
1108 .into_iter()
1109 .flatten()
1110 {
1111 f(mid);
1112 }
1113 }
1114 }
1115
1116 pub fn visit_elem_ids(&mut self, mut f: impl FnMut(&mut ElemId)) {
1119 for elem in &mut self.elements {
1120 f(&mut elem.id);
1121 }
1122 for lc in &mut self.load_cases {
1123 for ml in &mut lc.member {
1124 f(&mut ml.elem);
1125 }
1126 }
1127 self.shift_elem_attr_refs(&mut f);
1130 }
1131
1132 pub fn shift_elem_attr_refs(&mut self, mut f: impl FnMut(&mut ElemId)) {
1137 for a in &mut self.wall_attrs {
1138 f(&mut a.elem);
1139 }
1140 for a in &mut self.steel_design_attrs {
1141 f(&mut a.elem);
1142 }
1143 for a in &mut self.brb_attrs {
1144 f(&mut a.elem);
1145 }
1146 for a in &mut self.pca_attrs {
1147 f(&mut a.elem);
1148 }
1149 for a in &mut self.isolator_attrs {
1150 f(&mut a.elem);
1151 }
1152 for a in &mut self.member_hysteresis_attrs {
1153 f(&mut a.elem);
1154 }
1155 for a in &mut self.damper_attrs {
1156 f(&mut a.elem);
1157 }
1158 for a in &mut self.member_detail_attrs {
1159 f(&mut a.elem);
1160 }
1161 for group in &mut self.beam_groups {
1162 for e in group.iter_mut() {
1163 f(e);
1164 }
1165 }
1166 }
1167
1168 pub fn take_elem_attrs(&mut self, elem: ElemId) -> ElemAttrs {
1170 fn take_first<T>(v: &mut Vec<T>, get: impl Fn(&T) -> ElemId, elem: ElemId) -> Option<T> {
1172 v.iter()
1173 .position(|a| get(a) == elem)
1174 .map(|pos| v.remove(pos))
1175 }
1176 ElemAttrs {
1177 wall: take_first(&mut self.wall_attrs, |a| a.elem, elem),
1178 steel_design: take_first(&mut self.steel_design_attrs, |a| a.elem, elem),
1179 brb: take_first(&mut self.brb_attrs, |a| a.elem, elem),
1180 pca: take_first(&mut self.pca_attrs, |a| a.elem, elem),
1181 isolator: take_first(&mut self.isolator_attrs, |a| a.elem, elem),
1182 hysteresis: take_first(&mut self.member_hysteresis_attrs, |a| a.elem, elem),
1183 damper: take_first(&mut self.damper_attrs, |a| a.elem, elem),
1184 detail: take_first(&mut self.member_detail_attrs, |a| a.elem, elem),
1185 }
1186 }
1187
1188 pub fn restore_elem_attrs(&mut self, elem: ElemId, attrs: ElemAttrs) {
1191 if let Some(mut a) = attrs.wall {
1192 a.elem = elem;
1193 self.wall_attrs.push(a);
1194 }
1195 if let Some(mut a) = attrs.steel_design {
1196 a.elem = elem;
1197 self.steel_design_attrs.push(a);
1198 }
1199 if let Some(mut a) = attrs.brb {
1200 a.elem = elem;
1201 self.brb_attrs.push(a);
1202 }
1203 if let Some(mut a) = attrs.pca {
1204 a.elem = elem;
1205 self.pca_attrs.push(a);
1206 }
1207 if let Some(mut a) = attrs.isolator {
1208 a.elem = elem;
1209 self.isolator_attrs.push(a);
1210 }
1211 if let Some(mut a) = attrs.hysteresis {
1212 a.elem = elem;
1213 self.member_hysteresis_attrs.push(a);
1214 }
1215 if let Some(mut a) = attrs.damper {
1216 a.elem = elem;
1217 self.damper_attrs.push(a);
1218 }
1219 if let Some(mut a) = attrs.detail {
1220 a.elem = elem;
1221 self.member_detail_attrs.push(a);
1222 }
1223 }
1224
1225 pub fn member_detail(&self, elem: ElemId) -> Option<&MemberDetailAttr> {
1227 self.member_detail_attrs.iter().find(|a| a.elem == elem)
1228 }
1229
1230 pub fn member_hysteresis(&self, elem: ElemId) -> Option<HysteresisModel> {
1232 self.member_hysteresis_attrs
1233 .iter()
1234 .find(|a| a.elem == elem)
1235 .map(|a| a.rule)
1236 }
1237
1238 pub fn member_hysteresis_th(&self, elem: ElemId) -> Option<HysteresisModel> {
1242 self.member_hysteresis_attrs
1243 .iter()
1244 .find(|a| a.elem == elem)
1245 .map(|a| a.rule_th.unwrap_or(a.rule))
1246 }
1247
1248 pub fn member_hysteresis_th_raw(&self, elem: ElemId) -> Option<HysteresisModel> {
1252 self.member_hysteresis_attrs
1253 .iter()
1254 .find(|a| a.elem == elem)
1255 .and_then(|a| a.rule_th)
1256 }
1257
1258 fn prune_default_hysteresis(&mut self, elem: ElemId) {
1260 self.member_hysteresis_attrs.retain(|a| {
1261 !(a.elem == elem && a.rule == HysteresisModel::Auto && a.rule_th.is_none())
1262 });
1263 }
1264
1265 pub fn set_member_hysteresis(
1269 &mut self,
1270 elem: ElemId,
1271 rule: HysteresisModel,
1272 ) -> Option<HysteresisModel> {
1273 let old = self.member_hysteresis(elem);
1274 if let Some(a) = self
1275 .member_hysteresis_attrs
1276 .iter_mut()
1277 .find(|a| a.elem == elem)
1278 {
1279 a.rule = rule;
1280 } else if rule != HysteresisModel::Auto {
1281 self.member_hysteresis_attrs.push(MemberHysteresisAttr {
1282 elem,
1283 rule,
1284 rule_th: None,
1285 });
1286 }
1287 self.prune_default_hysteresis(elem);
1288 old
1289 }
1290
1291 pub fn set_member_hysteresis_th(
1295 &mut self,
1296 elem: ElemId,
1297 rule_th: Option<HysteresisModel>,
1298 ) -> Option<HysteresisModel> {
1299 let old = self.member_hysteresis_th_raw(elem);
1300 if let Some(a) = self
1301 .member_hysteresis_attrs
1302 .iter_mut()
1303 .find(|a| a.elem == elem)
1304 {
1305 a.rule_th = rule_th;
1306 } else if rule_th.is_some() {
1307 self.member_hysteresis_attrs.push(MemberHysteresisAttr {
1308 elem,
1309 rule: HysteresisModel::Auto,
1310 rule_th,
1311 });
1312 }
1313 self.prune_default_hysteresis(elem);
1314 old
1315 }
1316
1317 pub fn with_default_load_cases() -> Self {
1321 Model {
1322 load_cases: default_load_cases(),
1323 combinations: default_combinations(),
1324 ..Model::default()
1325 }
1326 }
1327
1328 pub fn validate_secondary_member(
1330 sm: &SecondaryMember,
1331 label: &str,
1332 nodes: &[Node],
1333 sections: &[Section],
1334 ) -> Result<(), crate::error::CoreError> {
1335 use crate::error::CoreError;
1336 for &nid in &sm.nodes {
1337 if nid.index() >= nodes.len() || nodes[nid.index()].id != nid {
1338 return Err(CoreError::DanglingRef(format!("{label} -> Node {}", nid.0)));
1339 }
1340 }
1341 if let Some(sid) = sm.section {
1342 if sid.index() >= sections.len() || sections[sid.index()].id != sid {
1343 return Err(CoreError::DanglingRef(format!(
1344 "{label} -> Section {}",
1345 sid.0
1346 )));
1347 }
1348 }
1349 Ok(())
1350 }
1351
1352 pub fn joists(&self) -> impl Iterator<Item = &SecondaryMember> {
1354 self.unassigned_joists.iter().chain(
1355 self.floor_regions
1356 .iter()
1357 .flat_map(|r| r.secondary_joists.iter()),
1358 )
1359 }
1360
1361 pub fn posts(&self) -> impl Iterator<Item = &SecondaryMember> {
1363 self.unassigned_posts
1364 .iter()
1365 .chain(self.wall_regions.iter().flat_map(|r| r.posts.iter()))
1366 }
1367
1368 pub fn visit_slab_ids(&mut self, mut f: impl FnMut(&mut SlabId)) {
1373 for slab in &mut self.slabs {
1374 f(&mut slab.id);
1375 }
1376 for region in &mut self.floor_regions {
1377 for sid in &mut region.slab_ids {
1378 f(sid);
1379 }
1380 }
1381 }
1382
1383 pub fn visit_wall_plate_ids(&mut self, mut f: impl FnMut(&mut WallPlateId)) {
1388 for plate in &mut self.wall_plates {
1389 f(&mut plate.id);
1390 }
1391 for region in &mut self.wall_regions {
1392 for pid in &mut region.wall_plate_ids {
1393 f(pid);
1394 }
1395 }
1396 }
1397
1398 pub fn retain_slabs(&mut self, mut keep: impl FnMut(&Slab) -> bool) {
1404 let mut remap: Vec<Option<SlabId>> = Vec::with_capacity(self.slabs.len());
1405 let mut next = 0u32;
1406 for slab in &self.slabs {
1407 if keep(slab) {
1408 remap.push(Some(SlabId(next)));
1409 next += 1;
1410 } else {
1411 remap.push(None);
1412 }
1413 }
1414 if remap.iter().all(|r| r.is_some()) {
1415 return;
1416 }
1417
1418 let mut i = 0usize;
1419 self.slabs.retain(|_| {
1420 let k = remap[i].is_some();
1421 i += 1;
1422 k
1423 });
1424 for (i, slab) in self.slabs.iter_mut().enumerate() {
1425 slab.id = SlabId(i as u32);
1426 }
1427
1428 for region in &mut self.floor_regions {
1429 region
1430 .slab_ids
1431 .retain_mut(|id| match remap.get(id.index()).copied().flatten() {
1432 Some(new_id) => {
1433 *id = new_id;
1434 true
1435 }
1436 None => false,
1437 });
1438 }
1439 }
1440
1441 pub fn migrate_legacy_auto_load_cases(&mut self) {
1457 const LEGACY_SELF_WEIGHT: &str = "自重(自動)";
1458 let renames = [
1459 ("床荷重(自動)", DL_CASE_NAME),
1460 ("床積載(自動)", LL_FRAME_CASE_NAME),
1461 ("床地震用積載(自動)", LL_SEISMIC_CASE_NAME),
1462 ];
1463 for (old, new) in renames {
1464 if self.load_cases.iter().any(|lc| lc.name == new) {
1465 continue;
1466 }
1467 if let Some(lc) = self.load_cases.iter_mut().find(|lc| lc.name == old) {
1468 lc.name = new.to_string();
1469 }
1470 }
1471
1472 let Some(sw_idx) = self
1473 .load_cases
1474 .iter()
1475 .position(|lc| lc.name == LEGACY_SELF_WEIGHT)
1476 else {
1477 return;
1478 };
1479 let sw_id = self.load_cases[sw_idx].id;
1480 match self
1481 .load_cases
1482 .iter()
1483 .find(|lc| lc.name == DL_CASE_NAME)
1484 .map(|lc| lc.id)
1485 {
1486 None => {
1487 self.load_cases[sw_idx].name = DL_CASE_NAME.to_string();
1489 self.load_cases[sw_idx].kind = LoadCaseKind::Dead;
1490 }
1491 Some(dl_id) => {
1492 for combo in &mut self.combinations {
1494 let has_dl = combo.terms.iter().any(|(id, _)| *id == dl_id);
1495 if has_dl {
1496 combo.terms.retain(|(id, _)| *id != sw_id);
1497 } else {
1498 for (id, _) in &mut combo.terms {
1499 if *id == sw_id {
1500 *id = dl_id;
1501 }
1502 }
1503 }
1504 }
1505 self.load_cases.remove(sw_idx);
1507 for lc in &mut self.load_cases {
1508 if lc.id.0 > sw_id.0 {
1509 lc.id.0 -= 1;
1510 }
1511 }
1512 for combo in &mut self.combinations {
1513 for (id, _) in &mut combo.terms {
1514 if id.0 > sw_id.0 {
1515 id.0 -= 1;
1516 }
1517 }
1518 }
1519 }
1520 }
1521 }
1522}
1523
1524#[cfg(test)]
1525mod node_reference_tests {
1526 use super::*;
1527
1528 fn node(id: u32) -> Node {
1529 Node {
1530 id: NodeId(id),
1531 coord: [f64::from(id) * 1000.0, 0.0, 0.0],
1532 restraint: Dof6Mask::FREE,
1533 mass: None,
1534 story: None,
1535 support_spring: None,
1536 }
1537 }
1538
1539 #[test]
1548 fn test_node_referenced_by_regions_or_plates_covers_every_field_kind() {
1549 let mut model = Model::default();
1550 for i in 0..12u32 {
1551 model.nodes.push(node(i));
1552 }
1553
1554 model.load_cases.push(LoadCase {
1556 id: LoadCaseId(0),
1557 name: "L".into(),
1558 nodal: vec![NodalLoad {
1559 node: NodeId(0),
1560 values: [0.0; 6],
1561 name: String::new(),
1562 source: LoadSource::Manual,
1563 }],
1564 member: Vec::new(),
1565 kind: LoadCaseKind::default(),
1566 });
1567
1568 let mut region = FloorRegion::new(FloorRegionId(0), vec![NodeId(1)]);
1570 region.secondary_joists.push(SecondaryMember {
1571 kind: SecondaryMemberKind::Joist,
1572 nodes: [NodeId(2), NodeId(2)],
1573 section: None,
1574 name: String::new(),
1575 });
1576 model.floor_regions.push(region);
1577
1578 model.slabs.push(Slab {
1580 id: SlabId(0),
1581 shape: SlabShape::Enclosed {
1582 boundary: vec![NodeId(3)],
1583 },
1584 plate: SlabPlate::default(),
1585 });
1586 model.slabs.push(Slab {
1587 id: SlabId(1),
1588 shape: SlabShape::Attached {
1589 anchor: RegionAnchor::Line {
1590 nodes: [NodeId(4), NodeId(4)],
1591 span: [0.0, 1.0],
1592 transfer: LoadTransfer::Anchor,
1593 },
1594 extent: [0.0, 0.0],
1595 },
1596 plate: SlabPlate::default(),
1597 });
1598
1599 model
1601 .wall_regions
1602 .push(WallRegion::new(WallRegionId(0), vec![NodeId(5)]));
1603
1604 model.wall_plates.push(WallPlate {
1606 id: WallPlateId(0),
1607 shape: WallPlateShape::Enclosed {
1608 boundary: vec![NodeId(6)],
1609 },
1610 section: None,
1611 opening_area: 0.0,
1612 opening_weight: 0.0,
1613 openings: Vec::new(),
1614 loads: vec![],
1615 slit: Default::default(),
1616 });
1617 model.wall_plates.push(WallPlate {
1618 id: WallPlateId(1),
1619 shape: WallPlateShape::Attached {
1620 anchor: RegionAnchor::Line {
1621 nodes: [NodeId(7), NodeId(7)],
1622 span: [0.0, 1.0],
1623 transfer: LoadTransfer::Anchor,
1624 },
1625 extent: Some([0.0, 0.0]),
1626 },
1627 section: None,
1628 opening_area: 0.0,
1629 opening_weight: 0.0,
1630 openings: Vec::new(),
1631 loads: vec![],
1632 slit: Default::default(),
1633 });
1634
1635 model.unassigned_joists.push(SecondaryMember {
1637 kind: SecondaryMemberKind::Joist,
1638 nodes: [NodeId(8), NodeId(8)],
1639 section: None,
1640 name: String::new(),
1641 });
1642
1643 model.constraints.push(Constraint::RigidDiaphragm {
1645 story: StoryId(0),
1646 master: NodeId(9),
1647 slaves: Vec::new(),
1648 weight: None,
1649 ci_override: None,
1650 });
1651
1652 model.wall_plates.push(WallPlate {
1654 id: WallPlateId(2),
1655 shape: WallPlateShape::Attached {
1656 anchor: RegionAnchor::FloorRegion {
1657 nodes: [NodeId(10), NodeId(10)],
1658 },
1659 extent: Some([0.0, 0.0]),
1660 },
1661 section: None,
1662 opening_area: 0.0,
1663 opening_weight: 0.0,
1664 openings: Vec::new(),
1665 loads: vec![],
1666 slit: Default::default(),
1667 });
1668
1669 for i in 0..=10u32 {
1670 assert!(
1671 model.node_referenced_by_regions_or_plates(NodeId(i)),
1672 "node {i} は参照されているはず"
1673 );
1674 }
1675 assert!(!model.node_referenced_by_regions_or_plates(NodeId(11)));
1677 }
1678}