flkit 0.1.0__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.
Files changed (60) hide show
  1. flkit/__init__.py +2 -0
  2. flkit/alg/__init__.py +68 -0
  3. flkit/alg/build_idx.py +9 -0
  4. flkit/alg/c_track.py +88 -0
  5. flkit/alg/collierydb.py +1217 -0
  6. flkit/alg/del2.py +421 -0
  7. flkit/alg/delaunay_triangulation.py +476 -0
  8. flkit/alg/finite_plane_distance.py +239 -0
  9. flkit/alg/get_up_low.py +32 -0
  10. flkit/alg/in_tin.py +139 -0
  11. flkit/alg/index.py +9 -0
  12. flkit/alg/inter/BasePredictor.py +69 -0
  13. flkit/alg/inter/Kriging.py +226 -0
  14. flkit/alg/inter/KrigingM.py +262 -0
  15. flkit/alg/inter/LossFuncs.py +164 -0
  16. flkit/alg/inter/__init__.py +178 -0
  17. flkit/alg/inter/idw.py +253 -0
  18. flkit/alg/inter/midw.py +145 -0
  19. flkit/alg/inter/nearest_neighbor.py +445 -0
  20. flkit/alg/inter/rbf.py +345 -0
  21. flkit/alg/inter/trilinear.py +572 -0
  22. flkit/alg/point_in_polygon.py +126 -0
  23. flkit/alg/point_in_volume.py +188 -0
  24. flkit/alg/point_in_volume_rs.pyi +7 -0
  25. flkit/alg/point_to_triangle.py +116 -0
  26. flkit/alg/point_to_triangle_mesh.py +309 -0
  27. flkit/alg/point_to_triangle_rs.pyi +9 -0
  28. flkit/alg/rotated_rect.py +184 -0
  29. flkit/db/__init__.py +17 -0
  30. flkit/db/db_model.py +724 -0
  31. flkit/db/duck_model.py +151 -0
  32. flkit/db/fileobj.py +72 -0
  33. flkit/db/path_utl.py +174 -0
  34. flkit/mtcli/__init__.py +18 -0
  35. flkit/mtcli/core.py +489 -0
  36. flkit/mtcli/parse.py +218 -0
  37. flkit/py.typed +0 -0
  38. flkit/tools/__init__.py +28 -0
  39. flkit/tools/del_key.py +13 -0
  40. flkit/tools/dict_to_list.py +10 -0
  41. flkit/tools/get_region.py +42 -0
  42. flkit/tools/max_key.py +7 -0
  43. flkit/tools/mongo.py +119 -0
  44. flkit/tools/mt_dash.py +121 -0
  45. flkit/tools/prase_type.py +27 -0
  46. flkit/typr/Res.py +21 -0
  47. flkit/typr/__init__.py +107 -0
  48. flkit/typr/c_grid.py +425 -0
  49. flkit/typr/console.py +283 -0
  50. flkit/typr/engine.py +59 -0
  51. flkit/typr/getenv.py +89 -0
  52. flkit/typr/result.py +81 -0
  53. flkit/typr/table.py +34 -0
  54. flkit/typr/utils.py +542 -0
  55. flkit/typr/verify.py +64 -0
  56. flkit/typr/webview.py +219 -0
  57. flkit/typr/wstask.py +344 -0
  58. flkit-0.1.0.dist-info/METADATA +186 -0
  59. flkit-0.1.0.dist-info/RECORD +60 -0
  60. flkit-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,1217 @@
1
+ import json
2
+ from pathlib import Path
3
+ import shutil
4
+ import pandas as pd
5
+ import numpy as np
6
+ import pyvista as pv
7
+ import flkit.alg as alg
8
+ import flkit.typr as typr
9
+ from flkit.alg.in_tin import is_point_in_triangles
10
+ from flkit.alg.point_in_polygon import PointInPolygon
11
+ from flkit.alg.rotated_rect import RotatedRect
12
+ from flkit.model.engine import engine
13
+ from flkit.utils import FileDB, getvar
14
+
15
+ import datetime
16
+ import tkinter.messagebox as messagebox
17
+
18
+ from flkit.utils.c_grid import Grid3D
19
+ from flkit.utils.utils import send_f
20
+
21
+ COAL_FACE_FMT = "=id"
22
+ COAL_VOXEL_FMT = "=i"
23
+ STRATUM_FACE_FMT = "=d"
24
+ STRATUM_VOXEL_FMT = "=i"
25
+ BOUDN_FMT = "=i"
26
+ FAULTAGE_FMT = "=iii"
27
+ MASHGAS_FMT = "=d"
28
+
29
+
30
+ # region 计算点是否在断层内
31
+ def comp_in_fault(range, distance, in_fault):
32
+ if not in_fault:
33
+ return 0
34
+ if distance > range * 10:
35
+ return 0
36
+ return 1
37
+
38
+
39
+ # endregion 计算点是否在断层内
40
+
41
+
42
+ # region 颜色映射
43
+ def get_colors(name: str):
44
+ """获取颜色列表"""
45
+ color_map = getvar(
46
+ "colliery",
47
+ "color_map",
48
+ {
49
+ "地表": "red",
50
+ "松散": "blue",
51
+ "砂岩层": "green",
52
+ "松散层2": "green",
53
+ "二1煤": "orange",
54
+ },
55
+ )
56
+ return color_map.get(name, "red")
57
+
58
+
59
+ # endregion 颜色映射
60
+
61
+
62
+ # region 当前时间
63
+ def now() -> datetime.datetime:
64
+ return datetime.datetime.now().isoformat()
65
+
66
+
67
+ # endregion 当前时间
68
+
69
+
70
+ # region 煤矿数据库元数据
71
+ class CollieryMeta:
72
+ def __init__(
73
+ self,
74
+ name: str, # 名称
75
+ create_at: None, # 创建时间
76
+ creator: str, # 创建人
77
+ bound: False, # 边界
78
+ blocks: False, # 块
79
+ voxel: False, # 体素参数
80
+ mashgas: False, # 瓦斯
81
+ tunnels: False, # 巷道
82
+ faultage: False, # 断层
83
+ stratum: False, # 地层
84
+ volume: False, # 地质体
85
+ coal: False, # 底抽巷煤层
86
+ ):
87
+ self.name = name
88
+ self.create_at = create_at if create_at else now()
89
+ self.creator = creator
90
+ self.bound = bound
91
+ self.blocks = blocks
92
+ if not voxel:
93
+ voxel = {"mode": "", "params": {}, "gen": False}
94
+ self.voxel = voxel
95
+ self.mashgas = mashgas
96
+ if not tunnels:
97
+ tunnels = {"names": [], "gen": False}
98
+ self.tunnels = tunnels
99
+ if not faultage:
100
+ faultage = {"names": [], "gen": False}
101
+ self.faultage = faultage
102
+ if not stratum:
103
+ stratum = {"names": [], "gen": False}
104
+ self.stratum = stratum
105
+ if not volume:
106
+ volume = {"names": [], "gen": False}
107
+ self.volume = volume
108
+ if not coal:
109
+ coal = {"names": [], "gen": False}
110
+ self.coal = coal
111
+
112
+ def to_dict(self):
113
+ return {
114
+ "name": self.name,
115
+ "create_at": self.create_at,
116
+ "creator": self.creator,
117
+ "bound": self.bound,
118
+ "blocks": self.blocks,
119
+ "voxel": self.voxel,
120
+ "mashgas": self.mashgas,
121
+ "tunnels": self.tunnels,
122
+ "faultage": self.faultage,
123
+ "stratum": self.stratum,
124
+ "volume": self.volume,
125
+ "coal": self.coal,
126
+ }
127
+
128
+ def save(self, path: Path):
129
+ path.write_text(
130
+ json.dumps(self.to_dict(), indent=2, ensure_ascii=False), encoding="utf-8"
131
+ )
132
+
133
+ @staticmethod
134
+ def init(base: Path) -> "CollieryMeta":
135
+ config_path = base / "meta.json"
136
+ # 如果不存在侧初始化meta文件
137
+ if not config_path.exists():
138
+ config_path.parent.mkdir(parents=True, exist_ok=True)
139
+ meta = CollieryMeta(
140
+ base.stem,
141
+ None,
142
+ None,
143
+ False,
144
+ False,
145
+ False,
146
+ False,
147
+ False,
148
+ False,
149
+ False,
150
+ False,
151
+ False,
152
+ )
153
+ meta.save(config_path)
154
+ return meta
155
+ # 从文件中加载meta
156
+ data = json.loads(config_path.read_text(encoding="utf-8"))
157
+ return CollieryMeta(**data)
158
+
159
+
160
+ # endregion 煤矿数据库元数据
161
+
162
+
163
+ # region 煤矿数据库
164
+ class CollieryDB:
165
+ def __init__(self, name: str, auto_init: bool = True):
166
+ self.name = name
167
+ self.base = Path(typr.getenv("JSON_DB_DIR", ".json_db")) / self.name
168
+ if not self.base.exists():
169
+ if auto_init:
170
+ self.base.mkdir(parents=True, exist_ok=True)
171
+ else:
172
+ raise ValueError(f"煤矿 {self.name} 不存在")
173
+ self.meta = CollieryMeta.init(self.base)
174
+
175
+ # region 边界
176
+ def set_bound(self, path: Path):
177
+ """
178
+ 设置煤矿边界
179
+
180
+ bound文件结构:
181
+ [
182
+ {
183
+ "x": 12,
184
+ "y": 34,
185
+ },
186
+ {
187
+ "x": 45,
188
+ "y": 6,
189
+ },
190
+ {
191
+ "x": 12,
192
+ "y": 34,
193
+ }
194
+ ]
195
+ """
196
+ data = pd.read_excel(path, sheet_name="info")
197
+ if "x" not in data.columns or "y" not in data.columns:
198
+ raise ValueError(f"Excel 文件 {path} 中必须包含 x, y 列")
199
+ data = data[["x", "y"]].values
200
+ if not np.array_equal(data[0], data[-1]):
201
+ raise ValueError(f"Excel 文件 {path} 中首尾坐标不一致,不是封闭区域")
202
+ np.save(self.base / "bound.npy", np.array(data))
203
+ self.meta.bound = False
204
+ self.meta.save(self.base / "meta.json")
205
+
206
+ def comp_bound(self):
207
+ """
208
+ 计算煤矿边界
209
+ """
210
+ grid3d = Grid3D.from_step(**self.meta.voxel["params"])
211
+ data = np.load(self.base / "bound.npy")
212
+ ppn = PointInPolygon(data)
213
+ file_db = FileDB(self.base / "voxel" / "bound" / "bound.cube", BOUDN_FMT)("wb")
214
+ up_in = False
215
+ up_index = [None, None]
216
+ total = grid3d.sum()
217
+ for idx, index, point in grid3d.enumerate():
218
+ if idx % 1000000 == 0:
219
+ print(f"已处理 {idx} 个点,共 {total} 个点 进度 {idx / total:.2%}")
220
+ if index[0] != up_index[0] or index[1] != up_index[1]:
221
+ up_index = index
222
+ up_in = ppn.is_inside(point)
223
+ file_db.append([int(up_in)])
224
+ self.meta.bound = True
225
+ self.meta.save(self.base / "meta.json")
226
+
227
+ # endregion 边界
228
+
229
+ # region 块段
230
+ def set_blocks(self, path: Path):
231
+ raise ValueError("尚未实现设置块功能")
232
+
233
+ # endregion 块
234
+
235
+ # region 体素
236
+ def set_voxel(
237
+ self,
238
+ mode: str,
239
+ z_min: float,
240
+ z_max: float,
241
+ x_min: float = None,
242
+ x_max: float = None,
243
+ y_min: float = None,
244
+ y_max: float = None,
245
+ x_step: float = None,
246
+ y_step: float = None,
247
+ z_step: float = None,
248
+ from_bound: bool = False,
249
+ ):
250
+ voxel_path = self.base / "voxel"
251
+ # 统一检查边界文件是否存在,避免后续重复判断
252
+ bound_exists = (self.base / "bound.npy").exists()
253
+ if from_bound:
254
+ if not bound_exists:
255
+ raise ValueError("未设置煤矿边界,无法依照边界生成范围")
256
+ bound = np.load(self.base / "bound.npy")
257
+ x_min, x_max = np.min(bound[:, 0]), np.max(bound[:, 0])
258
+ y_min, y_max = np.min(bound[:, 1]), np.max(bound[:, 1])
259
+
260
+ if mode == "signed":
261
+ # 提前检查边界是否存在,避免后续重复判断
262
+ if not bound_exists:
263
+ raise ValueError("未设置煤矿边界,无法生成体素")
264
+ if None in [
265
+ x_min,
266
+ x_max,
267
+ y_min,
268
+ y_max,
269
+ z_min,
270
+ z_max,
271
+ x_step,
272
+ y_step,
273
+ z_step,
274
+ ]:
275
+ raise ValueError("signed 模式下必须提供所有参数")
276
+ if x_min >= x_max or y_min >= y_max or z_min >= z_max:
277
+ raise ValueError("体素参数中,最小坐标必须小于最大坐标")
278
+ if x_step <= 0 or y_step <= 0 or z_step <= 0:
279
+ raise ValueError("体素参数中,步长必须大于0")
280
+ # 检查目录是否存在前,先确保不会误删
281
+ if voxel_path.exists():
282
+ raise ValueError(
283
+ f"voxel 目录 {voxel_path} 已存在,为防止数据丢失,已拒绝操作"
284
+ )
285
+ voxel_path.mkdir(parents=True, exist_ok=True)
286
+ self.meta.voxel["mode"] = mode
287
+ self.meta.voxel["params"] = {
288
+ "x_min": x_min,
289
+ "x_max": x_max,
290
+ "y_min": y_min,
291
+ "y_max": y_max,
292
+ "z_min": z_min,
293
+ "z_max": z_max,
294
+ "x_step": x_step,
295
+ "y_step": y_step,
296
+ "z_step": z_step,
297
+ }
298
+ self.meta.voxel["gen"] = False
299
+ self.meta.save(self.base / "meta.json")
300
+ elif mode == "auto":
301
+ if None in [x_min, x_max, y_min, y_max, z_min, z_max]:
302
+ raise ValueError("auto 模式下必须提供所有参数")
303
+ if voxel_path.exists():
304
+ raise ValueError(
305
+ f"voxel 目录 {voxel_path} 已存在,为防止数据丢失,已拒绝操作"
306
+ )
307
+ voxel_path.mkdir(parents=True, exist_ok=True)
308
+ # 此处可预留自动体素生成逻辑
309
+ raise ValueError("auto 模式下未实现自动体素生成功能")
310
+ else:
311
+ raise ValueError(f"不支持的体素生成模式: {mode}")
312
+
313
+ def show_one_voxel(
314
+ self,
315
+ typ: str = "bound",
316
+ opacity: float = 1,
317
+ edge_color: str = "black",
318
+ cmap: str = "viridis",
319
+ line_width: float = 0.5,
320
+ show_edges: bool = False,
321
+ show_scalar_bar: bool = False,
322
+ scalar_bar_args: dict = None,
323
+ bound: bool = True,
324
+ ):
325
+ dtype = np.int32
326
+ if typ == "stratum":
327
+
328
+ def l_map(values: list):
329
+ if values[0] == 0:
330
+ return False, 0
331
+ # if values[0] != 3:
332
+ # return False, 0
333
+ return True, values[0] # 使用整数标签而不是字符串
334
+
335
+ file_path = self.base / "voxel" / "stratum" / "stratum.cube"
336
+ fmt = STRATUM_VOXEL_FMT
337
+ elif typ == "mashgas":
338
+
339
+ def l_map(values: list):
340
+ # 检查是否为NaN值,如果是则跳过
341
+ if np.isnan(values[0]):
342
+ return False, 0
343
+ return True, values[0]
344
+
345
+ file_path = self.base / "voxel" / "mashgas" / "mashgas.cube"
346
+ fmt = MASHGAS_FMT
347
+ cmap = "cool"
348
+ elif typ == "coal":
349
+
350
+ def l_map(values: list):
351
+ if values[0] == 0:
352
+ return False, 0
353
+ return True, values[0]
354
+
355
+ file_path = self.base / "voxel" / "coal" / "coal.cube"
356
+ fmt = COAL_VOXEL_FMT
357
+ elif typ == "faultage":
358
+
359
+ def l_map(values: list):
360
+ if values[0] == 0:
361
+ return False, 0
362
+ return True, values[-1]
363
+
364
+ file_path = self.base / "voxel" / "faultage" / "faultage.cube"
365
+ fmt = FAULTAGE_FMT
366
+ else:
367
+ raise ValueError(f"不支持的体素类型: {typ}")
368
+
369
+ grid = Grid3D.from_step(**self.meta.voxel["params"])
370
+ bound_db = FileDB(self.base / "voxel" / "bound" / "bound.cube", "=i")("rb")
371
+ file_db = FileDB(file_path, fmt)("rb")
372
+ nx = int((grid.info.x_max - grid.info.x_min) // grid.info.x_step)
373
+ ny = int((grid.info.y_max - grid.info.y_min) // grid.info.y_step)
374
+ nz = int((grid.info.z_max - grid.info.z_min) // grid.info.z_step)
375
+ cell_nx, cell_ny, cell_nz = nx - 1, ny - 1, nz - 1
376
+ cell_label_ids = np.zeros((cell_nx, cell_ny, cell_nz), dtype=dtype)
377
+ vtk = engine()
378
+ sum_voxel = 0
379
+ for idx, index, point in grid.enumerate():
380
+ if idx % 1000000 == 0:
381
+ send_f(
382
+ f"结构进度: {idx}/{grid.sum()} ({idx / (grid.sum()) * 100:.2f}%)"
383
+ )
384
+ if bound:
385
+ in_bound = bound_db.search(idx)[0] == 1
386
+ if not in_bound:
387
+ continue
388
+ values = file_db.search(idx)
389
+ visible, label = l_map(values)
390
+ if not visible:
391
+ continue
392
+ x_i, y_i, z_i = index
393
+ if 0 <= x_i < cell_nx and 0 <= y_i < cell_ny and 0 <= z_i < cell_nz:
394
+ cell_label_ids[x_i, y_i, z_i] = label
395
+ sum_voxel += 1
396
+
397
+ if sum_voxel == 0:
398
+ send_f("没有可见体素")
399
+ return
400
+ else:
401
+ send_f(f"共发现 {sum_voxel} 个可见体素")
402
+
403
+ volume = pv.ImageData(
404
+ dimensions=(nx, ny, nz),
405
+ spacing=(grid.info.x_step, grid.info.y_step, grid.info.z_step),
406
+ origin=(grid.info.x_min, grid.info.y_min, grid.info.z_min),
407
+ )
408
+ volume.cell_data["label_id"] = cell_label_ids.flatten(order="F")
409
+ filtered_volume = volume.threshold(0.5, scalars="label_id")
410
+ vtk.add_mesh(
411
+ filtered_volume,
412
+ cmap=cmap,
413
+ show_edges=show_edges,
414
+ edge_color=edge_color,
415
+ line_width=line_width,
416
+ opacity=opacity,
417
+ interpolate_before_map=False,
418
+ show_scalar_bar=show_scalar_bar,
419
+ scalar_bar_args=scalar_bar_args
420
+ or {
421
+ "title": "地质结构",
422
+ "position_x": 0.02,
423
+ "position_y": 0.1,
424
+ },
425
+ )
426
+ send_f("体素可视化完成")
427
+
428
+ def show_voxel(
429
+ self,
430
+ opacity: float = 1,
431
+ edge_color: str = "black",
432
+ cmap: str = "viridis",
433
+ att_cmap: str = "cool",
434
+ line_width: float = 0.5,
435
+ show_edges: bool = False,
436
+ show_scalar_bar: bool = False,
437
+ scalar_bar_args: dict = None,
438
+ bound: bool = True,
439
+ ):
440
+ bound_db = FileDB(self.base / "voxel" / "bound" / "bound.cube", BOUDN_FMT)("rb")
441
+ stratum_db = FileDB(
442
+ self.base / "voxel" / "stratum" / "stratum.cube", STRATUM_VOXEL_FMT
443
+ )("rb")
444
+ coal_db = FileDB(self.base / "voxel" / "coal" / "coal.cube", COAL_VOXEL_FMT)(
445
+ "rb"
446
+ )
447
+ mashgas_db = FileDB(
448
+ self.base / "voxel" / "mashgas" / "mashgas.cube", MASHGAS_FMT
449
+ )("rb")
450
+ faultage_db = FileDB(
451
+ self.base / "voxel" / "faultage" / "faultage.cube", FAULTAGE_FMT
452
+ )("rb")
453
+ grid = Grid3D.from_step(**self.meta.voxel["params"])
454
+ nx = int((grid.info.x_max - grid.info.x_min) // grid.info.x_step)
455
+ ny = int((grid.info.y_max - grid.info.y_min) // grid.info.y_step)
456
+ nz = int((grid.info.z_max - grid.info.z_min) // grid.info.z_step)
457
+ cell_nx, cell_ny, cell_nz = nx - 1, ny - 1, nz - 1
458
+ cell_label_ids = np.zeros((cell_nx, cell_ny, cell_nz), dtype=np.float64)
459
+ cell_label_ids2 = np.zeros((cell_nx, cell_ny, cell_nz), dtype=np.float64)
460
+ cell_idx = np.zeros((cell_nx, cell_ny, cell_nz), dtype=np.int32)
461
+
462
+ all_labels = []
463
+ voxel_labels = (
464
+ self.meta.stratum["names"]
465
+ + self.meta.coal["names"]
466
+ + self.meta.faultage["names"]
467
+ )
468
+ print(voxel_labels)
469
+ labels = {
470
+ "x": [],
471
+ "y": [],
472
+ "z": [],
473
+ "stratum": [],
474
+ "coal": [],
475
+ "mashgas": [],
476
+ "faultage": [],
477
+ "voxel": [],
478
+ }
479
+ vtk = engine()
480
+ label_idx = 0
481
+ for idx, index, point in grid.enumerate():
482
+ if bound:
483
+ in_bound = bound_db.search(idx)[0] == 1
484
+ if not in_bound:
485
+ continue
486
+ stratum_val = stratum_db.search(idx)
487
+ if stratum_val[0] == 0:
488
+ continue
489
+ coal_val = coal_db.search(idx)
490
+ mashgas_val = mashgas_db.search(idx)
491
+ faultage_val = faultage_db.search(idx)
492
+ labels["x"].append(point[0])
493
+ labels["y"].append(point[1])
494
+ labels["z"].append(point[2])
495
+ stratum_idx = (
496
+ voxel_labels.index(self.meta.stratum["names"][stratum_val[0] - 1]) + 1
497
+ )
498
+ faultage_idx = (
499
+ voxel_labels.index(self.meta.faultage["names"][faultage_val[-1] - 1])
500
+ + 1
501
+ )
502
+ voxel_l = stratum_idx
503
+ all_labels.append(
504
+ {
505
+ "地层名称": voxel_labels[stratum_idx],
506
+ "工作面采区": (
507
+ self.meta.coal["names"][coal_val[0] - 1]
508
+ if coal_val[0] != 0
509
+ else "无"
510
+ ).replace("gzm", "4103工作面"),
511
+ "断层编号": (
512
+ self.meta.faultage["names"][faultage_val[-1] - 1]
513
+ if faultage_val[-1] != 0
514
+ else "无"
515
+ ).replace("id_7", "23-1"),
516
+ "瓦斯含量": mashgas_val[0],
517
+ }
518
+ )
519
+ label_idx += 1
520
+ voxel_l2 = 0
521
+ if coal_val[0] != 0:
522
+ voxel_l = (
523
+ voxel_labels.index(self.meta.coal["names"][coal_val[0] - 1]) + 1
524
+ )
525
+ voxel_l2 = 0
526
+ if "砂岩层" in voxel_labels[stratum_idx - 1]:
527
+ voxel_l = 0
528
+ voxel_l2 = mashgas_val[0]
529
+ if faultage_val[0] == 1:
530
+ voxel_l = faultage_idx
531
+ voxel_l2 = 0
532
+ x_i, y_i, z_i = index
533
+ if 0 <= x_i < cell_nx and 0 <= y_i < cell_ny and 0 <= z_i < cell_nz:
534
+ cell_label_ids[x_i, y_i, z_i] = voxel_l
535
+ cell_label_ids2[x_i, y_i, z_i] = voxel_l2
536
+ cell_idx[x_i, y_i, z_i] = label_idx
537
+ if idx % 1000000 == 0:
538
+ send_f(
539
+ f"结构进度: {idx}/{grid.sum()} ({idx / (grid.sum()) * 100:.2f}%)"
540
+ )
541
+ volume = pv.ImageData(
542
+ dimensions=(nx, ny, nz),
543
+ spacing=(grid.info.x_step, grid.info.y_step, grid.info.z_step),
544
+ origin=(grid.info.x_min, grid.info.y_min, grid.info.z_min),
545
+ )
546
+ volume.cell_data["label_id"] = cell_label_ids.flatten(order="F")
547
+ volume.cell_data["label_idx"] = cell_idx.flatten(order="F")
548
+ filtered_volume = volume.threshold(0.5, scalars="label_id")
549
+ volume2 = pv.ImageData(
550
+ dimensions=(nx, ny, nz),
551
+ spacing=(grid.info.x_step, grid.info.y_step, grid.info.z_step),
552
+ origin=(grid.info.x_min, grid.info.y_min, grid.info.z_min),
553
+ )
554
+ volume2.cell_data["label_id"] = cell_label_ids2.flatten(order="F")
555
+ volume2.cell_data["label_idx"] = cell_idx.flatten(order="F")
556
+ filtered_volume2 = volume2.threshold(0.5, scalars="label_id")
557
+ print(f"共有体素:{filtered_volume2.n_cells}")
558
+
559
+ def pick_callback(point):
560
+ """鼠标点击回调:显示被点击位置最近体素的信息"""
561
+ print("=" * 50)
562
+ print("🔍 体素点击事件触发")
563
+ print(f"📍 点击坐标: ({point[0]:.2f}, {point[1]:.2f}, {point[2]:.2f})")
564
+
565
+ try:
566
+ # 在第一个体素中查找最近的单元
567
+ cell_id = filtered_volume.find_closest_cell(point)
568
+ if cell_id is None or cell_id < 0 or cell_id >= filtered_volume.n_cells:
569
+ print("❌ 未找到有效体素")
570
+ print("=" * 50)
571
+ return
572
+
573
+ label_id = int(filtered_volume.cell_data["label_id"][cell_id])
574
+ label_idx = int(filtered_volume.cell_data["label_idx"][cell_id])
575
+ label_info = all_labels[label_idx]
576
+ print(f"✅ 最近体素ID: {cell_id},label_id = {label_id}")
577
+ print(f"📋 标签信息: {label_info}")
578
+
579
+ # 尝试使用 messagebox,如果失败则只用 print
580
+ try:
581
+ import tkinter as tk
582
+
583
+ root = tk.Tk()
584
+ root.withdraw() # 隐藏主窗口
585
+ # 将标签信息格式化为更易读的字符串
586
+ formatted_labels = []
587
+ for key, val in label_info.items():
588
+ if isinstance(val, (int, float)) and not isinstance(val, bool):
589
+ formatted_labels.append(f"{key}: {val:.2f}")
590
+ else:
591
+ formatted_labels.append(f"{key}: {val}")
592
+ label_str = "\n".join(formatted_labels)
593
+
594
+ messagebox.showinfo(
595
+ "体素信息",
596
+ f"体素ID: {cell_id}\nlabel_id: {voxel_labels[label_id - 1]}\n标签信息:\n{label_str}\n坐标: ({point[0]:.2f}, {point[1]:.2f}, {point[2]:.2f})",
597
+ )
598
+ root.destroy()
599
+ except Exception as e:
600
+ print(f"⚠️ 消息框显示失败: {e}")
601
+ print(
602
+ f"📋 体素信息: ID={cell_id}, label_id={label_id}, 标签={label_info}"
603
+ )
604
+ except Exception as e:
605
+ print(f"❌ 获取体素信息失败: {e}")
606
+ print("=" * 50)
607
+
608
+ vtk.add_mesh(
609
+ filtered_volume,
610
+ cmap=cmap,
611
+ show_edges=show_edges,
612
+ edge_color=edge_color,
613
+ line_width=line_width,
614
+ opacity=opacity,
615
+ interpolate_before_map=False,
616
+ show_scalar_bar=show_scalar_bar,
617
+ scalar_bar_args=scalar_bar_args
618
+ or {
619
+ "title": "地质结构",
620
+ "position_x": 0.06,
621
+ "position_y": 0.2,
622
+ },
623
+ pickable=True,
624
+ )
625
+ vtk.add_mesh(
626
+ filtered_volume2,
627
+ cmap=att_cmap,
628
+ show_edges=show_edges,
629
+ edge_color=edge_color,
630
+ line_width=line_width,
631
+ opacity=opacity,
632
+ interpolate_before_map=False,
633
+ show_scalar_bar=show_scalar_bar,
634
+ scalar_bar_args=scalar_bar_args
635
+ or {
636
+ "title": "瓦斯结构",
637
+ "position_x": 0.06,
638
+ "position_y": 0.1,
639
+ },
640
+ pickable=True,
641
+ )
642
+ # 启用点击拾取功能
643
+ vtk.enable_point_picking(callback=pick_callback, show_message=True)
644
+ send_f("体素可视化完成")
645
+
646
+ # endregion 体素
647
+
648
+ # region 瓦斯
649
+ def set_mashgas(self, path: Path):
650
+ data = pd.read_excel(path, sheet_name="info")
651
+ if (
652
+ "x" not in data.columns
653
+ or "y" not in data.columns
654
+ or "z" not in data.columns
655
+ ):
656
+ raise ValueError(f"Excel 文件 {path} 中必须包含 x, y, z 列")
657
+ # 以numpy数组的形式保存,保存为npy文件
658
+ data = data[["x", "y", "z", "value"]].values
659
+ np.save(self.base / "mashgas.npy", data)
660
+ self.meta.mashgas = False
661
+ self.meta.save(self.base / "meta.json")
662
+
663
+ def comp_mashgas(self, func: str = "idw"):
664
+ max_value = 0
665
+ grid3d = Grid3D.from_step(**self.meta.voxel["params"])
666
+ data = np.load(self.base / "mashgas.npy") # (N,4) [x,y,z,value]
667
+ interp = alg.inter.make_interpolator(func, dim=3).fit(data)
668
+ db = FileDB(self.base / "voxel" / "mashgas" / "mashgas.cube", MASHGAS_FMT)(
669
+ "w+b"
670
+ )
671
+ total = grid3d.sum()
672
+ for idx, index, point in grid3d.enumerate():
673
+ if idx % 1000000 == 0:
674
+ print(f"处理体素 {idx}/{total} 进度 {idx / total:.2%}")
675
+ val = interp.predict(np.array(point))
676
+ max_value = max(max_value, abs(val))
677
+ db.append([val])
678
+ self.meta.mashgas = True
679
+ self.meta.save(self.base / "meta.json")
680
+
681
+ # endregion mashgas
682
+
683
+ # region 巷道
684
+ def set_tunnels(self, path: Path):
685
+ raise ValueError("尚未实现设置巷道功能")
686
+
687
+ # endregion 巷道
688
+
689
+ # region 断层
690
+ def set_faultage(self, path: Path):
691
+ path_obj = self.base / "faultage.npy"
692
+
693
+ data = pd.read_excel(path, sheet_name="info")
694
+ if (
695
+ "x1" not in data.columns
696
+ or "y1" not in data.columns
697
+ or "z1" not in data.columns
698
+ or "x2" not in data.columns
699
+ or "y2" not in data.columns
700
+ or "z2" not in data.columns
701
+ or "x3" not in data.columns
702
+ or "y3" not in data.columns
703
+ or "z3" not in data.columns
704
+ or "gc" not in data.columns
705
+ or "lx" not in data.columns
706
+ or "range" not in data.columns
707
+ or "dcbh" not in data.columns
708
+ ):
709
+ raise ValueError(
710
+ f"Excel 文件 {path} 中必须包含 x1, y1, z1, x2, y2, z2, x3, y3, z3, gc, lx, range, dcbh 列"
711
+ )
712
+ data = data[
713
+ [
714
+ "x1",
715
+ "y1",
716
+ "z1",
717
+ "x2",
718
+ "y2",
719
+ "z2",
720
+ "x3",
721
+ "y3",
722
+ "z3",
723
+ "gc",
724
+ "lx",
725
+ "range",
726
+ "dcbh",
727
+ ]
728
+ ].values
729
+ if path_obj.exists():
730
+ old_data = np.load(self.base / "faultage.npy", allow_pickle=True)
731
+ data = np.vstack((old_data, data))
732
+ np.save(path_obj, data)
733
+ zkbhs = np.unique(data[:, -1])
734
+ self.meta.faultage["names"] = list(zkbhs)
735
+ self.meta.faultage["gen"] = False
736
+ self.meta.save(self.base / "meta.json")
737
+
738
+ def comp_faultage(self):
739
+ grid3d = Grid3D.from_step(**self.meta.voxel["params"])
740
+ voxel_faultage_dir = self.base / "voxel"
741
+ voxel_faultage_dir.mkdir(parents=True, exist_ok=True)
742
+ file_db = FileDB(
743
+ self.base / "voxel" / "faultage" / "faultage.cube", FAULTAGE_FMT
744
+ )("w+b")
745
+ total = grid3d.sum()
746
+ if not (self.base / "faultage.npy").exists():
747
+ raise ValueError(f"断层数据文件不存在: {self.base / 'faultage.npy'}")
748
+ faultage_data = np.load(self.base / "faultage.npy", allow_pickle=True)
749
+
750
+ if faultage_data.size == 0:
751
+ raise ValueError("没有找到任何断层数据文件")
752
+ tins = faultage_data[:, :9].reshape(-1, 3, 3)
753
+
754
+ for idx, _, point in grid3d.enumerate():
755
+ if idx % 1000000 == 0:
756
+ print(f"处理体素 {idx}/{total} 进度 {idx / total:.2%}")
757
+ is_inside, triangle_idx = is_point_in_triangles(
758
+ point, tins, faultage_data[:, -2]
759
+ )
760
+ file_db.append(
761
+ [
762
+ is_inside,
763
+ 1,
764
+ self.meta.faultage["names"].index(faultage_data[triangle_idx, -1])
765
+ + 1
766
+ if is_inside
767
+ else 0,
768
+ ]
769
+ )
770
+ self.meta.faultage["gen"] = True
771
+ self.meta.save(self.base / "meta.json")
772
+
773
+ # endregion 断层
774
+
775
+ # region 地层
776
+ def set_stratum(self, names: list, path: Path):
777
+ if path.suffix != "":
778
+ raise ValueError(f"路径 {path} 必须是目录")
779
+ stratum_path = self.base / "voxel" / "stratum"
780
+ if stratum_path.exists():
781
+ shutil.rmtree(stratum_path)
782
+ stratum_path.mkdir(parents=True, exist_ok=True)
783
+ for name in names:
784
+ name_path = path / f"{name}.xlsx"
785
+ if not name_path.exists():
786
+ raise ValueError(f"找不到文件 {name_path}")
787
+ # 读取Excel文件,检查是否包含 x, y, z 列
788
+ data = pd.read_excel(name_path, sheet_name="info")
789
+ if (
790
+ "x" not in data.columns
791
+ or "y" not in data.columns
792
+ or "z" not in data.columns
793
+ ):
794
+ raise ValueError(f"Excel 文件 {name_path} 中必须包含 x, y, z 列")
795
+ # 以numpy数组的形式保存,保存为npy文件
796
+ data = data[["x", "y", "z"]].values
797
+ np.save(stratum_path / f"{name}.npy", data)
798
+ self.meta.stratum["names"] = names
799
+ self.meta.save(self.base / "meta.json")
800
+
801
+ def comp_stratum(self, func: str = "idw"):
802
+ self.face_stratum(func)
803
+ print("完成面层插值")
804
+ self.class_stratum()
805
+ print("完成地层分类")
806
+
807
+ def face_stratum(self, func: str = "idw"):
808
+ grid3d = Grid3D.from_step(**self.meta.voxel["params"])
809
+ grid = grid3d.to_2d()
810
+ for name in self.meta.stratum["names"]:
811
+ data = np.load(self.base / "stratum" / f"{name}.npy") # (N,3) [x,y,z]
812
+ interp = alg.inter.make_interpolator(func, dim=2).fit(data)
813
+ db = FileDB(
814
+ self.base / "voxel" / "stratum" / f"{name}.bin", STRATUM_FACE_FMT
815
+ )("w+b")
816
+ total = grid.sum()
817
+ for idx, _, p in grid.enumerate():
818
+ if idx % 100000 == 0:
819
+ print(f"{name} 处理体素 {idx}/{total} 进度 {idx / total:.2%}")
820
+ z = interp.predict([np.array(p[:2])])[0]
821
+ db.append([z])
822
+
823
+ def class_stratum(self):
824
+ grid3d = Grid3D.from_step(**self.meta.voxel["params"])
825
+ grid = grid3d.to_2d()
826
+ stratums = self.meta.stratum["names"]
827
+ dbs = [
828
+ FileDB(self.base / "voxel" / "stratum" / f"{name}.bin", STRATUM_FACE_FMT)
829
+ for name in stratums
830
+ ]
831
+ for db in dbs:
832
+ db("r+b")
833
+ total = grid3d.sum()
834
+ file_db = FileDB(
835
+ self.base / "voxel" / "stratum" / "stratum.cube", STRATUM_VOXEL_FMT
836
+ )
837
+ file_db("w+b")
838
+ up_lays = []
839
+ up_index = [None, None]
840
+ for idx, index, point in grid3d.enumerate():
841
+ if idx % 1000000 == 0:
842
+ print(f"处理体素 {idx}/{total} 进度 {idx / total:.2%}")
843
+ z = point[2]
844
+
845
+ # 检查是否需要更新缓存(当x,y坐标发生变化时)
846
+ if up_index[0] != index[0] or up_index[1] != index[1]:
847
+ # 新的(x,y)位置,更新缓存
848
+ up_index[0] = index[0]
849
+ up_index[1] = index[1]
850
+ idx2d = grid.get_idx(index)
851
+
852
+ # 一次性查询所有地层数据并缓存到up_lays
853
+ up_lays = []
854
+ for lay_idx in range(len(stratums)):
855
+ up_lays.append(dbs[lay_idx].search(idx2d)[0])
856
+
857
+ # 使用缓存的数据进行地层分类
858
+ label = 0
859
+ for lay_idx in range(1, len(stratums)):
860
+ up_val = up_lays[lay_idx - 1]
861
+ down_val = up_lays[lay_idx]
862
+ if down_val < z <= up_val:
863
+ label = lay_idx
864
+ break
865
+ file_db.append([label])
866
+ self.meta.stratum["gen"] = True
867
+ self.meta.save(self.base / "meta.json")
868
+
869
+ def show_stratum(self, names: str = "*"):
870
+ if names == "*":
871
+ names = self.meta.stratum["names"]
872
+ grid3d = Grid3D.from_step(**self.meta.voxel["params"])
873
+ grid = grid3d.to_2d()
874
+ vtk = engine()
875
+ for name in names:
876
+ filedb = FileDB(
877
+ self.base / "voxel" / "stratum" / f"{name}.bin", STRATUM_FACE_FMT
878
+ )
879
+ filedb("r+b")
880
+ total = grid.sum()
881
+ layers_grid = []
882
+ for idx, index, point in grid.enumerate():
883
+ if idx % 1000000 == 0:
884
+ print(f"{name} 处理体素 {idx}/{total} 进度 {idx / total:.2%}")
885
+ pre_v = filedb.search(idx)[0]
886
+ layers_grid.append((point[0], point[1], pre_v))
887
+ layers_grid_array = np.array(layers_grid)
888
+ points = np.column_stack(
889
+ (
890
+ layers_grid_array[:, 0],
891
+ layers_grid_array[:, 1],
892
+ layers_grid_array[:, 2],
893
+ )
894
+ )
895
+ surface = pv.PolyData(points).delaunay_2d()
896
+ vtk.add_mesh(surface, scalars=points[:, 2], color=get_colors(name))
897
+
898
+ input("按任意键继续")
899
+ else:
900
+ for name in names.split(","):
901
+ data = np.load(self.base / "stratum" / f"{name}.npy")
902
+ send_f(f"已展示地层数据 - {name} ({data.shape[0]} 个点)")
903
+
904
+ # endregion 地层
905
+
906
+ # region 地质体
907
+ def set_volume(self, path: Path):
908
+ raise ValueError("尚未实现设置地质体功能")
909
+
910
+ # endregion 地质体
911
+
912
+ # region 煤体
913
+ def set_coal(self, path: Path, name: str):
914
+ # 确保coal目录存在
915
+ coal_dir = self.base / "coal"
916
+ coal_dir.mkdir(parents=True, exist_ok=True)
917
+
918
+ path_obj = coal_dir / f"{name}.npy"
919
+ if path_obj.exists():
920
+ raise ValueError(f"煤体名称 {name} 已存在")
921
+ data = pd.read_excel(path, sheet_name="info")
922
+ if (
923
+ "x1" not in data.columns
924
+ or "y1" not in data.columns
925
+ or "z1" not in data.columns
926
+ or "x2" not in data.columns
927
+ or "y2" not in data.columns
928
+ or "z2" not in data.columns
929
+ ):
930
+ raise ValueError(f"Excel 文件 {path} 中必须包含 x1, y1, z1, x2, y2, z2 列")
931
+ # 以numpy数组的形式保存,保存为npy文件
932
+ data = data[["x1", "y1", "z1", "x2", "y2", "z2"]].values
933
+ np.save(path_obj, data)
934
+ self.meta.coal["names"].append(name)
935
+
936
+ def comp_coal(self, func: str = "idw", auto_bound: bool = True):
937
+ self.face_coal(func, auto_bound)
938
+ send_f("煤体分类完成")
939
+ self.class_coal(auto_bound)
940
+ send_f("煤体分类完成")
941
+
942
+ def face_coal(self, func: str = "idw", auto_bound: bool = True):
943
+ # 确保voxel/coal目录存在
944
+ voxel_coal_dir = self.base / "voxel" / "coal"
945
+ voxel_coal_dir.mkdir(parents=True, exist_ok=True)
946
+
947
+ coals = self.meta.coal["names"]
948
+ if not coals:
949
+ raise ValueError("未设置煤体")
950
+ for coal in coals:
951
+ grid3d = Grid3D.from_step(**self.meta.voxel["params"])
952
+ grid = grid3d.to_2d()
953
+ file_db_up = FileDB(
954
+ self.base / "voxel" / "coal" / f"{coal}_up.bin", COAL_FACE_FMT
955
+ )("w+b")
956
+ file_db_down = FileDB(
957
+ self.base / "voxel" / "coal" / f"{coal}_down.bin", COAL_FACE_FMT
958
+ )("w+b")
959
+ total = grid.sum()
960
+ data = np.load(
961
+ self.base / "coal" / f"{coal}.npy"
962
+ ) # shape: (N, 6) [x1,y1,z1,x2,y2,z2]
963
+
964
+ # 分离顶板和底板数据
965
+ top_data = data[:, [0, 1, 2]] # [x1, y1, z1] - 顶板数据
966
+ bottom_data = data[:, [3, 4, 5]] # [x2, y2, z2] - 底板数据
967
+
968
+ if auto_bound:
969
+ # 使用xy坐标范围计算边界框(使用顶板数据的xy坐标)
970
+ rotate_rect = RotatedRect(bottom_data[:, :2])
971
+
972
+ # 分别训练顶板和底板的插值器
973
+ # IDW2D和Kriging2D的fit方法期望(N,3)数组[x,y,z]
974
+ interp_up = alg.inter.make_interpolator(func, dim=2).fit(top_data)
975
+ interp_down = alg.inter.make_interpolator(func, dim=2).fit(bottom_data)
976
+
977
+ if auto_bound:
978
+ box, _ = rotate_rect.min_bounding_rect()
979
+ vi_voxel = 0
980
+ for idx, index, point in grid.enumerate():
981
+ if idx % 1000000 == 0:
982
+ print(f"处理体素 {idx}/{total} 进度 {idx / total:.2%}")
983
+ is_coal = False
984
+ up_val = 0
985
+ down_val = 0
986
+ if auto_bound:
987
+ if rotate_rect.in_box(np.array(point[:2]).astype(np.float32), box):
988
+ is_coal = True
989
+ vi_voxel += 1
990
+ up_val = interp_up.predict(np.array(point[:2]))
991
+ down_val = interp_down.predict(np.array(point[:2]))
992
+ else:
993
+ is_coal = True
994
+ vi_voxel += 1
995
+ up_val = interp_up.predict(np.array(point[:2]))
996
+ down_val = interp_down.predict(np.array(point[:2]))
997
+ file_db_up.append([int(is_coal), up_val])
998
+ file_db_down.append([int(is_coal), down_val])
999
+
1000
+ def class_coal(self, auto_bound: bool = True):
1001
+ coals = self.meta.coal["names"]
1002
+ if not coals:
1003
+ raise ValueError("未设置煤体")
1004
+ grid3d = Grid3D.from_step(**self.meta.voxel["params"])
1005
+ grid = grid3d.to_2d()
1006
+ coal_obj = []
1007
+ for coal in coals:
1008
+ # 只读取之前face_coal生成的数据,不写入
1009
+ up_db = FileDB(
1010
+ self.base / "voxel" / "coal" / f"{coal}_up.bin", COAL_FACE_FMT
1011
+ )("rb")
1012
+ down_db = FileDB(
1013
+ self.base / "voxel" / "coal" / f"{coal}_down.bin", COAL_FACE_FMT
1014
+ )("rb")
1015
+ data = np.load(
1016
+ self.base / "coal" / f"{coal}.npy"
1017
+ ) # shape: (N, 6) [x1,y1,z1,x2,y2,z2]
1018
+
1019
+ # 分离顶板和底板数据
1020
+ top_data = data[:, [0, 1, 2]] # [x1, y1, z1] - 顶板数据
1021
+ bottom_data = data[:, [3, 4, 5]] # [x2, y2, z2] - 底板数据
1022
+ if auto_bound:
1023
+ # 使用xy坐标范围计算边界框(使用顶板数据的xy坐标)
1024
+ rotate_rect = RotatedRect(bottom_data[:, :2])
1025
+ box, _ = rotate_rect.min_bounding_rect()
1026
+ print(box)
1027
+ else:
1028
+ rotate_rect = None
1029
+ box = None
1030
+ coal_obj.append(
1031
+ {
1032
+ "name": coal,
1033
+ "up_db": up_db,
1034
+ "down_db": down_db,
1035
+ "box": box,
1036
+ "rotate_rect": rotate_rect,
1037
+ }
1038
+ )
1039
+ db_3d = FileDB(self.base / "voxel" / "coal" / "coal.cube", COAL_VOXEL_FMT)(
1040
+ "w+b"
1041
+ )
1042
+ total = grid3d.sum()
1043
+ up_index = [None, None]
1044
+ up_coal_valuse = []
1045
+ up_sum = 0
1046
+ for idx, index, point in grid3d.enumerate():
1047
+ if idx % 1000000 == 0:
1048
+ print(f"处理体素 {idx}/{total} 进度 {idx / total:.2%}")
1049
+ if index[0] != up_index[0] or index[1] != up_index[1]:
1050
+ up_index = index
1051
+ up_coal_valuse = []
1052
+ for coal in coal_obj:
1053
+ idx2d = grid.get_idx(index)
1054
+ up_in, up_val_up = coal["up_db"].search(idx2d)
1055
+ _, up_val_down = coal["down_db"].search(idx2d)
1056
+ up_coal_valuse.append(
1057
+ {
1058
+ "name": coal["name"],
1059
+ "up_val_up": up_val_up,
1060
+ "up_val_down": up_val_down,
1061
+ }
1062
+ )
1063
+ coal_idx = 0
1064
+ for idx, coal in enumerate(up_coal_valuse):
1065
+ if auto_bound:
1066
+ if coal_obj[idx]["rotate_rect"].in_box(
1067
+ np.array(point[:2]).astype(np.float32), coal_obj[idx]["box"]
1068
+ ):
1069
+ if (
1070
+ up_in == 1
1071
+ and coal["up_val_down"] <= point[2] <= coal["up_val_up"]
1072
+ ):
1073
+ coal_idx = self.meta.coal["names"].index(coal["name"]) + 1
1074
+ up_sum += 1
1075
+ else:
1076
+ if (
1077
+ up_in == 1
1078
+ and coal["up_val_down"] <= point[2] <= coal["up_val_up"]
1079
+ ):
1080
+ coal_idx = self.meta.coal["names"].index(coal["name"]) + 1
1081
+ up_sum += 1
1082
+ db_3d.append([coal_idx])
1083
+ if up_sum == 0:
1084
+ raise ValueError("未找到任何煤体")
1085
+ self.meta.coal["gen"] = True
1086
+ self.meta.save(self.base / "meta.json")
1087
+
1088
+ def show_coal(self, names: str = "*"):
1089
+ if names == "*":
1090
+ names = self.meta.coal["names"]
1091
+ grid3d = Grid3D.from_step(**self.meta.voxel["params"])
1092
+ grid = grid3d.to_2d()
1093
+ vtk = engine()
1094
+ for name in names:
1095
+ filedb_up = FileDB(
1096
+ self.base / "voxel" / "coal" / f"{name}_up.bin", COAL_FACE_FMT
1097
+ )
1098
+ filedb_down = FileDB(
1099
+ self.base / "voxel" / "coal" / f"{name}_down.bin", COAL_FACE_FMT
1100
+ )
1101
+ filedb_up("r+b")
1102
+ filedb_down("r+b")
1103
+ data = np.load(self.base / "coal" / f"{name}.npy")
1104
+ bottom_data = data[:, [3, 4, 5]]
1105
+ rotate_rect = RotatedRect(bottom_data[:, :2])
1106
+ box, _ = rotate_rect.min_bounding_rect()
1107
+ total = grid.sum()
1108
+ layers_up = []
1109
+ layers_down = []
1110
+ for idx, index, point in grid.enumerate():
1111
+ if idx % 1000000 == 0:
1112
+ print(f"{name} 处理体素 {idx}/{total} 进度 {idx / total:.2%}")
1113
+ if rotate_rect.in_box(np.array(point[:2]), box):
1114
+ _, pre_v_up = filedb_up.search(idx)
1115
+ _, pre_v_down = filedb_down.search(idx)
1116
+ layers_up.append((point[0], point[1], pre_v_up))
1117
+ layers_down.append((point[0], point[1], pre_v_down))
1118
+ layers_up_array = np.array(layers_up)
1119
+ layers_down_array = np.array(layers_down)
1120
+ points_up = np.column_stack(
1121
+ (
1122
+ layers_up_array[:, 0],
1123
+ layers_up_array[:, 1],
1124
+ layers_up_array[:, 2],
1125
+ )
1126
+ )
1127
+ points_down = np.column_stack(
1128
+ (
1129
+ layers_down_array[:, 0],
1130
+ layers_down_array[:, 1],
1131
+ layers_down_array[:, 2],
1132
+ )
1133
+ )
1134
+ surface_up = pv.PolyData(points_up).delaunay_2d()
1135
+ vtk.add_mesh(
1136
+ surface_up, scalars=points_up[:, 2], color=get_colors(name)
1137
+ )
1138
+ surface_down = pv.PolyData(points_down).delaunay_2d()
1139
+ vtk.add_mesh(
1140
+ surface_down, scalars=points_down[:, 2], color=get_colors(name)
1141
+ )
1142
+ input("按任意键继续")
1143
+ else:
1144
+ for name in names.split(","):
1145
+ data = np.load(self.base / "stratum" / f"{name}.npy")
1146
+
1147
+ # endregion 煤体分类
1148
+
1149
+ # region 体素
1150
+ def comp_voxel(self):
1151
+ all_cube = {}
1152
+ if self.meta.bound:
1153
+ all_cube["bound"] = FileDB(self.base / "voxel" / "stratum.cube", "=i")(
1154
+ "r+b"
1155
+ )
1156
+ if self.meta.blocks:
1157
+ raise ValueError("未实现")
1158
+ if self.meta.mashgas:
1159
+ all_cube["mashgas"] = FileDB(
1160
+ self.base / "voxel" / "mashgas" / "mashgas.cube", "=i"
1161
+ )("r+b")
1162
+ if self.meta.tunnels:
1163
+ raise ValueError("未实现")
1164
+ if self.meta.faultage["gen"]:
1165
+ all_cube["faultage"] = []
1166
+ faultages = list((self.base / "faultage").glob("*.npy"))
1167
+ for faultage in faultages:
1168
+ all_cube["faultage"].append(
1169
+ FileDB(
1170
+ self.base / "voxel" / "faultage" / f"{faultage.stem}.cube", "=i"
1171
+ )("r+b")
1172
+ )
1173
+ if self.meta.stratum["gen"]:
1174
+ all_cube["stratum"] = FileDB(self.base / "voxel" / "stratum.cube", "=i")(
1175
+ "r+b"
1176
+ )
1177
+ if self.meta.volume:
1178
+ raise ValueError("未实现")
1179
+ if self.meta.coal["gen"]:
1180
+ all_cube["coal"] = FileDB(self.base / "voxel" / "coal.cube", "=i")("r+b")
1181
+ self.meta.voxel["gen"] = True
1182
+ self.meta.save(self.base / "meta.json")
1183
+
1184
+ # endregion 体素
1185
+
1186
+ # region 删除
1187
+ def delete(self):
1188
+ # 删除所有数据
1189
+ shutil.rmtree(self.base)
1190
+
1191
+ # endregion 删除
1192
+
1193
+ # region 清空模型
1194
+ def clearm(self):
1195
+ # 清空模型
1196
+ shutil.rmtree(self.base / "voxel")
1197
+
1198
+ # endregion 清空模型
1199
+
1200
+ # region 判断是否是新数据库
1201
+ def is_new(self) -> bool:
1202
+ return not self.base.exists()
1203
+
1204
+ # endregion 新数据库
1205
+
1206
+
1207
+ # endregion 煤矿数据库
1208
+
1209
+
1210
+ # region 所有数据库
1211
+ def all_db():
1212
+ base = Path(typr.getenv("JSON_DB_DIR", ".json_db"))
1213
+ # 返回数据库名列表
1214
+ return [db.name for db in base.iterdir() if db.is_dir()]
1215
+
1216
+
1217
+ # endregion 所有数据库