Skip to main content

squid_n_core/model/
wall_region.rs

1//! 壁領域(`WallRegion`)。柱・梁が囲む鉛直構面内の閉領域。
2//!
3//! 床領域([`super::FloorRegion`]、[`super::region`])と対になる型で、位置づけは同じ
4//! ([`super::region`] のモジュール doc 参照)。壁領域は「柱・梁が囲む鉛直構面内の
5//! 閉領域ごとに 1 つ」(D1)であり、境界は [`crate::region_gen::wall`] の面走査から
6//! 作る。版の仕様は持たない([`super::WallPlate`] が持つ)。
7//!
8//! # 壁領域と壁版の違い
9//!
10//! - **壁領域**(本モジュール): 柱・梁が囲む鉛直構面内の閉領域そのもの。境界は
11//!   柱・梁の閉路(節点列)で、[`crate::region_gen::wall`] の面走査から作る。
12//!   **1 つの閉領域につき 1 つ**とする(D1)。版の仕様は持たない。間柱
13//!   ([`WallRegion::posts`])と、壁領域内の壁版一覧([`WallRegion::wall_plate_ids`])
14//!   を持つ。
15//! - **壁版**([`super::WallPlate`]): 柱・梁で囲まれた版、または主架構・床領域に
16//!   取り付く版(パラペット・腰壁・垂れ壁・自立壁)。断面(板厚・材料)・開口は
17//!   ここが持つ。1 つの壁領域は複数の壁版を持ちうる(E5。壁領域内が間柱でさらに
18//!   細かい壁パネルに分かれている場合)。取り付く壁版はどの壁領域からも参照
19//!   されない独立した壁版として存在する。
20//!
21//! `region_gen::wall` の出力から組み立てる経路([`crate::wall_region_rebuild`])は
22//! 準備計算・ST-Bridge 取り込みへ結線済み(`wall_plate_ids` へ壁版を割り当てる経路
23//! 含む)。**未結線(2026-08-26 時点)**なのは、壁版の ST-Bridge 取り込み・要素生成
24//! (D5)・断面力/保有水平耐力の参照張り替え。ST-Bridge 書き出しは壁版が正。詳細は
25//! `dev_docs/handoff/床領域・壁領域の再設計_申し送り.md` §5.10・§5.13。
26
27use super::*;
28
29/// 壁領域。柱・梁が囲む鉛直構面内の閉領域(D1)。版の仕様は持たない
30/// ([`WallPlate`] が持つ)。
31#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
32pub struct WallRegion {
33    /// 壁領域 ID(`Model::wall_regions` の配列インデックスと一致すること)。
34    pub id: WallRegionId,
35    /// 表示名(ナビゲータ・診断で領域を指し示すために用いる)。空文字は名前なし。
36    #[serde(default)]
37    pub name: String,
38    /// 境界の節点列(柱・梁の閉路。反時計回り、始点は繰り返さない)。
39    #[serde(default)]
40    pub boundary: Vec<NodeId>,
41    /// この壁領域に属する壁版([`WallPlate`])の ID リスト。壁領域内が間柱で複数の
42    /// 壁パネルに分かれている場合、複数持ちうる(E5)。順序は任意。重複・他領域との
43    /// 共有は許さない([`Model::validate`] が確認)。版なし壁領域(間柱のみの
44    /// 雑壁領域等)は空のままでよい。
45    #[serde(default)]
46    pub wall_plate_ids: Vec<WallPlateId>,
47    /// この壁領域に属する間柱(`SecondaryMember::Post`)の実体。
48    #[serde(default)]
49    pub posts: Vec<SecondaryMember>,
50}
51
52impl WallRegion {
53    /// 壁領域を作る(版なし・間柱なし)。
54    pub fn new(id: WallRegionId, boundary: Vec<NodeId>) -> Self {
55        WallRegion {
56            id,
57            name: String::new(),
58            boundary,
59            wall_plate_ids: Vec::new(),
60            posts: Vec::new(),
61        }
62    }
63
64    /// 境界多角形の座標列 [mm]。節点が引けない(陳腐化した参照)場合は `None`。
65    pub fn boundary_coords(&self, model: &Model) -> Option<Vec<[f64; 3]>> {
66        self.boundary
67            .iter()
68            .map(|n| model.nodes.get(n.index()).map(|n| n.coord))
69            .collect()
70    }
71
72    /// 境界の辺 `k` の両端節点。
73    pub fn edge_nodes(&self, k: usize) -> Option<[NodeId; 2]> {
74        let n = self.boundary.len();
75        (n >= 3 && k < n).then(|| [self.boundary[k], self.boundary[(k + 1) % n]])
76    }
77
78    /// 領域を代表する節点(境界の先頭)。
79    pub fn reference_node(&self) -> Option<NodeId> {
80        self.boundary.first().copied()
81    }
82
83    /// 境界の面積 [mm²]([`crate::geom::polygon::area_3d`]。ニューエルの公式による
84    /// 3 次元面積。理想平面への投影を経由しない。§3.2 E3)。座標が引けない場合は 0。
85    pub fn area(&self, model: &Model) -> f64 {
86        self.boundary_coords(model)
87            .map(|pts| crate::geom::polygon::area_3d(&pts))
88            .unwrap_or(0.0)
89    }
90}
91
92impl Model {
93    /// 壁領域 ID から壁領域を引く。存在しなければ `None`。
94    pub fn wall_region(&self, id: WallRegionId) -> Option<&WallRegion> {
95        match self.wall_regions.get(id.index()) {
96            Some(r) if r.id == id => Some(r),
97            _ => self.wall_regions.iter().find(|r| r.id == id),
98        }
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105    use crate::ids::NodeId;
106
107    fn model_with_nodes(pts: &[[f64; 3]]) -> Model {
108        let mut m = Model::default();
109        for (i, p) in pts.iter().enumerate() {
110            m.nodes.push(Node {
111                id: NodeId(i as u32),
112                coord: *p,
113                restraint: Default::default(),
114                mass: None,
115                story: None,
116                support_spring: None,
117            });
118        }
119        m
120    }
121
122    #[test]
123    fn test_boundary_coords_and_area() {
124        let m = model_with_nodes(&[
125            [0.0, 0.0, 0.0],
126            [4000.0, 0.0, 0.0],
127            [4000.0, 0.0, 3000.0],
128            [0.0, 0.0, 3000.0],
129        ]);
130        let r = WallRegion::new(
131            WallRegionId(0),
132            vec![NodeId(0), NodeId(1), NodeId(2), NodeId(3)],
133        );
134        let coords = r.boundary_coords(&m).expect("境界座標");
135        assert_eq!(coords.len(), 4);
136        assert!((r.area(&m) - 4000.0 * 3000.0).abs() < 1e-6);
137        assert_eq!(r.reference_node(), Some(NodeId(0)));
138        assert_eq!(r.edge_nodes(0), Some([NodeId(0), NodeId(1)]));
139    }
140}