qtmodel 1.1.15__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
qtmodel/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ from .mdb import mdb, Mdb
2
+ from .odb import odb, Odb
3
+
4
+ __version__ = "1.1.15"
@@ -0,0 +1 @@
1
+ __all__ = []
@@ -0,0 +1,327 @@
1
+ import math
2
+ import re
3
+ from typing import List, Optional, Union
4
+
5
+
6
+ class QtDataHelper:
7
+ """
8
+ 用于内部数据处理,不暴露给用户接口
9
+ """
10
+
11
+ @staticmethod
12
+ def str_concrete_box_beam(symmetry: bool = True, sec_info: list[float] = None, box_num: int = 3, box_height: float = 2,
13
+ charm_info: list[str] = None, sec_right: list[float] = None, charm_right: list[str] = None,
14
+ box_other_info: dict[str, list[float]] = None, box_other_right: dict[str, list[float]] = None):
15
+ """混凝土箱梁信息编码
16
+ """
17
+ bridge_width = (2 * sec_info[10]) if symmetry else (sec_info[10] + sec_right[10])
18
+ s = f"{bridge_width:g},{box_num},{box_height},{'YES' if symmetry else 'NO'}\r\n"
19
+
20
+ s += ",".join(f"{x:g}" for x in sec_info) + "\r\n"
21
+ s += ",".join("(" + ",".join(x for term in item.split(",") for x in term.split("*")) + ")" for item in
22
+ (charm_info[0], charm_info[2], charm_info[1], charm_info[3])) + "\r\n"
23
+
24
+ if box_other_info and any(k in box_other_info for k in ("i1", "B0", "B4", "T4")):
25
+ s += "\r\n".join(
26
+ f"L{k}=" + ",".join(f"{v:g}" for v in box_other_info[k]) for k in ("i1", "B0", "B4", "T4") if k in box_other_info) + "\r\n"
27
+
28
+ if not symmetry:
29
+ s += ",".join(f"{x:g}" for x in sec_right) + "\r\n"
30
+ s += ",".join("(" + ",".join(x for term in item.split(",") for x in term.split("*")) + ")" for item in
31
+ (charm_right[0], charm_right[2], charm_right[1], charm_right[3])) + "\r\n"
32
+
33
+ if box_other_right and any(k in box_other_right for k in ("i1", "B0", "B4", "T4")):
34
+ s += "\r\n".join(
35
+ f"R{k}=" + ",".join(f"{v:g}" for v in box_other_info[k]) for k in ("i1", "B0", "B4", "T4") if k in box_other_info) + "\r\n"
36
+ return s
37
+
38
+ @staticmethod
39
+ def str_steel_beam(sec_info: list[float] = None, rib_info: dict[str, list[float]] = None,
40
+ rib_place: list[tuple[int, int, float, str, int, str]] = None):
41
+ """钢梁信息编码
42
+ """
43
+ s = ",".join(f"{x:g}" for x in sec_info) + "\r\n"
44
+ if rib_info is not None:
45
+ s += "\r\n".join(f"RIB={name}," + ",".join(f"{x:g}" for x in params) for name, params in rib_info.items()) + "\r\n"
46
+ if rib_place is not None:
47
+ s += "\r\n".join(f"PLACE={','.join(f'{x:g}' if isinstance(x, float) else str(x) for x in row)}" for row in rib_place) + "\r\n"
48
+ return s
49
+
50
+ @staticmethod
51
+ def str_custom_compound_beam(mat_combine: list[float] = None, loop_segments: list[dict] = None, secondary_loop_segments: list[dict] = None):
52
+ """自定义组合梁信息编码
53
+ """
54
+ s = ",".join(f"{x:g}" for x in mat_combine) + "\r\n"
55
+ s += "M=\r\n"
56
+ if loop_segments:
57
+ for seg in loop_segments:
58
+ for key, pts in seg.items():
59
+ s += f'{"MAIN" if key.lower() == "main" else "SUB"}={",".join(f"({x:g},{y:g})" for x, y in pts)}\r\n'
60
+ s += "S=\r\n"
61
+ if secondary_loop_segments:
62
+ for seg in secondary_loop_segments:
63
+ for key, pts in seg.items():
64
+ s += f'{"MAIN" if key.lower() == "main" else "SUB"}={",".join(f"({x:g},{y:g})" for x, y in pts)}\r\n'
65
+ return s
66
+
67
+ @staticmethod
68
+ def str_compound_section(sec_info: list[float] = None, mat_combine: list[float] = None):
69
+ """组合截面信息编码
70
+ """
71
+ s = ",".join(f"{x:g}" for x in sec_info) + "\r\n"
72
+ s += ",".join(f"{x:g}" for x in mat_combine) + "\r\n"
73
+ return s
74
+
75
+ @staticmethod
76
+ def str_custom_section(loop_segments: list[dict] = None, sec_lines: list[tuple[float, float, float, float, float]] = None):
77
+ """自定义截面信息编码
78
+ """
79
+ s = ""
80
+ if loop_segments:
81
+ for seg in loop_segments:
82
+ for key, pts in seg.items():
83
+ s += f'{"MAIN" if key.lower() == "main" else "SUB"}={",".join(f"({x:g},{y:g})" for x, y in pts)}\r\n'
84
+ if sec_lines:
85
+ s += "\r\n".join(f"LINE={','.join(f'{x:g}' for x in row)}" for row in sec_lines) + "\r\n"
86
+ return s
87
+
88
+ @staticmethod
89
+ def get_str_by_data(sec_type: str, sec_data: dict):
90
+ s = QtDataHelper.str_section(
91
+ sec_type=sec_type,
92
+ sec_info=sec_data.get("sec_info", []),
93
+ symmetry=sec_data.get("symmetry", True),
94
+ charm_info=sec_data.get("charm_info", None),
95
+ sec_right=sec_data.get("sec_right", None),
96
+ charm_right=sec_data.get("charm_right", None),
97
+ box_num=sec_data.get("box_num", 3),
98
+ box_height=sec_data.get("box_height", 2),
99
+ box_other_info=sec_data.get("box_other_info", None),
100
+ box_other_right=sec_data.get("box_other_right", None),
101
+ mat_combine=sec_data.get("mat_combine", None),
102
+ rib_info=sec_data.get("rib_info", None),
103
+ rib_place=sec_data.get("rib_place", None),
104
+ loop_segments=sec_data.get("loop_segments", None),
105
+ sec_lines=sec_data.get("sec_lines", None),
106
+ secondary_loop_segments=sec_data.get("secondary_loop_segments", None)
107
+ )
108
+ return s
109
+
110
+ @staticmethod
111
+ def str_section(
112
+ sec_type: str = "矩形",
113
+ sec_info: list[float] = None,
114
+ symmetry: bool = True,
115
+ charm_info: list[str] = None,
116
+ sec_right: list[float] = None,
117
+ charm_right: list[str] = None,
118
+ box_num: int = 3,
119
+ box_height: float = 2,
120
+ box_other_info: dict[str, list[float]] = None,
121
+ box_other_right: dict[str, list[float]] = None,
122
+ mat_combine: list[float] = None,
123
+ rib_info: dict[str, list[float]] = None,
124
+ rib_place: list[tuple[int, int, float, str, int, str]] = None,
125
+ loop_segments: list[dict] = None,
126
+ sec_lines: list[tuple[float, float, float, float, float]] = None,
127
+ secondary_loop_segments: list[dict] = None):
128
+ """仅返回字符串片段,需要拼接"""
129
+ if sec_type == "混凝土箱梁":
130
+ s = QtDataHelper.str_concrete_box_beam(symmetry, sec_info, box_num, box_height, charm_info, sec_right, charm_right, box_other_info,
131
+ box_other_right)
132
+ elif sec_type == "工字钢梁" or sec_type == "箱型钢梁":
133
+ s = QtDataHelper.str_steel_beam(sec_info, rib_info, rib_place)
134
+ elif sec_type == "特性截面":
135
+ s = ",".join(f"{x:g}" for x in sec_info) + "\r\n"
136
+ elif sec_type.startswith("自定义组合"):
137
+ s = QtDataHelper.str_custom_compound_beam(mat_combine, loop_segments, secondary_loop_segments)
138
+ elif sec_type.endswith("组合梁") or sec_type in ("钢管砼", "钢箱砼", "哑铃型钢管混凝土", "哑铃型钢管混凝土竖向"):
139
+ s = QtDataHelper.str_compound_section(sec_info, mat_combine)
140
+ elif sec_type.startswith("自定义"):
141
+ s = QtDataHelper.str_custom_section(loop_segments, sec_lines)
142
+ else: # 一般参数截面
143
+ s = ",".join(f"{x:g}" for x in sec_info) + "\r\n"
144
+ return s
145
+
146
+ @staticmethod
147
+ def parse_int_list_to_str(ids: Union[int, List[int], str]) -> str:
148
+ """列表转XtoYbyZ
149
+ """
150
+ if ids is None or isinstance(ids, str):
151
+ return "" if ids is None else str(ids)
152
+ if isinstance(ids, int):
153
+ return str(ids)
154
+ sorted_ids = sorted(set(ids)) # 排序并去重
155
+ if len(sorted_ids) == 1:
156
+ return str(sorted_ids[0])
157
+ if len(sorted_ids) == 2:
158
+ return f"{sorted_ids[0]} {sorted_ids[1]}"
159
+
160
+ def create_id_expression(id_from: int, id_to: int, increment: int) -> str:
161
+ """生成等差数列表达式"""
162
+ if increment == 1:
163
+ return f"{id_from}to{id_to}"
164
+ else:
165
+ return f"{id_from}to{id_to}by{increment}"
166
+
167
+ result = []
168
+ id_count = len(sorted_ids)
169
+ start_index = 0
170
+ current_index = 2
171
+ id_increment = sorted_ids[start_index + 1] - sorted_ids[start_index]
172
+
173
+ while True:
174
+ current_increment = sorted_ids[current_index] - sorted_ids[current_index - 1]
175
+ if current_increment == id_increment:
176
+ if current_index >= id_count - 1:
177
+ result.append(create_id_expression(sorted_ids[start_index], sorted_ids[current_index], id_increment))
178
+ break
179
+ current_index += 1
180
+ continue
181
+
182
+ # 增量不一致
183
+ prev_count = (sorted_ids[current_index - 1] - sorted_ids[start_index]) // id_increment + 1
184
+ if prev_count <= 2:
185
+ # 前面只有 2 个
186
+ result.append(str(sorted_ids[start_index]))
187
+ if current_index >= id_count - 1:
188
+ result.append(f"{sorted_ids[current_index - 1]} {sorted_ids[current_index]}")
189
+ break
190
+ start_index = current_index - 1
191
+ id_increment = sorted_ids[start_index + 1] - sorted_ids[start_index]
192
+ current_index = start_index + 2
193
+ else:
194
+ # 前面有 3 个及以上
195
+ result.append(create_id_expression(sorted_ids[start_index], sorted_ids[current_index - 1], id_increment))
196
+ if current_index >= id_count - 1:
197
+ result.append(str(sorted_ids[current_index]))
198
+ break
199
+ if current_index == id_count - 2:
200
+ result.append(f"{sorted_ids[current_index]} {sorted_ids[current_index + 1]}")
201
+ break
202
+ start_index = current_index
203
+ id_increment = sorted_ids[start_index + 1] - sorted_ids[start_index]
204
+ current_index = start_index + 2
205
+
206
+ return " ".join(result)
207
+
208
+ @staticmethod
209
+ def convert_three_points_to_vectors(points):
210
+ """
211
+ 将三点转换为正交的向量格式
212
+ P1, P2, P3 -> V1, V2
213
+ """
214
+ if (len(points) != 3) or (len(points[0]) != 3):
215
+ raise ValueError("操作错误,需要三个三维坐标点")
216
+ p1, p2, p3 = points
217
+ # 计算向量 V1 = P2 - P1 (归一化)
218
+ v1 = [p2[i] - p1[i] for i in range(3)]
219
+ v1_length = math.sqrt(sum(x * x for x in v1))
220
+ v1 = [x / v1_length for x in v1] if v1_length > 0 else v1
221
+ # 计算向量 V2 = (P3 - P1) 在垂直于V1的平面上的投影 (归一化)
222
+ v3 = [p3[i] - p1[i] for i in range(3)]
223
+ dot_product = sum(v1[i] * v3[i] for i in range(3))
224
+ projection = [v1[i] * dot_product for i in range(3)]
225
+ v2 = [v3[i] - projection[i] for i in range(3)]
226
+ v2_length = math.sqrt(sum(x * x for x in v2))
227
+ v2 = [x / v2_length for x in v2] if v2_length > 0 else v2
228
+ return [v1, v2]
229
+
230
+ @staticmethod
231
+ def convert_angle_to_vectors(angles):
232
+ """
233
+ 将欧拉角转换为向量格式
234
+ 角度绕X,Y,Z旋转(弧度制)
235
+ """
236
+ if (len(angles) != 3) or (angles == [0, 0, 0]):
237
+ raise ValueError("操作错误,数据无效")
238
+ rx, ry, rz = map(math.radians, angles)
239
+ ca, sa = math.cos(rx), math.sin(rx)
240
+ cb, sb = math.cos(ry), math.sin(ry)
241
+ cg, sg = math.cos(rz), math.sin(rz)
242
+ # V1 = R @ [1,0,0] (局部X轴)
243
+ v1x = cb * cg
244
+ v1y = sa * sb * cg + ca * sg
245
+ v1z = -ca * sb * cg + sa * sg
246
+ # V2 = R @ [0,1,0] (局部Y轴)
247
+ v2x = -cb * sg
248
+ v2y = -sa * sb * sg + ca * cg
249
+ v2z = ca * sb * sg + sa * cg
250
+ # 四舍五入到6位小数
251
+ v1 = [round(v1x, 6), round(v1y, 6), round(v1z, 6)]
252
+ v2 = [round(v2x, 6), round(v2y, 6), round(v2z, 6)]
253
+ return [v1, v2]
254
+
255
+ @staticmethod
256
+ def parse_number_string(input_str: str) -> Optional[List[int]]:
257
+ """
258
+ 将带“to/by”的字符串解析为 int 列表。
259
+ 规则与给定 C# 版本一致:
260
+ - 以空白分隔各段;段内若包含 'to' 则按 'start to end [by step]' 解析
261
+ - 仅支持紧凑写法:例如 '3to10by2' 或 '3to10'(不支持 '3 to 10 by 2')
262
+ - step 缺省为 1;返回为包含端点的等差序列(若整除)
263
+ - 对于无法解析的段、step<=0、end<start 的段会跳过
264
+ - 空或全空白字符串返回 None
265
+ """
266
+ if input_str is None:
267
+ return None
268
+ s = input_str.strip()
269
+ if s == "":
270
+ return None
271
+
272
+ ids: List[int] = []
273
+ tokens = s.split()
274
+ for tok in tokens:
275
+ if "to" in tok:
276
+ # 按 'to'/'by' 拆分;例如 '3to10by2' -> ['3','10','2']
277
+ parts = re.split(r'to|by', tok)
278
+ if len(parts) >= 2:
279
+ try:
280
+ start = int(parts[0])
281
+ end = int(parts[1])
282
+ except ValueError:
283
+ continue
284
+ step = 1
285
+ if len(parts) > 2:
286
+ try:
287
+ step = int(parts[2])
288
+ except ValueError:
289
+ step = 1
290
+ if step <= 0 or end < start:
291
+ continue
292
+ count = (end - start) // step + 1
293
+ ids.extend(start + n * step for n in range(count))
294
+ else:
295
+ try:
296
+ ids.append(int(tok))
297
+ except ValueError:
298
+ continue
299
+
300
+ return ids
301
+
302
+
303
+
304
+ @staticmethod
305
+ def live_load_set_line(code: int, calc_type: int, groups: list[str]):
306
+ """用于更新移动荷载分析设置"""
307
+ if groups is None:
308
+ return f"{code},{calc_type},\r\n"
309
+ return f"{code},{calc_type}," + ",".join(groups) + "\r\n"
310
+
311
+ @staticmethod
312
+ def parse_ids_to_array(ids: Union[int, List[int], str, None],allow_empty = True) -> List[int]:
313
+ """
314
+ 支持整形、列表、XtoYbyZ形式字符串 统一解析为 int 列表
315
+ """
316
+ result_ids: List[int] = []
317
+ if ids is None:
318
+ return result_ids
319
+ if isinstance(ids, int):
320
+ result_ids.append(ids)
321
+ elif isinstance(ids, str):
322
+ result_ids.extend(QtDataHelper.parse_number_string(ids))
323
+ else:
324
+ result_ids.extend(ids)
325
+ if len(result_ids) == 0 and allow_empty is False:
326
+ raise Exception("集合不可为空,请核查数据")
327
+ return result_ids