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 +4 -0
- qtmodel/core/__init__.py +1 -0
- qtmodel/core/data_helper.py +327 -0
- qtmodel/core/model_db.py +930 -0
- qtmodel/core/qt_server.py +47 -0
- qtmodel/core/result_db.py +757 -0
- qtmodel/mdb/__init__.py +29 -0
- qtmodel/mdb/mdb_analysis_setting.py +299 -0
- qtmodel/mdb/mdb_assistant.py +34 -0
- qtmodel/mdb/mdb_boundary.py +470 -0
- qtmodel/mdb/mdb_construction_stage.py +312 -0
- qtmodel/mdb/mdb_dynamic_load.py +756 -0
- qtmodel/mdb/mdb_live_load.py +550 -0
- qtmodel/mdb/mdb_load.py +185 -0
- qtmodel/mdb/mdb_project.py +227 -0
- qtmodel/mdb/mdb_property.py +443 -0
- qtmodel/mdb/mdb_section.py +523 -0
- qtmodel/mdb/mdb_sink_load.py +166 -0
- qtmodel/mdb/mdb_static_load.py +482 -0
- qtmodel/mdb/mdb_structure.py +525 -0
- qtmodel/mdb/mdb_temperature_load.py +382 -0
- qtmodel/mdb/mdb_tendon.py +397 -0
- qtmodel/odb/__init__.py +19 -0
- qtmodel/odb/odb_model_boundary.py +123 -0
- qtmodel/odb/odb_model_load.py +143 -0
- qtmodel/odb/odb_model_material.py +30 -0
- qtmodel/odb/odb_model_section.py +112 -0
- qtmodel/odb/odb_model_stage.py +55 -0
- qtmodel/odb/odb_model_structure.py +199 -0
- qtmodel/odb/odb_result_data.py +355 -0
- qtmodel/odb/odb_result_plot.py +636 -0
- qtmodel/odb/odb_view.py +204 -0
- qtmodel-1.1.15.dist-info/METADATA +4053 -0
- qtmodel-1.1.15.dist-info/RECORD +36 -0
- qtmodel-1.1.15.dist-info/WHEEL +5 -0
- qtmodel-1.1.15.dist-info/top_level.txt +1 -0
qtmodel/__init__.py
ADDED
qtmodel/core/__init__.py
ADDED
|
@@ -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
|