civil-tools-v 0.0.1__py3-none-any.whl → 0.0.3__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.
- CivilTools/Const/CAD.py +2 -0
- CivilTools/Const/Concrete.py +144 -0
- CivilTools/Const/Steel.py +21 -0
- CivilTools/Const/__init__.py +3 -0
- CivilTools/DXFGenerator/BasicDXF.py +238 -1
- CivilTools/DXFGenerator/DetailDXF.py +324 -0
- CivilTools/DXFGenerator/DrawingAttribs.py +45 -0
- CivilTools/DXFGenerator/LayerManager.py +37 -0
- CivilTools/DXFGenerator/__init__.py +3 -0
- CivilTools/FigureGenerator/BasicPNGPlotter.py +28 -25
- CivilTools/FigureGenerator/BasicPltPlotter.py +115 -1
- CivilTools/FigureGenerator/SeismicReport/ShearMassRatio.py +73 -0
- CivilTools/FigureGenerator/SeismicReport/ShearMoment.py +71 -0
- CivilTools/FigureGenerator/SeismicReport/__init__.py +2 -0
- CivilTools/FigureGenerator/StairCalculationSheetPNGPlotter.py +2 -8
- CivilTools/ReportGenerator/BasicGenerator.py +109 -83
- CivilTools/ReportGenerator/DocParagraph.py +3 -5
- CivilTools/ReportGenerator/DocPicture.py +7 -8
- CivilTools/ReportGenerator/DocTable.py +11 -11
- CivilTools/ReportGenerator/SeismicReport.py +302 -143
- CivilTools/ReportGenerator/SeismicReportTemplate.py +523 -202
- CivilTools/ReportGenerator/StairCalculationReport.py +249 -185
- CivilTools/ReportGenerator/UtilFunctions.py +108 -104
- CivilTools/ReportGenerator/__init__.py +2 -2
- CivilTools/YDBLoader/BuildingDefine/Beam/Beam.py +12 -15
- CivilTools/YDBLoader/BuildingDefine/Column/Column.py +5 -5
- CivilTools/YDBLoader/BuildingDefine/ComponentType.py +1 -1
- CivilTools/YDBLoader/BuildingDefine/Geometry/Grid.py +8 -12
- CivilTools/YDBLoader/BuildingDefine/Geometry/Joint.py +11 -10
- CivilTools/YDBLoader/BuildingDefine/Geometry/StandFloor.py +1 -1
- CivilTools/YDBLoader/BuildingDefine/GlobalResult/BasicResult.py +44 -24
- CivilTools/YDBLoader/BuildingDefine/GlobalResult/SeismicResult.py +168 -54
- CivilTools/YDBLoader/BuildingDefine/Section/Section.py +26 -31
- CivilTools/YDBLoader/BuildingDefine/Section/ShapeEnum.py +9 -9
- CivilTools/YDBLoader/BuildingDefine/Slab/Slab.py +1 -1
- CivilTools/YDBLoader/BuildingDefine/StairPart/LoadDefine.py +16 -10
- CivilTools/YDBLoader/BuildingDefine/StairPart/StairComponent.py +41 -37
- CivilTools/YDBLoader/BuildingDefine/StairPart/StairPart.py +133 -78
- CivilTools/YDBLoader/SQLiteConnector/Connector.py +16 -8
- CivilTools/YDBLoader/SQLiteConnector/RowDataFactory.py +19 -17
- CivilTools/YDBLoader/SQLiteConnector/YDBTableName.py +31 -20
- CivilTools/YDBLoader/YDBLoader.py +128 -110
- {civil_tools_v-0.0.1.dist-info → civil_tools_v-0.0.3.dist-info}/METADATA +88 -5
- civil_tools_v-0.0.3.dist-info/RECORD +60 -0
- {civil_tools_v-0.0.1.dist-info → civil_tools_v-0.0.3.dist-info}/WHEEL +1 -1
- civil_tools_v-0.0.1.dist-info/RECORD +0 -50
- {civil_tools_v-0.0.1.dist-info → civil_tools_v-0.0.3.dist-info}/LICENSE +0 -0
- {civil_tools_v-0.0.1.dist-info → civil_tools_v-0.0.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,324 @@
|
|
1
|
+
from typing import List
|
2
|
+
from .BasicDXF import BasicDXF
|
3
|
+
from .DrawingAttribs import (
|
4
|
+
DrawingAttribs,
|
5
|
+
PolylineAttribs,
|
6
|
+
TextAttribs,
|
7
|
+
TextEntityAlignment,
|
8
|
+
)
|
9
|
+
from .LayerManager import DefaultLayers, CADColor
|
10
|
+
from CivilTools.Const import Concrete, ConcreteLevel, Steel, SteelLevel
|
11
|
+
|
12
|
+
|
13
|
+
class DetailDXF(BasicDXF):
|
14
|
+
def __init__(self):
|
15
|
+
super().__init__()
|
16
|
+
|
17
|
+
|
18
|
+
default_floor_height_table_attribs = {
|
19
|
+
"column_con_level": None,
|
20
|
+
"column_steel_level": None,
|
21
|
+
"beam_con_level": None,
|
22
|
+
"beam_steel_level": None,
|
23
|
+
}
|
24
|
+
|
25
|
+
|
26
|
+
class FloorInformation:
|
27
|
+
def __init__(
|
28
|
+
self,
|
29
|
+
name: str,
|
30
|
+
archi_elevation: float,
|
31
|
+
plaster_thickness: float = 200,
|
32
|
+
beam_slab_con_mat: ConcreteLevel = Concrete.C35,
|
33
|
+
wall_con_mat: ConcreteLevel = Concrete.C60,
|
34
|
+
column_con_mat: ConcreteLevel = Concrete.C50,
|
35
|
+
brace_con_mat: ConcreteLevel = Concrete.C50,
|
36
|
+
beam_steel_mat: SteelLevel = Steel.HRB300,
|
37
|
+
column_steel_mat: SteelLevel = Steel.Q355,
|
38
|
+
brace_steel_mat: SteelLevel = Steel.Q355,
|
39
|
+
):
|
40
|
+
if plaster_thickness < 0:
|
41
|
+
raise ValueError("Plaster value should be non-negitive.")
|
42
|
+
self.floor_name = name
|
43
|
+
self.__struct_elevation = archi_elevation - plaster_thickness
|
44
|
+
self.__archi_elevation = archi_elevation
|
45
|
+
|
46
|
+
self.beam_slab_con_mat = beam_slab_con_mat
|
47
|
+
self.wall_con_mat = wall_con_mat
|
48
|
+
self.column_con_mat = column_con_mat
|
49
|
+
self.brace_con_mat = brace_con_mat
|
50
|
+
|
51
|
+
self.beam_steel_mat = beam_steel_mat
|
52
|
+
self.column_steel_mat = column_steel_mat
|
53
|
+
self.brace_steel_mat = brace_steel_mat
|
54
|
+
self.is_embedded = False
|
55
|
+
|
56
|
+
def get_attribute_by_name(self, attrib_name: str):
|
57
|
+
if "楼层号" in attrib_name:
|
58
|
+
return self.floor_name
|
59
|
+
if "建筑" in attrib_name and "标高" in attrib_name:
|
60
|
+
return self.archi_elevation
|
61
|
+
if "结构" in attrib_name and "标高" in attrib_name:
|
62
|
+
return self.struct_elevation
|
63
|
+
if "柱" in attrib_name:
|
64
|
+
return self.column_con_mat.name
|
65
|
+
if "墙" in attrib_name:
|
66
|
+
return self.wall_con_mat.name
|
67
|
+
if "板" in attrib_name:
|
68
|
+
return self.beam_slab_con_mat.name
|
69
|
+
|
70
|
+
@property
|
71
|
+
def struct_elevation(self):
|
72
|
+
if self.__struct_elevation == 0:
|
73
|
+
return "±0.000"
|
74
|
+
return f"{self.__struct_elevation/1000:.3f}"
|
75
|
+
|
76
|
+
@property
|
77
|
+
def archi_elevation(self):
|
78
|
+
if self.__archi_elevation == 0:
|
79
|
+
return "±0.000"
|
80
|
+
return f"{self.__archi_elevation/1000:.3f}"
|
81
|
+
|
82
|
+
|
83
|
+
class FloorHeightTableDXF(BasicDXF):
|
84
|
+
def __prepare(self):
|
85
|
+
useful_layers = [DefaultLayers.TEXT, DefaultLayers.SYMBOL]
|
86
|
+
self.init_layers(useful_layers)
|
87
|
+
|
88
|
+
def __init__(
|
89
|
+
self,
|
90
|
+
above_floor_num: int,
|
91
|
+
under_floor_num: int,
|
92
|
+
base_elevation: float,
|
93
|
+
font_size: float = 300,
|
94
|
+
):
|
95
|
+
super().__init__()
|
96
|
+
self.__prepare()
|
97
|
+
self.__above_floor_num = above_floor_num
|
98
|
+
self.__under_floor_num = under_floor_num
|
99
|
+
self.__base_elevation = base_elevation
|
100
|
+
self.floor_num = above_floor_num + under_floor_num
|
101
|
+
self.floors = self.__create_floors()
|
102
|
+
self.__font_size = font_size
|
103
|
+
self.__table_title = None
|
104
|
+
self.has_embeded_floor = False
|
105
|
+
self.embeded_floor = None
|
106
|
+
self.insert_point = [0, 0]
|
107
|
+
|
108
|
+
for key, value in default_floor_height_table_attribs.items():
|
109
|
+
setattr(self, key, value)
|
110
|
+
|
111
|
+
def __create_floors(self) -> List[FloorInformation]:
|
112
|
+
result = []
|
113
|
+
for i in range(self.__above_floor_num):
|
114
|
+
temp_floor = FloorInformation(f"F{i+1}", self.__base_elevation + 3500 * i)
|
115
|
+
result.append(temp_floor)
|
116
|
+
for i in range(self.__under_floor_num):
|
117
|
+
temp_floor = FloorInformation(
|
118
|
+
f"B{i+1}", self.__base_elevation - 3500 * (i + 1)
|
119
|
+
)
|
120
|
+
result.insert(0, temp_floor)
|
121
|
+
return result
|
122
|
+
|
123
|
+
def export_dxf(self, path):
|
124
|
+
self._remove_all_entities()
|
125
|
+
self.__draw_table_title()
|
126
|
+
self.__analysis_table_columns()
|
127
|
+
self.__draw_table_grid()
|
128
|
+
self.__draw_table_column_title()
|
129
|
+
self.__draw_context()
|
130
|
+
self.__draw_supplement_info()
|
131
|
+
self._save(path)
|
132
|
+
|
133
|
+
def set_table_title(self, title: str):
|
134
|
+
self.__table_title = title
|
135
|
+
|
136
|
+
def set_font_size(self, font_size: float):
|
137
|
+
self.__font_size = font_size
|
138
|
+
|
139
|
+
def set_embeding_floor(self, floor_name: str | None):
|
140
|
+
"""Use None to reset embeded floor."""
|
141
|
+
if floor_name == None:
|
142
|
+
self.has_embeded_floor = False
|
143
|
+
self.embeded_floor = None
|
144
|
+
return
|
145
|
+
for floor in self.floors:
|
146
|
+
if floor.floor_name == floor_name:
|
147
|
+
floor.is_embedded = True
|
148
|
+
self.has_embeded_floor = True
|
149
|
+
self.embeded_floor = floor
|
150
|
+
return
|
151
|
+
raise ValueError(f"No floor named as {floor_name}")
|
152
|
+
|
153
|
+
def __analysis_table_columns(self):
|
154
|
+
self.__columns = []
|
155
|
+
self.__column_widths = []
|
156
|
+
|
157
|
+
self.__columns.append("当前层")
|
158
|
+
self.__column_widths.append(3)
|
159
|
+
|
160
|
+
self.__columns.append("建筑楼层号")
|
161
|
+
self.__column_widths.append(5)
|
162
|
+
|
163
|
+
self.__columns.append("建筑标高(m)")
|
164
|
+
self.__column_widths.append(6)
|
165
|
+
|
166
|
+
self.__columns.append("结构标高(m)")
|
167
|
+
self.__column_widths.append(6)
|
168
|
+
|
169
|
+
self.__first_part_column_num = len(self.__columns)
|
170
|
+
|
171
|
+
self.__columns.append("结构柱")
|
172
|
+
self.__column_widths.append(3)
|
173
|
+
|
174
|
+
self.__columns.append("剪力墙")
|
175
|
+
self.__column_widths.append(3)
|
176
|
+
|
177
|
+
self.__columns.append("梁/板")
|
178
|
+
self.__column_widths.append(3)
|
179
|
+
|
180
|
+
def __draw_table_title(self):
|
181
|
+
if self.__table_title == None:
|
182
|
+
return
|
183
|
+
text_attrib = TextAttribs(
|
184
|
+
DefaultLayers.TEXT.name,
|
185
|
+
text_height=self.__font_size,
|
186
|
+
text_align=TextEntityAlignment.MIDDLE_LEFT,
|
187
|
+
)
|
188
|
+
self._add_text(
|
189
|
+
self.__table_title,
|
190
|
+
[self.insert_point[0], self.insert_point[1] + self.__font_size * 1.5],
|
191
|
+
text_attrib,
|
192
|
+
)
|
193
|
+
|
194
|
+
def __draw_table_grid(self):
|
195
|
+
first_part_column_num = self.__first_part_column_num
|
196
|
+
start_x = self.insert_point[0]
|
197
|
+
start_y = self.insert_point[1]
|
198
|
+
font_factor = self.__font_size
|
199
|
+
|
200
|
+
total_width = font_factor * sum(self.__column_widths)
|
201
|
+
first_part_width = font_factor * sum(
|
202
|
+
self.__column_widths[:first_part_column_num]
|
203
|
+
)
|
204
|
+
|
205
|
+
total_height = font_factor * 2 * (self.floor_num + 2)
|
206
|
+
grid_draw_attrib = PolylineAttribs(DefaultLayers.SYMBOL.name)
|
207
|
+
for i in range(self.floor_num + 3):
|
208
|
+
if i == 1:
|
209
|
+
self._add_horizental_line(
|
210
|
+
start_x + first_part_width,
|
211
|
+
start_y,
|
212
|
+
total_width - first_part_width,
|
213
|
+
grid_draw_attrib,
|
214
|
+
)
|
215
|
+
else:
|
216
|
+
self._add_horizental_line(
|
217
|
+
start_x, start_y, total_width, grid_draw_attrib
|
218
|
+
)
|
219
|
+
start_y -= font_factor * 2
|
220
|
+
if (
|
221
|
+
i - 2 >= 0
|
222
|
+
and i - 2 < self.floor_num
|
223
|
+
and self.floors[::-1][i - 2].is_embedded
|
224
|
+
):
|
225
|
+
self._add_horizental_line(
|
226
|
+
start_x, start_y + font_factor * 0.2, total_width, grid_draw_attrib
|
227
|
+
)
|
228
|
+
start_y = 0
|
229
|
+
for i in range(len(self.__columns) + 1):
|
230
|
+
if i <= first_part_column_num or i == len(self.__columns):
|
231
|
+
self._add_vertical_line(
|
232
|
+
start_x, start_y, -total_height, grid_draw_attrib
|
233
|
+
)
|
234
|
+
else:
|
235
|
+
self._add_vertical_line(
|
236
|
+
start_x,
|
237
|
+
start_y - font_factor * 2,
|
238
|
+
-total_height + font_factor * 2,
|
239
|
+
grid_draw_attrib,
|
240
|
+
)
|
241
|
+
if i == len(self.__columns):
|
242
|
+
continue
|
243
|
+
start_x += font_factor * self.__column_widths[i]
|
244
|
+
|
245
|
+
def __draw_table_column_title(self):
|
246
|
+
font_factor = self.__font_size
|
247
|
+
start_x = self.insert_point[0]
|
248
|
+
start_y = self.insert_point[1]
|
249
|
+
|
250
|
+
table_column_title_attrib = TextAttribs(
|
251
|
+
DefaultLayers.TEXT.name,
|
252
|
+
text_height=font_factor,
|
253
|
+
text_align=TextEntityAlignment.MIDDLE_CENTER,
|
254
|
+
)
|
255
|
+
|
256
|
+
for i in range(len(self.__columns)):
|
257
|
+
temp_string = self.__columns[i]
|
258
|
+
temp_x = (
|
259
|
+
sum(self.__column_widths[: i + 1]) - self.__column_widths[i] / 2
|
260
|
+
) * font_factor
|
261
|
+
temp_y = (
|
262
|
+
-font_factor * 2
|
263
|
+
if i < self.__first_part_column_num
|
264
|
+
else -font_factor * 3
|
265
|
+
)
|
266
|
+
self._add_text(
|
267
|
+
temp_string,
|
268
|
+
[start_x + temp_x, start_y + temp_y],
|
269
|
+
table_column_title_attrib,
|
270
|
+
)
|
271
|
+
temp_x = (
|
272
|
+
(
|
273
|
+
sum(self.__column_widths)
|
274
|
+
+ sum(self.__column_widths[: self.__first_part_column_num])
|
275
|
+
)
|
276
|
+
/ 2.0
|
277
|
+
* font_factor
|
278
|
+
)
|
279
|
+
temp_y = -font_factor * 1
|
280
|
+
self._add_text("混凝土结构", [temp_x, temp_y], table_column_title_attrib)
|
281
|
+
|
282
|
+
def __draw_context(self):
|
283
|
+
font_factor = self.__font_size
|
284
|
+
|
285
|
+
start_x = self.insert_point[0]
|
286
|
+
start_y = self.insert_point[1] - font_factor * 4
|
287
|
+
for i in range(len(self.floors)):
|
288
|
+
temp_floor = self.floors[::-1][i]
|
289
|
+
for j in range(1, len(self.__columns)):
|
290
|
+
temp_insert_point = [
|
291
|
+
start_x + font_factor * (sum(self.__column_widths[: j + 1]) - 0.5),
|
292
|
+
start_y - font_factor * (2 * i + 1),
|
293
|
+
]
|
294
|
+
temp_string = temp_floor.get_attribute_by_name(self.__columns[j])
|
295
|
+
self._add_text(
|
296
|
+
temp_string,
|
297
|
+
temp_insert_point,
|
298
|
+
TextAttribs(
|
299
|
+
DefaultLayers.TEXT.name,
|
300
|
+
color_index=CADColor.Yellow.value,
|
301
|
+
text_height=font_factor,
|
302
|
+
text_align=TextEntityAlignment.MIDDLE_RIGHT,
|
303
|
+
),
|
304
|
+
)
|
305
|
+
|
306
|
+
def __draw_supplement_info(self):
|
307
|
+
if not self.has_embeded_floor:
|
308
|
+
return
|
309
|
+
font_factor = self.__font_size
|
310
|
+
temp_x = self.insert_point[0]
|
311
|
+
temp_y = self.insert_point[1] - font_factor * (self.floor_num + 2.5) * 2
|
312
|
+
table_supplement_info_attrib = TextAttribs(
|
313
|
+
DefaultLayers.TEXT.name,
|
314
|
+
text_height=font_factor,
|
315
|
+
text_align=TextEntityAlignment.MIDDLE_LEFT,
|
316
|
+
)
|
317
|
+
self._add_text(
|
318
|
+
f"上部嵌固层:{self.embeded_floor.floor_name},标高:{self.embeded_floor.struct_elevation}。",
|
319
|
+
[temp_x, temp_y],
|
320
|
+
table_supplement_info_attrib,
|
321
|
+
)
|
322
|
+
|
323
|
+
def __draw_embeding_dimension(self):
|
324
|
+
return NotImplemented
|
@@ -0,0 +1,45 @@
|
|
1
|
+
from ezdxf.enums import TextEntityAlignment
|
2
|
+
from CivilTools.Const import CADConst
|
3
|
+
|
4
|
+
|
5
|
+
class DrawingAttribs:
|
6
|
+
"""这是一个基础的属性,包含图层、颜色等所有人都有的信息"""
|
7
|
+
|
8
|
+
def __init__(
|
9
|
+
self,
|
10
|
+
layer: str,
|
11
|
+
color_index: int = CADConst.BY_LAYER,
|
12
|
+
):
|
13
|
+
self.layer = layer
|
14
|
+
self.color_index = color_index
|
15
|
+
|
16
|
+
|
17
|
+
class PolylineAttribs(DrawingAttribs):
|
18
|
+
"""Polyline对象的属性,包括是否封闭等"""
|
19
|
+
|
20
|
+
def __init__(
|
21
|
+
self,
|
22
|
+
layer,
|
23
|
+
color_index: int = CADConst.BY_LAYER,
|
24
|
+
close: bool = False,
|
25
|
+
constant_width: float = 0,
|
26
|
+
):
|
27
|
+
super().__init__(layer, color_index)
|
28
|
+
self.close = close
|
29
|
+
self.constant_width = constant_width
|
30
|
+
"""全局宽度"""
|
31
|
+
|
32
|
+
|
33
|
+
class TextAttribs(DrawingAttribs):
|
34
|
+
def __init__(
|
35
|
+
self,
|
36
|
+
layer,
|
37
|
+
color_index: int = CADConst.BY_LAYER,
|
38
|
+
text_height: float = 300,
|
39
|
+
text_width_factor: float = 0.7,
|
40
|
+
text_align: TextEntityAlignment = TextEntityAlignment.MIDDLE_CENTER,
|
41
|
+
):
|
42
|
+
super().__init__(layer, color_index)
|
43
|
+
self.text_height = text_height
|
44
|
+
self.text_width_factor = text_width_factor
|
45
|
+
self.text_align = text_align
|
@@ -0,0 +1,37 @@
|
|
1
|
+
from enum import Enum
|
2
|
+
|
3
|
+
|
4
|
+
class CADColor(Enum):
|
5
|
+
Red = 1
|
6
|
+
Yellow = 2
|
7
|
+
Green = 3
|
8
|
+
Cyan = 4
|
9
|
+
Blue = 5
|
10
|
+
Magenta = 6
|
11
|
+
White = 7
|
12
|
+
Gray = 8
|
13
|
+
LightGray = 9
|
14
|
+
|
15
|
+
|
16
|
+
class CADLineType(Enum):
|
17
|
+
Continuous = 1
|
18
|
+
DASHED = 2
|
19
|
+
CENTER = 3
|
20
|
+
DASHDOT = 4
|
21
|
+
|
22
|
+
|
23
|
+
class CADLayer:
|
24
|
+
def __init__(
|
25
|
+
self,
|
26
|
+
name: str,
|
27
|
+
color: CADColor,
|
28
|
+
line_type: CADLineType = CADLineType.Continuous,
|
29
|
+
):
|
30
|
+
self.name = name
|
31
|
+
self.color = color
|
32
|
+
self.line_type = line_type
|
33
|
+
|
34
|
+
|
35
|
+
class DefaultLayers:
|
36
|
+
TEXT = CADLayer("0S-TEXT", CADColor.Cyan)
|
37
|
+
SYMBOL = CADLayer("0S-SYMB", CADColor.Red)
|
@@ -1,43 +1,46 @@
|
|
1
1
|
from PIL import Image, ImageDraw
|
2
2
|
import warnings
|
3
3
|
|
4
|
+
|
4
5
|
class BasicPNGPlotter:
|
5
|
-
def __init__(self, width, height, bg
|
6
|
+
def __init__(self, width, height, bg=(0, 0, 0, 0)):
|
6
7
|
self.width = width
|
7
8
|
self.height = height
|
8
|
-
self.image = Image.new(
|
9
|
+
self.image = Image.new("RGBA", (width, height), bg)
|
9
10
|
self.draw = ImageDraw.Draw(self.image)
|
10
|
-
|
11
|
-
def draw_horizental_line(self,s_x,s_y,length,color
|
11
|
+
|
12
|
+
def draw_horizental_line(self, s_x, s_y, length, color="black", line_width=2):
|
12
13
|
"""画一段水平线,length为长度,正代表向右"""
|
13
|
-
self.draw_line(s_x,s_y,s_x + length, s_y,color,line_width)
|
14
|
-
|
15
|
-
def draw_vertical_line(self,s_x,s_y,length,color
|
14
|
+
self.draw_line(s_x, s_y, s_x + length, s_y, color, line_width)
|
15
|
+
|
16
|
+
def draw_vertical_line(self, s_x, s_y, length, color="black", line_width=2):
|
16
17
|
"""画一段竖直线,length为长度,正代表向下"""
|
17
|
-
self.draw_line(s_x,s_y,s_x + length, s_y,color,line_width)
|
18
|
-
|
19
|
-
def draw_rectangle(
|
18
|
+
self.draw_line(s_x, s_y, s_x + length, s_y, color, line_width)
|
19
|
+
|
20
|
+
def draw_rectangle(
|
21
|
+
self, s_x, s_y, rec_width, rec_height, color="black", line_width=2
|
22
|
+
):
|
20
23
|
"""画一个矩形,左上角点为s_x,s_y,width正代表向右,height正代表向下"""
|
21
|
-
self.draw_horizental_line(s_x,s_y,rec_width,color,line_width)
|
22
|
-
self.draw_horizental_line(s_x,s_y + rec_height,rec_width,color,line_width)
|
23
|
-
self.draw_vertical_line(s_x,s_y,rec_height,color,line_width)
|
24
|
-
self.draw_vertical_line(s_x + rec_width,s_y,rec_height,color,line_width)
|
25
|
-
|
26
|
-
def draw_line(self,s_x,s_y,e_x,e_y,color
|
27
|
-
if s_x<0 or s_x > self.width or s_y <0 or s_y> self.height:
|
24
|
+
self.draw_horizental_line(s_x, s_y, rec_width, color, line_width)
|
25
|
+
self.draw_horizental_line(s_x, s_y + rec_height, rec_width, color, line_width)
|
26
|
+
self.draw_vertical_line(s_x, s_y, rec_height, color, line_width)
|
27
|
+
self.draw_vertical_line(s_x + rec_width, s_y, rec_height, color, line_width)
|
28
|
+
|
29
|
+
def draw_line(self, s_x, s_y, e_x, e_y, color="black", width=2):
|
30
|
+
if s_x < 0 or s_x > self.width or s_y < 0 or s_y > self.height:
|
28
31
|
warnings.warn("Start point is out of figure.")
|
29
|
-
if e_x<0 or e_x > self.width or e_y <0 or e_y> self.height:
|
32
|
+
if e_x < 0 or e_x > self.width or e_y < 0 or e_y > self.height:
|
30
33
|
warnings.warn("End point is out of figure.")
|
31
|
-
self.draw.line((s_x,s_y,e_x,e_y), fill=color, width=width)
|
32
|
-
|
34
|
+
self.draw.line((s_x, s_y, e_x, e_y), fill=color, width=width)
|
35
|
+
|
33
36
|
def draw_png(self):
|
34
|
-
self.draw.line((0, 0, 500, 500), fill=
|
37
|
+
self.draw.line((0, 0, 500, 500), fill="black", width=2)
|
35
38
|
|
36
|
-
def save(self,path):
|
37
|
-
self.image.save(path,
|
39
|
+
def save(self, path):
|
40
|
+
self.image.save(path, "PNG")
|
38
41
|
|
39
42
|
|
40
43
|
if __name__ == "__main__":
|
41
|
-
p = BasicPNGPlotter(1500,800)
|
44
|
+
p = BasicPNGPlotter(1500, 800)
|
42
45
|
p.draw_png()
|
43
|
-
p.save("testfiles/output.png")
|
46
|
+
p.save("testfiles/output.png")
|
@@ -1,3 +1,117 @@
|
|
1
|
+
from typing import List
|
2
|
+
import matplotlib.pyplot as plt
|
3
|
+
from matplotlib.axes import Axes
|
4
|
+
import io
|
5
|
+
import numpy as np
|
6
|
+
|
7
|
+
plt.rcParams["font.sans-serif"] = ["SimHei"] # 用来正常显示中文标签
|
8
|
+
plt.rcParams["axes.unicode_minus"] = False
|
9
|
+
plt.rcParams["font.size"] = 14
|
10
|
+
|
11
|
+
|
12
|
+
def GetTicks(n_max=1):
|
13
|
+
if n_max == 0:
|
14
|
+
n_max = 100
|
15
|
+
m = int(np.log10(n_max))
|
16
|
+
if m <= 0 and n_max <= 1:
|
17
|
+
m -= 1
|
18
|
+
p = n_max / (10**m)
|
19
|
+
if p <= 2.2:
|
20
|
+
result = [i * 1 * 10**m for i in range(3)]
|
21
|
+
elif p <= 3.5:
|
22
|
+
result = [i * 2 * 10**m for i in range(3)]
|
23
|
+
elif p <= 5:
|
24
|
+
result = [i * 2.5 * 10**m for i in range(3)]
|
25
|
+
elif p <= 6.9:
|
26
|
+
result = [i * 3 * 10**m for i in range(4)]
|
27
|
+
elif p <= 8:
|
28
|
+
result = [i * 2 * 10**m for i in range(5)]
|
29
|
+
else:
|
30
|
+
result = [i * 2 * 10**m for i in range(6)]
|
31
|
+
return result
|
32
|
+
|
1
33
|
|
2
34
|
class BasicPltPlotter:
|
3
|
-
|
35
|
+
def __init__(self, fig_num: int = 1, fig_size=(10, 10)):
|
36
|
+
self.fig, _axes = plt.subplots(1, fig_num, figsize=fig_size)
|
37
|
+
if fig_num == 1:
|
38
|
+
self.axes = [_axes]
|
39
|
+
else:
|
40
|
+
self.axes = _axes
|
41
|
+
self.fig.patch.set_facecolor("none")
|
42
|
+
for ax in self.axes:
|
43
|
+
ax.patch.set_facecolor("none")
|
44
|
+
self.axes: List[Axes]
|
45
|
+
self.__remove_border()
|
46
|
+
|
47
|
+
def __remove_border(self):
|
48
|
+
for ax in self.axes:
|
49
|
+
ax.spines["right"].set_color("none")
|
50
|
+
ax.spines["top"].set_color("none")
|
51
|
+
|
52
|
+
def save_to_stream(self):
|
53
|
+
# 将图片保存到内存中的 BytesIO 对象
|
54
|
+
img_buffer = io.BytesIO()
|
55
|
+
self.fig.savefig(img_buffer, format="png", dpi=150) # 保存为 PNG 格式
|
56
|
+
plt.close() # 关闭图形,释放内存
|
57
|
+
# 将指针重置到流的开头,以便后续读取
|
58
|
+
img_buffer.seek(0)
|
59
|
+
return img_buffer
|
60
|
+
|
61
|
+
|
62
|
+
class SeismicPlotter(BasicPltPlotter):
|
63
|
+
def __init__(self, fig_num=2, floor_num: int = 8):
|
64
|
+
if fig_num != 1 and fig_num != 2:
|
65
|
+
raise ValueError("Only 1 or 2 is accepted for fig_num.")
|
66
|
+
if fig_num == 1:
|
67
|
+
fig_size = (3, 5)
|
68
|
+
else:
|
69
|
+
fig_size = (6, 5)
|
70
|
+
super().__init__(fig_num, fig_size)
|
71
|
+
self.kwargs_x = {
|
72
|
+
"label": "X",
|
73
|
+
"ls": "-",
|
74
|
+
"color": "k",
|
75
|
+
"marker": "o",
|
76
|
+
"ms": 3,
|
77
|
+
}
|
78
|
+
self.kwargs_y = {
|
79
|
+
"label": "X",
|
80
|
+
"ls": "-",
|
81
|
+
"color": "r",
|
82
|
+
"marker": "o",
|
83
|
+
"ms": 3,
|
84
|
+
}
|
85
|
+
self.floor_num = floor_num
|
86
|
+
self.y_label = "层号"
|
87
|
+
self._y_values = [i + 1 for i in range(self.floor_num)]
|
88
|
+
self._y_major_ticks = self.__create_y_ticks()
|
89
|
+
self._y_minor_ticks = [i + 1 for i in range(self.floor_num)]
|
90
|
+
self._ax1_x = [i for i in range(self.floor_num)]
|
91
|
+
self._ax1_y = [i * 0.5 for i in range(self.floor_num)]
|
92
|
+
self._ax2_x = [i for i in range(self.floor_num)]
|
93
|
+
self._ax2_y = [i * 0.5 for i in range(self.floor_num)]
|
94
|
+
|
95
|
+
def test_plot(self):
|
96
|
+
self.__plot()
|
97
|
+
|
98
|
+
def __plot(self):
|
99
|
+
self.axes[0].plot(self._ax1_x, self._y_values, **self.kwargs_x)
|
100
|
+
self.axes[0].plot(self._ax1_y, self._y_values, **self.kwargs_y)
|
101
|
+
self.axes[1].plot(self._ax2_x, self._y_values, **self.kwargs_x)
|
102
|
+
self.axes[1].plot(self._ax2_y, self._y_values, **self.kwargs_y)
|
103
|
+
|
104
|
+
def __create_y_ticks(self):
|
105
|
+
floor_num = self.floor_num
|
106
|
+
return range(0, int(floor_num) + 1, int(floor_num // 5) + 1)
|
107
|
+
|
108
|
+
def _validate_list_length(self, data_list, name):
|
109
|
+
"""
|
110
|
+
验证列表长度是否与楼层数相等
|
111
|
+
:param data_list: 待验证的列表
|
112
|
+
:param name: 列表的名称,用于异常信息
|
113
|
+
"""
|
114
|
+
if len(data_list) != self.floor_num:
|
115
|
+
raise ValueError(
|
116
|
+
f"Length of {name} is not equal to floor number: {self.floor_num}!"
|
117
|
+
)
|
@@ -0,0 +1,73 @@
|
|
1
|
+
from ..BasicPltPlotter import SeismicPlotter, GetTicks
|
2
|
+
from typing import List
|
3
|
+
import numpy as np
|
4
|
+
import matplotlib.pyplot as plt
|
5
|
+
|
6
|
+
|
7
|
+
class ShearMassRatioPlotter(SeismicPlotter):
|
8
|
+
def __init__(self, fig_num=2, floor_num=8):
|
9
|
+
super().__init__(fig_num, floor_num)
|
10
|
+
self.__limit = None
|
11
|
+
self.type = "剪重比"
|
12
|
+
|
13
|
+
def set_data(self, shear_x: List[float], shear_y: List[float], mass: List[float]):
|
14
|
+
if len(shear_x) != self.floor_num:
|
15
|
+
raise ValueError(
|
16
|
+
f"Lenght of shear_x is not equal to floor number: {self.floor_num}!"
|
17
|
+
)
|
18
|
+
if len(shear_y) != self.floor_num:
|
19
|
+
raise ValueError(
|
20
|
+
f"Lenght of shear_y is not equal to floor number: {self.floor_num}!"
|
21
|
+
)
|
22
|
+
if len(mass) != self.floor_num:
|
23
|
+
raise ValueError(
|
24
|
+
f"Lenght of mass is not equal to floor number: {self.floor_num}!"
|
25
|
+
)
|
26
|
+
|
27
|
+
self._ax1_x = np.array(shear_x) / np.array(mass)
|
28
|
+
self._ax2_x = np.array(shear_y) / np.array(mass)
|
29
|
+
|
30
|
+
def set_limit(self, limit: float):
|
31
|
+
self.__limit = limit
|
32
|
+
|
33
|
+
def plot(self):
|
34
|
+
if self.__limit:
|
35
|
+
self.__plot_limit()
|
36
|
+
kwargs_x = self.kwargs_x.copy()
|
37
|
+
kwargs_x["label"] = "X"
|
38
|
+
kwargs_y = self.kwargs_x.copy()
|
39
|
+
kwargs_y["label"] = "Y"
|
40
|
+
self.axes[0].plot(self._ax1_x, self._y_values, **kwargs_x)
|
41
|
+
self.axes[1].plot(self._ax2_x, self._y_values, **kwargs_y)
|
42
|
+
self.__adjust_lim()
|
43
|
+
self.__add_titles()
|
44
|
+
|
45
|
+
def __plot_limit(self):
|
46
|
+
limitation = self.__limit
|
47
|
+
for ax in self.axes:
|
48
|
+
ax.vlines(
|
49
|
+
x=limitation,
|
50
|
+
ymin=0,
|
51
|
+
ymax=self.floor_num,
|
52
|
+
color="r",
|
53
|
+
linewidth=3,
|
54
|
+
ls="--",
|
55
|
+
label=f"限值{limitation*100:.1f}%",
|
56
|
+
)
|
57
|
+
|
58
|
+
def __adjust_lim(self):
|
59
|
+
xmaxs = [self._ax1_x.max(), self._ax2_x.max()]
|
60
|
+
for i in range(2):
|
61
|
+
self.axes[i].set_xlim(left=0, right=xmaxs[i] * 1.2)
|
62
|
+
self.axes[i].set_yticks(self._y_major_ticks)
|
63
|
+
self.axes[i].set_yticks(self._y_minor_ticks, minor=True)
|
64
|
+
x_ticks = GetTicks(xmaxs[i])
|
65
|
+
self.axes[i].set_xticks(x_ticks)
|
66
|
+
self.axes[i].set_xticklabels([f"{i*100:.1f}%" for i in x_ticks])
|
67
|
+
|
68
|
+
def __add_titles(self):
|
69
|
+
self.axes[0].set_ylabel(self.y_label)
|
70
|
+
self.axes[0].set_xlabel(f"X小震下{self.type}")
|
71
|
+
self.axes[1].set_xlabel(f"Y小震下{self.type}")
|
72
|
+
self.axes[0].legend(framealpha=0, fontsize=12, loc=4)
|
73
|
+
self.axes[1].legend(framealpha=0, fontsize=12, loc=4)
|